How to presentViewController embedded in UITabBarController and UINavigationBarController - ios

When using 3D Touch Shortcuts from the home screen I am trying to segue to different view controller.
The application is embedded within a UITabBarController and each tab root controller is a UINavigationController.
Here is how I attempted handling the shortcuts to load the view controller for each shortcut.
private func handleShortcutItem(shortcutItem: UIApplicationShortcutItem) {
if let rootViewController = window?.rootViewController, let shortcutItemType = ShortcutItemType(shortcutItem: shortcutItem) {
let sb = UIStoryboard(name: "main", bundle: nil)
let helloVC = sb.instantiateViewControllerWithIdentifier("HelloVC") as! HelloViewController
let goodbyeVC = sb.instantiateViewControllerWithIdentifier("GoodbyeVC") as! GoodbyeViewController
switch shortcutItemType {
case .Hello:
rootViewController.presentViewController(helloVC, animated: true, completion: nil)
break
case .Goodbye:
rootViewController.presentViewController(goodbyeVC, animated: true, completion: nil)
break
}
}
}
With this code the shortcuts only open the application to the initial view controller and not to the helloVC and goodbyeVC controllers which are in different tabs.
I am presuming this is because the ViewControllers I am trying to load are embedded within a UINavigationController as well as embedded within the UITabBarController.
How can I presentViewController which is embedded within the UITabBarController and UINavigationController ?
UPDATE
I am unsure if the following works as I have not got a iPhone 6S with me atm. But I have changed the code to the following, hopefully this will load the selected tab index when the 3D Touch Action is performed. From there it should post a notification to the view controller to perform a segue.
private func handleShortcutItem(shortcutItem: UIApplicationShortcutItem) {
if let rootViewController = window?.rootViewController, let shortcutItemType = ShortcutItemType(shortcutItem: shortcutItem) {
let tababarController = rootViewController as! UITabBarController
switch shortcutItemType {
case .Hello:
tababarController.selectedIndex = 1
NSNotificationCenter.defaultCenter().postNotificationName("performsegueHello", object: nil)
break
case .Goodbye:
tababarController.selectedIndex = 4
NSNotificationCenter.defaultCenter().postNotificationName("performsegueGoodbye", object: nil)
break
}
}
}

Please try the following code. I think you are not reaching the navigation controller of tab bar.you can do this by :
let insideNvc = tvc?.selectedViewController as? UINavigationController
Now, here is your navigation controller you can present or push anything on it.
private func handleShortcutItem(shortcutItem: UIApplicationShortcutItem) {
let nvc = self.window?.rootViewController as? UINavigationController
let tvc = nvc?.topViewController as? TabBarController
let insideNvc = tvc?.selectedViewController as? UINavigationController
if let shortcutItemType = ShortcutItemType(shortcutItem: shortcutItem) {
let sb = UIStoryboard(name: "main", bundle: nil)
let helloVC = sb.instantiateViewControllerWithIdentifier("HelloVC") as! HelloViewController
let goodbyeVC = sb.instantiateViewControllerWithIdentifier("GoodbyeVC") as! GoodbyeViewController
switch shortcutItemType {
case .Hello:
insideNvc?.presentViewController(helloVC, animated: true, completion: nil)
break
case .Goodbye:
insideNvc?.presentViewController(goodbyeVC, animated: true, completion: nil)
break
}
}
}

Related

Push view controller not working in Swift

let storyboard = UIStoryboard(name: "Main", bundle: nil)
let playGameViewController = (storyboard.instantiateViewController(withIdentifier: "PlayGameViewController") as! PlayGameViewController)
self.navigationController?.pushViewController(playGameViewController, animated: true)
push not worked but I tried present working, I want navigate with push.
Try running the below code to know where you have gone wrong.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let playGameViewController = storyboard.instantiateViewController(withIdentifier: "PlayGameViewController") as? PlayGameViewController else {
print("This means you haven't set your view controller identifier properly.")
return
}
guard let navigationController = navigationController else {
print("This means you current view controller doesn't have a navigation controller")
return
}
navigationController.pushViewController(playGameViewController, animated: true)
Try using breakpoints to figure out if any variable is nil. In your case, there is more probability for having your navigationController being nil.

