UINavigationControllerDelegate methods are not called - ios

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

Related

coordinator is nil and not navigating to the next screen on button click

I have been trying to refactor my source code so that it would conform to the Coordinator Pattern. I have used UITabBarController as the parent viewController of my app which contains 4 viewControllers.
I have been following the tutorials on how to implement the Coordinator pattern for iOS apps, and I have created and set up the protocols and classes of the Coordinator. I have a button inside my viewController (child viewController of the TabbarViewController), however, on button click, coordinator is not pushing / navigating to the desired VC, and I see the coordinator is returning nil on the debug console while debugging through the breakpoint, and I could not figure it out how to resolve this issue.
MainCoordinator.swift:
class MainCoordinator: SubCoordinator {
var subCoordinators = [SubCoordinator]()
var navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
print("Initialized.. .")
UIApplication.app().window?.rootViewController = self.navigationController
let vc = SplashViewController.instantiate()
vc.coordinator = self
navigationController.pushViewController(vc, animated: false)
}
}
// testing using a simple Viewcontroller class, its background color is set to red, so if the
// navigation works, a blank red VC should appear. but not working so far
func testView() {
let vc = ViewController.instantiate()
vc.coordinator = self
navigationController.pushViewController(vc, animated: false)
}
}
SubCoordinator.swift:
protocol SubCoordinator {
var subCoordinators: [SubCoordinator] { get set }
var navigationController: UINavigationController { get set }
func start()
}
StoryBoarded.swift:
protocol StoryBoarded {
static func instantiate() -> Self
}
// I am using storyBoard, and `instantiate()` should instantiate and return the specified VC
// from the Storyboard with the specified VC id (?)
extension StoryBoarded where Self: UIViewController {
static func instantiate() -> Self {
let id = String(describing: self)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
return storyboard.instantiateViewController(withIdentifier: id) as! Self
}
}
FirstViewController.Swift:
class FirstViewController: UIViewController, StoryBoarded {
#IBOutlet weak var button: UIButton!
var coordinator: MainCoordinator?
//MARK: - viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
// If uncommented the below line, coordinator is not returning `nil`, but not navigating
anyways!
//coordinator = MainCoordinator(navigationController: UINavigationController())
}
#IBAction func onButtonTap(_ sender: Any) {
// So, basically I would expect the coordinator to navigate to the testView, but not
navigating
coordinator?.testView()
}
}
ViewController.swift:
// testView
class ViewController: UIViewController, StoryBoarded {
var coordinator: MainCoordinator?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.view.backgroundColor = .red
}
}
and
// TabbarController, set as the root VC after the splashVC is completed
class MainViewController: UITabBarController, StoryBoarded {
var coordinator: MainCoordinator?
override func viewDidLoad() {
super.viewDidLoad()
let firstVC = UIStoryboard.firstViewController()
let secondVC = UIStoryboard.secondViewController()
let views: [UIViewController] = [firstVC, secondVC]
self.setViewControllers(views, animated: false)
self.navigationController?.navigationBar.isHidden = false
}
}
start() is being called, and splashVC appears and updates rootViewController with MainViewontroller on completion, But the navigation is not working at all on button click event.
Any feedback or help would highly be appreciated!
Since you're using the StoryBoarded protocol, you should follow the pattern and call instantiate() for initialization. Then, just set the coordinator.
class MainViewController: UITabBarController, StoryBoarded {
var coordinator: MainCoordinator?
override func viewDidLoad() {
super.viewDidLoad()
let firstVC = FirstViewController.instantiate()
let secondVC = SecondViewController.instantiate()
firstVC.coordinator = self.coordinator
secondVC.coordinator = self.coordinator
let views: [UIViewController] = [firstVC, secondVC]
self.setViewControllers(views, animated: false)
self.navigationController?.navigationBar.isHidden = false
}
}

UINavigationControllerDelegate willShow method is not called

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

UITabBarController subclass methods not available on child view controllers?

