Access ViewController in didFinishLaunchingWithOptions - 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
}

Related

Issue with presenting viewcontroller in iOS 13, Xcode 11

I'm trying to present a viewcontroller on topMostViewController. It's working in iOS 12 and lower. But on iOS 13 I'm getting this error:
Manually adding the rootViewController's view to the view hierarchy is no longer supported. Please allow UIWindow to add the rootViewController's view to the view hierarchy itself.
I have checked on iOS 12 and lower, and the code below works fine. But on iOS 13 I am having trouble presenting the view controller. I printed on viewDidLoad; it's getting printed but the view doesn't appear.
func presentInWindow(animated flag: Bool = true, completion: (() -> Void)? = nil) {
DispatchQueue.main.async {
var alertWindow: UIWindow?
alertWindow = UIWindow(frame: UIScreen.main.bounds)
alertWindow?.windowLevel = UIWindow.Level.alert + 1
alertWindow?.rootViewController = UIApplication.topViewController()
if let rootViewController = alertWindow?.rootViewController {
alertWindow?.makeKeyAndVisible()
rootViewController.present(self, animated: flag, completion: completion)
}
}
}
static func topViewController() -> UIViewController? {
var topViewController: UIViewController?
if #available(iOS 13.0, *) {
topViewController = shared.connectedScenes
.filter({$0.activationState == .foregroundActive})
.map({$0 as? UIWindowScene})
.compactMap({$0})
.first?.windows
.filter({$0.isKeyWindow}).first?.rootViewController
} else {
topViewController = shared.delegate?.window??.rootViewController
}
while true {
if let presented = topViewController?.presentedViewController {
topViewController = presented
} else if let nav = topViewController as? UINavigationController {
topViewController = nav.visibleViewController
} else {
break
}
}
return topViewController
}
This code will work to create a view controller on top. You can adjust the size of the view controller on this line: popOverVC.view.frame = lSs I'm not sure if this code is exactly what you are asking for, but if you need a quick solution, it will present view controllers in swift 5, iOS 13, and xcode 11. Note that it is a child view controller, so if you remove the parent, it will leave too. Simply change self to ViewController that you want to present on.
let popOverVC = UIStoryboard(name: "yourSB", bundle: nil).instantiateViewController(withIdentifier: "vcYouWantID") as! vcYouWant
self.addChild(popOverVC)
let lSs = UIScreen.main.bounds
popOverVC.view.frame = lSs
popOverVC.view.tag = tag
self.view.addSubview(popOverVC.view)
popOverVC.didMove(toParent: self)
I received the same error while updating an app which worked fine previously. It looks like the new requirement for AppDelegate.swift is:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) { }
}
hth
I encountered the same issue tonight. I tried the methods suggested by people on the Net including Stackoverflow, but none had worked.
Just now, as if magic was working, I tried to form an outlet from the ViewController (visually the "background" layer for all subviews) in the Main.storyboard to the ViewConstroller class in ViewController.swift. It worked in my project for iOS 13 simulator in Xcode 12.2.
Hope this trick will also work for you.
I found this to fix my problem (in old Objective-C code):
// old way broken in iOS 13
//appDelegate.window.rootViewController = vc;
// this works in iOS 13
[appDelegate.window setRootViewController:vc];

Present login view controller modally in swift tabbed application

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)
}

Swift: Passing managedObjectContext from AppDelegate to UINavigationController

I have some iOS project (Project) with an #escaping method in the AppDelegate that is used to create the NSPersistentContainer for this project. This method is then called within the didFinishLaunchingWithOptions method as follows:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var persistentContainer: NSPersistentContainer!
func createProjectContainer(completion: #escaping (NSPersistentContainer) -> ()) {
let container = NSPersistentContainer(name: "Project")
container.loadPersistentStores { (_, error) in
guard error == nil else { fatalError("Failed to load store: \(error!)") }
DispatchQueue.main.async { completion(container) }
}
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
createProjectContainer { container in
self.persistentContainer = container
let storyboard = self.window?.rootViewController?.storyboard
guard let vc = storyboard?.instantiateViewController(withIdentifier: "NavigationController") as? NavigationController else {
fatalError("Cannot instantiate root view controller")
}
vc.managedObjectContext = container.viewContext
self.window?.rootViewController = vc
}
return true
}
}
The NavigationController class is simply a subclass of UINavigationController, which embeds a UIViewController (ChildViewController) and has an NSManagedObjectContext variable (managedObjectContext), which I want to pass to the first ChildViewController:
class NavigationController: UINavigationController {
var managedObjectContext: NSManagedObjectContext!
override func viewDidLoad() {
super.viewDidLoad()
guard let vc = self.childViewControllers.first as? ChildViewController else { fatalError("") }
vc.managedObjectContext = managedObjectContext
print("NavigationController: viewDidLoad")
print("managedObjectContext ---> \(managedObjectContext)")
}
}
Now the print("NavigationController: viewDidLoad") appears to be called twice. On the first call, managedObjectContext = nil, on the second call it has been assigned. Does this matter that the app is loading this twice?
It seems to be happening during the storyboard?.instantiateViewController(withIdentifier: "NavigationController") which is called after the first time that NavigationController has been loaded due to the #escaping property of the closure. However, if I exclude that line, as just get the reference to the NavigationController the managedObjectContext appears never to be assigned.

Blank screen first time launching app xcode