Properly redirect to a view controller inside tab bar's navigation controller when push notification is clicked

I have a tabBar. Each of its tabs' ViewControllers have been embedded inside their respective NavigationControllers.
Hierarchy:
Tabbarcontroller --> NavigationController --> VC1 --> VC2 -->...VCn (for tab1)
I have to redirect to one of the viewcontrollers say VC2 when i click on push notifications. The code i am using is as follows.
let navigationController = UINavigationController()
let storyboard = UIStoryboard.init(name: "Main", bundle: Bundle.main)
if let chatViewController = storyboard.instantiateViewController(withIdentifier: "chatViewController") as? ChatViewController {
navigationController.viewControllers = [chatViewController]
window?.rootViewController = navigationController
}
By using this, I am redirected to the respective ViewController. However, i am not able to get back to the tabBar. So is it possible to redirect in such a way that it allows me the get back to the tab bar, thus providing the same hierarchy as in the UI?
EDIT:
The image below shows my layout.
I would like to accomplish the following:
When the user taps the push notification, the app should be directed to VC-B. *It should not create new object for VC-B and add to the navigation stack if VC-B is already on top of navigation stack.
If the app had been terminated and the user taps on the notification, it should open VC-B.
For determining if the app had been terminated, I set a flag as:
func applicationWillTerminate(_ application: UIApplication) {
UserDefaults.standard.set(true, forKey: UserDefaultsKey.isTerminated)
}
This flag is set false at the end of didFinishLaunchingWithOptions function.
For redirection, I check this flag to determine if the app had been terminated:
func performRedirectionToSuitableViewController(userInfo: [AnyHashable: Any]) {
let isTerminated = UserDefaults.standard.object(forKey: UserDefaultsKey.isTerminated) as! Bool
if isTerminated {
let storyboard = UIStoryboard.init(name: "Main", bundle: Bundle.main)
let tab = storyboard.instantiateViewController(withIdentifier: "tabBar") as! UITabBarController
tab.selectedIndex = 0
let nav = tab.viewControllers![0] as! UINavigationController
let chatViewController = storyboard.instantiateViewController(withIdentifier: "chatViewController") as! ChatViewController
chatViewController.chatThreadId = userInfo["thread_id"] as? String
nav.pushViewController(chatViewController, animated: true)
} else {
if let tab = window?.rootViewController?.presentedViewController as? UITabBarController {
let storyboard = UIStoryboard.init(name: "Main", bundle: Bundle.main)
let chatViewController = storyboard.instantiateViewController(withIdentifier: "chatViewController") as! ChatViewController
chatViewController.chatThreadId = userInfo["thread_id"] as? String
if let nav = tab.selectedViewController as? UINavigationController {
nav.pushViewController(chatViewController, animated: true)
}
}
}
}
With this code, my first requirement is partially fulfilled. Need to determine if the viewcontroller is at the top of the navigation stack.
And, in the case of terminated app, clicking the push notification opens the tab bar, with the default selection index being selected.
I have spent several days trying to fix this. But cannot get it work.
I suppose you have to do something like this:
let tabBar: UITabBarController // get your tab bar
tabBar.selectedIndex = 0 // eg. zero. To be sure that you are on correct tab
if let navigation = tabBar.viewControllers?[tabBar.selectedIndex] as? UINavigationController {
let storyboard = UIStoryboard.init(name: "Main", bundle: Bundle.main)
if let chatViewController = storyboard.instantiateViewController(withIdentifier: "chatViewController") as? ChatViewController {
navigation.pushViewController(chatViewController, animated: true)
}
}
Well, you are setting current window.rootViewController, which should be that TabBarViewController I'd say. That's why you cannot get back to the TabBar
What you should do is
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
if let chatViewController = storyboard.instantiateViewController(withIdentifier: "chatViewController") as? ChatViewController, let tabBar = storyboard.instantiateViewController(withIdentifier: "tabBar") as? UITabBarController {
let navigationController = UINavigationController(rootViewController: chatViewController)
navigationController.viewControllers = [chatViewController]
tabBar.viewControllers = [navigationController]
window?.rootViewController = tabBar
}
To get the current navigation controller from the tabbar controller. You can use this function
func getVisibleViewController(_ rootViewController: UIViewController?) -> UIViewController? {
var rootVC = rootViewController
if rootVC == nil {
rootVC = UIApplication.shared.keyWindow?.rootViewController
}
if rootVC?.presentedViewController == nil {
return rootVC
}
if let presented = rootVC?.presentedViewController {
if presented.isKind(of: UINavigationController.self) {
let navigationController = presented as! UINavigationController
return navigationController.viewControllers.last!
}
if presented.isKind(of: UITabBarController.self) {
let tabBarController = presented as! UITabBarController
return tabBarController.selectedViewController!
}
//UIAlertController
if presented.isKind(of: UIAlertController.self) {
let alertController = presented as! UIAlertController
return alertController.presentingViewController
}
return getVisibleViewController(presented)
}
return nil
}
Using the function you can navigate your viewcontroller like below.
let mainStoryboardIpad : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let yourView = mainStoryboardIpad.instantiateViewController(withIdentifier: "yourView") as! NewnoteViewController
let navi = self.getVisibleViewController(self.window!.rootViewController) as! UINavigationController
navi.pushViewController(yourView, animated: true)
You can create a method in your RootViewController that will redirect user to a specific view after receiving a push notification. Here's what I did in my previous project.
class RootViewController: UIViewController {
private var currentView: UIViewController
init() {
self.currentView = ViewController()
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
addChildViewController(currentView) // 1
currentView.view.frame = view.bounds // 2
view.addSubview(currentView.view) // 3
currentView.didMove(toParentViewController: self) // 4
}
func showDetailsScreen() {
if let updateDetailView = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "UpdateDetailsNotif") as? UpdateDetailsViewController{
let navController = UINavigationController(rootViewController: updateDetailView)
addChildViewController(navController)
navController.view.frame = view.bounds
view.addSubview(navController.view)
navController.didMove(toParentViewController: self)
currentView.willMove(toParentViewController: nil)
currentView.view.removeFromSuperview()
currentView.removeFromParentViewController()
currentView = navController
}
}
}
Then you can call that method on your AppDelegate like this:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let application = UIApplication.shared
AppDelegate.shared.rootViewController.showDetailsScreen()
completionHandler()
}
}

