Swift - Permanent View Controller Segue (outside of Navigation Controller) - swift

Having some trouble permanently moving one from view controller to another. The normal segues seem to all have a 'go back' option.
I know I can imbed the VC in a navigation controller and create a custom segue which rewrites the hierarchy/changes root VC
class ReplaceSegue: UIStoryboardSegue {
override func perform() {
source.navigationController?.setViewControllers([self.destination], animated: false)
}
}
But I want to avoid a navigation controller as it will confuse things when I add a SWRevealViewController later.
Can/Should I change the storyboard VC (outside of AppDelegate that is)?
Thanks - apologies if this is a 'beginner' question

You can either change rootViewController of your main window:
self.window.rootViewController = vc
or you can just simply set vc's modalPresentationStyle to fullscreen (this doesn't have a "go back" option by itself, only you can dismiss it calling dismiss)
let vc = UIViewController()
vc.modalPresentationStyle = .fullScreen
or from storyboard change it's presentation style to fullscreen.

Related

UIKit - UINavigationController : UIViewController properties subclass or protocol?

I am trying to understand relationship between UINavigationController and UIViewController.
According to apple documentation
A container view controller that defines a stack-based scheme for navigating hierarchical content.
When I open UIKit and UINavigationController class there are some properties which I could not imagine, how they is written by Apple.
UINavigationController : UIViewController { }
so UINavigationController subclass of the UIViewController and inherit its properties.
then what about these properties in this extension ? These ones inherits from a protocol which I could not see in UIKit. Could you explain a little bit please ?
extension UIViewController {
open var navigationItem: UINavigationItem { get } // Created on-demand so that a view controller may customize its navigation appearance.
open var hidesBottomBarWhenPushed: Bool // If YES, then when this view controller is pushed into a controller hierarchy with a bottom bar (like a tab bar), the bottom bar will slide out. Default is NO.
open var navigationController: UINavigationController? { get } // If this view controller has been pushed onto a navigation controller, return it.
}
I am not sure what you need to understand but i will assume that it's why those properties are there and exposed for the UINavigationController.
As the definition address it UINavigationController is a basically a stack of UIViewControllers, that uses, push, pop functions to add and remove to the stack here is more about how stack works.
for the relationship, it's clear that's every UINavigationController is inheriting a UIViewController but it's tricky because you are thinking why? since it hold a UIViewController why it inherits from it the answer rely on the usage this allows us to have multiple navigations embedded in each others so a navigation controller can hold another navigation controller inside of it, that's why you see those properties so in a scenario you can have the following stack and those properties are there if you need to use them on the UINavigationController it self as it gets pushed.
let firstVC = UIViewController()
let secondVC = UIViewController()
let firstNav = UINavigationController(rootViewController: firstVC)
let secondNav = UINavigationController(rootViewController: secondVC)
firstNav.hidesBottomBarWhenPushed = true
let thirdNav = UINavigationController(rootViewController: firstNav)
thirdNav.pushViewController(secondNav, animated: false)

How to do popViewController from another class?

My app has a TabBarController. Each tabBarItem relates to a ViewController embedded in a NavigationController.
When in first tabBarItem and selecting another tabBarItem I want to do some stuff before moving to the selected ViewController. Therefore I created a class for my tabBarController and made it UITabBarControllerDelegate.
What I want to do is present an alert with two buttons; button A cancels the move to the selected viewController and button B lets the move happen.
My problem is that when button B is pressed, I want to popToRootViewController. I gave the navigationController a storyboardID and tried to instantiate it like shown below.
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if let alert = self.storyboard?.instantiateViewController(withIdentifier: "ActiveSessionWarningAlert") as? ActiveSessionWarningAlert {
alert.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
alert.modalTransitionStyle = UIModalTransitionStyle.flipHorizontal
let alertWasDismissed: (Bool) -> Void = { userWantsToMoveToSelectedViewController in
if userWantsToMoveToSelectedViewController {
if let navContr = self.storyboard?.instantiateViewController(withIdentifier: "firstNavContr") as? UINavigationController {
navContr.popToRootViewController(animated: true)
}
tabBarController.selectedViewController = viewController
}
}
alert.alertWasDismissed = alertWasDismissed
self.present(alert, animated: true, completion: nil)
}
return false
}
Everything works as expected, but the popToRootViewController doesn't seem to occur; when selecting the first tabBarItem again the same viewController that was 'active' when we left the item is still showing.
I checked so that the viewController I want to pop is actually in the navigation stack and that the navContr != nil.
What am I missing?
You don't say so, but I'm assuming that the alertWasDismissed closure you pass to your alert view controller gets invoked when the user dismisses the alert.
The problem with your closure is this bit:
if let navContr = self.storyboard?.instantiateViewController(withIdentifier: "firstNavContr") as? UINavigationController
Any time you call instantiateViewController(withIdentifier:), you are creating a brand new, never before seen instance of a view controller (a navigation controller in this case.) That navigation controller has nothing to do with the one that belongs to the current tab that you are trying to dismiss. It doesn't have anything in it's navigation stack other than the root view controller that is defined in the storyboard.
What you need to do is find the navigation controller of the current tab in your tab bar controller's tabBarController(_:shouldSelect:) method, and pass that navigation controller to your alertWasDismissed closure.
At the time the tabBarController(_:shouldSelect:) method is called, the tab bar controller's selectedViewController should contain the current view controller. Replace the line above with:
if let navContr = tabBarController.selectedViewController? as? UINavigationController {}
That should work.

