Open first View based on condition - swift

I want my app to check at start conditionaly if a variable is correct or not. Based on that it should either go to an intro screen (where he can select a variable in my case select a team) or it should start the main view. After searching i found this code and edited it.
But there still seems to be problems. First of all I dont have two identifier. The Intro has one but not the main view. My main View is called WeatherViewController and the Intro screen is called FirstScreenViewController.
I also added a picture of my Main.storyboard.
I also googled a lot about conditional UINavigationController but I can only understand with a video and did not found a video about it.
I tried to use the code from here.
var id = hello ? "goToIntro" : "???"
self.window = UIWindow(frame: UIScreen.main.bounds)
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let WeatherViewController: UIViewController = mainStoryboard.FirstScreenViewController(withIdentifier: WVC has no identifier??) as UIViewController
self.window?.rootViewController = WeatherViewController
self.window?.makeKeyAndVisible()
if hello {
self.performSegue(withIdentifier: "goToIntro", sender: self)
} else {
/here nothing should happen. It should open the Main View
self.performSegue(withIdentifier: "???", sender: self)
}

Simply repeat the steps you did for creating the segue goToIntro.
Add a new UIViewController (MainViewController) in your storyboard.
Add a segue and give it an identifier.
Provide the identifier value in the else condition of the code that you've shown in your post to perform the segue to the UIViewController that you've just created.
The final code should look something like this:
var id = hello ? "goToIntro" : "goToMain"
guard let windowScene = scene as? UIWindowScene else { return }
window = UIWindow(windowScene: windowScene)
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
let weatherViewController = mainStoryboard.instantiateInitialViewController()
window?.rootViewController = weatherViewController
window?.makeKeyAndVisible()
weatherViewController.performSegue(withIdentifier: id, sender: self)
You don't need the if else block since the id is already determined using the ? ternary operator.

Related

NavigationController PushViewController does not show

I am trying to navigate to the ClassroomViewController once I receive the push notification. I have put a break point, and it hits all the lines, but it does not show the ClassroomViewController. I am wondering what I am missing in my current implementation.
I added identifier on the ClassroomViewController on storyboard.
AppDelegate
public var keyWindow: UIWindow? {
return UIApplication.shared.windows.first { $0.isKeyWindow }
}
Helper Method which does not work. I am wondering why the following approach does not work.
private func notificationToNavigate() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let topViewController = appDelegate.keyWindow?.rootViewController
else { return }
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let cVC = storyboard.instantiateViewController(withIdentifier:"ClassroomViewController") as! ClassroomViewController
topViewController.navigationController?.pushViewController(cVC, animated: true)
}
Helper Method which works
private func notificationToNavigate() {
guard let window = UIApplication.shared.keyWindow else { return }
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let cVC = storyboard.instantiateViewController(withIdentifier:"ClassroomViewController") as! ClassroomViewController
window.rootViewController = UINavigationController(rootViewController: cVC)
window.makeKeyAndVisible()
}
If topViewController is indeed the key window's root view controller, then it cannot have a navigation controller; if pushing is possible at all, then it must be a navigation controller. So you would say
(topViewController as? navigationController)?.pushViewController...
On the other hand, if topViewController is not a navigation controller (so that the above fails), then pushing is simply impossible and you need to think of something else to do.

Instantiating deep View Controller with Tab bar and Navigation

I am using Xcode 11 and Swift 5.
Upon receiving an APNS notification, I need to jump to a view controller deep in my storyboard from AppDelegate. This viewController (chatVC) is behind a tabbar and a navigation controller and several other view controllers. See the image below. I know how to check the notification for tags and how to use launchOptions in AppDelegate to trigger the jump. But I am struggling with how to establish the context for that last view controller so that the user can use the back button all the way back to the tab bar controller.
I have read many SO answers, and tried many approaches but none seem to have the same embedding of a nav controller inside a tab bar controller. Here is my code in AppDelegate (after reading the tag in the notification):
if tag == "CS" {
// Set up a Chat View Controller.
if let chatVC = UIStoryboard(name: "Main", bundle: .main).instantiateViewController(withIdentifier: Constants.Storyboard.chatVC) as? NewChatViewController,
let tabBarVC = UIStoryboard(name: "Main", bundle: .main).instantiateViewController(withIdentifier: Constants.Storyboard.tabBarController) as? UITabBarController,
let csVC = UIStoryboard(name: "Main", bundle: .main).instantiateViewController(withIdentifier: Constants.Storyboard.csViewController) as? CustomerServiceViewController,
let helpVC = UIStoryboard(name:"Main", bundle: .main).instantiateViewController(withIdentifier: Constants.Storyboard.helpVC) as? HelpViewController
{
// Set the customer service document Id.
chatVC.cs = cs
// Make the tabBarVC the Root View Controller
self.window?.rootViewController = tabBarVC
self.window?.makeKeyAndVisible()
// Select the Favorites Index.
tabBarVC.selectedIndex = 0
// Push the Customer Service VC on top.
tabBarVC.show(csVC, sender: Any?.self)
// Push the Help VC on top of Customer Service VC.
csVC.show(helpVC, sender: Any?.self)
// Push the the chat detail page on top.
helpVC.show(chatVC, sender: Any?.self)
}
}
}
return true
What can I do to jump to the chatVC and set up the navigation context beneath it so the back button can be used?
Here is how you can do it:
guard let tabBarVC = UIApplication.shared.windows.filter( {$0.rootViewController is UITabBarController } ).first?.rootViewController as? UITabBarController else { return }
tabBarVC.selectedIndex = 0 //you can select another tab if needed
guard let chatVC = UIStoryboard(name: "Main", bundle: .main).instantiateViewController(withIdentifier: Constants.Storyboard.chatVC) as? NewChatViewController else { return }
if let navController = tabBarVC.viewControllers?[0] as? UINavigationController {
navController.pushViewController(chatVC, animated: true)
}
You can also pass objects from your notification to your chatVC here before pushing it, in case you need to do that.
Hope this works for you!