Trying to Embed VC into NavigationVC from performActionFor shortcutItem via 3d touch on App icon

I have an app that is using 3d touch to jump to a certain VC in my app. The issue is that when the app is launched normally, all of my VC's are embedded into a Navigation View Controller. But since I am skipping the launch sequence and jumping right into a VC, the VC that I jump to isn't embedded into the Nav VC.
Here is what I am trying in the App Delegate
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: #escaping (Bool) -> Void) {
guard TouchActions(rawValue: shortcutItem.type) != nil else {
print("3d not working")
completionHandler(false)
return
}
print("3d touch workig")
let mainStoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let addPersonVC : AddPersonViewController = mainStoryboard.instantiateViewController(withIdentifier: "AddPerson") as! AddPersonViewController
// pass the stack to the addPersonVC
addPersonVC.coreDataStack = coreDataStack
let navController:UINavigationController = mainStoryboard.instantiateViewController(withIdentifier: "navController") as! UINavigationController
navController.pushViewController(addPersonVC, animated: true)
// self.window? = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = navController
self.window?.makeKeyAndVisible()
completionHandler(true)
}
This code works, but you when I try to leave this VC, the app just sits there unresponsive. I some how need to embedded addPersonVC into the main Nav VC that is set up in storyboard. (The embedded navigation controller was set up in storyboard, incase that matters.)
Option1: Add a Storyboard Identifier to your UINavigationController , and instantiate the UINavigationController instead.
Option2: Delete the UINavigationController from storyboard. Just initialize a new UINavigationController by code and set the rootView programatically.
I am not a Swift developer, but since you don't see the examples I wrote I did some quick pasting to help you understand the basics, try this code:
let mainStoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let addPersonVC : AddPersonViewController = mainStoryboard.instantiateViewController(withIdentifier: "AddPerson") as! AddPersonViewController
// pass the stack to the addPersonVC
addPersonVC.coreDataStack = coreDataStack
let navController:UINavigationController = mainStoryboard.instantiateViewController(withIdentifier: "navController") as! UINavigationController
navController.viewControllers = [addPersonVC]
self.window?.rootViewController?.present(navController, animated: true, completion: nil)
self.window?.makeKeyAndVisible()

