How does the SignIn screen get called in Cannonball for iOS? - swift

I'm new to Digits for iOS. I got the authentication part working, and now I'm trying to integrate the sign-in with the rest of my app.
One thing that really puzzles me is that in Cannonball, the Initial View Controller is the main screen. However, when running Cannonball, the first screen I see is the Sign In screen.
I don't see any segue from the Navigation Controller to the Sign In. Also, I've looked at the code in the Theme Chooser View Controller, and don't see any reference to the Sign In's View Controller.
So, how does the Sign In screen actually get executed? Where is that logic happening? Thanks.

The answer is that the AppDelegate.swift file does the logic. The code below were copy/pasted from Cannonball's. I went ahead and added comments (some came with Cannonball).
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
// Developers: Welcome! Get started with Fabric.app.
let welcome = "Welcome to Cannonball! Please onboard with the Fabric Mac app. Check the instructions in the README file."
assert(NSBundle.mainBundle().objectForInfoDictionaryKey("Fabric") != nil, welcome)
// Register Crashlytics, Twitter, Digits and MoPub with Fabric.
//Fabric.with([Crashlytics(), Twitter(), Digits(), MoPub()])
Fabric.with([Digits()])
// Check for an existing Twitter or Digits session before presenting the sign in screen.
// Detect if the Digits session is nil
// if true, set the root view controller to the Sign In's view controller
if Twitter.sharedInstance().session() == nil && Digits.sharedInstance().session() == nil {
//Main corresponds to Main.storyboard
let storyboard = UIStoryboard(name: "Main", bundle: nil)
//Sign In's View Controller
let signInViewController: AnyObject! = storyboard.instantiateViewControllerWithIdentifier("SignInViewController")
//Set the UIWindow's ViewController to the SignIn's ViewController
window?.rootViewController = signInViewController as? UIViewController
}
return true
}

Related

Present Pre-built FirebaseUI Authentication page

I am using the Pre-built Firebase UI authentication for my swift project. The goal is to present the authentication page when the user clicks on the "user profile" button. Otherwise, the user doesn't have to register or sign in.
I have initialized a navigationController. So my initial thought was to simply push the Auth controller to my navigationController.
self.navigationController?.pushViewController(authUI.authViewController(), animated: true)
It failed with the error that no nested navigation controller is allowed as the authViewController() will return an instance of the initial navigation view controller of AuthUI.
My second thought was to simply call Window and set the rootViewController as the authViewController. Then just use the authViewController as the new navigation controller
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.rootViewController = authUI.authViewController()()
Unfortunately, it failed. Nothing showed up but a black screen.
The only way I figured is to call
self.present(authUI.authViewController(), animated: true, completion: nil)
However, the screen will be shown separately, which is not really what I want (see below)
Any thoughts/ideas/ suggestions are greatly appreciated.
I hope this can help you.
let authVC = authUI.authViewController()
authVC.modalPresentationStyle = .fullScreen
self.present(authVC, animated: true, completion: nil)
Thanks.

Performing a completion handler before app launches

