How to set self as new window.rootViewController? - ios

I want to set one of my ViewControllers from my navigation stack as my apps window.rootViewController while I am somewhere in another controller's navigation stack. That other controller is currently my window.rootViewController. Therefore I use this code:
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.window?.rootViewController = self
}
This results in just a black screen.
When I instantiate a new ViewController from Storyboard it's working fine, but all entries are gone. I don't want to reconfigure the new viewController, if that is avoidable.
Update A
I put self to a strong reference in appDelegate to check that, because I thought it's view was unloaded somewhere on the way, but it still resulted in a black screen.
The documentation says:
The root view controller provides the content view of the window. Assigning a view controller to this property (either programmatically or using Interface Builder) installs the view controller’s view as the content view of the window. The new content view is configured to track the window size, changing as the window size changes. If the window has an existing view hierarchy, the old views are removed before the new ones are installed.
Clarification: I don't want to instantiate a new VC, I want to use the current one, without putting a navigationController around it, it should not be necessary. I'm sure it is possible without any workaround, I am just missing something here.

To be more precise, you can use navigation controller with your view controller
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let appDelegate = UIApplication.shared.delegate as? AppDelegate
let initialViewController = storyboard.instantiateViewController(withIdentifier: "yourViewController") as! yourViewController
let nvc: UINavigationController = UINavigationController(rootViewController: initialViewController)
appDelegate?.window?.rootViewController = nvc
appDelegate?.window?.makeKeyAndVisible()

The documentation says: The root view controller provides the content view of the window. Assigning a view controller to this property (either programmatically or using Interface Builder) installs the view controller’s view as the content view of the window. The new content view is configured to track the window size, changing as the window size changes. If the window has an existing view hierarchy, the old views are removed before the new ones are installed.
Just as the documentation says: It removes all views in the stack if the rootViewController is exchanged. No matter what's with the controller. So I had to remove self from the stack to assure my view won't be removed. This resulted in following solution:
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
guard let pageVC = self.onboardingDelegate as? OnboardingPageViewController else { return } // my current stack is in a pageViewController, it also is my delegate
let vc = self // holding myself
pageVC.subViewControllers.removeLast() // removing myself from the list
pageVC.setViewControllers([pageVC.subViewControllers[0]], direction: .forward, animated: false, completion: nil) // remove the current presented VC
appDelegate.window?.rootViewController = vc
vc.onboardingDelegate = nil
appDelegate.window?.makeKeyAndVisible()
}
And it is working fine, just like I wanted.

You have to remove it from the navigation controller before setting it as the root view controller:
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
willMove(toParentViewController: nil)
view.removeFromSuperview()
removeFromParentViewController()
appDelegate.window?.rootViewController = self
}
NOTE: this is not the best approach, you should revisit your navigation stack.

if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
let objLoginViewController = UIStoryboard(name: "Main", bundle:nil).instantiateViewController(withIdentifier: "LoginViewController") as! LoginViewController
appdelegate.window?.rootViewController = objLoginViewController
appdelegate.window?.makeKeyAndVisible()
}
if you don't want to instantiate new controller then you can find that
controller from your navigation stack and assign into
rootViewController

You are on the right track, but just add your self to NavigationController and then set it as rootViewController and makeKeyAndVisible the window.
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
let nav = UINavigationController(rootViewController: self)
// IF YOU DON'T WANT NAVIGATION BAR, set it hidden
nav.navigationBar.isHidden = true
appDelegate.window?.rootViewController = nav
appDelegate.window?.makeKeyAndVisible()
}
Try and share your results!

Related

Opening specific view with navigation controller from push notication

Im trying to open a certain view from a push notification but i keep losing the nav bar and the back and next references. this what my storyboard looks like this (with the view i want to open)
this is what i have in my AppDelagate:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "notification") as? NotificationViewController
self.window?.rootViewController = vc
What you're doing is completely replacing the root view controller of your application, this means that all the current UI will be discarded.
What you should do instead is to use your knowledge of your application to direct it to the new content. For example, if your root view controller is a navigation controller, you can cast rootViewController to a nav controller and push it (this will fail if your root view controller is something else, like a tab bar controller).
guard let vc = storyboard.instantiateViewController(withIdentifier: "notification") as? NotificationViewController else {
fatalError("Main Storyboard doesn't have a notification controller")
}
guard let nav = self.window?.rootViewController as? UINavigationController else {
return //handle unexpected state
}
nav.push(vc, animated: true)
Another option would be to embed your notification controller into a navigation controller, add a Close button, and present it modally, that way you can present it on top of rootViewController no matter what that controller is.
As we can see in the screenshot your provided, the application's root view controller is the UINavigationController instance.
And according to that, let me offer the next code:
func handleNotification(){
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let vc = storyboard.instantiateViewController(withIdentifier: "notification") as? NotificationViewController else{
debugPrint("NotificationViewController with identifier 'notification' not found")
return
}
guard let navVC = self.window?.rootViewController as? UINavigationController else{
debugPrint("RootViewController is not an UINavigationController")
return
}
navVC.pushViewController(vc, animated: true) //perhaps your will prefer to use false
}
Beside that, you can use more flexible implementation.
In your AppDelegate post a (NS)Notification when notification intercepted, the relevant view-controller(s) observe the notification, and act when notification broadcasted.
You can also set an identifier to the segue and invoke performSegue method from the observing view-controller
You can set from storyboard -> add view controller -> Embed in navigation controller -> set second view controller -> Attach seque between that controllers. You will see same view controllers like that image .

