iOS: Pass data to modal VC when wrapped in a Navigation Controller - ios

I have a VC that I'm presenting modally however that VC is wrapped in a UINavigationController. To present the Navigation Controller connected to my VC I added an identifier in storyboard and present like below:
if let nvc = self.storyboard?.instantiateViewController(withIdentifier: "EditTaskNavController") {
self.present(nvc, animated: true){
success(true)
}
}
This presents fine.
The problem arrises when I try to pass data to my VC. Because nvc is the navigation controller I tried to get the vc with nvc.rootViewController but I get the error: Value of type 'UIViewController' has no member 'rootViewController'. If I print out nvc I see that it is in fact a UINavigationController. I assume this error occurs because I used instantiateViewController to get the Navigation Controller from storyboard, but I'm confused why it prints out as a UINavigationController.
I've also tried to cast nvc as? TaskEditViewController but nvc is the navigation controller and not the vc so this doesn't work.
Ultimately I want to pass data to my VC as such before presenting modally:
vc.detail = "example"
Any ideas how to do this?

You need to cast result of instantiateViewController to the appropriate type as the type of the vc you set the identifier to in storyboard as it returns a generic object of type UIViewController , so cast it to UINavigationController , same case also is for property topViewController that needs to be casted too
if let nvc = self.storyboard?.instantiateViewController(withIdentifier: "EditTaskNavController") as? UINavigationController, let top = nvc.topViewController as? TaskEditViewController {
top.someProperty = /* some value */
self.present(nvc, animated: true) {
/* some code */
}
}

Related

Opening specific view with navigation controller from push notication

Im trying to open a certain view from a push notification but i keep losing the nav bar and the back and next references. this what my storyboard looks like this (with the view i want to open)
this is what i have in my AppDelagate:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "notification") as? NotificationViewController
self.window?.rootViewController = vc
What you're doing is completely replacing the root view controller of your application, this means that all the current UI will be discarded.
What you should do instead is to use your knowledge of your application to direct it to the new content. For example, if your root view controller is a navigation controller, you can cast rootViewController to a nav controller and push it (this will fail if your root view controller is something else, like a tab bar controller).
guard let vc = storyboard.instantiateViewController(withIdentifier: "notification") as? NotificationViewController else {
fatalError("Main Storyboard doesn't have a notification controller")
}
guard let nav = self.window?.rootViewController as? UINavigationController else {
return //handle unexpected state
}
nav.push(vc, animated: true)
Another option would be to embed your notification controller into a navigation controller, add a Close button, and present it modally, that way you can present it on top of rootViewController no matter what that controller is.
As we can see in the screenshot your provided, the application's root view controller is the UINavigationController instance.
And according to that, let me offer the next code:
func handleNotification(){
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let vc = storyboard.instantiateViewController(withIdentifier: "notification") as? NotificationViewController else{
debugPrint("NotificationViewController with identifier 'notification' not found")
return
}
guard let navVC = self.window?.rootViewController as? UINavigationController else{
debugPrint("RootViewController is not an UINavigationController")
return
}
navVC.pushViewController(vc, animated: true) //perhaps your will prefer to use false
}
Beside that, you can use more flexible implementation.
In your AppDelegate post a (NS)Notification when notification intercepted, the relevant view-controller(s) observe the notification, and act when notification broadcasted.
You can also set an identifier to the segue and invoke performSegue method from the observing view-controller
You can set from storyboard -> add view controller -> Embed in navigation controller -> set second view controller -> Attach seque between that controllers. You will see same view controllers like that image .

iOS (Swift): Presenting View Controller Embedded in Navigation Controller

I have a UIViewController (AVC) that is embedded in a UINavigationController. AVC (present modally) segues to another UIViewController (BVC). Inside BVC, the variable self.presentingViewController is of type an optional NavigationController rather than a AVC as I would have expected.
I have to downcast the first childViewControllers as an AVC as follows:
let pvc = self.presentingViewController
if let avc = pvc?.childViewControllers.first as? AVC {
// ...
}
Why is self.presentingViewController not as I expected it to, i.e. an AVC?
Many thanks.
To access it
if let pvc = self.presentingViewController as? UINavigationController {
if let avc = pvc.viewControllers.first as? AVC {
// ...
}
}
//
From Docs
When you present a view controller modally (either explicitly or
implicitly) using the present(_:animated:completion:) method, the view
controller that was presented has this property set to the view
controller that presented it. If the view controller was not presented
modally, but one of its ancestors was, this property contains the view
controller that presented the ancestor. If neither the current view
controller or any of its ancestors were presented modally, the value
in this property is nil.

Trigger segue in another UITabBarController VC with payload?