I am attempting to open an app in one of two ways:
If the user has no UserDefaults saved, then open up a WelcomeViewController
If the user has UserDefaults saved, then open up a MenuContainerViewController as a home page
In step 2, if there are UserDefaults saved, then I need to log a user in using Firebase which I have through a function with a completion handler. If step 2 is the case, I want to open MenuContainerViewController within the completion block without any UI hiccups.
Here is the code I have currently:
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.window = UIWindow(frame: UIScreen.main.bounds)
FirebaseApp.configure()
guard
let email = UserDefaults.standard.string(forKey: "email"),
let password = UserDefaults.standard.string(forKey: "password")
else {
// User has no defaults, open welcome screen
let welcomeViewController = WelcomeViewController()
self.window?.rootViewController = welcomeViewController
self.window?.makeKeyAndVisible()
return true
}
// User has defaults saved locally, open home screen of app
let authentificator = Authentificator()
authentificator.login(with: email, password) { result, _ in
if result {
let menuContainerViewController = MenuContainerViewController()
self.window?.rootViewController = menuContainerViewController
self.window?.makeKeyAndVisible()
}
}
return true
}
Here is a video of the current UI, when I need to run the completion handler, the transition is not smooth into the app (there is a brief second with a black screen).
Please help me figure out how to make a smooth app launch.
I've had to handle situations similarly in my Firebase applications. What I typically do is make an InitialViewController. This is the view controller that is always loaded, no matter what. This view controller is initially set up to seamlessly look exactly like the launch screen.
This is what the InitialViewController looks like in the interface builder:
And this is what my launch screen looks like:
So when I say they look exactly the same, I mean they look exactly the same. The sole purpose of this InitialViewController is to handle this asynchronous check and decide what to do next, all while looking like the launch screen. You may even copy/paste interface builder elements between the two view controllers.
So, within this InitialViewController, you make the authentication check in viewDidAppear(). If the user is logged in, we perform a segue to the home view controller. If not, we animate the user onboarding elements into place. The gifs demonstrating what I mean are pretty large (dimension-wise and data-wise), so they may take some time to load. You can find each one below:
User previously logged in.
User not previously logged in.
This is how I perform the check within InitialViewController:
#IBOutlet var loginButton: UIButton!
#IBOutlet var signupButton: UIButton!
#IBOutlet var stackView: UIStackView!
#IBOutlet var stackViewVerticalCenterConstraint: NSLayoutConstraint!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
//When the view appears, we want to check to see if the user is logged in.
//Remember, the interface builder is set up so that this view controller **initially** looks identical to the launch screen
//This gives the effect that the authentication check is occurring before the app actually finishes launching
checkLoginStatus()
}
func checkLoginStatus() {
//If the user was previously logged in, go ahead and segue to the main app without making them login again
guard
let email = UserDefaults.standard.string(forKey: "email"),
let password = UserDefaults.standard.string(forKey: "password")
else {
// User has no defaults, animate onboarding elements into place
presentElements()
return
}
let authentificator = Authentificator()
authentificator.login(with: email, password) { result, _ in
if result {
//User is authenticated, perform the segue to the first view controller you want the user to see when they are logged in
self.performSegue(withIdentifier: "SkipLogin", sender: self)
}
}
}
func presentElements() {
//This is the part where the illusion comes into play
//The storyboard elements, like the login and signup buttons were always here, they were just hidden
//Now, we are going to animate the onboarding UI elements into place
//If this function is never called, then the user will be unaware that the launchscreen was even replaced with this view controller that handles the authentication check for us
//Make buttons visible, but...
loginButton.isHidden = false
signupButton.isHidden = false
//...set their alpha to 0
loginButton.alpha = 0
signupButton.alpha = 0
//Calculate distance to slide up
//(stackView is the stack view that holds our elements like loginButton and signupButton. It is invisible, but it contains these buttons.)
//(stackViewVerticalCenterConstraint is the NSLayoutConstraint that determines our stackView's vertical position)
self.stackViewVerticalCenterConstraint.constant = (view.frame.height / 2) + (stackView.frame.height / 2)
//After half a second, we are going to animate the UI elements into place
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
UIView.animate(withDuration: 0.75) {
self.loginButton.alpha = 1
self.signupButton.alpha = 1
//Create correct vertical position for stackView
self.stackViewVerticalCenterConstraint.constant = (self.view.frame.height - self.navigationController!.navigationBar.frame.size.height - self.signupButton.frame.maxY - (self.stackView.frame.size.height / 2)) / 3
self.view.layoutIfNeeded()
}
}
}

How can I present a ViewController using applicationdidBecomeActive func with some exceptions?

