Reputation: 41
I have set up a very simple project with no storyboard, one window and one UINavigationController containing a plain old UIViewController as rootViewController. In the AppDelegate I've set the UINavigationController's delegate to self and implemented
navigationController:didShowViewController:animated
which contains 1 line:
NSLog("didShow viewController")
When I launch my app the UINavigationControllerDelegate method navigationController:didShowViewController:animated
gets called twice.
AppDelegate:
import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDelegate { var window: UIWindow? var vc1: FirstViewController? var nav1: UINavigationController? func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { NSLog("didShow viewController") } func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { vc1 = FirstViewController() nav1 = UINavigationController(rootViewController: vc1!) nav1?.delegate = self window = UIWindow(frame: UIScreen.main.bounds) if let window = window { window.backgroundColor = UIColor.white window.rootViewController = nav1 window.makeKeyAndVisible() } return true } }
FirstViewController:
import UIKit class FirstViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = UIColor.blue // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }
I have tried this in more complicated environments - An app with a UITabBarController and 2 UINavigationControllers as the viewControllers for the UITabBarController. Oddly the UINavigationControllerDelegate methods fire twice the first time a UINavigationController is shown, but will only fire once after that.
Anyone have any insight as to how to correct this? What I believe to be correct behavior based on the documentation is that navigationController:didShowViewController:animated
should be only called once in this sample app. I have also checke dot make sure the navigationController
and viewController
params in the delegate method are the same object.
Thanks in advance!
EDIT: Here's the callstack from each call. All in UIKit code, not mine!
1st call:
29 elements - 0 : "0 ??? 0x0000000115145377 0x0 + 4648620919" - 1 : "1 ??? 0x0000000115145462 0x0 + 4648621154" - 2 : "2 Test 0x0000000105f22d00 main + 0" - 3 : "3 Test 0x0000000105f21e11 _TToFC4Test11AppDelegate20navigationControllerfTCSo22UINavigationController7didShowCSo16UIViewController8animatedSb_T_ + 97" - 4 : "4 UIKit 0x0000000106bab7a8 -[UINavigationController navigationTransitionView:didEndTransition:fromView:toView:] + 1660" - 5 : "5 UIKit 0x0000000106e8839e -[UINavigationTransitionView _notifyDelegateTransitionDidStopWithContext:] + 421" - 6 : "6 UIKit 0x0000000106e88677 -[UINavigationTransitionView _cleanupTransition] + 629" - 7 : "7 UIKit 0x0000000106a58f07 -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 222" - 8 : "8 UIKit 0x0000000106a54bcb +[UIViewAnimationState popAnimationState] + 305" - 9 : "9 UIKit 0x0000000106e8810b -[UINavigationTransitionView transition:fromView:toView:] + 2582" - 10 : "10 UIKit 0x0000000106bb01d1 -[UINavigationController _startTransition:fromViewController:toViewController:] + 3301" - 11 : "11 UIKit 0x0000000106bb06b3 -[UINavigationController _startDeferredTransitionIfNeeded:] + 843" - 12 : "12 UIKit 0x0000000106bb17f1 -[UINavigationController __viewWillLayoutSubviews] + 58" - 13 : "13 UIKit 0x0000000106da32bc -[UILayoutContainerView layoutSubviews] + 231" - 14 : "14 UIKit 0x0000000106a9020b -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1268" - 15 : "15 QuartzCore 0x000000010bfbf904 -[CALayer layoutSublayers] + 146" - 16 : "16 QuartzCore 0x000000010bfb3526 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 370" - 17 : "17 QuartzCore 0x000000010bfb33a0 _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 24" - 18 : "18 QuartzCore 0x000000010bf42e92 _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 294" - 19 : "19 QuartzCore 0x000000010bf6f130 _ZN2CA11Transaction6commitEv + 468" - 20 : "20 QuartzCore 0x000000010bf6fb37 _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 115" - 21 : "21 CoreFoundation 0x000000010910f717 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23" - 22 : "22 CoreFoundation 0x000000010910f687 __CFRunLoopDoObservers + 391" - 23 : "23 CoreFoundation 0x00000001090f4038 CFRunLoopRunSpecific + 440" - 24 : "24 UIKit 0x00000001069c702f -[UIApplication _run] + 468" - 25 : "25 UIKit 0x00000001069cd0d4 UIApplicationMain + 159" - 26 : "26 Test 0x0000000105f22d37 main + 55" - 27 : "27 libdyld.dylib 0x000000010a19a65d start + 1" - 28 : "28 ??? 0x0000000000000001 0x0 + 1"
2nd call:
20 elements - 0 : "0 ??? 0x00000001151456e7 0x0 + 4648621799" - 1 : "1 ??? 0x00000001151457d2 0x0 + 4648622034" - 2 : "2 Test 0x0000000105f22d00 main + 0" - 3 : "3 Test 0x0000000105f21e11 _TToFC4Test11AppDelegate20navigationControllerfTCSo22UINavigationController7didShowCSo16UIViewController8animatedSb_T_ + 97" - 4 : "4 UIKit 0x0000000106ba949b -[UINavigationController viewDidAppear:] + 421" - 5 : "5 UIKit 0x0000000106b7595e -[UIViewController _setViewAppearState:isAnimating:] + 704" - 6 : "6 UIKit 0x0000000106b7863b __64-[UIViewController viewDidMoveToWindow:shouldAppearOrDisappear:]_block_invoke + 42" - 7 : "7 UIKit 0x0000000106b76a7b -[UIViewController _executeAfterAppearanceBlock] + 86" - 8 : "8 UIKit 0x00000001069d992f _runAfterCACommitDeferredBlocks + 634" - 9 : "9 UIKit 0x00000001069c67bc _cleanUpAfterCAFlushAndRunDeferredBlocks + 532" - 10 : "10 UIKit 0x00000001069e957d __84-[UIApplication _handleApplicationActivationWithScene:transitionContext:completion:]_block_invoke_2 + 155" - 11 : "11 CoreFoundation 0x000000010910fb5c __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12" - 12 : "12 CoreFoundation 0x00000001090f4e54 __CFRunLoopDoBlocks + 356" - 13 : "13 CoreFoundation 0x00000001090f45ee __CFRunLoopRun + 894" - 14 : "14 CoreFoundation 0x00000001090f4016 CFRunLoopRunSpecific + 406" - 15 : "15 GraphicsServices 0x000000010b100a24 GSEventRunModal + 62" - 16 : "16 UIKit 0x00000001069cd0d4 UIApplicationMain + 159" - 17 : "17 Test 0x0000000105f22d37 main + 55" - 18 : "18 libdyld.dylib 0x000000010a19a65d start + 1" - 19 : "19 ??? 0x0000000000000001 0x0 + 1"
Upvotes: 4
Views: 1285
Reputation: 1406
After experimenting, I found that iOS 13 had made this issue for the first UINavigationController in UITabBarController more complicated.
the first UINavigationController won't be called for the call stack [UINavigationController viewDidAppear:]
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.delegate = self
}
}
extension ViewController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
print("willShow")
}
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
print("didShow")
}
}
the output will be
willShow
didShow -> this one is from call stack `[UINavigationController __viewWillLayoutSubviews]`
for the first tab.
Which means that if you are using the old workaround down below below iOS 13 then the first time show first VC and only first VC's didShow
won't be called.
the new workaround I can think of will be still set the delegate in viewDidLoad
but try to stop the first call of VC other than first in TabBarVC.
the didShow
will be call in this order
LAUNCH APP
VC1 willShow
VC1 didShow `[UINavigationController __viewWillLayoutSubviews]`
SELECT SECOND TAB
VC2 willShow
VC2 didShow `[UINavigationController __viewWillLayoutSubviews]`
VC2 didShow `[UINavigationController viewDidAppear:]` -> this is the one I try to get rid off
SELECT BACK TO FIRST TAB
VC1 willShow
VC1 didShow `[UINavigationController viewDidAppear:]`
SELECT BACK TO SECOND TAB
VC2 willShow
VC2 didShow `[UINavigationController viewDidAppear:]`
so make a flag to let other than first UINavigationControllers' didShow
not be called only for the first time.
here is the demo project to solve it Demo
below iOS 13 if you only need
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool)
then there is work around to set like this:
class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationController?.delegate = self
}
}
extension ViewController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
// do something
}
}
but there is a issue that
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool)
will not be called at the first time cause you set delegate too late, but the second time the same ViewController
instance willShow
method get called normally.
p.s. if you want to implement didShow
method in UINavigationController, you have to set delegate like this:
class NavigationController: UINavigationController {
override func viewDidAppear(_ animated: Bool) {
delegate = self
super.viewDidAppear(animated)
}
}
extension NavigationController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
print("will")
}
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
print("did")
}
}
since didShow
method get called in super.viewDidAppear(animated)
Upvotes: 3