Parse Facebook User logged in, perform segue - ios

I am trying to skip over my login view once a user is logged in. How can I check to see if a user is logged in through Facebook while the app is starting?
I currently have the following code in a LoginViewController:
override func viewWillAppear(animated: Bool) {
var loggedIn = PFFacebookUtils.session().isOpen;
if (loggedIn) {
performSegueWithIdentifier("skipLogin", sender: self)
}
}
This does not move to my next view even after the user has clicked the "Log in with Facebook" button.
I get the following error:
Warning: Attempt to persent <_Project.HomeViewController: 0x7fa331d3af00> on
<_Project.LoginViewController: 0x7fa331f08950> whose view is not in the window hierarchy!

As discussed in chat, you have basically two options here:
Let the user "see" the animation from the login view controller to the second one. In that case you should do the push in viewDidAppear instead of viewWillAppear (where the view is not fully prepared, as the runtime warning clearly states).
If you prefer showing the final view controller immediately, without any animation, then it's better to put that logic inside your app delegate, and choose which initial view controller should be loaded from here. In that case, you're not actually performing any segue, you're just assigning one or another view controller to the main window (or your navigation controller).
Parse has the "AnyWall" sample app that implements the second logic. See here for more details: https://parse.com/tutorials/anywall#2-user-management. In particular, the chapter 2.4 is of special interest, as it explains how you can keep a user logged-in.
Simply put, here's how they did it (I've adapted their Objective-C code to Swift):
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
...
navigationController = UINavigationController()
...
// If we have a cached user, we'll get it back here
if PFFacebookUtils.session().isOpen {
// A user was cached, so skip straight to the main view
presentWallViewController(animated: false)
} else {
// No cached user, go to the welcome screen and
// have them log in or create an account.
presentLoginViewController(animated: true)
}
...
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window.rootViewController = navigationController
window.makeKeyAndVisible()
return true
}
In each of the two methods present...ViewController, they use the following skeleton:
func presentxxxViewController(#animated: Bool) {
NSLog("Presenting xxx view controller")
// Go to the welcome screen and have them log in or create an account.
let storyboard = UIStoryboard(name: "Main", bundle: nil) // Here you need to replace "Main" by the name of your storyboard as defined in interface designer
let viewController = storyboard.instantiateViewControllerWithIdentifier("xxx") as xxxViewController // Same here, replace "xxx" by the exact name of the view controller as defined in interface designer
//viewController.delegate = self
navigationController?.setViewControllers([viewController], animated: animated)
}
The navigationController and window vars should be defined like this in AppDelegate:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var navigationController: UINavigationController?
...
}
If your app also uses a navigation controller as its root view controller, you can probably use the same code.

Related

How do I transfer back to a view controller from a view controller that is not connected to other view controllers?

I am working on an app for iOS that has a view controller not connected to other view controllers. Currently I am trying to get a button to transfer back to a login/signup view controller from the main view controller that is instantiated after the user has logged in. The view controller that is instantiated after the user has logged in is not connected to the other view controllers. The way I get to the unconnected view controller is with
func transitionToHome()
{
let homeViewController = storyboard?.instantiateViewController(identifier: Constants.Storyboard.homeViewControllers) as? homeViewController
view.window?.rootViewController = homeViewController
view.window?.makeKeyAndVisible()}
I am also including an image of the view controllers so that it is easier to understand how I have it set up photo of the view controllers
I have tried hooking up the "log out" button to transition back to the login/signup view controller but that causes a separate, smaller scene to be brought up that I can just swipe away and be back at the main view controller. I have also tried using the code above to transfer back to the login/sign up view controller, but that caused the same problem.
Looks like you will need to reset the view.window?.rootViewController to login/signup view controller. Something like this
view.window?.rootViewController = LoginViewController()
view.window?.makeKeyAndVisible()}
Although you can swap the rootViewController after app launch, it may just be simpler to arrange view controllers so they all link back to a single rootViewController.
If you want an initial login screen that is skipped on subsequent launches of the app, then you could make the login view controller the rootViewController and in viewDidAppear() jump straight to the main view controller when the login is not required. Then you can unwind the segue for "logout".
In the app delegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let loginViewController = UIStoryboard(name: "Login", bundle: nil).instantiateInitialViewController()
self.window?.rootViewController = loginViewController
self.window?.makeKeyAndVisible()
}
Then in LoginViewController:
func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if alreadyAuthenticated {
performSegue(withIdentifier: SegueIdentifier.goToMain, sender: self)
}
}
Other combinations like this are possible depending on your exact needs. There may be some benefit in that you can access the default presentation and dismiss animations, which would be much harder to implement if you are swapping the rootViewController.

Switching UIViewController without presenting it modally or stacking it on top of each other