Swift 3 - Close current ViewController and Open new ViewController

I've gone through a tad amount of SO posts about this but none of them clearly state how to get this done in Swift 3.
Simply put, I need to close the current view that is displayed and open a new view controller. Such that the only view controller open would be the 2nd VC
This is the code I tried using, but when I try this the view controller gets closed without the rest of the code getting executed
//Close current VC
self.dismiss(animated: true, completion: nil)
//Open the new VC
let mainStoryboard: UIStoryboard = UIStoryboard(name:"Main",bundle:Bundle.main)
let secondViewController: SecondViewController = mainStoryboard.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
self.present(secondViewController, animated: true, completion: nil)
Can someone help me out? I just need a simple transition from 1st VC to 2nd VC and close the 1st VC.
The problem is, that "self" can not present the new ViewController since it is the ViewController that is getting dismissed.
You could try calling self.presentingViewController?.present(secondViewController, animated: true, completion: nil)
After spend full day i got a solution on Apple' developer website (on this link)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let secondVC = storyboard.instantiateViewController(identifier: "second_view_controller")
secondVC.modalPresentationStyle = .fullScreen
secondVC.modalTransitionStyle = .crossDissolve
present(secondVC, animated: true, completion: nil)
If you presented that view controller from some view controller or it is the viewcontroller from navigation or tab viewcontroller, then try to get last visible view controller, code is in this link
Get the current displaying UIViewController on the screen in AppDelegate.m
or else use this code (I have copied from that link)
public extension UIWindow {
public var visibleViewController: UIViewController? {
return UIWindow.getVisibleViewControllerFrom(self.rootViewController)
}
public static func getVisibleViewControllerFrom(_ vc: UIViewController?) -> UIViewController? {
if let nc = vc as? UINavigationController {
return UIWindow.getVisibleViewControllerFrom(nc.visibleViewController)
} else if let tc = vc as? UITabBarController {
return UIWindow.getVisibleViewControllerFrom(tc.selectedViewController)
} else {
if let pvc = vc?.presentedViewController {
return UIWindow.getVisibleViewControllerFrom(pvc)
} else {
return vc
}
}
}
}
Then present your viewcontroller from that visibleViewController -
//Close current VC
self.dismiss(animated: true, completion: nil)
//Open the new VC
let mainStoryboard: UIStoryboard = UIStoryboard(name:"Main",bundle:Bundle.main)
let secondViewController: SecondViewController = mainStoryboard.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
window?.visibleViewController?.present(secondViewController, animated: true, completion: nil)
If your view controller is the root view controller, then set it to root view controller
if let delegate = UIApplication.shared.delegate as? AppDelegate {
delegate.window?.rootViewController = secondViewController
}

Navigation and tab bar missing when presenting view controller

