Issue Present View Controller with Custom URL Scheme - ios

In Storyboard: NavigationController -> UITableViewController
appdelegate.swift
func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navVC = storyboard.instantiateViewControllerWithIdentifier("navViewController") as! UINavigationController
if let tableVC = navVC.topViewController as? UITableViewController {
tableVC.searchText = // URL query param
}
self.window?.rootViewController?.presentViewController(navVC, animated: false, completion: nil)
}
return true
}
The problem was when I enter with custom URL in Safari in second time
appUrl://search?q=...
The first time it is Ok, open correctly, but if I come back to Safari to open another URL I got
Warning: Attempt to present <UINavigationController: 0x7fb1fb090000> on <UINavigationController: 0x7fb1fa843400> whose view is not in the window hierarchy!

I think the problem is that the rootViewController tries to present two UIViewController which triggers the warning message.How about presenting the second UIViewController on the presented UIViewController.
func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navVC = storyboard.instantiateViewControllerWithIdentifier("navViewController") as! UINavigationController
if let tableVC = navVC.topViewController as? UITableViewController {
tableVC.searchText = // URL query param
}
self.window?.rootViewController?.presentedViewController?.presentViewController(navVC, animated: false, completion: nil)
}
return true
}

Try this
func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navVC = storyboard.instantiateViewControllerWithIdentifier("navViewController") as! UINavigationController
if let tableVC = navVC.topViewController as? UITableViewController
{
tableVC.searchText
}
if let presented = window?.rootViewController?.presentedViewController?.presentedViewController
{
presented.dismissViewControllerAnimated(false, completion: {
self.window?.rootViewController?.presentedViewController?.presentViewController(navVC, animated: false, completion: nil)
})
}
else
{
self.window?.rootViewController?.presentedViewController?.presentViewController(navVC, animated: false, completion: nil)
}
return true
}

Related

Presenting different UIViewController on the app Launch

I need to present different UIViewController on the launch of my app. I have a "login" page for the user to interact with. Once the user logs in or creates an account it takes to a different UIViewController with a map to interact with. I've looked online and so far I know that we should do it within AppDelegate.swift file. I have not completed the statement whether or not the user has logged in since I am still running into some errors
AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Thread.sleep(forTimeInterval: 2.0)
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.rootViewController = MainNavigationContoller()
return true
}
I also have another swift file with MainNavigationContoller that should call the mainviewController
override func viewDidLoad() {
super.viewDidLoad()
let isloggedIn = false
if isloggedIn == false {
self.present(mainViewController(), animated: true, completion: nil)
} else {
self.present(mapViewController(), animated: true, completion: nil)
}
}
The app launches with the launchScreen but then sends errors to mainViewController such as Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
You are not instantiating the View Controllers properly.
Here is a function that I use to make a root view controller, put this into your AppDelegate.
func makeRootVC(storyBoardName : String, vcName : String) {
let vc = UIStoryboard(name: storyBoardName, bundle: Bundle.main).instantiateViewController(withIdentifier: vcName)
let nav = UINavigationController(rootViewController: vc)
nav.navigationBar.isHidden = true
self.window?.rootViewController = nav
let options: UIView.AnimationOptions = .transitionCrossDissolve
let duration: TimeInterval = 0.6
UIView.transition(with: self.window!, duration: duration, options: options, animations: {}, completion: nil)
}
Now in your Appdelegate, replace your code with this:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Thread.sleep(forTimeInterval: 2.0)
self.makeRootVC(storyboardName: "Main", vcName : "YourVCStoryboardId")
return true
}
and in your MainNavigationController,
override func viewDidLoad() {
super.viewDidLoad()
let isloggedIn = false
let appDelegateObj = UIApplication.shared.delegate as! AppDelegate
if isloggedIn == false {
appDelegateObj.makeRootVC(storyboardName: "Main", vcName: "mainViewController")
} else {
appDelegateObj.makeRootVC(storyboardName: "Main", vcName: "mapViewController")
}
}
NOTE: Open storyboard and give every controller a StoryboardID. What I prefer is naming them the same as ViewController's name as it is easy to remember. and in vcName, we need to pass the storyboarID of the controller we want to present.
UPDATE:
Above code is for making a root view controller, if you want to push controllers, you can use this code instead:
extension UIViewController {
func pushVC(storyboardName : String, vcname : String) {
let vc = UIStoryboard.init(name: storyboardName, bundle: Bundle.main).instantiateViewController(withIdentifier: vcname)
vc.hidesBottomBarWhenPushed = true
self.navigationController?.pushViewController(vc, animated: true)
}
}
In your MainNavigationController if instead of making root view controllers in viewDidLoad, you want to just push the controller, then use the above code like this:
override func viewDidLoad() {
super.viewDidLoad()
let isloggedIn = false
if isloggedIn == false {
self.pushVC(storyboardName: "Main", vcName: "mainViewController")
} else {
self.pushVC(storyboardName: "Main", vcName: "mapViewController")
}
}