Using applicationDidBecomeActive in AppDelegate is a good way of presenting a specific ViewController each time an ios app becomes active. But how can I override This function and make a few exceptions of when to show the view controller or not after the app becomes active again. When I pick an image from UIImgaePickerController, the view controller shows up again. how can I make my app active even if it’s not to avoid the rootcontroller assigned in applicationDidBecomeActive() to popup again.
If I understood the question correctly you want to change the root view controller when something happens while using your app, so you can put this piece of code in your AppDelegate: this just catches the current rootViewController, set the new one and dismisses and remove the old one inside a transition
func changeRootViewController(with viewController: UIViewController) {
guard let oldViewController = self.window?.rootViewController else { return }
UIView.transition(from: oldViewController.view, to: viewController.view, duration: 0.3, options: [.transitionCrossDissolve, .allowAnimatedContent]) { _ in
self.window!.rootViewController = viewController
self.window!.makeKeyAndVisible()
oldViewController.dismiss(animated: false) {
oldViewController.view.removeFromSuperview()
}
}
}
then, when you need to change the rootViewController you can just:
let appDelegate = UIApplication.shared.delegate as? AppDelegate
appDelegate?.changeRootViewController(with: MyNewViewController())

Swift - TabViewController with SwipeViewControler in one of its tabs

Another newbie to swift here. I want my application to have four tabs on the bottom and inside one of the tabs I want to be able to swipe between two 'pager' style tabs.
I have been able to make the four tabs work at the bottom by starting a new tabbed app and adding two additional view controllers.
Then I tried adding a SwipeViewController (https://github.com/fortmarek/SwipeViewController) to one of the tabs and it worked but it overrode the entry point so all I see is the two SwipeView tabs without the TabController tabs on the bottom.
!I want this inside of the Home tab]1
I think the problem is in my delegate code as it is setting self.window?.rootViewController to the SwipViewController. Getting rid of that assignment makes the Tab Bar show back up, but it is black inside of the Home tab so it is still not working.
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let pageController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
let navigationController = SecondViewController(rootViewController: pageController)
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()
return true
}
I also tried getting the corresponding window for the "Home" tab from the storyboard and setting the navigation controller to that.
let secondViewControllerWindow = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Home").view.window
secondViewControllerWindow?.rootViewController = navigationController
secondViewControllerWindow?.makeKeyAndVisible()
But Home is still empty. Here is the ViewController code that is represented by the Home tab and should control the SwipViewController
import UIKit
class SecondViewController: SwipeViewController {
override func viewDidLoad() {
super.viewDidLoad()
let stb = UIStoryboard(name: "Main", bundle: nil)
let page_one = stb.instantiateViewController(withIdentifier: "nba") as UIViewController
let page_two = stb.instantiateViewController(withIdentifier: "mlb") as UIViewController
setViewControllerArray([page_one, page_two])
page_one.title = "NBA"
page_two.title = "MLB"
setFirstViewController(0)
equalSpaces = true
//setButtons(UIFont.systemFontOfSize(18), color: UIColor.blackColor())
//setSelectionBar(80, height: 3, color: UIColor.blackColor())
setButtonsOffset(40, bottomOffset: 5)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
My questions is similar to these other questions but I did not find an answer.
Using Page View Controller inside Tab Swift
Xcode 6 - Swift - Custom Tabbar with Navigation
I faced the same problem recently, and I found the solution here.
https://github.com/fortmarek/SwipeViewController/issues/26
I didn't put the code in didFinishLaunchingWithOptions.
I tried to work on main.storyboard.
I dragged a PageViewController in and set it as your(SecondViewController)'s RootView.
It worked for me.

Calling a function of the current UIViewController when using SWRevealViewController

I have an app written in swift that displays location information on different views. I am receiving location updates in AppDelegate (I don't want to use a singleton class since I want the location updates even when the app is in the background).
Now, I am using SWRevealViewController to implement a sidebar menu to toggle between the different views. When a new location update is received, how do I call the function of the viewController that is currently active to update the UI?
I searched a lot and all the solutions that talk about how to find the current UIViewController actually return SWRevealViewController as the current UIViewController, which doesn't help.
I gave it another shot and was able to find my viewController in my app from another view controller. However, I couldn't get my child view controllers app delegate.
Looking forward to other answers...
here's what I did:
let app = UIApplication.sharedApplication().delegate! as! AppDelegate
if let nav = app.window?.rootViewController?.childViewControllers {
nav.forEach { vc in
if let viewControllers = vc as? UINavigationController {
viewControllers.childViewControllers.forEach { vc in
if let x = vc as? ViewController {
print("Got it \(x)")
}
}
}
}
}