Show view controller from main storyboard

I have a case in one of my Swift files which runs the following method:
case .editProfile:
vc = PoiDetailViewController()
self.navigationController?.popViewController(vc,animated: true)
It doesn't seem to work when I run the code.
The view controller is located on the main storyboard and I would like PoiDetailViewController to display when this case is called.
If I understand it correctly, you should be able to achieve what you're looking for by present in your code. Like so:
case .editProfile:
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyBoard.instantiateViewController(withIdentifier: "PoiDetailViewController") // or whatever identifier you have given it
self.present(vc, animated: true, completion: nil)
That should work, however, make sure you set a storyboard ID for your view controller in the identity inspector.
No segues are required.
To set up a storyboard ID click on the View Controller you would like to use (in this case, PoiDetailViewController) and click the identity inspector icon and set a storyboard ID where it asks. I have attached an image so you can see where it needs to go (in the field marked 'Storyboard ID')
Hope that helps.
As per my Understanding you need to pop to specific View Controller if it is in stack and if not then you need to push to that controller
var loginVCFound:Bool = false;
let navigationController : UINavigationController! = self.window!.rootViewController as! UINavigationController;
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil);
let viewControllers: [UIViewController] = navigationController.viewControllers
for aViewController in viewControllers {
if aViewController is LoginVC {// Pass the name of your controller here
loginVCFound = true;
navigationController.popToViewController(aViewController, animated: true)
break;
}
}
if !loginVCFound { // change the identifier and VC with yours
let objLoginVC = mainStoryboard.instantiateViewController(withIdentifier: "LoginVC") as! LoginVC
navigationController.pushViewController(objLoginVC, animated: true)
}

Attempt to present * whose view is not in the window hierarchy

I am using the following code in AppDelegate to display a popup window within the app when a button is selected, i will eventually move this to a label hyperlink but just testing currently.
let storyboard = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("test") as! ViewController
let popOverVC = UIStoryboard(name:"Main", bundle: nil).instantiateViewControllerWithIdentifier("sbPopUpID") as! PopUpViewController
storyboard.addChildViewController(popOverVC)
popOverVC.view.frame = storyboard.view.frame
storyboard.view.addSubview(popOverVC.view)
popOverVC.didMoveToParentViewController(storyboard)
self.window?.rootViewController?.presentViewController(storyboard, animated: true, completion: nil)
The first time i select a button this works correctly, however on all subsequent button presses the following error is displayed.
2016-10-28 11:27:40.551 testfordeeplinks[20496:104536] Warning:
Attempt to present
on whose view is not
in the window hierarchy!
For anyone who stumbles across this in the future, this is how i resolved;
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window?.rootViewController = storyboard
rootViewController doesn't necessarily refer to the view controller that is currently visible. For example, if you utilise a UINavigationController, rootViewController will hold a reference to that navigation controller which in itself is buried deep in the view hierarchy and hence when you try to present a view controller on it get an error. What I would recommend doing is grabbing the last view controller in navController.viewControllers and presenting your popup on that. See the following code:
let appDel = UIApplication.sharedApplication().delegate as! AppDelegate
if let navController = appDel.keyWindow?.rootViewController as? UINavigationController {
if let visibleVC = navController.viewControllers.last {
visibleVC.presentViewController(storyboard, animated: true, completion: nil)
}
}
Give that a try and let me know. :)

How to set a new root view controller