navigation bar disappears when opening view controller via deep link

I've used a tutorial to implement a method in app delegate to open specific view controllers from a HTML button from safari. my app delegate looks like this:
func application(_ app: UIApplication, open URL: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
print("url \(url)")
print("url host :\(url.host!)")
print("url path :\(url.path)")
let urlPath : String = (url.path as String?)!
let urlHost : String = (url.host as String?)!
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if(urlHost != "mywebsite.ir")
{
print("Host is not correct")
return false
}
if(urlPath == "/index"){
let innerPage: ViewController = mainStoryboard.instantiateViewController(withIdentifier: "HomeViewController") as! ViewController
self.window?.rootViewController = innerPage
}
self.window?.makeKeyAndVisible()
return true
}
and my HTML button reference is:
open app
but when my app home page opens, the navigation bar is gone and the user gets stuck and doesn't have any access to menu buttons or back button(in case of internal view controllers).
screenshots of home view controller before and after deep link:
Please update your code as below..you should have to put your navigation controller as a window root view controller..
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
print("url \(url)")
print("url host :\(url.host!)")
print("url path :\(url.path)")
let urlPath : String = (url.path as String?)!
let urlHost : String = (url.host as String?)!
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if(urlHost != "mywebsite.ir")
{
print("Host is not correct")
return false
}
if(urlPath == "/index"){
let innerPage: ViewController = mainStoryboard.instantiateViewController(withIdentifier: "HomeViewController") as! ViewController
let nav = UINavigationController.init(rootViewController: innerPage)
self.window?.rootViewController = nav
}
self.window?.makeKeyAndVisible()
return true
Does HomeViewController is the initial ViewController? If not, you could call the initial view controller that should be the view controller with a navigation controller.
Try to do this:
mainStoryboard.instantiateInitialViewController()
If the behaviour persists you could embed a NavigationController on code
let viewController = mainStoryboard.instantiateViewController(withIdentifier: "HomeViewController") as! ViewController
let navigationController = UINavigationController(rootViewController: viewController)
self.window?.rootViewController = navigationController
Also you don't need to call window.makeKeyAndVisible()

PresentViewController is showing nil of my RootViewController

