Xcode Master-Detail模板添加UITabBarController作为UISplitViewController的主人

时间:2016-01-08 23:59:32

标签: ios xcode cocoa-touch

我正在使用Xcode 7(虽然我认为这也适用于6),主要细节模板稍作修改。修改是用UITabBarController替换主UINavigationController。

UITabBarController包含几个UINavigationControllers,它们又与细节UINavigationController相隔。

使用iPhone 6+模拟器,它在横向模式下工作正常(非折叠)。折叠UISplitViewController时,应用程序会停止按预期运行。发生了两件事:

  • 在AppDelegate的didFinishLaunchingWithOptions中,splitViewController.viewControllers数组包含UITabBarController(主)和UINavigationController(详细信息)。在didFinishLaunchingWithOptionssplitViewController:collapseSecondaryViewController:ontoPrimaryViewController之间的某处,详细信息UINavigationController已从splitViewController.viewControllers中删除。如果master和detail都是UINavigationController个对象,则不会发生这种情况。值得注意的是,在Apple的UISplitViewController文档中,他们说viewControllers属性:"当展开拆分视图界面时,此属性包含两个查看控制器;折叠时,此属性只包含一个视图控制器。"在我修改故事板以将UITabBarController作为主文件后,文档中的断言似乎仍然成立,在默认的主 - 详细信息模板中,断言没有保持(即数组中始终有两个viewControllers,无论如何分割视图是折叠还是展开的。)
  • 如果我在折叠模式下运行时添加项目,则详细视图将以模态方式显示,而不是被推送到选定选项卡的导航控制器,因此无法返回主视图。旋转到横向会导致EXC_BAD_ACCESS崩溃 - 由于某种原因,我也会收到错误:"<Error>: CGImageCreate: invalid image size: 0 x 0."

香草的唯一变化是&#39;此阶段的模板应用程序将使用包含UINavigationControllers的UITabBarController替换Master UINavigationController,故事板如下所示:

enter image description here

我还注释了MasterViewController.m中的以下行以防止早期崩溃:

self.detailViewController = (DetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController];