I wonder if its possible to set a new root VC?
My app gets init with a uinavigation controller that has a table view to be the root VC.
Then from the table view I am running another segue to a login window (present modally) If you then login you end up in the red VC/account page. What I want to do now is to set the red VC to be the new root VC of the app, and remove all underlying VC's. So that I can show a menu button/icon instead of a "Back" button
I have found this but I dont understand how to use it:
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let yourViewController: ViewController = storyboard.instantiateViewControllerWithIdentifier("respectiveIdentifier") as! ViewController
let navigationController = self.window?.rootViewController as! UINavigationController
navigationController.setViewControllers([yourViewController], animated: true)
But I cannot get it to work. So is it possible to make the red vc in the picture act as the new root VC.
Swift 4.2
May be you should try this
let mainStoryBoard = UIStoryboard(name: "Main", bundle: nil)
let redViewController = mainStoryBoard.instantiateViewController(withIdentifier: "respectiveIdentifier") as! ViewController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = redViewController
Swift 4, 5, 5.1
let story = UIStoryboard(name: "Main", bundle:nil)
let vc = story.instantiateViewController(withIdentifier: "NewViewController") as! NewViewController
UIApplication.shared.windows.first?.rootViewController = vc
UIApplication.shared.windows.first?.makeKeyAndVisible()
Swift 3 Update:-
let testController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "testController") as! TestController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = testController
Swift 4 Answer
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewController(withIdentifier: "YourViewController") as! YourViewController
let navigationController = UINavigationController(rootViewController: nextViewController)
let appdelegate = UIApplication.shared.delegate as! AppDelegate
appdelegate.window!.rootViewController = navigationController
UINavigationController has a viewControllers property, which is a NSArray, And you can replaced it with your own NSArray of view controllrs.
This can be done as show in below sample code.
let newViewController = self.storyboard?.instantiateViewControllerWithIdentifier("YourViewControllerollerID") as! YourViewController
let customViewControllersArray : NSArray = [newViewController]
navigationController?.viewControllers = customViewControllersArray as! [UIViewController]
And if you want to show this new root view controller you can just call UINavigationController's popToRootViewController() method.
navigationController?.popToRootViewControllerAnimated(true)
In order to get the code snippet from the original question to work, I had to make a change to the third line
let navigationController = self.navigationController!
I am using this code in an #IBAction in the view controller that precedes the new root view controller.
Using the original code, I was receiving an error saying that my view controller had no member called window. After looking at the documentation, I could find no property named window. I'm wondering if the original block of code above was intended to be used inside a UINavigationController file.
Here is the block in its entirety.
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let todayViewController: TodaysFrequencyViewController = storyboard.instantiateViewControllerWithIdentifier("todaysFrequency") as! TodaysFrequencyViewController
let navigationController = self.navigationController!
navigationController.setViewControllers([todayViewControl ler], animated: true)
I wonder if its possible to set a new root VC?
Yes, it's possible. How you do it depends on the context...
My app gets init with a uinavigation controller that has a table view to be the root VC.
There are actually two things that are commonly called the "root view controller":
UIWindow has a rootViewController property, which is writeable.
UINavigationController has no rootViewController property, but it does have an initializer called -initWithRootViewController:. You can set the nav controller's "root" view controller by setting it's viewControllers property.
It sounds like you're trying to change the window's root view controller, but the code you show only changes the nav controller's viewControllers property. Try setting the window's rootViewController property directly. Understand, however, that if you take that approach then the navigation controller will go away too. If you want to keep the nav controller, on the other hand, go with your current approach.
But I cannot get it to work. So is it possible to make the red vc in the picture act as the new root VC.
More information here would be helpful. What do you mean by "cannot get it to work"? What happens, and what do you expect to happen?
For swift 4.0.
In your AppDelegate.swift file in didfinishedlaunchingWithOptions method, put the following code.
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
let rootVC = MainViewController() // your custom viewController. You can instantiate using nib too. UIViewController(nib name, bundle)
let navController = UINavigationController(rootViewController: rootVC) // Integrate navigation controller programmatically if you want
window?.rootViewController = navController
return true
}
Hope it will work just fine.
For Swift 5 Users you can do this way and this will definitely work for you.
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
manageLoginSession()
guard let _ = (scene as? UIWindowScene) else { return }
}
func manageLoginSession() {
guard let window = window else {return}
if UserDefaults.standard.bool(forKey: "_key_AlreadyLogin") == true {
window.rootViewController = UIStoryboard(name: "Dashboard", bundle: nil).instantiateInitialViewController()
}else{
window.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()
}
}
You can use this code when you click the login button :-
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var vc = mainStoryboard.instantiateViewControllerWithIdentifier("respectiveIdentifier") as ViewController
UIApplication.sharedApplication().keyWindow.rootViewController = vc
How and from where are you presenting redVC?
You could make it root view controller of your UINavigationController, so you would still have ability to push and pop view controllers.
self.navigationController?.viewControllers = [self];
You can use this bit of code:
let newViewController = self.storyboard?.instantiateViewController(withIdentifier: "HomeViewController") as! HomeViewController
let customViewControllersArray : NSArray = [newViewController]
self.navigationController?.viewControllers = customViewControllersArray as! [UIViewController]
self.navigationController?.pushViewController(newViewController, animated: true)
If you need to set rootViewController with some animations, here is the code:
guard let window = UIApplication.shared.keyWindow else {
return
}
guard let rootViewController = window.rootViewController else {
return
}
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "MainTabbar")
vc.view.frame = rootViewController.view.frame
vc.view.layoutIfNeeded()
UIView.transition(with: window, duration: 0.3, options: .transitionCrossDissolve, animations: {
window.rootViewController = vc
}, completion: { completed in
// maybe do something here
})
For Swift 5 and above this may work for you.
if let delegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate {
delegate.window?.rootViewController = newViewController
delegate.window?.makeKeyAndVisible()
}
Swift 4,5
Use this below code for RootViewController
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let mainViewController = storyBoard.instantiateViewController(withIdentifier: "MainViewController") as! MainViewController
let navigationController = UINavigationController(rootViewController: nextViewController)
if let window = UIApplication.shared.delegate?.window {
window?.rootViewController = navigationController
}
once you are in the vc that you want to set as root, just add in your viewDidLoad:
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = self
*As per best practice you should check if it is already the root, if not execute the code above.
Swift 3
AppDelegate file:::
#IBAction func btnGoBack(_ sender: UIButton){
self.goToHomeVC()
}
func goToHomeVC(){
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier :"HomeVC") as! HomeVC
let navController = UINavigationController.init(rootViewController: viewController)
if let window = self.appDelegate.window, let rootViewController = window.rootViewController {
var currentController = rootViewController
while let presentedController = currentController.presentedViewController {
currentController = presentedController
}
currentController.present(navController, animated: true, completion: nil)
}
}
You can try out this code
func switchRootViewController(rootViewController: UIViewController, animated: Bool, completion: (() -> Void)?) {
guard let window = UIApplication.shared.keyWindow else { return }
if animated {
UIView.transition(with: window, duration: 0.5, options: .transitionCrossDissolve, animations: {
let oldState: Bool = UIView.areAnimationsEnabled
UIView.setAnimationsEnabled(false)
window.rootViewController = rootViewController
UIView.setAnimationsEnabled(oldState)
}, completion: { (finished: Bool) -> () in
if (completion != nil) {
completion!()
}
})
} else {
window.rootViewController = rootViewController
}
}
Any view controller you want to set root just call the below function like
UIApplication.shared.setRootVC(vc)
extension UIApplication {
func setRootVC(_ vc : UIViewController){
self.windows.first?.rootViewController = vc
self.windows.first?.makeKeyAndVisible()
}
}
Just write this and you are good to go.
let sb = UIStoryboard(name: "Main", bundle: nil)
let VC = sb.instantiateViewController(withIdentifier: "LoginViewController") as! LoginViewController
let navRootView = UINavigationController(rootViewController: VC)
self.present(navRootView, animated: true, completion: nil)
This is how you can set the nib as root view controller.
let vc = HomeViewController(nibName: "HomeViewController", bundle: nil)
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = vc
window?.makeKeyAndVisible()
Link to a related question
This answer applies to usage of an existing ViewController from somewhere in the current stack without instantiating and reconfiguring a new controller.
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 remove the ViewController from the stack to assure its view won't be removed.
This resulted in my case in the 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()
}
Swift 4
let storyBoard = UIStoryboard(name: "Your_Storyboard_Name", bundle:Bundle.main)
self.window = UIWindow(frame: UIScreen.main.bounds)
let yourVc = storyBoard.instantiateViewController(withIdentifier: "YourIdentifier") as? YourViewController
if let window = window {
window.rootViewController = yourVc
}
window?.makeKeyAndVisible()
Swift 4,5 and above
If you Use Multiple or single Story board you want to set Different Root view controller of Navigation Controler then I use This Method:
In My case StoryBaord Name is Auth.
func setRootToLogin(transition :CATransition) {
let storyboard = UIStoryboard(name: "Auth", bundle: nil)
let loginNav = storyboard.instantiateViewController(withIdentifier: "AuthNavigationController") as! UINavigationController
window.set(rootViewController: loginNav, withTransition: transition)
let vc = window.rootViewController as! UINavigationController
let loginvc = LoginViewController.instantiateAuth()
vc.setViewControllers([loginvc], animated: true)
}