I am trying to call a custom method on my UITabBarController subclass from within one of the child view controllers. I have instantiated my CustomTabBarController class as the root view controller in AppDelegate.swift, however, the .tabBarController property on my child view controllers is of the class UITabBarController instead of CustomTabBarController.
Why does this happen? Is it possible to have the .tabBarController property on my view controllers reflect my subclass instead of the default UITabBarController class?
Here is my subclass:
import UIKit
class CustomTabBarController: UITabBarController, UITabBarControllerDelegate, LoginControllerDelegate {
let defaults = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
setupViews()
}
override func viewDidAppear(_ animated: Bool) {
checkLoginStatus()
}
func checkLoginStatus() {
if defaults.bool(forKey: "isLoggedIn") == false {
let loginController = LoginController()
loginController.delegate = self
present(loginController, animated: true, completion: nil)
}
}
func loginControllerDidDismiss() {
print("Delegation is working...")
}
func setupViews() {
let homeController = HomeController()
homeController.tabBarItem = CustomTabBarItem(title: "Home", imageNames: ["courthouse-icon-unselected", "courthouse-icon"])
let homeNavController = UINavigationController(rootViewController: homeController)
homeNavController.navigationBar.applyCustomStyle()
tabBar.tintColor = UIColor(red:0.18, green:0.34, blue:0.65, alpha:1.00)
self.setViewControllers([homeNavController], animated: true)
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
print(viewController.title)
return true
}
}
In my view controller, I would like to access this class like so:
import UIKit
class HomeController: ListController {
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Home"
self.tabBarController??? // Right now this is a UITabBarController, but I would like to it be a CustomTabBarController
}
}
The best approach is to test whether it's what you believe it to be and cast it so that the compiler knows the correct class.
e.g.:
if let custom = self.tabBarController as? CustomTabBarController {
custom.checkLoginStatus()
} else {
print("Unexpected controller \(self.tabBarController)")
}

UINavigationControllerDelegate‘s didShowViewController method was called twice

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)
}

View Controller lifecycle on UIModalPresentationOverCurrentContext

How do I determine when the parent view controller has been hidden or shown when I use the UIModalPresentationOverCurrentContext modal presentation style? On normal situations I can use the viewWillAppear: and viewWillDisappear:, but they seem not to be firing on this.
UIModalPresentationOverCurrentContext is intended to be used to present the content over your current viewController. What that means is, if you have animation or view changes inside your parentViewController, you can still see through the childViewController if the view is transparent. So, it also means that view never disappears for view over current context. It seems legit that viewWillAppear:, viewDidAppear:, viewWillDisappear: and viewDidDisappear do not get called.
You can however use UIModalPresentationStyle.Custom to have exact same behavior to present over current context. You wont receive view appearance callbacks but you can create your own custom UIPresentationController to get those callbacks.
Here is an example implementation,
class MyFirstViewController: UIViewController {
....
func presentNextViewController() {
let myNextViewController = MyNextViewController()
myNextViewController.modalPresentationStyle = UIModalPresentationStyle.Custom
myNextViewController.transitioningDelegate = self
presentViewController(myNextViewController, animated: true) { _ in
}
}
...
}
extension MyFirstViewController: UIViewControllerTransitioningDelegate {
func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController?
{
let customPresentationController = MyCustomPresentationController(presentedViewController: presented, presentingViewController: presenting)
customPresentationController.appearanceDelegate = self
return customPresentationController
}
}
extension MyFirstViewController: MyCustomApprearanceDelegate {
func customPresentationTransitionWillBegin() {
print("presentationWillBegin")
}
func customPresentationTransitionDidEnd() {
print("presentationDidEnd")
}
func customPresentationDismissalWillBegin() {
print("dismissalWillBegin")
}
func customPresentationDismissalDidEnd() {
print("dismissalDidEnd")
}
}
protocol MyCustomApprearanceDelegate: class {
func customPresentationTransitionWillBegin()
func customPresentationTransitionDidEnd()
func customPresentationDismissalWillBegin()
func customPresentationDismissalDidEnd()
}
class MyCustomPresentationController: UIPresentationController {
weak var appearanceDelegate: MyCustomApprearanceDelegate!
override init(presentedViewController: UIViewController, presentingViewController: UIViewController) {
super.init(presentedViewController: presentedViewController, presentingViewController: presentingViewController)
}
override func presentationTransitionWillBegin() {
appearanceDelegate.customPresentationTransitionWillBegin()
}
override func presentationTransitionDidEnd(completed: Bool) {
appearanceDelegate.customPresentationTransitionDidEnd()
}
override func dismissalTransitionWillBegin() {
appearanceDelegate.customPresentationDismissalWillBegin()
}
override func dismissalTransitionDidEnd(completed: Bool) {
appearanceDelegate.customPresentationDismissalDidEnd()
}
}

Resources