Performing segue with UINavigationController (without IBAction)

It's easier to show you a drawing and then explain.
Dashboard Storyboard
I have 2 separate UIViewControllers (i've included just one in the drawing, the other is irrelevant) embedded in container view called ContainerViewController.
Post Storyboard
NewPostViewController shows a UIButton that presents TextPostViewController. As you can see, all of them are embedded in UINavigationControllers. Now, once the completion block of the new post is being called, I have to present the ContainerViewController and it needs to handle it's own logic. The problem is that it's embedded in UINavigationController and once I present it, the UITaBbar is hidden.
I tried to do this:
self.performSegue(withIdentifier: "TextPostToNavContainerVC", sender: nil)
The transition is successful but I'm losing the UITabBar, even though in the DashboardViewController and the ContainerViewController I called:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
tabBarController?.tabBar.isHidden = false
}
What am I doing wrong or is there are better way to do that?
You should instantiate the tab bar controller. not the view controller.
Imagine you're putting a initial view controller ahead of your tab bar controller. Making your tab bar not being pushed
If I undestand it correctly.
You are doing this
Segue connect to a view controller
But you should actually do this Segue connected to a tab bar controller
You can try to add it as a child to control it's frame like this
let textPost = self.storyboard?.instantiateViewController(withIdentifier: "containerID") as! TextPostToNavContainerVC
textPost.view.frame = CGRect(x:20,y:0,width:self.view.frame.width,height:self.view.frame.height-50)
self.view.addSubview(nvc.view)
self.addChildViewController(textPost)
textPost.didMove(toParentViewController: self)

How to pop to root controller in app where root viewcontroller is of type UIViewController?

I have implemented SWRevealViewController class for revealing a rear (left and/or right) view controller behind a front controller. SWRevealViewController of type UIViewController is the entry point in the app.
What do I want?
In class AppDelegate when applicationDidEnterBackground is called, I want to pop all the view controllers on the stack except the root view controller and updates the display.
What I tried?
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.window?.rootViewController?.dismiss(animated: true, completion: nil)
}
I know that if SWRevealViewController were embedded in a UINavigationController, I could simply do (appDelegate.window?.rootViewController as? UINavigationController)?.popToRootViewController(animated: true) , but it is not embedded in nav controller and if I embed it, then I will not get the same behaviour, so how can I sort this out?
I believe what you are trying to do is an "unwindSegue".
They are are documented in a technical note:
https://developer.apple.com/library/content/technotes/tn2298/_index.html
With that you should be able to identify your root view controller as the target of the unwind segue and, of needed, follow the unwind through various steps as it occurs.
You can also look at the documentation for the various methods of UIViewController that contain the term "unwind"
https://developer.apple.com/documentation/uikit/uiviewcontroller
Try This:
let navVC = ((UIApplication.shared.delegate as? AppDelegate)?.window)?.rootViewController as? UINavigationController
for VC: UIViewController in (navVC?.viewControllers)! {
if (VC is MYViewController) {
navVC?.popToViewController(VC, animated: true)
break
}
}

navigationController is nil in swift

I have two storyboard (Main and SB2)
I have one view controller in storyboard in Main and one in SB2
I perform
let SB2 = UIStoryboard(name: "SB2", bundle:nil)
let vc : UIViewController = self.SB2.instantiateViewControllerWithIdentifier("VC2")
self.showViewController(vc, sender: self)
After the second viewcontroller loads and this command is run, it prints nil:
print(self.navigationController) // prints nil
In SB2 (second storyboard) I clicked on the viewcontroller (VC2) and then clicked on Editor > Embed In > Navigation Controller. This placed a navigation controller with the storyboard and the root view controller is VC2. I triple checked this. The first sign of it being connected was the gray navigation bar on top. The second being that there is segue connecting the navigation controller to VC2 and the last place I could have checked is in the navigation controller utilities.
I am thinking that maybe I shouldn't transition from VC1 to VC2 directly but rather VC1 to NavigationController however I don't know how to do this or if its even possible.
I don't know when sb2 prints nil when in face a navigation controller is connected to it. Any help is appreciated.
You need to push the view controller (VC2) on to the navigation controller.
let SB2 = UIStoryboard(name: "SB2", bundle:nil)v
let nvc = SB2.instantiateViewControllerWithIdentifier("NVC") as! UINavigationController
let vc : UIViewController = self.SB2.instantiateViewControllerWithIdentifier("VC2")
nvc.pushViewController(vc, animated: true)
Then in your view controller try calling
self.navigationController
In your storyboard,
select the initial ViewController. With this view controller selected, choose the menu item
Editor -> Embed In -> Navigation Controller