Programatically creating UINavigationController which isn't a rootViewController

I am aware that it is possible for you to do:
let foo = MyView()
let nav = UINavigationController(rootViewController: foo)
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = nav
window?.makeKeyAndVisible()
Or something like that in AppDelegate.swift and have a navigation controller up and running. However, what if my intended navigation controller isn't my rootViewController? Do I do something like this or is there a completely different approach?
Since a UINavigationController is merely a subclassed UIViewController, I believe the only other code-only approach is to make it a child view controller to your actual root controller:
let navController = UINavigationController(nibName: nil, bundle: nil)
addChild(navController)
view.addSubview(navController.view)
navController.didMove(toParent: self)
That code is for your "root" UIViewController that will containnavController`.
My usage is subtly different. I actually have *two custom/subclassed nav controllers inside a view controller, and I use auto layout to place them where I need them. Your needs were pretty vague on details, but if you do not want your nav controller to be a root, I see no reason why you couldn't make one a child of the root and have it be full screen. (You should even be able to present it.
If my code isn't enough to help you, let me know and I'll expand on things.
If you wish to use a storyboard, there is another option: Create a new storyboard, make everything in it embedded in a nav controller, and open it.
let storyboard = UIStoryboard(name: "mySecondStoryboard", bundle: nil)
let vc = storyboardCompose.instantiateViewController(withIdentifier: "myRootVC") as! MyNextViewController
let navController = UINavigationController(rootViewController: myRootVC)
self.present(navController, animated:true, completion: nil)
But please note, you are both presenting a view controller and making it a root!
Just remember, a UINavigationController is merely a subclassed "container" UIViewController with some enhancements for navigation like push/pop.

Perform Segue from ViewController that isn't open

I have a background task that runs from AppDelegate, when it needs to it displays notifications.
When these notifications are tapped they should direct the user to the ViewController that relates to the notification.
I was wondering if it was possible to perform segues from AppDelagate.
My ViewControllers are in a navigation controller. I'm guessing I have to instantiate my root view controller and perform segue there, just not sure how to do that from appdelegate.
Edit:
Here is my code so far, it works it just isn't embedded in my navigation controller
if let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ConversationVC") as? conversationTableViewController {
if let window = self.window, let rootViewController = window.rootViewController {
var currentController = rootViewController
while let presentedController = currentController.presentedViewController {
currentController = presentedController
}
currentController.present(controller, animated: true, completion: nil)
}
}
You can't segue from a storyboard that isn't loaded yet.
Depending on your case you could:
1: Send data from the AppDelegate to the initial root view controller of your current storyboard and from there, create multiple segues to the respectable VC depending on the data.
OR
2: Create multiple storyboards (one for each case) and launch the appropriate ones from the AppDelegate according to the notification.
Edit: Need to see the storyboard. For the variable, add it to conversationTableViewController and set it up just before you present it:
controller.myVar = "someValue"
self.window.rootViewController = controller
self.window.makeKeyAndVisible()

How to dismiss any popover viewcontrollers when activated in iOS?

I have some ViewControlls for settings, info etc.
Users can close the app settings ViewController open(I mean popover the ViewController).
When local notification is received I want the the app go to the root viewcontroller and dismiss any popovers.
EDIT
this answer is only good if the VC you are trying to go back to does not need any special initialization, since this method creates a new instance of it. keep that in mind.
Try this method, it will remove anything in your stack of View Controllers and make a specific View Controller presented on screen:
func dismissAllAndNavigate(){
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let window = appDelegate.window
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let rootController = mainStoryboard.instantiateViewController(withIdentifier: "some identifier") as! UIViewController
window?.rootViewController = rootController
}
Just make sure that the name of the storyboard is correct and the identifier of the view controller in that storyboard is defined.

Setting RootViewController in Appdelegate

I have storyboard in my application, but i don't want to set the Main storyboard file base name in info.plist, as well as I don't want to set the entry point i.e Is Initial View Controller in storyboard for any viewcontroller scene.
Though I want to launch the app with some scene from the storyboard, but all shows me a black screen.
When I tried
let testob:testClass = testClass()
self.window?.rootViewController = testob
in AppDelegate, it didn't worked and got the same black screen.
But when I set the Main storyboard file base name in info.plist, and the entry point i.e Is Initial View Controller in storyboard every thing works.
Is there any solution to do so?
You need to instantiate the window and make it key and visible.
let bounds = UIScreen.main.bounds
self.window = UIWindow(frame: bounds)
self.window?.rootViewController = rootViewController
self.window?.makeKeyAndVisible()
You can load a particular view controller from storyboard.
See this answer.
Example:
window?.rootViewController = initialViewController()
where
private func initialViewController() -> UIViewController {
if isUserLoggedIn {
return UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("LoadingViewControllerIdentifier")
} else {
return UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("HomeViewControllerIdentifier")
}
}
After setting self.window?.rootViewController = testob, you have to do the following
[self.window makeKeyAndVisible]
Also, initialize the viewController from storyboard using appropriate method

Resources