在iOS 8中的UISplitViewController的主视图中有一个UINavigationController

时间:2014-09-26 13:35:21

标签: uinavigationcontroller ios8 uisplitviewcontroller

在我的UISplitViewController中,主视图是一个包含UITableViewController的UINavigationController。有时,当用户选择表中的项时,我必须在主视图中将另一个tableViewController压到现有表上。

在iOS 7中,在我的第一个UITableViewController中,我只需要调用

[self.navigationController pushViewController:otherTableVC animated:YES];

在iOS 8中:

折叠拆分视图时,otherTableVC将成为详细视图! 然后旋转设备后,我们并排看到两张桌子......

更糟糕的是,如果设备显示两个窗格,则代码工作正常,第二个表在主视图中被推到第一个窗格。但是,经过两次旋转后,两张桌子又是并排的。似乎UISplitViewController的折叠模式干扰了我自己的导航控制器...

如何在主视图中管理自己的UINavigationController?

谢谢

编辑:

我的主视图和详细信息视图都有一个导航控制器。为了解决我的问题,我刚刚发现,在折叠模式下,我必须创建一个额外的导航控制器并将其推到主导航控制器上。

UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:otherTableVC];
[self.navigationController pushViewController:navController animated:YES];

所以我发现帽子我们可以将导航控制器推到另一个导航控制器中。

2 个答案:

答案 0 :(得分:24)

简短回答,您可以通过UISplitViewControllerDelegate方法控制此行为:

splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:
splitViewController:separateSecondaryViewControllerFromPrimaryViewController:

我怀疑你真正想要做的是处理你有一个基于iOS 8 UISplitViewController的应用程序的情况,你的主要和详细视图都是UINavigationControllers,并且你想要的是一些viewControllers(在这些导航控制器中)仅出现在拆分视图的主要或细节一侧。以下答案涉及此问题。它还可以处理您有时希望视图替换Detail导航控制器中的视图而不是被推到那里的情况。

一个小警告:下面的代码并不涉及所有可能的情况,并且有一些假设:

  • 我们不希望在拆分拆分视图时,详细导航控制器堆栈上的任何内容都会发生变化,并且这些视图会被上面的详细视图遮挡。
  • 我们的UIViewController子类都有一个shouldDisplayInDetailedView和shouldReplaceDetailedView属性
  • 我们假设我们只将视图推送到具有shouldDisplayInDetailedView属性集的详细导航控制器。
  • 视图控制器通过splitViewController添加到Detail端:showDetailViewController:或pushViewController:animated:在详细视图中的视图的navigationController属性上(处于展开状态或折叠状态)。
  • 应该替换细节导航控制器中的视图控制器的控制器只能通过splitViewController:showDetailViewController添加:并且只能通过与主视图控制器中的视图交互来添加,即,这只能在以下情况下发生:处于折叠状态时,主视图控制器不会被遮挡。
  • 当拆分视图控制器扩展时,我们有一个BlankViewController在详细信息视图中显示,但我们只有视图控制器应保留在主要端。

我不建议只实现splitViewController的一面:collapseSecondaryViewController:ontoPrimaryViewController:/ splitViewController:separateSecondaryViewControllerFromPrimaryViewController:logic并取决于另一方的默认实现。 Apple做了一些奇怪的事情,例如将UINavigationViewController从Detail侧放入主要侧作为Primary导航控制器堆栈中的一个viewControllers,然后推送其上方的其他视图控制器,即使你完全理解仍然不能从您自己的代码复制。因此,最好自己处理这两个过程。

这就是我使用的:

#pragma mark -
#pragma mark Split View Controller delegate.

- (BOOL)splitViewController:(UISplitViewController *)splitViewController showViewController:(UIViewController *)vc sender:(id)sender
{
    //Standard behaviour.  This won't get called in our case when the split view is collapsed and the primary view controllers are obscured.
    return NO;
}

// Since we treat warnings as errors, silence warning about unknown selector below on UIViewController subclasses.
#pragma GCC diagnostic ignored "-Wundeclared-selector"


- (BOOL)splitViewController:(UISplitViewController *)splitViewController showDetailViewController:(UIViewController *)vc sender:(id)sender
{
    if (splitViewController.collapsed == NO)
    {
        // The navigation controller we'll be adding the view controller vc to.
        UINavigationController *navController = splitViewController.viewControllers[1];

        UIViewController *topDetailViewController = [navController.viewControllers lastObject];
        if ([topDetailViewController isKindOfClass:[BlankViewController class]] ||
           ([vc respondsToSelector:@selector(shouldReplaceDetailedView)] && [vc performSelector:@selector(shouldReplaceDetailedView)]))
        {
            // Replace the (expanded) detail view with this new view controller.
            [navController setViewControllers:@[vc] animated:NO];
        }
        else
        {
            // Otherwise, just push.
            [navController pushViewController:vc animated:YES];
        }
    }
    else
    {
        // Collapsed.  Just push onto the conbined primary and detailed navigation controller.
        UINavigationController *navController = splitViewController.viewControllers[0];
        [navController pushViewController:vc animated:YES];
    }

    // We've handled this ourselves.
    return YES;
}

