(目标-C)通过 window.rootView 和 keyWindow 访问导航堆栈的区别

(Objective-C) Difference between accessing the navigation stack through the window.rootView and keyWindow

提问人:valeriana 提问时间:1/30/2021 更新时间:2/3/2021 访问量:59

问:

有谁知道两者之间有什么区别

TabBarController* tabBar = (TabBarController *)_window.rootViewController;
UINavigationController* navigationController = tabBar.selectedViewController;
ViewController* initialViewController = (ViewController *)navigationController.topViewController;

和这个

UINavigationController* navigationController = [UIApplication sharedApplication].keyWindow.rootViewController.navigationController;

ViewController* initialViewController = (ViewController *)navigationController.topViewController;

我的假设:

我的结果:

  • 示例 A 成功地将我的新 VC 推送到堆栈上,而示例 B 则没有

撇开设计原则不谈(示例 B 现在看起来很笨拙,但到目前为止我所拥有的只是这些),我真的不明白这两个示例的区别,它们的性能不应该一样吗?谁能说明为什么这些不同?

iOS Objective-C UIAnageController 窗口 RootViewController

评论


答:

0赞 valeriana 2/1/2021 #1
  1. UIViewController 上的 navigationController 属性可为 null,因此它可能返回 nil。

  2. UINavigationController 上的 topViewController 属性也可为 null,并返回 UIViewController?,而不是特定类。简单地将任何 UIViewController 强制转换为 ViewController 是不安全的,因此应针对 isKindOfClass 进行检查。

  3. 同样的事情也适用于 rootViewController,它可能并不总是 TabBarController,因此无条件地强制转换它是不安全的。

  • 此外,还发现如果 rootViewController 的类型为 UITableViewController,则 navigationController 为 nil。
1赞 Carl Lindberg 2/1/2021 #2

navigationController 方法将返回一个父视图控制器,即最接近的 UINavigationController。在您的示例中,它看起来好像根视图控制器是 UITabBarController,每个选项卡中都有一个 UINavigationController。在选项卡控制器上调用 -navigationController 将返回 nil,因为它没有父视图控制器(实际上,它是根)。

如果应用程序中有多个窗口,则 -keyWindow 可以更改。这种方法在 iOS 13 中也被弃用,因为现在可以有多个窗口场景,所以如果你知道你所处的特定窗口,最好使用它,而不是从 UIApplication 开始并向下钻取。

编辑:如果目标是呈现一个新的视图控制器,您将从根视图控制器开始,然后沿着呈现的视图控制器走,并在其上呈现您的新视图控制器。

@implementation UIViewController (MyOvertop)

- (void)my_presentOvertopViewController:(UIViewController *)other animated:(BOOL)animated completion:(void (^ _Nullable)(void))completion
{
    UIViewController *topmost = self;

    while (topmost.presentedViewController &&
           ![topmost.presentedViewController isBeingDismissed])
    {
        topmost = topmost.presentedViewController;
    }

    if (topmost.transitionCoordinator) {
        // transition already going on, wait til it's done and try again
        [topmost.transitionCoordinator animateAlongsideTransition:nil completion:^(id context) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [self my_presentOvertopViewController:vc animated:animated completion:completion];
            });
        }];
        return;
    }

    if (topmost != other) {
        [topmost presentViewController:other animated:animated completion:completion];
    }
}

@end

(上面可能有错别字,但这就是这个想法)

评论

0赞 valeriana 2/2/2021
嗨,卡尔,谢谢!不过,这正是问题所在。这是在 AppDelegate 中,用于在处理通用链接的任何部分中处理应用程序的重新加载。这意味着我永远不知道用户将在哪个窗口。我真的不知道如何处理这个问题.对此有任何提示或想法吗?
0赞 Carl Lindberg 2/3/2021
通常,AppDelegate 实现“window”属性,该属性是应用的“主”窗口(iOS 可以自动为您创建该窗口)。如果您没有多个窗口场景,那通常是最好的去处。如果不存在,则可以使用 keyWindow 作为次优回退。UIWindowSceneDelegate 还具有一个 window 属性,如果您使用多个场景,则该属性与该属性等效。从那里开始,窗口根目录可能是选项卡控制器,其 selectedViewController 将是你想要的导航。
0赞 valeriana 2/3/2021
似乎在该selectedViewController之上还有另一个navigationController,用于处理视图层次结构的其余部分。这是这种方法中所有邪恶的之母。我不知道有任何方法可以访问位于selectedViewController导航控制器顶部的navigationController。你知道怎么做吗?从本质上讲,我有这个 (lldb) po self.window.rootViewController.presentedViewController <NavigationController: 0x10805ce00>卡在这一点上。po self.window.rootViewController.presentedViewController.navigationController 返回 nil
0赞 Carl Lindberg 2/3/2021
presentedViewController 完全是另一回事。这是一个模式,不是根视图控制器的子项。如果你的目标是在当前存在的任何内容之上呈现一个新的 VC,你只需要从根开始,遍历呈现的视图控制器,直到找到顶部的视图控制器,然后使用它来呈现。这有几个陷阱;我会编辑我的答案。