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

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.

Related

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

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 */
}
}

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)

How can I pass data to a segue when the destination controller is embedded in a navigation controller

I need to segue to destination view controller (which is a tableview controller) to which I need to pass some data. I also need to have a navbar in the destination view controller. So I embedded the destination view controller in a navigation controller. segue.destination now points to the embedded navigation controller. It does not help to access the destination controller directly since it probably creates another instance. How can I either (1) create a navigation bar directly in the destination view controller (which is a tableview controller) or (2) send the data to the destination controller
You can cast the segue.destination to a UINavigationController and then the the navigation controller's root view controller to a view controller class of your choice.
switch segue.destination {
case let navigationController as UINavigationController:
if let controller = navigationController.viewControllers.first as? UIViewController {
// controller.property = value
}
}

popoverPresentationController is nil when used with a Navigation View Controller

This code was working properly before I embed my view in a navigation controller
if segue.identifier == "PopupInfo" {
let controller = segue.destinationViewController as! PopoverInfoViewController
controller.popoverPresentationController!.sourceRect = sender!.frame
}
However, after adding the navigation controller, this slightly edited code is no longer working because popoverPresenetationController is now nil! I need to set its sourceRect programmatically not with the storyboard because the sender is a control inside a cell in a table view
if segue.identifier == "PopupInfo" {
let navcontroller = segue.destinationViewController as! UINavigationController
let controller = navcontroller.topViewController as! PopoverInfoViewController
controller.popoverPresentationController!.sourceRect = sender!.frame
}
Note: controller is not nil, only its popoverPresentationController property
You embedded PopoverInfoViewController in a UINavigationController. That navigation controller is what is directly embedded in a UIPopoverPresentationController. It should now have a non-nil popoverPresentationController property, as it's the view controller that is directly embedded in the popover controller. The setting of these kind of parent view controller properties do not propagate past the first child view controller. This is why navcontroller.popoverPresentationController will be non-nil while any child of navcontroller will have nil for popoverPresentationController.
You should use navcontroller.popoverPresentationController!.sourceRect = sender!.frame

Present modal view controller over modal view controller

I have a view controller VC1 that is presented modally over full screen from some other VC0. In my storyboard I have a modal segue from VC1 to VC2 also presenting over full screen. When in my app I can plainly see VC2 over VC1 over VC0, because some parts of their views are transparent. Perfect.
However, I'm going to reuse VC2 many times so I don't want to have a segue to it for each controller in my storyboard, so I want to accomplish this same thing programmatically. However, when I call presentViewController:animated:completion in VC1 to present VC2, the view of VC1 disappears when the modal transition is complete. When VC2 is dismissed, the view of VC1 reappears when the transition animation is complete.
How can I get the same effect programmatically as when I'm using the storyboard segue?
let newView = self.storyboard!.instantiateViewControllerWithIdentifier("NewViewController") as! NewViewController
newView.modalPresentationStyle = UIModalPresentationStyle.OverFullScreen
self.presentViewController(newView, animated: true, completion: nil)
You can only present on a visible controller, which is usually the rootViewController. But when there is a modally-presented controller, it covers the root controller, so you can't use that. But you can present on the modal, which is accessed though rootViewController.prsentedViewController. Here's some code:
let rootVC = window?.rootViewController
let presentingVC = (rootVC?.presentedViewController ?? rootVC)
presentingVC?.present(myController, animated: true, completion: nil)
You don't need to change the modalPresentationStyle.
You need to set the modalPresentationStyle property of the presented controller to UIModalPresentationOverFullScreen. Set that property just before you call presentViewController:animated:completion.

Resources