I am implementing home screen shortcuts using 3D Touch, and it's working well, however the way I currently have it means that when the shortcut takes the user to a specific view controller, the tab bar and navigation bar is missing.
This is my code:
func handleShortCutItem(shortcutItem: UIApplicationShortcutItem) -> Bool {
var handled = false
if let shortcutType = ShortcutType.init(rawValue: shortcutItem.type) {
let rootViewController = window!.rootViewController
switch shortcutType {
case .Favourites:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let rootController = storyboard.instantiateViewControllerWithIdentifier("favourites") as! FavouritesTableViewController
rootController.parkPassed = DataManager.sharedInstance.getParkByName(NSUserDefaults.standardUserDefaults().stringForKey("currentPark")!)
self.window?.rootViewController = rootController
self.window?.makeKeyAndVisible()
handled = true
}
return handled
}
Can anyone suggest what I need to change in the code?
This is the starboard layout (FavouritesTableViewController is indicated):
EDIT:
Here is my updated code:
#available(iOS 9.0, *)
func handleShortCutItem(shortcutItem: UIApplicationShortcutItem) -> Bool {
var handled = false
if let shortcutType = ShortcutType.init(rawValue: shortcutItem.type) {
switch shortcutType {
case .Favourites:
print("favourites")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let rootController = storyboard.instantiateViewControllerWithIdentifier("favourites") as! FavouritesViewController
rootController.parkPassed = DataManager.sharedInstance.getParkByName(NSUserDefaults.standardUserDefaults().stringForKey("currentPark")!)
let root = UIApplication.sharedApplication().delegate as! AppDelegate
if let navCont = root.window?.rootViewController?.navigationController {
navCont.presentViewController(rootController, animated: true, completion: nil)
} else {
root.window?.rootViewController?.presentViewController(rootController, animated: true, completion: nil)
}
root.window?.makeKeyAndVisible()
handled = true
}
}
return handled
}
Try this:
Get the delegate from app delegate:
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
GEt the controller you would like to to present from storyboard:
Controller *cont=//get the reference from storyboard either using storyboard ID
and then make your rootview present the other controller:
[appDelegate.window.rootViewController presentViewController:cont animated:YES completion:^{
DLog(#"presented your view ON TOP of the tab bar controller");
}];
Swift:
var appDelegate: AppDelegate = UIApplication.sharedApplication().delegate()
var cont: Controller = //get the reference form storyboard
appDelegate.window.rootViewController.presentViewController(cont, animated: true, completion: { DLog("presented your view ON TOP of the tab bar controller")
})
you can move the presenting stuff on main thread,if you like!!!!
So I got the solution I hope that will work for you.
First you have to set your Storyboard instance.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
after that you need to set where you want to start the navigation.
let mynVC = storyboard.instantiateViewControllerWithIdentifier("root") as! UINavigationController
right now you can set the viewcontroller that you want to appear
let playVC = storyboard.instantiateViewControllerWithIdentifier("playVC")
So and now you can start the workflow but note that you have to do this in a completion like this
self.window?.rootViewController?.presentViewController(rootVC, animated: true, completion: { () -> Void in
rootVC.pushViewController(playVC, animated: true)
})
So your rootViewController will present you "rootVC" and after that your playVC.
I hope It will help you guys:)
The problem is, you are setting the view controller(named:rootController) to be the window?.rootViewController, other than presenting the rootController by the window?.rootViewController. Just change
self.window?.rootViewController = rootController
to
self.window?.rootViewController.presentViewController(rootController, animated: true, completion: nil)
if you use it like this you will be replaced rootview to this viewcontroller. So this will be a new solo page and its normal to dont have navigationController or tabbarController. Because you have only this new page in view hierarchy.
If you want to present this from rootview, you may try
let root=UIApplication.sharedApplication().delegate as! AppDelegate
if let navCont=root.window?.rootViewController?.navigationController
{
navCont.presentViewController(viewControllerToPresent: UIViewController, animated: true, completion: nil)
}
else{
root.window?.rootViewController?.presentViewController(viewControllerToPresent: UIViewController, animated: true, completion: nil)
}

Resources