I want to display a modal view controller to the user should there not be a user logged in. Here is my method implementation:
func application(application: UIApplication, willFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool
{
// Notifications
//
// User
//
NSNotificationCenter.defaultCenter().addObserver(self, selector: "noCurrentUser:", name: UserCurrentUserNotSetNotificationName, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "currentUserDidChange:", name: UserCurrentUserDidChangeNotificationName, object: nil)
// Root window
//
if managedObjectContext != nil && User.currentUser(managedObjectContext!) == nil
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let logInViewController = storyboard.instantiateViewControllerWithIdentifier("Log In View Controller") as? LogInViewController
{
window?.rootViewController?.presentViewController(logInViewController, animated: false, completion: nil)
}
}
return true
}
I include the notifications because currently, the noCurrentUser: method presents my log in view controller modally and animated. This works well except when the app launches, the user sees a flash of the app (the root view controller) before the the notification is sent and the modal log in view controller is presented.
I’ve tried setting the modal animation option to false on presenting, but because it’s not the root view controller, this still doesn’t work.
So how do I properly set a root view controller to a modal view controller which I can then dismiss modally.
You can't. The root view controller should be considered the visual representation of your application. To make this modal implies that your application itself is modal, which obviously isn't the case.
What you are really looking to be able to do is present the modal view controller as the first thing users see. This is not the same as being the root view controller.
Set the root view controller to a regular view controller (or navigation controller) and then push your modal view controller onto it. When the modal view controller is dismissed, your application may begin.
Related
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.
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
}
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.
(reposting due to initial wrong title)
When users receive a push notification for our app (and they tap on it), the app should open up a details screen. From a navigation perspective, the app usually has this structure anytime the user opens it:
TabBarController -> Navigation Controller -> View Controller
Once a use open the push notification, I'd like to instantiate a UIViewController. However, this VC should be part of the tabbarcontroller, so that the user can navigate to other areas of the app as well. Right now I am able to display the VC itself, but I can't make it appear as part of the tabbarcontroller:
class private func instantiateJobDetailsViewController(job: JobModel) {
if let currentViewController = UIApplication.sharedApplication().delegate?.window??.rootViewController {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let jobDetailViewController = storyboard.instantiateViewControllerWithIdentifier("JobDetailController") as! JobDetailController
jobDetailViewController.job = job
currentViewController.presentViewController(jobDetailViewController, animated: true, completion: nil)
} else {
ClientHelper.displayAlertAsync("Error", message: "Info for support: cannot present jobDetails view controller", controller: nil)
}
}
Can someone provide some guidance about how to instantiate such a VC and, at the same time, preserve the tab bar controller so the user can navigate within the app?
For reference, here is how I resolved this situation:
class private func instantiateJobDetailsViewController(job: JobModel) {
if let tabBarController = UIApplication.sharedApplication().delegate?.window??.rootViewController as? UITabBarController {
tabBarController.selectedIndex = 0
let currentNavigationController = tabBarController.selectedViewController as! UINavigationController
let currentViewController = currentNavigationController.topViewController!
currentViewController.performSegueWithIdentifier("JobDetailSegue", sender: job)
} else {
ClientHelper.displayAlertAsync("Error", message: "Info for support: cannot present jobDetails view controller", controller: nil)
}
}
So it is basically:
Get the instance of the tab bar controller.
Make sure you are in a relevant screen on the tab bar controller by setting its selectedIndex property.
Get the instance of the navigation controller
Get the instance of the view controller being presented by the navigation controller.
In this way, I can open up a relevant screen from a push notification while a) showing the tab bar controller and b) preserving the navigation element to allow the user to navigate back from the screen.
You try change to structure
Navigation Controller -> TabBarController -> View Controller
You have a main navigationcontroller, set it is a global variable-a singleton ( var mainNavigation:UINavigationController?)
and when users receive a push notification, you can push to detailScreen from mainNavigation. Like this storyboard
When a user taps a push notification, I want them to be taken to a specific table view controller in my app. This table view controller is embedded in a navigation controller, which is embedded in a tab bar controller (my root view controller). The image shown below visualizes this.
The root view Tab Bar Controller has a storyboard ID of "HomeVC" and a class name of "HomeViewController", the Navigation Controller has a storyboard ID of "SettingsNavigationVC", and the Table View Controller has a storyboard ID of "SettingsTableVC" and a class name of "SettingsNavigationVC".
I have push notifications working. By working, I mean I can send a message from a device and receive it on another device, but when the receiver taps the notification I can't seem to get any other view controller to open other than the root view controller. According to the push notification guide I'm using, I'm using the following code in the didReceiveRemoteNotification method:
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject: AnyObject]) {
let rootVC = self.window!.rootViewController
if PFUser.currentUser() != nil {
let settingsTableVC = SettingsTableViewController()
rootVC?.navigationController?.pushViewController(settingsTableVC, animated: false)
}
}
What am I doing wrong, or what must I do to present the right view controller?
Try this
let storyboard = UIStoryboard(name: "YourStoryboardName", bundle: nil)
let rootVC = storyboard.instantiateViewControllerWithIdentifier("HomeVC") as! UITabBarController
if PFUser.currentUser() != nil {
rootVC.selectedIndex = 2 // Index of the tab bar item you want to present, as shown in question it seems is item 2
self.window!.rootViewController = rootVC
}
In order to push to the specific view controller, you must replace below line:
Yours:
rootVC?.navigationController?.pushViewController(settingsTableVC, animated: false)
Mine:
rootVC?.visibleViewController.navigationController?.pushViewController(settingsTableVC, animated: true)
And for presenting any view controller, you must use this:
rootVC?.visibleViewController.navigationController?.presentViewController(settingsTableVC, animated: false, completion: nil)