So let me explain.
I have two ViewControllers (Login-Page and Main-Page).
Login page contains two buttons (FB and G+ login buttons).
Everything works fine with login credentials and is presenting the next view which is Main-Page.
In Main-Page I have button (sign-out).Button should work as singing out and bring me back to Login-Page, unfortunately is not working.
Below is my code
let vc = self.storyboard?.instantiateViewController(withIdentifier: "LoginPage") as! LoginPage
vc.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
self.present(vc, animated: true, completion: nil)
Is showing
fatal error: unexpectedly found nil while unwrapping an Optional value
and the output is nil for all declared IBOutlet in Main-Page
Please I need help!!!??
First we verify if the view controller has a Storyboard ID on the Identity inspector:
Then:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let loginViewController = storyboard.instantiateViewController(withIdentifier: "LoginPage")
let window = UIApplication.shared.windows[0] as UIWindow
UIView.transition(with: window,
duration: 0.5,
options: UIViewAnimationOptions.transitionCrossDissolve,
animations: {
window.rootViewController = loginViewController
}, completion: nil)
With that, you are replacing the presented view controller for your login view controller.
Try using notifications to change the root when you did logout for the app.
First in your appDelegate addObserver:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Notification used in logout action
let notf = NSNotificationCenter.defaultCenter()
notf.addObserverForName("logout", object: nil, queue: NSOperationQueue.mainQueue()) { (notification) in
let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("LoginPage") as! LoginPage
self.window?.rootViewController = controller
}
return true
}
then in the logoutAction simply postNotification
func logout() {
NSNotificationCenter.defaultCenter().postNotificationName("logout", object: nil)
}

3D touch quick actions preview view controller only one time

I have the following issue. When I run my app on my phone, 3d touch the icon and select the quick action it launches the app presenting the correct view controller but when I put the app in background and try to invoke the quick action it just opens the app where I have left it. So to make it work I have to kill my app every time.
Here is my code:
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
if shortcutItem.type == "com.traning.Search" {
let sb = UIStoryboard(name: "Main", bundle: nil)
let searchVC = sb.instantiateViewControllerWithIdentifier("searchVC") as! UINavigationController
let root = UIApplication.sharedApplication().keyWindow?.rootViewController
root?.presentViewController(searchVC, animated: false, completion: { () -> Void in
completionHandler(true)
})
}
}
Thanks in advance.
I'm guessing you're trying to present a view controller from a view controller that's not visible. You can use extensions like:
extension UIViewController {
func topMostViewController() -> UIViewController {
if self.presentedViewController == nil {
return self
}
if let navigation = self.presentedViewController as? UINavigationController {
return navigation.visibleViewController.topMostViewController()
}
if let tab = self.presentedViewController as? UITabBarController {
if let selectedTab = tab.selectedViewController {
return selectedTab.topMostViewController()
}
return tab.topMostViewController()
}
return self.presentedViewController!.topMostViewController()
}
}
extension UIApplication {
func topMostViewController() -> UIViewController? {
return self.keyWindow?.rootViewController?.topMostViewController()
}
}
You can place both of these in your app delegate.swift, above your app delegate class, to get the currently visible view controller. Then present the search view controller on that. For example:
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
if shortcutItem.type == "com.traning.Search" {
let sb = UIStoryboard(name: "Main", bundle: nil)
let searchVC = sb.instantiateViewControllerWithIdentifier("searchVC") as! UINavigationController
let topViewController = UIApplication.sharedApplication.topMostViewController()
topViewController.presentViewController(searchVC, animated: false, completion: { () -> Void in
completionHandler(true)
})
}
}

didReceiveRemoteNotification presentViewController from anywhere when running

I'm trying to present a UIViewController when a remote notification is received.
My code works to point, but when the app is running and the user is on any other than the first screen/navigation stack, the UIViewController isn't presented.
Can anyone help please? Note I want to keep the navigation bar when the UIViewController is presented
I get this warning when I try to present the 'UIViewController' elsewhere
Warning: Attempt to present on whose view is not in the window hierarchy!
Thanks in advance
Here is my code for didReceiveRemoteNotification:
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
var payload = userInfo
let requestID = payload["requestID"] as! String
let rootViewController = self.window!.rootViewController as! UINavigationController
let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc : RequestViewController = storyboard.instantiateViewControllerWithIdentifier("RequestViewController") as! RequestViewController
vc.requestID = requestID
let navigationController = UINavigationController(rootViewController: vc)
rootViewController.presentViewController(navigationController, animated: true, completion: nil)
}
Fixed it by changing:
rootViewController.presentViewController(navigationController, animated: true, completion: nil)
to:
rootViewController.visibleViewController!.presentViewController(navigationController, animated: false, completion: nil)

Resources