我的问题是:

  1. 当我将UITabBarController添加为master而不是将UINavigationController保留在原位时,为什么viewControllers属性的内容会发生变化?我问,因为我认为这与确定为什么细节VC以模态方式呈现而不是被推送相关。我假设,因为在折叠模式下,SplitViewController的行为类似于UINavigationController,当UISplitViewController和详细视图之间存在非UINavigationController时,存在一些困难。
  2. 继问题1之后,为什么细节控制器会以模态呈现?
  3. 我试图了解正在发生的事情,因为如果有一个(或更多),我正试图找到一个正确的方法让它发挥作用。

    进一步讨论和相关问题

    我的问题与此非常相似: UINavigationController inside a UITabBarController inside a UISplitViewController presented modally on iPhone

    我使用“二元梦想”的答案并取得了一定的成功。和&#39; HpTerm&#39;。

    如果我首先采用Dreaming In Binary的答案(使用该答案中的代码逐字逐句),那么该应用程序的行为几乎与以前相同;我特别注意以下几点:

    • 从纵向视图中选择时,详细视图仍以模态显示;由于我们添加了这种方法(取自前面提到的答案),因此基本上会有一个稍微不同的细节视图呈现路径:

      -(BOOL)splitViewController:(UISplitViewController *)splitViewController showDetailViewController:(UIViewController *)detailVC sender:(id)sender
      {
        UITabBarController *masterVC = splitViewController.viewControllers[0];
      
        if (splitViewController.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact)
          [masterVC.selectedViewController showViewController:detailVC sender:sender];
        else
          [splitViewController setViewControllers:@[masterVC, detailVC]];
      
        return YES;
      }
      

      showViewController仍会触发detailViewController的模态表示。纵向右上方没有导航按钮,但如果将设备旋转为横向,则详细视图将拍摄整个横向,​​但我们确实看到使用splitViewController.displayModeButton项设置的navigationItem的leftBarButtonItem。麻烦的是按钮除了显示“扩展”按钮之外绝对没有任何影响。按钮(用于隐藏主视图);如果按下按钮,视图没有任何反应(主视图已经隐藏了),但按钮变为“后退”状态。通常允许您再次显示主视图的样式。在这种情况下,&#39;返回&#39;按下按钮无效。基本上你可以切换到&#39; expand&#39;之间的按钮。和&#39;回来&#39;按下它时没有其他效果。如果您旋转回横向,那么您再次拥有模态显示的详细视图,无法返回到主视图。

      • 我仍然看到错误<Error>: CGImageCreate: invalid image size: 0 x 0.,但它没有伴随EXC_BAD_ACCESS崩溃。

    接下来,是HpTerm在Swift中的解决方案,在我的世界中看起来像这样:

    - (BOOL)splitViewController:(UISplitViewController *)splitViewController showDetailViewController:(UIViewController *)detailViewController sender:(id)sender
    {
        UITabBarController * masterViewController = splitViewController.viewControllers[0];
    
        if (splitViewController.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact)
        {
            UIViewController * detailViewSubController = ((UINavigationController *)detailViewController).viewControllers[0];
            [((UINavigationController *)masterViewController.selectedViewController) pushViewController:(UIViewController *)detailViewSubController animated:NO];
          return YES;
        }
        else
        {
          return NO;
        }
    }
    

    因此,在上文中,我们不是尝试使用详细信息showViewControllerUINavigationController,而是从详细信息DetailViewController中提取UINavigationController并实际推送DetailViewController到主人UINavigationController。不将细节UINavigationController推送到主UINavigationController的原因是抛出异常(如果将任何导航控制器推到另一个导航控制器上,则适用)。

    这实际上产生了几乎完美的结果,但并不完全。首先,好位:

    • 在肖像中,一切正常,我们将细节推到了上面 掌握选择,当我们导航回来时,我们回到了 掌握正确。
    • 在旋转到横向时,拆分视图会正确显示主图和细节,我们可以使用条形按钮项正确显示/隐藏主视图。

    现在坏了:

    • 在旋转回肖像时(主人选择了一个项目),我们转到视图,实际上我们应该转到所选的详细信息项。

    所以这里的问题是,&#39;坏&#39;要更正这一点,以便在纵向选择项目时,旋转回横向将我们带到选定的细节项目?

    道歉,这太久了,如果你已经做到这一点,我向你致敬!我也很乐意提出减轻问题的建议,但希望提供足够的细节来突出显示正在发生的事情。

2 个答案:

答案 0 :(得分:0)

我仍然愿意接受其他答案,因为有几种方法可以解决这个问题,但有一种方法可以修改Master-Detail App Template的splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:实现。

首先解释一下;在上面问题的最后部分中splitViewController:showDetailViewController:sender:的实现解决了UI方面的一个(重大)问题。它允许将详细视图推送到主UINavigationController的堆栈,同时保持主UITabBarController可见并允许导航回主视图。它通过从分割视图控制器的详细导航控制器中取出DetailViewController并将DetailViewController推入堆栈,在折叠模式下执行此操作。这与默认的Master-Detail模板不同,因为在该版本中,细节导航控制器被推到主导航控制器上并且仍然可以工作(我只是无法找到一种方法使其在添加后像这样工作UITabBarController)。完成此操作后,当我们旋转到横向(展开)视图时,一切仍然正常工作(细节导航控制器返回),但是当我们旋转到纵向(折叠)视图时,我们最终返回主视图,即使选择了详细信息项目。

我强制推送详细信息视图(如果有数据要显示),方法是通过从主控制器触发详细视图的segue向splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:添加一个附加条件:

if ([secondaryViewController isKindOfClass:[DetailViewController class]] && [(DetailViewController *)secondaryViewController detailItem] != nil && [primaryViewController isKindOfClass:[UITabBarController class]])
{
    UINavigationController * currentMasterNavigationController = ((UITabBarController *)primaryViewController).selectedViewController;
    MasterViewController * masterViewController = (MasterViewController *)[currentMasterNavigationController visibleViewController];
    [masterViewController performSegueWithIdentifier:@"showDetail" sender:masterViewController];
    return YES;
}

