Set global default back button display mode for all UIViewController instances

1.1k views Asked by At

iOS 14 introduced configurable Back button mode. Eg. you can have "Back" label text on the button, but in the compact history menu you can still see the proper title of previous controllers.

I'm looking for a easy, pleasant way to configure the default mode, so all UIViewController instances during runtime will have default mode set, like UINavigationItemBackButtonDisplayModeGeneric

I wonder if there is a way to do that without subclassing UIViewController or remember to always configure manually every instance of UIViewController (via viewController.navigationItem.backButtonDisplayMode = UINavigationItemBackButtonDisplayModeGeneric).

Any handy method which does not require extensive refactoring of hundreds of UIViewController instances greatly appreciated!

2

There are 2 answers

0
Mohmmad S On

Without subclassing I think is not possible since navigationItem requires an instance to work with and can't be modified directly from extensions

class GenericViewController: UIViewController {
override func viewDidLoad() {
    super.viewDidLoad()
    // your code here  
    viewController.navigationItem.backButtonDisplayMode = UINavigationItemBackButtonDisplayModeGeneric
  }
}

And use that where ever you need

class viewController: GenericViewController

This is a really good approach since you have control over what implements it and what's not considering that it might not be there in all scenes

0
Giorgio On

To solve the same problem, I used the swizzling technique

import UIKit

private let swizzling: (UIViewController.Type, Selector, Selector) -> Void = { forClass, originalSelector, swizzledSelector in
    if let originalMethod = class_getInstanceMethod(forClass, originalSelector), let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector) {
        let didAddMethod = class_addMethod(forClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
        if didAddMethod {
            class_replaceMethod(forClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod)
        }
    }
}

extension UIViewController {
    
    static func swizzle() {
        let originalSelector1 = #selector(viewDidLoad)
        let swizzledSelector1 = #selector(swizzled_viewDidLoad)
        swizzling(UIViewController.self, originalSelector1, swizzledSelector1)
    }
    
    @objc open func swizzled_viewDidLoad() {
        if let _ = navigationController {
            if #available(iOS 14.0, *) {
                navigationItem.backButtonDisplayMode = .generic
            } else {
                // Fallback on earlier versions
                navigationItem.backButtonTitle = "Back"
            }
        }
        swizzled_viewDidLoad()
    }
}

And in application(_:didFinishLaunchingWithOptions:) call

UIViewController.swizzle()