class ViewController: UIViewController, UINavigationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
navigationController!.delegate = self
}
func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool) {
print("showViewController")
}
func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
print("sss")
}
func update() {
let vc = SecondViewController()
navigationController!.pushViewController(vc, animated: true)
}
}
this is the first controller of my demo , and in console:
sss
showViewController
showViewController
the "didShowViewController" was called twice.
I'm not sure what's going on
-----------------some test----------------------
I add some log in these method of controller : loadView,viewDidLoad ,viewWillAppear,viewDidAppear , and the order of these log is:
loadView
viewDidLoad
viewWillAppear
will:<NaviDemo.ViewController: 0x7fe8c9533050>
<NaviDemo.ViewController: 0x7fe8c9533050>
viewDidAppear
<NaviDemo.ViewController: 0x7fe8c9533050>
I hit the same issue in my code. I was able to work around it by waiting until viewDidAppear to set the navigation delegate instead of setting it in viewDidLoad. To translate it to your example:
override func viewDidLoad() {
super.viewDidLoad()
}
// ...
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationController!.delegate = self
}
didShowViewController is called twice because the first time it is called when the navigation controller transitions to showing the view controller. And then it is called again by the navigation controller's own viewDidAppear when it appears on screen, using the topViewController as the controller param which in this case is the same as the controller the first time it was called.
The UINavigationController has displayed two instances of a UIViewController
From the UINavigationControllerDelegate documentation
Called just after the navigation controller displays a view
controller’s view and navigation item properties.
Instead of logging "showViewController", log the UIViewController instance to see what's going on
func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool) {
print(viewController)
}
Related
I'd like to implement a custom transition to ONE specific view-controller inside my UINavigationController.
I can do so by providing my custom TransitionAnimator inside
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?
or return nil otherwise and everything works fine! So the animation part is solved and working!
BUT by returning nil for the times I want the default transition, I loose the back-swipe gesture.
So my question is:
How can I provide my custom transition to one specific VC, but keep everything else as is?!?
You can try to subclass UINavigationController and manage interactivePopGestureRecognizer on your own.
class CustomNavController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = self
}
}
extension CustomNavController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer === interactivePopGestureRecognizer {
return viewControllers.count > 1
}
return true
}
}
You could make the view controller the navigation delegate and then reset it back to nil. like:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationController?.interactivePopGestureRecognizer?.delegate = self
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
navigationController?.interactivePopGestureRecognizer?.delegate = nil
}
Then perform your custom animation in the view controller or make an object/class to handle it for you
I want to know that when user navigates to another screen. If there is any change in navigation stack, It should notify me. To do that, I do not want to write any code in viewwillappear of any viewcontroller. I wish to write it once and thus I can observe that which screen is navigated.
Consider writing a super UIViewController which all your UIViewControllers inherit from. Then you just write the code in your super ViewController
Sample code for #Nikolaj Nielsen answer:
class BaseViewController: UIViewController {
var identifier: String {
return ""
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("Appeared VC: \(identifier)")
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
print("Disappeared VC: \(identifier)")
}
}
class CustomViewController: BaseViewController {
override var identifier: String {
return "CustomViewController"
}
}
I'm currently creating an app that uses UINavigationController for screen transitions.
The problem I encountered was the print string in the willShow delegate method on screen B will not be displayed. The transition from screen A to B to A willShow delegate method will be called correctly and even though it is displayed in the console, the transition from screen A to B to C to B to A will cause the method to it will not be called and will not be displayed.
ViewController.swift
// First view
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Home"
}
}
SecondViewController.swift
class SecondViewController: UIViewController, UINavigationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.delegate = self
navigationItem.title = "Second"
}
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
if let _ = viewController as? SecondViewController {
print("=====(open)willShow::SecondViewController=====")
} else {
print("=====(close)willShow::SecondViewController=====")
}
}
}
ThirdViewController.swift
class ThirdViewController: UIViewController, UINavigationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.delegate = self
navigationItem.title = "Third"
}
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
if let _ = viewController as? ThirdViewController {
print("=====(open)willShow::ThirdViewController=====")
} else {
print("=====(close)willShow::ThirdViewController=====")
}
}
}
Logs of the transition from screen A to B to A
=====(open)willShow::SecondViewController=====
=====(close)willShow::SecondViewController=====
Logs of the transition from screen A to B to C to B to A
=====(open)willShow::SecondViewController=====
=====(open)willShow::ThirdViewController=====
=====(close)willShow::ThirdViewController=====
Logically, you should see this message at the end.
=====(close)willShow::SecondViewController=====
FirstViewController
SecondViewController
ThirdViewController
I'm trying to make custom transitions when pushing/popping viewControllers from a custom UINavigationController class. I'm implementing the UINavigationControllerDelegate method
navigationController(_:animationControllerFor:from:to:), but it does not get called.
I'm creating a UINavigationController in storyboard and putting it's class as CustomNavigationController. I'm also assigning it a root ViewController in the storyboard (let's call the root VC CustomViewControllerRoot).
Here is the code I'm using (simplified and not tested):
protocol NavigationDelegate {
func pushCustomViewController()
func popViewController()
}
class CustomNavigationController: UINavigationController, NavigationDelegate {
init() {
super.init(nibName: nil, bundle: nil)
delegate = self
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
self.navigationBar.isHidden = true
guard viewControllers.first is CustomViewControllerRoot else {fatalError("Error")}
rootVC = viewControllers.first as? CustomViewControllerRoot
rootVC?.navigationDelegate = self
//Setup the rest of the viewControllers that are to be used
customVC = CustomUIViewController()
customVC?.navigationDelegate = self
}
var rootVC: CustomViewControllerRoot?
var customVC: CustomViewController?
func pushCustomViewController() {
if customVC != nil {
self.pushViewController(customVC!, animated: true)
}
}
func popViewController() {
self.popViewController(animated: true)
}
}
extension CustomNavigationController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
// This is never called, even though the delegate is set in the initializer to CustomNavigationController
print("TEST")
return nil
}
}
I then let each custom UIViewController subclass in my navigation hierarchy delegate push or pops to this CustomNavigationController above. For example this is the root vc assigned to the navigation controller. Since it lies as root it never needs to push itself or be popped, as it is presented when the CustomNavigationController is presented. It delegates to CustomNavigationController when it finds that another VC should be presented on top of it:
class CustomViewControllerRoot {
var navigationDelegate: NavigationDelegate?
override func viewDidLoad(){
super.viewDidLoad()
}
#objc func someButtonPressedToPresentCustomVC(){
navigationDelegate?.pushCustomViewController()
}
}
The dismissal is handled inside each CustomViewController which also delegates the pop down to the CustomNavigationController (I don't want to use the navbar for dismissal so there is no "back button" from the start):
class CustomViewController: UIViewController {
var navigationDelegate: NavigationDelegate?
override func viewDidLoad(){
super.viewDidLoad()
}
#objc func dismissViewController(){
navigationDelegate?.popViewController()
}
}
To my understanding the UINavigationControllerDelegate method inside the extension of CustomNavigationController should be called whenever a push or pop is performed since I'm setting the delegate variable to self in the initializer?
Your navigation controller should have a root view controller.
And then you should push custom view controller from your root view controller. And delegate method calls
Navigation Controller Code:import UIKit
class CustomNV: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
}
extension CustomNV: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
print("TEST")
return nil
}
}
RootViewController code:
class RootViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let viewController = UIViewController(nibName: nil, bundle: nil)
viewController.view.backgroundColor = .green
navigationController?.pushViewController(viewController, animated: true)
}
}
Set root view controller as root for navigation controller in storyboard
I have several views in my app, and I only want a navigationbar on one of them.... I used a navigationcontroller and at first I was using this code (while my app was in its infancy and only had 2 views)
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.setNavigationBarHidden(true, animated: animated)
super.viewWillAppear(animated)
}
override func viewWillDisappear(_ animated: Bool) {
self.navigationController?.setNavigationBarHidden(false, animated: animated)
super.viewWillDisappear(animated)
}
It worked fine - however, the app has become more complex - I have these views
lazy var orderedViewControllers: [UIViewController] = {
return [self.newVc(viewController: "pageOne"),
self.newVc(viewController: "pageTwo"),
self.newVc(viewController: "pageThree"),
self.newVc(viewController: "pageFour"),
self.newVc(viewController: "activate")
]
}()
Where this code isn't applied to, even if I create a custom view controller for each view.
I thought the way to do this would be to put the top chunk of code in every view, but it's not working for the bottom chunk. In essence my question is how do I use NavigationController to create a bar ONLY on one view.
One option: use a "base view controller" class which handles hiding / showing the Navigation Bar, and make your "pages" sub-classes of the "base" class.
import UIKit
class BaseViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: animated)
}
}
class ViewController: UIViewController {
// has buttons with
// Show (e.g. push)
// segues to Settings, First, Second, Third view controllers
}
class SettingsViewController: UIViewController {
// Settings VC is a normal UIViewController, because
// we *want* the NavBar to remain visible
}
class FirstViewController: BaseViewController {
#IBAction func backTapped(_ sender: Any) {
self.navigationController?.popViewController(animated: true)
}
}
class SecondViewController: BaseViewController {
#IBAction func backTapped(_ sender: Any) {
self.navigationController?.popViewController(animated: true)
}
}
class ThirdViewController: BaseViewController {
#IBAction func backTapped(_ sender: Any) {
self.navigationController?.popViewController(animated: true)
}
}
You can use this method of UINavigationControllerDelegate
optional func navigationController(_ navigationController: UINavigationController,
willShow viewController: UIViewController,
animated: Bool){
if viewController == self."desired view controller" {
self.isNavigationBarHidden = true
}else{
self.isNavigationBarHidden = false
}
}
Thank you all for the support. I have resolved my issue by doing the following:
I put the only view controller I wanted to have a navigation bar in a navigation controller view the Embed menu.
I added a custom back button.