I can swap from one UIViewController to another within a UITabBarController using tabBarController?.selectedIndex = targetIndex. I would like to know how to trigger a segue after the switch.
I have tried doing if let vc = tabBarController?.viewControllers?[0] as MyViewController {} and calling vc.performSegue() but it errors out, complaining that it's a UINavigationController. (I couldn't even get vc.prepare() to work but I can't work out how to create a UIStoryBoardSegue, which it requires as the first argument, from scratch using the segue identifier.) I think this is because all of my UIViewControllers in the UITabBarController are within Navigation Controllers.
How do I switch from index 0 to 1 in the tab bar controller, and then trigger a segue in the view controller embedded in the navigation controller? Note, I need to pass in a copy of the calling object (my UIViewController) or a payload for contextual purposes to the UIViewController being segued to.
Well like you said the viewController which is presented at the index is a UINavigationController. You should get the rootViewController of the UINavigationController and perform the Segue on the RootViewController.
Below code should work:
self.tabBarController?.selectedIndex = 1
guard let vc = self.tabBarController?.viewControllers?[1] as? UINavigationController else {
return
}
guard let rootVC = vc.viewControllers.first as? SecondViewController else {
return
}
rootVC.vc = self
rootVC.performSegue(withIdentifier: "test", sender: self)

remove navigation controller from superview

So i have an app were you're supposed to chose a location from a map but the way i'm doing it is by a popup off a view controller that has a MapKit and a search bar and a button for choosing users location, the problem lies on when i'm done with the View I normally called the
self.view.removeFromSuperview()
but the thing that is being remove is the View controller itself but il leaves behind the navigation bar and it doesn't let me do nothing else (in fact when I go to another tab and return to the same one the VC returns).
the way i'm instantiating the VC is like this:
func location ()
{
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = mainStoryboard.instantiateViewController(withIdentifier: "mapV") as! UBICACIONViewController
let nav1 = UINavigationController()
nav1.viewControllers = [vc]
self.addChildViewController(nav1)
self.view.addSubview(nav1.view)
vc.delegate = self //this for the info that i'm getting afte(works fine)
nav1.didMove(toParentViewController: self)
}
So until here works fin but when the user pick te button that says "Use My Location" and tries to return there's the problem.
in my VC view controller this is what happens when finished:
func Remove()
{
if let del = delegate {
del.dataChanged(str: Event.sharedInstance.Location!)
}
self.view.removeFromSuperview() //Problem Here :/
}
Second VC
Return to First VC
Actually you have added a child view controller nav1. And nav1 has a subview nav1.view.
while removing you are removing this view from its superview, not the child view controller added.
Any ways, From you screen short..I understand that the pop up view is off full screen then why dont you present this VC modally, or else push it to navigation stack.

Swift ios send user to a specific ViewController in tab based application

I want to send a user to a specific ViewController in my app once a notification is clicked.
I now that I can do something like this:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let destinationViewController = storyboard.instantiateViewControllerWithIdentifier("Home") as? HomeViewController
presentedVC?.presentViewController(destinationViewController!, animated: true, completion: nil)
But my app has a tab bar and looks like this
Tab bar
tab1: navigationController -> VC1
tab2: navigationController -> VC2 -> HomeVC
tab: navigationController -> VC3
Each tab has a navigationController as a infront of it.
So how can I send the user to HomeVC? I must first select tab 2 then the navigation controller then push the user tvice:
tab2: navigationController -> VC2 -> HomeVC
And the other problem, if there any way to tell if the user is already in HomeVC? I dont want to send the user to the same VC if his already there.
You must have access to your UITabbarController in you UIApplicationDelegate or wherever you're handling the notification tap.
let tabBar:UITabBarController = self.window?.rootViewController as! UITabBarController //or whatever your way of getting reference is
So first you'll get the reference to UINavigationController in your second tab like this:
let navInTab:UINavigationController = tabBar.viewControllers?[1] as! UINavigationController
Now push your home view at second tab's navigation controller:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let destinationViewController = storyboard.instantiateViewControllerWithIdentifier("Home") as? HomeViewController
navInTab.pushViewController(destinationViewController!, animated: true)
And finally switch your tab to second to show the just pushed home controller
tabBar.selectedIndex = 1
Keep in mind, this answer assumes that your application has already set the tab bar as the root view controller of application window prior handling the notification tap.
Try something like this:
if let tabBarController = window?.rootViewController as? UITabBarController {
tabBarController.selectedIndex = 1 // in your case the second tab
}
The idea is to switch to get the tab bar instance and switch it to your desired tab (where you have your view controller).
The above code works in AppDelegate / you can easily call it anywhere by getting the tabBarController instance.
You can check which tab is selected by user with the method var selectedIndex: Int. You can check which view controller is present like this self.navigationController?.presentingViewController?.presentedViewController. This will solve your problem.

Resources