iPhone 6+ 状态恢复与主拆分视图中的标签栏

Posted

技术标签:

【中文标题】iPhone 6+ 状态恢复与主拆分视图中的标签栏【英文标题】:iPhone 6+ state restoration with tab bar in master split view 【发布时间】:2015-02-24 13:00:32 【问题描述】:

我在 iPhone 6+ 中支持状态恢复时遇到问题。

这是我的层次结构:

问题是当状态恢复发生在纵向时,然后在稍后点,在横向时进行状态解码尝试。

说明: 在竖屏模式下,主视图只有TabBar(实际上不存在详细视图),所以它被推入TabBar导航的控制器中。

然后,稍后,当应用程序尝试执行横向状态恢复时,我的详细信息视图被推送到主导航控制器上(当它应该是详细信息时)。

由于自定义层次结构,我已经相应地实现了 UISplitViewControllerDelegate 方法,并且它们工作正常。 UISplitViewControllerDelegate 方法还确保状态恢复在以下情况下有效:

Landscape -> Landscape
Landscape -> Portrait
 Portrait -> Portrait

不起作用的是:纵向 -> 横向,因为正如我所说,在未折叠状态时不会调用委托方法,因此视图层次结构不知道如何从主视图中拆分细节并将其嵌入到细节中导航控制器。

【问题讨论】:

UISplitViewController state restoration in ios 8的可能重复 【参考方案1】:

到目前为止,我已经解决了这个问题:在 viewWillAppear 中,所有细节控制器都是从标签栏的导航控制器中删除的。 存储状态时,会保存设备方向和 SplitViewController.viewControllers(对我来说,它不会像在 iOS7 上那样自动存储它们)。

- (void) encodeRestorableStateWithCoder:(NSCoder*)coder

    [super encodeRestorableStateWithCoder:coder];
    [coder encodeObject:self.viewControllers forKey:@"viewControllers"];
    [coder encodeInteger:[UIApplication sharedApplication].statusBarOrientation forKey:@"orientation"];


- (void) decodeRestorableStateWithCoder:(NSCoder*)coder

    [super decodeRestorableStateWithCoder:coder];
    NSArray* viewControllers = [coder decodeObjectForKey:@"viewControllers"];
    if (viewControllers.count > 0)
    
        self.viewControllers = viewControllers;
    
    restoredOrientation = (UIInterfaceOrientation) [coder decodeIntegerForKey:@"orientation"];

这里是 viewWillAppear 的实现:

- (void) viewWillAppear:(BOOL)animated

    [super viewWillAppear:animated];

    // iPhone6+: if state restoration happened while in portrait orientation and app is launched while in landscape,
    // then all detail views should be cut from master view and split details view is set appropriately
    if (firstLoad &&
            [UIScreen mainScreen].scale > 2.9 && // assure it's iPhone 6+
            UIInterfaceOrientationIsPortrait(restoredOrientation) &&
            UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication.statusBarOrientation))
    
        UITabBarController* tbc = self.viewControllers[0];
        NSArray* detachedControllers = [tbc cutControllersFrom:DocumentViewController.class];
        if (detachedControllers.count > 0)
        
            UINavigationController* documentNavigation = [self.storyboard
                    instantiateViewControllerWithIdentifier:@"NavigationController"];
            documentNavigation.viewControllers = detachedControllers;
            self.viewControllers = @[ self.viewControllers[0], documentNavigation ];
        
        else // place some default no-selection controller in detail
        
            UINavigationController* noSelectionNavigation = [self.storyboard
                    instantiateViewControllerWithIdentifier:@"NoSelectionSID"];
            self.viewControllers = @[ self.viewControllers.firstObject, noSelectionNavigation ];
        
    
    firstLoad = NO;

其中 cutControllersFrom 方法是 UITabBarController 上的一个类别:

- (NSArray*) cutControllersFrom:(Class)controllerClass

    NSArray* ret;
    for (UIViewController* vc in self.viewControllers)
    
        if (![vc isKindOfClass:UINavigationController.class])
        
            continue;
        

        UINavigationController* nc = (UINavigationController*) vc;
        NSArray* removed = [nc cutFrom:controllerClass];
        if (vc == self.selectedViewController)
        
            ret = removed;
        
    

    return ret;

调用 cutFrom: 方法,该方法是 UINavigationController 上的一个类别:

- (NSArray*) cutFrom:(Class)controllerClass

    NSMutableArray* toRemove = [NSMutableArray array];
    BOOL startRemoving = NO;
    UIViewController* endingViewController;

    for (NSUInteger i = 0; i < self.viewControllers.count; i++)
    
        UIViewController* vc = self.viewControllers[i];
        if ([vc isKindOfClass:controllerClass])
        
            startRemoving = YES;
            endingViewController = self.viewControllers[i - 1];
        

        if (startRemoving)
        
            [toRemove addObject:vc];
        
    
    if (endingViewController)
    
        [self popToViewController:endingViewController animated:NO];
    

    return toRemove;

【讨论】:

似乎您别无选择,并且在弄清楚这一切方面做得很好。如果您准备改变方法,一个选择是不使用标签栏并移至侧边栏。我在我当前的应用程序中执行此操作,其中一个侧面菜单允许我在多个拆分视图控制器之间切换。好消息是每个拆分视图都是独立的,并且具有用于主视图和详细信息的导航控制器,因此更简单。所以你的标签栏可以变成侧边栏菜单,它可以让你在 4 个拆分视图控制器之间切换:每个 ControllerX 一个。只是一个想法。 啊,是的,侧边菜单是个不错的主意。目前,我必须坚持使用 TabBar,但为了将来参考,记住它会非常好。【参考方案2】:

不确定我是否完全理解您的问题,因为在 iPhone 6+ 上从纵向到横向应该从折叠状态变为拆分状态,所以应该调用这个委托:

- (UIViewController *)splitViewController:(UISplitViewController *)splitViewController separateSecondaryViewControllerFromPrimaryViewController:(UIViewController *)primaryViewController

但是您似乎暗示这没有被调用,并且在 Portrait 中它处于非折叠状态?

在我使用的拆分视图控制器上,我发现最好让控制器自己解决问题。如果您将 showDetail segues 用于详细视图,它应该为您处理拆分。您是否确保使用 showDetail segues 从 Controller1->4 推送 DetailController 而不是 show segues?

因此假设您对细节使用了正确的 segues,只需在代表中返回 nil 以让拆分视图控制器自行排序?

【讨论】:

如果master是UINavigationController,SplitViewController可以解决问题,但在我的情况下,它是UITabBarController,它有自己的UINavigationControllers,这是UISplitViewController不知道如何解决的问题,所以我不得不这样做通过实现 UISplitViewControllerDelegate 方法手动实现。 好的。也许您需要在拆分视图控制器中实现- splitViewController:showDetailViewController:sender: 以让您将细节放在正确的位置? 我已经在实现该方法,但在状态恢复时并未调用该方法。【参考方案3】:

当状态保留为纵向时,恢复标识符路径将不同于预期的横向,这是使用拆分视图控制器时的默认初始状态。即细节在主要而不是次要中。因此,当应用程序重新启动时,它将无法重新创建以前的层次结构,并将诉诸于在单独的配置中重新创建这些控制器(About the UI Restoration Process 中的第 4 步),这意味着不会调用 separateSecondaryViewControllerFromPrimaryViewController,因为这些控件已经分开了。您可能会注意到在启动时创建了两个细节控制器,第一个可能设置了默认属性的控制器被丢弃了,或者您甚至可能会看到两个细节控制器被推送到导航堆栈上。我不确定你为什么设法让 Portrait->Portrait 工作,因为它应该遇到同样的问题。

要解决这些问题,您可以实现application:viewControllerWithRestorationIdentifierPath:coder,它(在 Portrait->Landscape 场景中)将使用纵向层次结构调用,您可以通过在从故事板。在它恢复视图控制器后,它将检测到从保留的纵向到当前横向的方向发生了变化,并且将调用separateSecondaryViewControllerFromPrimaryViewController。但是,我还不确定这是正确的方法,因为拆分控制器中有一些未保存的状态,例如_preservedDetailController(这就是拆分视图控制器如何在一个之前的崩溃)所以如果在方向更改后重新启动,我们有可能只是丢弃状态,我还不是 100% 确定。

这是我的测试应用程序中的一个示例:

- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
    if([identifierComponents.lastObject isEqualToString:@"DetailViewController"])
        UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController;
        UINavigationController *secondaryNavigationController = splitViewController.viewControllers.lastObject;;
        DetailViewController *detail = (DetailViewController *)secondaryNavigationController.viewControllers.firstObject;
        NSURL *objectURI = [coder decodeObjectForKey:@"object"];
        if(objectURI)
            NSManagedObjectContext *moc = self.persistentContainer.viewContext;
            NSManagedObjectID *objectID = [moc.persistentStoreCoordinator managedObjectIDForURIRepresentation:objectURI];
            NSManagedObject *object = [moc objectWithID:objectID];
            detail.object = object;
        
        // attempt to workaround a bug for _preservedDetailController not being restored.
        [splitViewController _willShowCollapsedDetailViewController:secondaryNavigationController inTargetController:nil];
        return detail;
    
    else if([identifierComponents.lastObject isEqualToString:@"DetailNavigationController"])
        UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController;
        UINavigationController *secondaryNavigationController = splitViewController.viewControllers.lastObject;
        return secondaryNavigationController;
    
    return nil;

【讨论】:

以上是关于iPhone 6+ 状态恢复与主拆分视图中的标签栏的主要内容,如果未能解决你的问题,请参考以下文章

为啥我在 iPhone 6+ 上旋转时导航栏会从拆分视图中消失?

标签栏和导航控制器应用程序中的状态恢复

iOS 获取状态栏、导航栏、tabBar高度

iOS-导航栏、状态栏及Tabbar高度(区分iPhone X与其他iPhone机型)

如何添加 iPad 子视图控制器以充当标签栏

iPhone 可扩展视图位于另一个视图之上