How to present a view controller from appdelegate xcode 11 swift 5 - ios

I have been searching all day on how to present a view controller from within the appdelegate. It appears that in xcode 11 the window property was moved to the scenedelegate which has been confusing me. I want to present a view controller from within the appdelegate from the didReceiveRemoteNotification function so when the user receives a notification it takes them to a separate view controller with information. I have tried to do:
self.window?.rootViewController?.present(LoginViewController(), animated: false, completion: nil)
within the appdelegate which used to work in a previous application of mine but it does not seem to work anymore. Any help would be much appreciated.

I was able to solve this issue by using shared windows to get the window from scenedelegate to present the view controller on.
UIApplication.shared.windows.first?.rootViewController?.present(vc, animated: false, completion: nil)

Best approach to present view controller through app delegate is without falling for hierarchy like below:
if let vc = UIStoryboard(name: "YOURSTORYBOARD", bundle: nil).instantiateViewController(withIdentifier: "YOURVIEWCONTROLLER") as? YOURVIEWCONTROLLER {
if let window = self.window, let rootViewController = window.rootViewController {
var currentController = rootViewController
while let presentController = currentController.presentedViewController {
currentController = presentController
}
currentController.present(vc, animated: true, completion: nil)
}
}

Related

Use Storyboards in Dynamic Framework

I am trying to create a dynamic framework in swift.
My Framework contains a ViewController inside a Storyboard.
I am not able to open my ViewController when a push notification comes inside my framework.
However i tried to use the below code :
let bundle = Bundle(identifier: "my_Framework_bundleID")
let frameworkStoryboard: UIStoryboard = UIStoryboard.init(name: "MyFrameworkStoryboard", bundle: bundle)
let viewController = frameworkStoryboard.instantiateViewController(withIdentifier: "MyViewController")
self.present(viewController, animated: true, completion: nil)
Doing this only executes the code inside MyViewController's viewDidLoad method but does not show the loads the UI.
I also tried this:
let bundle = Bundle(identifier: "my_Framework_bundleID")
let controller = UIViewController(nibName: "MyViewController", bundle: bundle)
present(controller, animated: true, completion: nil)
Doing this i get the following error:
Attempt to present UIViewController: 0x135124920 on MyFramework.anotherViewController: 0x133df1a40 whose view is not in the window hierarchy!
self.pushviewController() and self.show() doesn't work as well.
I am stuck in this problem for two days.
I figured out a way to make it work
In the Appdelegate of my app where i am using my framework i passed the instance of rootviewcontroller like this:
test.myFunc(withRootView: (self.window?.rootViewController)!)
Now in my Framework i created a xib and a corresponding UIView file for it.
To navigate to this UIView file i used below code in "myFunc" function.
public func myFunc(withRootView rootview: UIViewController)
let modalView = MyUIViewClass(frame: self.view.bounds)
if rootview.presentedViewController == nil {
rootview.view.addSubview(modalView)
} else {
rootview.presentedViewController?.view.addSubview(modalView)
}

Pop to rootviewcontroler containing presented navigation as well

My app architecture is :
RootNavigationController
-(rootVC)> HomeVC
-(pushed)> SecondVC
-(presented)> SecondNavigationController
-(rootVC)> ThirdVC
-(pushed)> FourthVC.
Now there is an option of returning to HomeVC on FourthVC.
What I have tried:
1)
APPDELEGATE.window?.rootViewController = UIStoryboard.HomeNavigationViewController()
Problem :
This way all VCs added on RootNavigationController are not deinitilized and memory leaked.
2)
self.dismiss(animated: true, completion: nil)
(self.navigationController?.presentingViewController as? UINavigationController)?.viewControllers.removeAll()
APPDELEGATE.window?.rootViewController = UIStoryboard.HomeNavigationViewController()
This way its working fine but what I need to know is there any better way of doing it.

iOS Swift Navigate to certain ViewController programmatically from push notification

I use push notifications to inform the user about different types of events happening in the app. A certain type of push notification should open a special viewcontroller (f.e a notification about a new chat message does navigate to the chat on click)
To achieve this, i tried the following code inside my app delegate:
func applicationDidBecomeActive(_ application: UIApplication) {
if var topController = UIApplication.shared.keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
topController.tabBarController?.selectedIndex = 3
}
}
It does not move anywhere. What am I missing here?
I wrote a simple class to navigate to any view controller in the view hierarchy from anywhere in one line of code by just passing the class type, so the code you'll write will be also decoupled from the view hierarchy itself, for instance:
Navigator.find(MyViewController.self)?.doSomethingSync()
Navigator.navigate(to: MyViewController.self)?.doSomethingSync()
..or you can execute methods asynchronously on the main thread also:
Navigator.navigate(to: MyViewController.self) { (MyViewControllerContainer, MyViewControllerInstance) in
MyViewControllerInstance?.doSomethingAsync()
}
Here's the GitHub project link: https://github.com/oblq/Navigator
This is what you need.
Use your navigation controller to push your notification view controller
let mainStoryBoard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let navigationController : UINavigationController = mainStoryBoard.instantiateViewController(withIdentifier: "MainNavigationController") as! UINavigationController
let notifcontrol : UIViewController = (mainStoryBoard.instantiateViewController(withIdentifier: "NotificationViewController") as? NotificationViewController)!
navigationController.pushViewController(notifcontrol, animated: true)
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()
The topController was already my TabBarController in this case!

How to present different view controllers from appdelegate in ios