完整修改后的功能是:

- (BOOL)splitViewController:(UISplitViewController *)splitViewController collapseSecondaryViewController:(UIViewController *)secondaryViewController ontoPrimaryViewController:(UIViewController *)primaryViewController
{
    if ([secondaryViewController isKindOfClass:[UINavigationController class]] && [[(UINavigationController *)secondaryViewController topViewController] isKindOfClass:[DetailViewController class]] && ([(DetailViewController *)[(UINavigationController *)secondaryViewController topViewController] detailItem] == nil))
    {
        // Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
        return YES;
    }
    else if ([secondaryViewController isKindOfClass:[DetailViewController class]] && [(DetailViewController *)secondaryViewController detailItem] != nil && [primaryViewController isKindOfClass:[UITabBarController class]])
    {
        UINavigationController * currentMasterNavigationController = ((UITabBarController *)primaryViewController).selectedViewController;
        MasterViewController * masterViewController = (MasterViewController *)[currentMasterNavigationController visibleViewController];
        [masterViewController performSegueWithIdentifier:@"showDetail" sender:masterViewController];
        return YES;
    }
    return NO;
}

这对我来说似乎仍然是一个黑客,但它确实有效。我希望看到一个比这更清晰的答案,它更像默认模板,同时仍允许UITabBarController

答案 1 :(得分:0)

我相信你已经对工作实施有所了解。我花了几周时间才完成它。

Apple的UISplitViewController确实需要一些额外的调整来正确地改变大小类。我已将自适应UISplitViewController与UITabBarController一起作为GitHub indievox-inc/TabBarSplitViewController上开源的主视图控制器,应该值得检查一下。

您可以查看the original implementation,这样可以更轻松地移植到Objective-C代码。

以下主要功能:

 // MARK: to Compact Width size class (collapse)
    public func primaryViewControllerForCollapsingSplitViewController(splitViewController: UISplitViewController) -> UIViewController? {
        if let primaryTabViewController = splitViewController.viewControllers[0] as? UITabBarController,
               primaryNavViewController = primaryTabViewController.selectedViewController as? UINavigationController {
                let secondaryViewController = splitViewController.viewControllers[1]
                if !(secondaryViewController.dynamicType === DetailViewControllerType.Empty) {
                    dispatch_async(dispatch_get_main_queue()) {     // otherwise we may get console error "<Error>: CGImageCreate: invalid image size: 0 x 0."
                        primaryNavViewController.showViewController(secondaryViewController, sender: secondaryViewController)
                    }
                    return primaryTabViewController
                }
        }

        return nil
    }

// MARK: to Regular Width size class (separate/expand)
    public func splitViewController(splitViewController: UISplitViewController, separateSecondaryViewControllerFromPrimaryViewController primaryViewController: UIViewController) -> UIViewController? {
        if let primaryTabViewController = splitViewController.viewControllers[0] as? UITabBarController,
               primaryNavViewController = primaryTabViewController.selectedViewController as? UINavigationController {
                let primaryTopViewController = primaryNavViewController.topViewController
                if let primaryTopViewController = primaryTopViewController {
                    if ((primaryTopViewController.dynamicType === DetailViewControllerType.General)
                     || (primaryTopViewController.dynamicType === DetailViewControllerType.Empty)) {
                        primaryNavViewController.popViewControllerAnimated(false)
                        return primaryTopViewController
                    }
                }
        }

        return DetailViewControllerType.Empty.init()
    }

// MARK: override showDetailViewController
    public func splitViewController(splitViewController: UISplitViewController, showDetailViewController vc: UIViewController, sender: AnyObject?) -> Bool {
        let isCompactWidth = (splitViewController.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClass.Compact)
        if isCompactWidth {
            if let primaryTabViewController = splitViewController.viewControllers[0] as? UITabBarController,
                      primaryViewController = primaryTabViewController.selectedViewController as? UINavigationController {
                        primaryViewController.showViewController(vc, sender: sender)
                        return true
            }
        }

        return false
    }