【问题标题】:UISplitViewController in portrait on iPhone shows detail VC instead of masteriPhone上的UISplitViewController纵向显示详细VC而不是master
【发布时间】:2014-11-10 14:34:20
【问题描述】:

我在 Xcode 6 中使用 Universal Storyboard,针对 iOS 7 及更高版本。我已经实现了UISplitViewController,它现在在运行 iOS 8 的 iPhone 上得到原生支持,并且 Xcode 会自动将其向后移植到 iOS 7。它工作得非常好,除非你在运行 iOS 8 的 iPhone 上纵向启动应用程序时,拆分当我希望第一次看到主视图控制器时,会显示视图的详细视图控制器。我认为这是 iOS 8 的错误,因为当您在 iOS 7 上运行应用程序时,它会正确显示主视图控制器。但是 iOS 8 现在是通用的,而且这种情况仍在发生。我该如何设置它,以便当拆分视图控制器将被折叠时(屏幕上仅显示一个视图控制器),当显示拆分视图控制器时它显示主视图控制器而不是细节?

我在 Interface Builder 中创建了这个拆分视图控制器。拆分视图控制器是标签栏控制器中的第一个视图控制器。 master 和 detail VC 都是导航控制器,其中嵌入了 table view 控制器。

【问题讨论】:

    标签: ios interface-builder ios8 uisplitviewcontroller master-detail


    【解决方案1】:

    哦,伙计,这让我头疼了几天,不知道该怎么做。最糟糕的是,使用主从模板创建一个新的 Xcode iOS 项目工作得很好。幸运的是,最后,我找到了解决方案。

    我发现一些帖子表明解决方案是在UISplitViewControllerDelegate 上实现新的primaryViewControllerForCollapsingSplitViewController: 方法。我试过没有用。 Apple 在似乎有效的主-详细信息模板中所做的是实现新的(深吸一口气说出所有这一切)splitViewController:collapseSecondaryViewController:ontoPrimaryViewController: 委托方法(再次在 UISplitViewControllerDelegate 上)。根据docs,这个方法:

    要求代理调整主视图控制器并将辅助视图控制器合并到折叠界面中。

    请务必阅读该方法的讨论部分以了解更多具体细节。

    Apple 的处理方式是:

    - (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 {
    
            return NO;
    
        }
    }
    

    这个实现基本上做了以下事情:

    1. 如果secondaryViewController 是我们所期待的(UINavigationController),它显示了我们所期待的(DetailViewController——您的视图控制器),但没有模型(detailItem),然后是“Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.”
    2. 否则,返回“NO 让拆分视图控制器尝试将辅助视图控制器的内容合并到折叠界面中”

    iPhone 纵向显示的结果如下(从纵向开始或旋转到纵向 - 或更准确地说是紧凑尺寸类):

    1. 如果你的观点是正确的
      • 并有一个模型,显示详细视图控制器
      • 但没有模型,显示主视图控制器
    2. 如果您的观点不正确
      • 显示主视图控制器

    清如泥。

    【讨论】:

    • 绝妙的答案!我只是将UISplitViewController 子类化并始终从该方法返回YES,然后只是更改了Storyboard 中的拆分视图类,因为我总是想在iPhone 上以纵向显示主人。 :)
    • 如果“iPhone”处于“纵向”模式,我希望隐藏我的主视图控制器,因为我有一个默认的详细视图控制器设置。我怎样才能做到这一点。我的 master 和 detail 都是 VC 类型。具体来说,我的详细信息是 MMDrawerController。请帮忙
    • 我尝试了乔伊关于子类化UISplitViewController 的建议,但发现这不起作用:splitViewController:collapseSecondaryViewController:ontoPrimaryViewController: 从未被调用过。相反,我复制了 Apple 的模板并将其放入 AppDelagate。这需要对在application didFinishLaunchingWithOptions: 下创建 UISplitViewController 进行一些更改(我还复制了 Apple 的模板)。
    • @joey 的评论适用于设置 self.delegate = self;在viewdidload中!并在 .h 中添加 谢谢!
    • 这对我来说似乎是正确的答案,因为我遇到了完全相同的问题。但是,出于某种原因,我的splitViewController:collapseSecondaryViewController:ontoPrimaryViewController: 永远不会被调用。看来委托正在正确设置我的应用委托的applicationDidFinishLaunchingWithOptions: 方法。有没有其他人看到这个问题,但没有解决这个问题?
    【解决方案2】:

    这是 Swift 中公认的答案。只需创建此子类并将其分配给情节提要中的 splitViewController。

    //GlobalSplitViewController.swift
    
    import UIKit
    
    class GlobalSplitViewController: UISplitViewController, UISplitViewControllerDelegate {
    
      override func viewDidLoad() {
        super.viewDidLoad()
    
        self.delegate = self
      }
    
      func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController: UIViewController!, ontoPrimaryViewController primaryViewController: UIViewController!) -> Bool{
        return true
      }
    
    }
    

    【讨论】:

    • 太好了,这很有帮助。但是新的问题出现了。将我带到主人那里的后退按钮现在消失了(从不显示)。我该如何取回它?编辑:没关系,我想:-)。对于其他用户:在 detailView 中添加:self.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem() self.navigationItem.leftItemsSupplementBackButton = true
    • 现在在 Swift 中,不管它是什么 func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
    • 似乎只有在类大小紧凑时才调用委托方法。它在 iPhone 上被调用,但在 iPad 肖像上没有被调用,这意味着它不能解决问题,因为 iPad 肖像也处于折叠模式。使用 iOS 12.1 测试
    【解决方案3】:

    Mark S 正确答案的 Swift 版本

    由 Apple 的 Master-Detail 模板提供。

    func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController:UIViewController, ontoPrimaryViewController primaryViewController:UIViewController) -> Bool {
        guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false }
        guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false }
        if topAsDetailController.detailItem == nil {
            // Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
            return true
        }
        return false
    }
    

    澄清

    (Mark S 说的有点混乱)

    这个委托方法被称为splitViewController: collapseSecondaryViewController: ontoPrimaryViewController:,因为它就是这样做的。当更改为更紧凑的宽度尺寸时(例如将手机从横向旋转到纵向时),它需要将拆分视图控制器折叠成其中一个。

    这个函数返回一个布尔值来决定它是否应该折叠细节并显示主人。

    因此,在我们的案例中,我们将根据是否选择了细节来决定。我们如何知道我们的细节是否被选中?如果我们遵循 Apple 的 Master-Detail 模板,详细视图控制器应该有一个包含详细信息的可选变量,所以如果它是 nil (.None),则还没有选择任何内容,我们应该显示 Master,以便用户可以选择一些东西。

    就是这样。

    【讨论】:

    • 只是为了澄清我为什么从@sschale 的编辑中回滚。该代码是来自Apple's Master-Detail template 的引用,它的目的不是很好或简洁,只是事实。 :)
    【解决方案4】:
       #import <UIKit/UIKit.h>
    
        @interface SplitProductView : UISplitViewController<UISplitViewControllerDelegate>
    
    
    
    
        @end
    

    .m:

    #import "SplitProductView.h"
    #import "PriceDetailTableView.h"
    
    @interface SplitProductView ()
    
    @end
    
    @implementation SplitProductView
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        self.delegate = self;
    }
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    /*
    #pragma mark - Navigation
    
    // In a storyboard-based application, you will often want to do a little preparation before navigation
    - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
        // Get the new view controller using [segue destinationViewController].
        // Pass the selected object to the new view controller.
    }
    */
    - (BOOL)splitViewController:(UISplitViewController *)splitViewController
    collapseSecondaryViewController:(UIViewController *)secondaryViewController
      ontoPrimaryViewController:(UIViewController *)primaryViewController {
    
        if ([secondaryViewController isKindOfClass:[UINavigationController class]]
            && [[(UINavigationController *)secondaryViewController topViewController] isKindOfClass:[PriceDetailTableView class]]
    
            //&& ([(PriceDetailTableView *)[(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 {
    
            return NO;
    
        }
    }
    @end
    

    【讨论】:

      【解决方案5】:

      我的应用是用 Swift 2.x 编写的,可以很好地运行。将其转换为 Swift 3.0(使用 XCode 转换器)后,它首先开始显示细节,而不是纵向模式下的 master。问题是函数 splitViewController 的名称没有更改以匹配新的 UISplitViewControllerDelegate。

      手动更改该函数的名称后,我的应用现在可以正常工作了:

      func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
          guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false }
          guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false }
          if topAsDetailController.game == nil {
              // Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
              return true
          }
          return false
      }
      

      【讨论】:

      • 我和你有同样的问题,但我不明白你的解决方案。我没有看到您在此处发布的代码有任何变化。你可以再详细一点吗。谢谢
      • 很多方法稍微改名了。
      • Tony 的答案是 @NiñoScript 答案的 Swift 3 语法(这是为以前的 Swift 版本编写的)
      • 对于swift 3,别忘了把self.delegate = self放在viewDidLoad方法上。
      【解决方案6】:

      从documentation,您需要使用委托告诉UISplitViewController不将详细视图合并到“折叠界面”中(即您的情况下的“纵向模式” )。在 Swift 4 中,要实现的委托方法已被重命名:

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

      【讨论】:

        【解决方案7】:

        如果您没有要在详细视图控制器中显示的默认值,您可以简单地删除故事板中 SplitViewController 和您的详细 UIViewController 之间的默认 segue。 这将使它总是首先进入主视图控制器。

        这样做的副作用是,您将在 SplitViewController 中看到一个完整大小的视图,而不是在横向中看到两个视图,直到在主视图控制器中显示细节 Segue 被触发。

        【讨论】:

        • 好把戏。我的应用程序仅处于纵向模式,我可以这样做。
        • 这是真的,除非在横向模式下,您会看到视图的右侧空白部分可能是灰色填充的。
        【解决方案8】:

        在我看来,您应该更通用地解决这个问题。您可以继承 UISplitViewController 并在嵌入式视图控制器中实现协议。

        class MasterShowingSplitViewController: UISplitViewController {
            override func viewDidLoad() {
                super.viewDidLoad()
                delegate = self
            }
        }
        
        extension MasterShowingSplitViewController: UISplitViewControllerDelegate {
            func splitViewController(splitViewController: UISplitViewController,
                                     collapseSecondaryViewController secondaryViewController: UIViewController,
                                     ontoPrimaryViewController primaryViewController: UIViewController) -> Bool {
                guard let masterNavigationController = primaryViewController as? UINavigationController,
                          master = masterNavigationController.topViewController as? SplitViewControllerCollapseProtocol else {
                    return true
                }
                return master.shouldShowMasterOnCollapse()
        
            }
        }
        
        protocol SplitViewControllerCollapseProtocol {
            func shouldShowMasterOnCollapse() -> Bool
        }
        

        UITableViewController 中的示例实现:

        extension SettingsTableViewController: SplitViewControllerCollapseProtocol {
            func shouldShowMasterOnCollapse() -> Bool {
                return tableView.indexPathForSelectedRow == nil
            }
        }
        

        希望对您有所帮助。 所以你可以重用这个类,只需要实现一个协议。

        【讨论】:

        【解决方案9】:

        对于所有找不到cs193p的星期五部分的人:

        在 Swift 3.1.1 中,创建 UISplitViewController 的子类并实现它的一个委托方法对我来说就像一个魅力:

        class MainSplitViewController: UISplitViewController, UISplitViewControllerDelegate {
        
        override func viewDidLoad() {
            super.viewDidLoad()
            self.delegate = self
        }
        
        func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
            return true
        } }
        

        My storyboard

        【讨论】:

        • 正如@olito 所指出的,在 Swift 4 中,此语法已更改为:public func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -&gt; Bool
        【解决方案10】:

        当您需要从 Master 启动时,只需从 SplitView 控制器中删除 DetailViewController。

        UISplitViewController *splitViewController = (UISplitViewController *)[self.storyboard instantiateViewControllerWithIdentifier:@"SETTINGS"];
        splitViewController.delegate = self;
        [self.navigationController presentViewController:splitViewController animated:YES completion:nil];
        if (IPHONE) {
            NSMutableArray * cntlrs = [splitViewController.viewControllers mutableCopy];
            [cntlrs removeLastObject];
            splitViewController.viewControllers = cntlrs;
        }
        

        【讨论】:

          【解决方案11】:

          这在 iOS-11 和 Swift 4 上对我有用:

          //Following code in application didFinishLaunching (inside Application Delegate)
          guard let splitViewController = window?.rootViewController as? UISplitViewController,
                      let masterNavVC = splitViewController.viewControllers.first as? UINavigationController,
                      let masterVC = masterNavVC.topViewController as? MasterViewController
          else { fatalError() }
          splitViewController.delegate = masterVC
          
          //Following code in MasterViewController class
          extension MasterViewController:UISplitViewControllerDelegate {
              func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
                  return true
              }
          }
          

          【讨论】:

            【解决方案12】:

            该函数在新版本的 Swift 中被重命名,所以这段代码适用于 Swift 4:

            import UIKit
            
            class GlobalSplitViewController: UISplitViewController, UISplitViewControllerDelegate {
            
                override func viewDidLoad() {
                    super.viewDidLoad()
                    self.delegate = self
                }
            
                func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
                    return true
                }
            
            }
            

            【讨论】:

              【解决方案13】:

              Xamarin / C# 解决方案

              public partial class MainSplitViewController : UISplitViewController
              {
                  public MainSplitViewController(IntPtr handle) : base(handle)
                  {
                  }
              
                  public override void ViewDidLoad()
                  {
                      base.ViewDidLoad();
              
                      Delegate = new MainSplitViewControllerDelegate();
                  }
              }
              
              public class MainSplitViewControllerDelegate : UISplitViewControllerDelegate
              {
                  public override bool CollapseSecondViewController(UISplitViewController splitViewController, UIViewController secondaryViewController, UIViewController primaryViewController)
                  {
                      return true;
                  }
              }
              

              【讨论】:

                【解决方案14】:

                只需将UISplitViewController的preferredDisplayMode属性设置为.allVisible

                class MySplitViewController: UISplitViewController {
                
                    override func viewDidLoad() {
                        super.viewDidLoad()
                
                        self.preferredDisplayMode = .allVisible
                    }
                
                }
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2015-03-07
                  • 2017-07-11
                  • 1970-01-01
                  • 1970-01-01
                  • 2015-04-12
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多