- (BOOL)splitViewController:(UISplitViewController *)splitViewController collapseSecondaryViewController:(UIViewController *)secondaryViewController ontoPrimaryViewController:(UIViewController *)primaryViewController
{
    UINavigationController *primaryNavController = (UINavigationController *)primaryViewController;
    UINavigationController *secondaryNavController = (UINavigationController *)secondaryViewController;
    UIViewController *bottomSecondaryView = [secondaryNavController.viewControllers firstObject];
    if ([bottomSecondaryView isKindOfClass:[BlankViewController class]])
    {
        NSAssert([secondaryNavController.viewControllers count] == 1, @"BlankViewController is not only detail view controller");
        // If our secondary controller is blank, do the collapse ourself by doing nothing.
        return YES;
    }

    // We need to shift these view controllers ourselves.
    // This should be the primary views and then the detailed views on top.
    // Otherwise the UISplitViewController does wacky things like embedding a UINavigationController inside another UINavigation Controller, which causes problems for us later.
    NSMutableArray *newPrimaryViewControllers = [NSMutableArray arrayWithArray:primaryNavController.viewControllers];
    [newPrimaryViewControllers addObjectsFromArray:secondaryNavController.viewControllers];
    primaryNavController.viewControllers = newPrimaryViewControllers;

    return YES;
}

- (UIViewController *)splitViewController:(UISplitViewController *)splitViewController separateSecondaryViewControllerFromPrimaryViewController:(UIViewController *)primaryViewController
{
    UINavigationController *primaryNavController = (UINavigationController *)primaryViewController;

    // Split up the combined primary and detail navigation controller in their component primary and detail view controller lists, but with same ordering.
    NSMutableArray *newPrimaryViewControllers = [NSMutableArray array];
    NSMutableArray *newDetailViewControllers = [NSMutableArray array];
    for (UIViewController *controller in primaryNavController.viewControllers)
    {
        if ([controller respondsToSelector:@selector(shouldDisplayInDetailedView)] && [controller performSelector:@selector(shouldDisplayInDetailedView)])
        {
            [newDetailViewControllers addObject:controller];
        }
        else
        {
            [newPrimaryViewControllers addObject:controller];
        }
    }

    if (newDetailViewControllers.count == 0)
    {
        // If there's no detailed views on the top of the navigation stack, return a blank view  (in navigation controller) for detailed side.
        UINavigationController *blankDetailNavController = [[UINavigationController alloc] initWithRootViewController:[[BlankViewController alloc] init]];
        return blankDetailNavController;
    }

    // Set the new primary views.
    primaryNavController.viewControllers = newPrimaryViewControllers;

    // Return the new detail navigation controller and views.
    UINavigationController *detailNavController = [[UINavigationController alloc] init];
    detailNavController.viewControllers = newDetailViewControllers;
    return detailNavController;
}

答案 1 :(得分:0)

Swift 4版本,进行了较小的更改以使其与我的代码兼容:

func splitViewController(_ splitViewController: UISplitViewController, showDetail vc: UIViewController, sender: Any?) -> Bool {
    if !isCollapsed {
        // in expanded mode set new VC as top view controller of the detail nav controller
        if let detailNavigationController = viewControllers[1] as? UINavigationController {
           detailNavigationController.setViewControllers([vc], animated: false)
        }
    } else {
        // in collapsed mode push the new view controller on the master nav controller
        if let masterNavigationController = viewControllers[0] as? UINavigationController {
            masterNavigationController.pushViewController(vc, animated: true)
        }
    }
    return true
}

func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {

    let masterNavigationController = primaryViewController as? UINavigationController
    let detailNavigationController = secondaryViewController as? UINavigationController
    let episodeDetailViewController = detailNavigationController?.viewControllers.first as? EpisodeDetailTableViewController
    if episodeDetailViewController?.episode == nil {
        // detail view is blank. We do not need to push this onto the master
        return true
    }

    guard var newMasterViewControllers = masterNavigationController?.viewControllers else { return false }
    newMasterViewControllers.append(contentsOf: detailNavigationController?.viewControllers ?? [])
    masterNavigationController?.setViewControllers(newMasterViewControllers, animated: false)
    return true
}


func splitViewController(_ splitViewController: UISplitViewController, separateSecondaryFrom primaryViewController: UIViewController) -> UIViewController? {
    let masterNavigationViewController = primaryViewController as? UINavigationController

    var newMasterViewControllers = [UIViewController]()
    var newDetailViewControllers = [UIViewController]()

    for vc in masterNavigationViewController?.viewControllers ?? [] {
        if vc is PodcastsTableViewController || vc is EpisodesTableViewController {
            newMasterViewControllers.append(vc)
        } else {
            newDetailViewControllers.append(vc)
        }
    }

    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let detailNavigationController = storyboard.instantiateViewController(withIdentifier: "splitViewDetailViewController") as! UINavigationController

    if newDetailViewControllers.count == 0 {
        let emptyEpisodeDetailViewController = storyboard.instantiateViewController(withIdentifier: "episodeDetail")
        newDetailViewControllers.append(emptyEpisodeDetailViewController)
    }

    masterNavigationViewController?.setViewControllers(newMasterViewControllers, animated: false)
    detailNavigationController.setViewControllers(newDetailViewControllers, animated: false)
    return detailNavigationController
}