I am having a little issue with logging into an app the first time with a blank screen and I am getting the warning "Attempt to present on whose view is not in the window hierarchy!" After I close and relaunch, the views appear fine. I believe it has something to do with the rootViewController but not sure... Thanks in advance for any help or direction!
App delegate
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var ref:FIRDatabaseReference?
var databaseHandle:FIRDatabaseHandle?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.rootViewController = MainNavigationController()
FIRApp.configure()
ref = FIRDatabase.database().reference()
return true
}
Navigation controller as rootViewController
class MainNavigationController: UINavigationController {
var segmentedController: UISegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
let vc1 = TravelersFeedVC()
let vc2 = ProfileVC()
if isLoggedIn() {
// assume user is logged in
let homeController = HomeController()
viewControllers = [homeController]
homeController.firstViewController = vc1
homeController.secondViewController = vc2
} else {
perform(#selector(showLoginController), with: nil, afterDelay: 0.01)
}
}
fileprivate func isLoggedIn() -> Bool {
return UserDefaults.standard.isLoggedIn()
}
func showLoginController() {
let loginController = LoginController()
present(loginController, animated: true, completion: {
// perhaps do something here later
})
}
}
// log in function called
func finishLoggingIn() {
let rootViewController = UIApplication.shared.keyWindow?.rootViewController
guard let mainNavigationController = rootViewController as? MainNavigationController else { return }
let vc1 = TravelersFeedVC()
let vc2 = ProfileVC()
if isLoggedIn() {
// assume user is logged in
let homeController = HomeController()
mainNavigationController.viewControllers = [HomeController()]
homeController.firstViewController = vc1
homeController.secondViewController = vc2
} else {
perform(#selector(showLoginController), with: nil, afterDelay: 0.01)
}
UserDefaults.standard.setIsLoggedIn(value: true)
dismiss(animated: true, completion: nil)
}
Ok, so my last answer was wrong (i deleted it), the things is that in app there is a keywindow, which is your navcontroller, and you cannot present anything on this one untill it will load its subviews (even if it hasn't any), and that would happend on viewdidappear, so you should put your code from viewdidload there.

iOS firebase: FIRAuthUIDelegate.authUI not being called

I am trying to launch google login from AppDelegate.swift and then launch my app's main screen upon login success.
I am able to
show the google login button as shown above
the user is sent to google to sign in
the user is sent back to original (step 1)
After step 3. I'd like to send the user to my app's main page.
My code is below. The problem I'm having is that authUI is not being called.
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, FIRAuthUIDelegate {
var window: UIWindow?
var authUI: FIRAuthUI?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
FIRApp.configure()
authUI = FIRAuthUI.defaultAuthUI()
authUI?.delegate = self
let providers: [FIRAuthProviderUI] = [FIRGoogleAuthUI()]
authUI?.providers = providers
// show google login button
let authViewController = authUI?.authViewController()
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window?.rootViewController = authViewController
self.window?.makeKeyAndVisible()
return true
}
func application(application: UIApplication, openURL url: NSURL, options: [String: AnyObject]) -> Bool {
return GIDSignIn.sharedInstance().handleURL(url, sourceApplication: options[UIApplicationOpenURLOptionsSourceApplicationKey] as? String, annotation: options[UIApplicationOpenURLOptionsAnnotationKey])
}
func authUI(authUI: FIRAuthUI, didSignInWithUser user: FIRUser?, error: NSError?) {
// launch main view controller
}
}
EDIT: This appears to be a duplicate of another question. The other question's title is quite general and only gets to the details a few lines deep. In any case, I believe Chris's answer is more thorough than the one there. I think both the question and answers here are clearer, more pointed and more thorough so it would be a mistake to just direct people here to go there as would happen if this was marked as a duplicate.
I think your problem lies here, in the - (void)signInWithProviderUI:(id<FIRAuthProviderUI>)providerUI method.
The delegate method is called in the dismissViewControllerAnimated:completion: completion block.
[self.navigationController dismissViewControllerAnimated:YES completion:^{
[self.authUI invokeResultCallbackWithUser:user error:error];
}];
As you can see from the Apple docs, this method is expected to be called on a modally presented viewController. You are displaying it as a root view controller. Try displaying it with a modal from a UIViewController, and things should work out. To debug this try and set a breakpoint at line 193 to see that it won't get hit. I would be very surprised if this doesn't work when you display the authController modally.
To come up with a possible solution to your problem (I am assuming you want to ensure a user is signed in before using your app). The below is a simplification of what I am using in an app currently.
EDIT: Updated for the new 1.0.0 FirebaseUI syntax.
class MainTabController: UITabBarController, FIRAuthUIDelegate {
let authUI: FUIAuth? = FUIAuth.defaultAuthUI()
override func viewDidLoad() {
super.viewDidLoad()
var authProviders = [FUIFacebookAuth(), FUIGoogleAuth()]
authUI.delegate = self
authUI.providers = authProviders
//I use this method for signing out when I'm developing
//try! FIRAuth.auth()?.signOut()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !isUserSignedIn() {
showLoginView()
}
}
private func isUserSignedIn() -> Bool {
guard FIRAuth.auth()?.currentUser != nil else { return false }
return true
}
private func showLoginView() {
if let authVC = FUIAuth.defaultAuthUI()?.authViewController() {
present(authVC, animated: true, completion: nil)
}
}
func authUI(_ authUI: FUIAuth, didSignInWith user: FIRUser?, error: Error?) {
guard let user = user else {
print(error)
return
}
...
}
It must be a problem of reference.
class AppDelegate: UIResponder, UIApplicationDelegate, FIRAuthUIDelegate {
var window: UIWindow?
let authUI = FIRAuthUI.defaultAuthUI()
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
FIRApp.configure()
authUI.delegate = self
let providers: [FIRAuthProviderUI] = [FIRGoogleAuthUI()]
authUI.providers = providers
// show google login button
let authViewController = authUI.authViewController()
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window?.rootViewController = authViewController
self.window?.makeKeyAndVisible()
return true
}
}
Try this. AppDelegate will hold the reference of authUI and its delegate.