I know there are two ways to show a new UIViewController in Swift. There are:
self.present(controllerToPresent, animated: true, completion: nil)
and
self.performSegue(withIdentifier: "controllerToPresent", sender: nil)
But both of them show the new UIViewController on top of the other. Assume I don't want to stack controllers on each other rather than just switch the controllers. The new presented UIViewController should be the new root-controller. An example for this would be a login page. Once a user logged in I don't use the login-controller anymore, so why would I like to stack the new controller on top of it. So the question is, is there a method to switch (not stacking) UIViewControllers?
Furthermore I want to know what happens to the memory that was allocated for a new instance of an UIViewController when I use one of these two functions above. I'm not sure if at some time ARC frees the memory or if I run out of memory at some time calling these functions too often.
There are many ways to do what you want...
One approach, since you comment that you want animation:
Use a "container" view as your "root" view controller
On launch, check if user is "logged in"
If not logged in, instantiate "login" view controller, and use addChildViewController() and addSubview() to show your "login" view.
Else, if already logged in on launch, instantiate "main" view controller, and use addChildViewController() and addSubview() to show your "main" view.
In the case of 3, when user completes the log=on process, instantiate "main" view controller, and use addChildViewController()... then addSubview(), but add it hidden and/or off-screen, and use a UIView animation to replace the "login" view with the "main" view... then remove the login view and controller from memory (removeFromSuperview, removeFromParentViewController, set vc reference to nil, etc).
If at some point you want to "log-off" and return to the login screen, do the same thing... instantiate loginVC, addsubview, animate subview, remove mainVC.
Specifically, for the case of (as you mentioned as an example of what are looking for):
An example for this would be a login page. Once a user logged in I
don't use the login-controller anymore
You would need to determine the desired rootViewController in the app delegate, example:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// let's assume that you impelemnted to logic of how to determine whether the user loggedin or not,
// by using 'isLoggedin' flag:
if let wnwrappedWindow = self.window {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if isLoggedin {
let rootHomeVC = storyboard.instantiateViewControllerWithIdentifier("HomeViewController") as! HomeViewController
//...
wnwrappedWindow.rootViewController = rootHomeVC
} else {
let rootloginVC = storyboard.instantiateViewControllerWithIdentifier("HomeViewController") as! HomeViewController
//...
wnwrappedWindow.rootViewController = rootloginVC
}
}
return true
}
In case of you want to change the root view controller in the login view controller, you could implement the following code when it is a success login:
let ad = UIApplication.shared.delegate as! AppDelegate
if let window = ad.window {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let rootHomeVC = storyboard.instantiateViewControllerWithIdentifier("HomeViewController") as! HomeViewController
//...
window.rootViewController = rootHomeVC
}

ViewController appears mulitple times when creating a navigationController programmatically in swift

I have been creating my app completely from scratch programmatically, without using Storyboards.
My app is integrated with Firebase, and uses Facebook login.
My setup is fairly simple:
Launch the app -> takes you to the first VC called WelcomeViewController.
There is a check that happens in the viewDidLoad method to see if a user is already signed in and exists. If there is, it sends you straight to the second VC called FilmsViewController
The FilmsViewController is a collectionViewController that displays films. The user can press a film, and it takes them to more information about that film.
(For reference, I am already signed in with Facebook in my app)
I have a current issue, where when step 2 above happens, it transitions to the FilmsViewController, but it does it like 2 or 3 times. So you see the new VC appear like 2 or 3 times, then the content loads. If you press the Back button in the nav bar, it takes you back through the 2 or 3 viewControllers that it loaded before taking you back to the WelcomeViewController.
I have set my views up as follows.
In AppDelegate.swift:
var window: UIWindow?
var navController: UINavigationController?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
navController = UINavigationController()
let firstViewController: WelcomeViewController = WelcomeViewController()
self.navController!.pushViewController(firstViewController, animated: true)
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = navController
window?.makeKeyAndVisible()
return true
}
In the WelcomeViewController in the viewDidLoad:
FIRAuth.auth()?.addStateDidChangeListener { auth, user in
if let user = user {
// User is signed in.
// Direct the user to the home screen
let toFilmListVC = FilmsViewController(collectionViewLayout: UICollectionViewFlowLayout())
self.navigationController?.pushViewController(toFilmListVC, animated: true)
} else { ...
}
}
I have looked loads for a solution - and nothing. I've only found one post on this issue, where someone said the solution was to change the class name of that controller, which I have already done and it didn't change anything.
Can anyone help me resolve this, please? Thank you.
The addStateDidChangeListener is probably being called multiple times.
You should modify it to check whether a FilmsViewController has already been pushed, to prevent pushing another one:
FIRAuth.auth()?.addStateDidChangeListener { auth, user in
if let user = user {
// User is signed in.
// Direct the user to the home screen
// Only push one FilmsViewController onto the navigation stack!
var shouldPush = true
if let navigationController = self.navigationController {
for viewController in navigationController.viewControllers {
if viewController is FilmsViewController {
shouldPush = false
}
}
}
if shouldPush {
let toFilmListVC = FilmsViewController(collectionViewLayout: UICollectionViewFlowLayout())
self.navigationController?.pushViewController(toFilmListVC, animated: true)
}
} else { ...
}
}
I know the above solutions works, but to make it look extremely simple.
here is the code i wrote in my project.
let someController = SomeController.someController()
if !(self.navigationController!.viewControllers.contains(someController)){
self.navigationController?.pushViewController(someController, animated:true)
}
only push the controller to navigation bar if the controller is not exist.

