I'm trying to present a view controller modally from my app delegate, however, I'm receiving the below error. When creating my project I chose a tabbed application. I need help resolving this error and presenting the view controller. Would also appreciate code to dismiss the view controller too
2018-10-03 20:28:57.324 App[74397:2825933] Warning: Attempt to present <App.SignUpViewController: 0x7fbd7b608c60> on <UITabBarController: 0x7fbd7b407620> whose view is not in the window hierarchy!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
showSignUpView()
return true
}
func showSignUpView() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let signUpViewController = storyboard.instantiateViewController(withIdentifier:"SignUpViewController") as! SignUpViewController
signUpViewController.modalPresentationStyle = UIModalPresentationStyle.fullScreen
signUpViewController.modalTransitionStyle = UIModalTransitionStyle.coverVertical
window?.rootViewController?.present(signUpViewController, animated: true, completion: nil)
}
Related
I am trying to Navigate to my main content after integration google sign in a separate Login Page. Before creating the login page, my app's entry point was the Navigation controller.. Now I needed users to login before that so I added a View Controller with google sign in button, and the sign in is working fine. this is my storyboard:
Now I want if the sign in succeed, to display the app's main content view (which is as if the entry point is the Navigation Controller from now on). I tried the following approach:
#IBAction func signIn(_ sender: Any) {
let signInConfig = GIDConfiguration.init(clientID: "xyz.apps.googleusercontent.com")
GIDSignIn.sharedInstance.signIn(with: signInConfig, presenting: self) { user, error in
guard error == nil else { return }
// If sign in succeeded, display the app's main content View.
let vc = self.storyboard?.instantiateViewController(withIdentifier: "scanReceiptsViewController") as! ViewController
self.navigationController?.pushViewController(vc, animated: true)
self.present(vc, animated: true, completion: nil)
}
}
There are 2 problems:
the new viewcontroller is presented modally, users can very easily go back to the login page and it distrubs the flow of my app
user will need to login everytime to navigate to the main content view controller, I want that if signed in the entry point becomes the main storyboard from now on. if that makes sense
Thank you and sorry I'm new into learning swift!
I would recommend that you keep your navigation controller (I will refer to it from now on as ScanRecipientsViewController) as the default app entry point, since users only need to see the login screen if they are not logged in, which is most of the time not the case. This will give us the benefit of a slightly faster and smoother performance at the app start when the user is logged in.
You could handle this case in your AppDelegate's func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool method. For example, check if the user is logged in, if not, set your LoginController as the new root controller, which will replace your ScanRecipientsViewController that was set as root by the storyboard.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if GIDSignIn.sharedInstance.currentUser == nil { // just a guess, please refer to the documentation on how to check if the user is logged in
let storyboard = UIStoryboard(name: "Login", bundle: nil)
/*
If you use the SceneDelegate in your project, you will need to retrieve the window object from it instead of from the AppDelegate.
let window = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window
*/
window?.rootViewController = storyboard.instantiateViewController(withIdentifier: "LoginController")
}
return true
}
After the user successfully logs in, you can create a new instance of your ScanRecipientsViewController and set it as the root controller back again. You can do this by e.g. creating a public method in your AppDelegate:
extension AppDelegate {
func openRecipientsController() {
let storyboard = UIStoryboard(name: "Home", bundle: nil)
/*
If you use the SceneDelegate in your project, you will need to retrieve the window object from it instead of from the AppDelegate.
let window = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window
*/
window?.rootViewController = storyboard.instantiateInitialViewController()
}
}
and in your LoginViewController:
#IBAction func signIn(_ sender: Any) {
let signInConfig = GIDConfiguration.init(clientID: "xyz.apps.googleusercontent.com")
GIDSignIn.sharedInstance.signIn(with: signInConfig, presenting: self) { user, error in
guard error == nil else { return }
// If sign in succeeded, display the app's main content View.
(UIApplication.shared.delegate as? AppDelegate)?.openRecipientsController()
}
}
As a last thing, you normally don't use both pushing and presenting at the same time, you need to choose one of the two options.
let vc = self.storyboard?.instantiateViewController(withIdentifier: "scanReceiptsViewController") as! ViewController
self.navigationController?.pushViewController(vc, animated: true)
self.present(vc, animated: true, completion: nil)
I wish you lots of fun learning iOS development with Swift!
I want to access the initial view controller through
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
I happen to be using a navigation controller - but that doesn't matter really.
I've tried the following:
UIApplication.shared.keyWindow!.rootViewController
UIApplication.shared.delegate?.window
application.windows[0].rootViewController
self.window?.rootViewController
application.keyWindow
and all are nil.
I want to use the storyboard and inject my dependency here. I don't want to instantiate a view controller here, I still want to use the initial view from the storyboard.
How can I access the view in didFinishLaunchingWithOptions - I want this for iOS12?
If I understand your question you want to access the TopMost ViewController presented, if so you can try to use this function in your AppDelegate
func topMostController() -> UIViewController? {
if var topController = UIApplication.shared.keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
return topController
}
return nil
}
To use this:
if let vc = self.topMostController() {
// Your code to use vc instance of topMostController
}
so when i launch my app the first time there is a welcome ViewController. How can i set a function, that shows the second ViewController when the user launches the app the second time.
Add a Boolean in the user default in your application to check whether the application is launch first time or not. Based on this Boolean value load another ViewController in your app delegate class.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let isFirst = UserDefaults.standard.bool(forKey: “isLaunched”) // edited this after rmaddy's comment
var viewControllerWithIdentifier = "SecondViewController"
if !isFirst {
UserDefaults.standard.set(true, forKey: “isLaunched”)
viewControllerWithIdentifier = "FirstViewController"
}
let mainStoryboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController : UIViewController = mainStoryboard.instantiateViewControllerWithIdentifier(viewControllerWithIdentifier) as UIViewController
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
return true
}
I have an Onboarding screen that I'm showing to the new users when they first open the app.
In my appDelegate I check whether is the first launch or not.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.main.bounds)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
var initialViewController = storyboard.instantiateViewController(withIdentifier: "OnBoarding")
let userDefaults = UserDefaults.standard
if userDefaults.bool(forKey: "onBoardingComplete") {
initialViewController = storyboard.instantiateViewController(withIdentifier: "MainApp")
}
window?.rootViewController = initialViewController
window?.makeKeyAndVisible()
}
Also I have a collectionViewCell that I have some buttons and when I click them I get an Alert with informations.
Example of one button
#IBAction func guide3Btn(_ sender: Any) {
let infoVC = infoService.info(title: "Title", body: "Information")
self.window?.rootViewController?.present(infoVC, animated: true, completion: nil)
}
When the user first launches the app if he clicks the info button gets this:
Warning: Attempt to present <MyApp.InfoViewController: 0x7f91db45cfb0> on <MyApp.OnbBoardViewController: 0x7f91db506af0> whose view is not in the window hierarchy!
If the user reopens the app everything is ok. I know that when we have first launch we have onBoarding as root controller but I can't understand how to fix this.
Update
This is the infoService class. I use a new storyboard to create the alert.
class InfoService {
func info(title: String, body: String) -> InfoViewController {
let storyboard = UIStoryboard(name: "InfoStoryboard", bundle: .main)
let infoVC = storyboard.instantiateViewController(withIdentifier: "InfoVC") as! InfoViewController
infoVC.infoBody = body
infoVC.infoTitle = title
return infoVC
}
}
You can try add your storyboard instantiate code blocks to main thread using DispatchQueue.main.async like below:
I solved almost all of my whose view is not in the window hierarchy! problems.
DispatchQueue.main.async {
let infoVC = storyboard.instantiateViewController(withIdentifier: "InfoVC") as! InfoViewController
infoVC.infoBody = body
infoVC.infoTitle = title
}
return infoVC
Referenced from : https://stackoverflow.com/a/45126338/4442254
After completion of launchScreeen(Splash) I want to be able to select which storyboard to launch. For example launch login.storyboard if user is not logged in or launch dashboard.storyboard if user is logged in. Currently Main.storyboard is launched after launchScreen. in appdelegate I have a code for checking the login status as follows:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
var launchDashBoard = false
let isUserLoggedIn = UserDefaults.standard.object(forKey: TAG_IS_USER_LOGGEDIN) as? Bool
if isUserLoggedIn != nil {
launchDashBoard = isUserLoggedIn!
}
if launchDashBoard {
self.loadDashBoard()
}else{
self.loadIntro()
}
return true
}
func loadHome(){
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let home = storyboard.instantiateViewController(withIdentifier: "dashboard") as! Dashboard
let navigationController = UINavigationController(rootViewController: home)
self.window?.rootViewController = navigationController
}
func loadLogin(_ viewController: UIViewController?){
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let home = storyboard.instantiateViewController(withIdentifier: "signIn") as! SignInVC
home.previousViewController = viewController
let navigationController = UINavigationController(rootViewController: home)
self.window?.rootViewController = navigationController
}
If I run the app with this code it crashes with following log:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Pushing a navigation controller is not supported'
*** First throw call stack:
Is there a way to include a function that determines which storyboard to launch on launchScreen loading is finished or is there something wrong with my code?
You can create a new Navigation.storyboard. That storyboard can have an initial view controller(Call it StartUpNavigationViewController), that will be opened when Launch screen has been shown.
In that StartUpNavigationViewController, check for the logic if the user is logged in or not, based on that you can navigate to login or dashboard storyboard.