UINavigationControllerDelegate方法调用两次

时间:2017-05-16 16:27:20

标签: ios iphone swift uiviewcontroller uinavigationcontroller

我已经设置了一个非常简单的项目,没有故事板,一个窗口和一个UINavigationController包含一个普通的旧UIViewController作为rootViewController。在AppDelegate中,我将UINavigationController的委托设置为self并实现了

navigationController:didShowViewController:animated包含1行:

NSLog("didShow viewController")

当我启动我的应用程序时,UINavigationControllerDelegate方法navigationController:didShowViewController:animated被调用两次。

的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.
    }
}

我在更复杂的环境中尝试了这个 - 一个带有UITabBarController和2个UINavigationControllers的应用程序作为UITabBarController的viewControllers。奇怪的是,UINavigationControllerDelegate方法在第一次显示UINavigationController时会触发两次,但在此之后只会触发一次。

任何人都有任何关于如何纠正这一点的见解?我认为基于文档的正确行为是navigationController:didShowViewController:animated应该只在此示例应用程序中调用一次。我还有checke dot确保委托方法中的navigationControllerviewController参数是同一个对象。

提前致谢!

编辑:这是每个电话的callstack。所有在UIKit代码,而不是我的!

第一个电话:

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"
  

第二次电话:

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"

1 个答案:

答案 0 :(得分:1)

经过试验,我发现iOS 13使UITabBarController中的第一个UINavigationController的问题更加复杂。

不会为调用堆栈[UINavigationController viewDidAppear:]

调用第一个UINavigationController
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")
    }

}

输出将为

willShow
didShow -> this one is from call stack `[UINavigationController __viewWillLayoutSubviews]`

第一个标签。

这意味着,如果您使用的是iOS 13以下的旧变通方法,则第一次显示第一个VC,而不会调用第一个VC的didShow

我能想到的新解决方法仍然是在viewDidLoad中设置委托,但尝试停止VC的第一个调用,而不是TabBarVC中的第一个调用。

didShow将按此顺序调用

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:]`

因此设置一个标志,以使除第一次UINavigationControllers的didShow之外的其他对象不会被首次调用。

这是解决该问题的演示项目Demo


低于iOS 13 如果只需要

func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool)

然后可以进行以下设置:

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
    }

}

但是有一个问题

func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool)

由于您设置的委托时间太晚,不会在第一次调用,而是第二次正常调用相同的ViewController实例willShow方法。

p.s。如果要在UINavigationController中实现didShow方法,则必须这样设置委托:

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

}

因为didShow中的super.viewDidAppear(animated)方法被调用