Attempt to present controller whose view is not in the hierarchy

I am trying to accomplish what I think is a pretty common set of steps:
When my app starts, load a home controller in a navigation controller:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let window = UIWindow(frame: UIScreen.mainScreen().bounds)
window.rootViewController = UINavigationController(rootViewController: HomeViewController())
window.makeKeyAndVisible()
self.window = window
return true
}
When my app loads check if the user is logged in. If not, present a registration view controller:
func applicationDidBecomeActive(application: UIApplication) {
if ref.loggedIn != nil {
// user authenticated
print(ref.userData)
} else {
// No user is signed in
let registerViewController = RegisterViewController()
if let navController = window?.rootViewController as? UINavigationController {
navController.presentViewController(registerViewController, animated: true, completion: nil)
}
}
}
In my RegisterViewController, provide a button that switches to a LoginViewController:
#IBAction func login(sender: UIButton) {
print("LOGIN")
let loginViewController = LoginViewController()
// How do I present the view controller here?
}
My question is: how can I present the LoginViewController so that calling self.dismissViewControllerAnimated(false, completion: nil) will return to my HomeViewController?
Things I've tried:
If I call self.presentViewController from the RegisterViewController then dismissing returns back to the RegisterViewController instead of HomeViewController
If I try to get a reference to rootViewController via UIApplication.sharedApplication() and present the login controller on rootViewController then I get an error "Attempt to present ... on ... whose view is not in the window hierarchy!"
Thanks!
It's obvious that you will get the error "Attempt to present ... on ... whose view is not in the window hierarchy!"
First you need to make the instance of ViewController using storyboard identifier.
Please try this :-
let storyboard = UIStoryboard(name: "YourStoryboardName", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("viewControllerToBePresented") as! UIViewController //use your class name here to cast it.
self.presentViewController(vc, animated: true, completion: nil)
EDIT
In your case you can use protocol on register screen which will get
implemented on home screen.
And in that implementation you can write code to dismiss register view
and then present Login view.
If you are trying to present a new view controller add that view controller in navigation controller and then present the navigation controller..
From the presented view controller you can dismiss to previous view controller.
I hope this will work..
In your case you can use protocol on register screen which will get implemented on home screen.
And in that implementation you can write code to dismiss register view and then present Login view.

Navigation Bar gone

How can I present a view controller from my AppDelegate and have a Navigation bar added to that view with a back button to the previous view? I need to do this programmatically from my AppDelegate. Currently I can push a controller from there, but it doesn't act like a segue. It doesn't add a nav bar with a back button. Now I know I should be able to add one myself, but when I do it gets hidden. Currently I'm using pushViewController(), but I imagine that's not the best way to do it.
I had something that I think is similar, if not the same:
HIGH LEVEL VIEW
The general composition of my App (thus far, and specific to the issue at hand - note: details about classes provided for context, not required for resolution) is as follows:
UIViewController (ViewController.swift) embedded in a UINavigationController
Buttons on UIViewController segue to a view with a custom class:
ExistingLocationViewController - subclass of:
UITableViewController
One of the buttons (Add New Location) in the UINavigationController's Toolbar segues to view with another custom class:
NewLocationViewController - subclass of:
UIViewController
CLLocationManagerDelegate
UITextFieldDelegate
There are a number of other items here, but I believe the above is sufficient as the foundation for the issue at hand
RESOLUTION
In order to preserve the navigation-bar (and tool-bar) going both forward and back - I have the following code in my custom classes (note: the following is Swift-3 code, you may have to adjust for Swift-2):
override func viewDidLoad() {
super.viewDidLoad()
//...
navigationController?.isNavigationBarHidden = false
navigationController?.isToolbarHidden = false
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated) // #=# not sure if this is needed
navigationController?.isNavigationBarHidden = false
navigationController?.isToolbarHidden = false
}
You could actually omit the last two lines in viewWillDisappear, or perhaps even omit the entire override function
The net result (for me) was as depicted below:
Hope that helps.
If you want add a NavigationController in appDelegate you can do it like this,in this way,your viewcontroller is load from storyboard
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
let vc = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier("vc") as! ViewController
let nav = UINavigationController(rootViewController: vc)
self.window?.rootViewController = nav
self.window?.backgroundColor = UIColor.whiteColor()
self.window?.makeKeyAndVisible()
return true
}

Resources