Actually I am having navigationcontroller as root controller it is embed in main.storyboard,i am having two screens one screen login and another one home as per login credentials i need to skip login screen and i need to show home screen.From appdelegate i am doing this skipping it is not working properly
Unbalanced calls to begin/end appearance transitions for <UINavigationController: 0x7fadf384c600>.
let storyboard=UIStoryboard.init(name: "Main", bundle: nil)
let navigationController=storyboard.instantiateInitialViewController()
let username=UserDefaultUtil.getString(key: AppConstants.PREF_USERID)
print(username!)
if username != ""
{
window?.rootViewController=navigationController
let sectionController=SectionController(nibName: "SectionController" , bundle: nil)
navigationController?.present(sectionController, animated: true, completion: nil)
}
I guess you are trying to present your sectionController in navigationController, its not really how it works, try this code:
let navigationController = self.storyboard?.instantiateInitialViewController() as! UINavigationController
and replace the present with this:
navigationController.setViewControllers([sectionController], animated: false)
or just drop the navigationController instantiate and create it with code and set it as window?.rootViewController:
let sectionController=SectionController(nibName: "SectionController" , bundle: nil)
let nav = UINavigationController(rootViewController: sectionController)
window?.rootViewController = nav
First, check the user credentials in the login page. Then use:
if hasCredentials {
let vc:AnyObject! = self.storyboard?.instantiateViewController(withIdentifier: "someViewController")
self.show(vc as! UIViewController, sender: vc)
}
Sidenote: Personally, I do this from the login page because it simplifies the process and I do not like having weight sitting in my AppDelegate. If you were thinking you did not want people seeing your login screen who are already members, you can do it from an AppDelegate, but take into account the user experience might be diminished during the loading process if this is the route you decide to take.

Attempt to present UINavigationController whose view

AppDelegate
initialViewController = storyboard.instantiateViewControllerWithIdentifier("LoginViewController")as! UIViewController
}
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
Takes me to LoginViewController
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("CardsNavController") as? UIViewController
self.presentViewController(vc!, animated: true, completion: nil)
I click on the login button takes me to CardsViewController
func goToProfile(button: UIBarButtonItem) {
pageController.goToPreviousVC()
}
Clicking on back button which is part of the UINavigationController runs goToProfile (above) and takes me to ViewController.swift because that's where the pageController is declared.
let pageController = ViewController(transitionStyle: UIPageViewControllerTransitionStyle.Scroll, navigationOrientation: UIPageViewControllerNavigationOrientation.Horizontal, options: nil)
The error shows up here
func goToPreviousVC() {
//let currentControllers = self.navigationController?.viewControllers
if viewControllers.isEmpty {
setViewControllers([profileVC], direction: UIPageViewControllerNavigationDirection.Reverse, animated: true, completion: nil)
pageController.presentViewController(profileVC, animated: true, completion: nil)
else {
let previousVC = pageViewController(self, viewControllerBeforeViewController: viewControllers[0] as! UIViewController)!
setViewControllers([previousVC], direction: UIPageViewControllerNavigationDirection.Reverse, animated: true, completion: nil)
}
Error:
Warning: Attempt to present <UINavigationController: 0x15ce314e0> on <MyApp.ViewController: 0x15cd2c6f0> whose view is not in the window hierarchy!
Basically the flow is like this AppDelegate -> LoginViewController -> ViewController and I need to get to ProfileViewController.
ProfileView has ProfileNavController while CardsView has CardsNavController
ViewController has a storyboardID of pageController.
Both ProfileView and and CardsView are embedded within UINavigationControllers (hence the NavController extension).
The second time I run the app after a fresh install it works perfectly (all the controllers get loaded okay). Should I push viewControllers in AppDelegate?
I've checked your code using Xcode 7, which may not be ideal for resolving this issue because I had to covert your code to Swift 2.0, but here was what I found out.
ISSUE
First time opening the app, this block:
if currentUser() != nil {
initialViewController = pageController
}
else {
initialViewController = storyboard.instantiateViewControllerWithIdentifier("LoginViewController") as UIViewController
}
self.window?.rootViewController = initialViewController
Will initialize LoginViewController and make it the current window's rootViewController.
At this point there is no pageController initialized
When user taps on the button to go to the Profile screen, this method will be called
func goToProfile(button: UIBarButtonItem) {
pageController.goToPreviousVC()
}
At this point, pageController is initialized, and off course, there is NOTHING in the viewControllers array. Let's see what happen in the goToPreviousVC method:
Original method looks like this:
let nextVC = pageViewController(self, viewControllerAfterViewController: viewControllers[0] as UIViewController)!
setViewControllers([nextVC], direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: nil)
One thing you can see obviously is: calling viewControllers[0] could give you a crash because viewControllers is an empty array.
If you use Swift 2.0, it doesn't even let you compile your code :)
SOLUTION
Let's go directly to the solution: Ensure that the pageController is available before trying to call it's viewControllers.
I blindly tried fixing you code in Swift 2.0 and found out that this method would work for you:
BEFORE: In LoginViewController.swift line 63
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("CardsNavController") as? UIViewController
self.presentViewController(vc!, animated: true, completion: nil)
AFTER: Let's fix it like this
let navc = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("CardsNavController") as! UINavigationController
if let viewControllers = pageController.viewControllers where viewControllers.count == 0 {
pageController.setViewControllers([navc.viewControllers[0]], direction: .Forward, animated: false, completion: nil)
}
self.presentViewController(pageController, animated: true, completion: nil)
It's working well here and probably I don't need to show you how the screen transition should look like :)
In case you would like to have the fixed source code as well, please find it HERE. Basically I converted your code to Swift 2.0 and ignored unnecessary parts like Facebook authentication for faster investigation.
Good luck & Happy coding!

Resources