【问题标题】:topLayoutGuide in child view controller子视图控制器中的 topLayoutGuide
【发布时间】:2013-10-09 01:11:51
【问题描述】:

我有一个带有半透明状态栏和导航栏的UIPageViewController。正如预期的那样,它的topLayoutGuide 是 64 像素。

但是,UIPageViewController 的子视图控制器报告 topLayoutGuide 为 0 像素,即使它们显示在状态栏和导航栏下方。

这是预期的行为吗?如果是这样,将子视图控制器的视图放置在真实的topLayoutGuide 下的最佳方法是什么?

(没有使用parentViewController.topLayoutGuide,我认为这是一种黑客攻击)

【问题讨论】:

  • 我想知道他们是否还没有彻底实现topLayoutGuide 以实现嵌套的 ViewController 遏制。提出了一个问题,如果实现自定义容器,如何处理它......
  • 您确定要在 viewWillLayoutSubviews 或 viewDidLayoutSubviews 中测试 topLayoutGuide 属性吗?如果我没记错的话,它不能保证在 viewWillAppear / viewDidAppear 方法中返回正确的值......
  • 我正在viewWillLayoutSubviews 测试它,是的。
  • 是的,我也没有找到解决方案。子视图控制器和扩展边缘存在许多错误。 iOS7 还没有准备好。
  • 我认为肯定发生了一些奇怪的事情。我有一个 UINavigationController,里面有一个 UITabbarController。最初选择的选项卡始终以正确的间距显示在导航栏下方。但是当切换到其他选项卡时,顶部单元格会出现在顶部栏下方。

标签: ios uiviewcontroller ios7 autolayout uipageviewcontroller


【解决方案1】:

我可能错了,但我认为这种行为是正确的。容器视图控制器可以使用 topLayout 值来布局其视图的子视图。

参考说:

要使用顶部布局指南而不使用约束,请获取指南的位置相对于包含视图的顶部边界

在父视图中,相对于包含视图,该值为 64。

在子视图中,相对于包含视图(父视图),该值将为 0。

在容器视图控制器中,您可以这样使用属性:

- (void) viewWillLayoutSubviews {

    CGRect viewBounds = self.view.bounds;
    CGFloat topBarOffset = self.topLayoutGuide.length;

    for (UIView *view in [self.view subviews]){
        view.frame = CGRectMake(viewBounds.origin.x, viewBounds.origin.y+topBarOffset, viewBounds.size.width, viewBounds.size.height-topBarOffset);
    }
}

子视图控制器不需要知道有一个导航和一个状态栏:它的父级已经考虑到了它的子视图。

如果我创建一个基于页面的新项目,将其嵌入导航控制器中,然后将此代码添加到父视图控制器,它似乎工作正常:

【讨论】:

  • 但是如果子视图控制器是UIScrollView子类的话,如果你这样设置frame,就不会得到内容显示在半透明导航栏下的效果,就是整体首先要有一个半透明的导航栏。
  • 你不叫super,这很整洁LOL
  • 此解决方案适用于页面控制器,如果有人有页面控制器并为子视图添加了约束
【解决方案2】:

我认为这些指南绝对是为嵌套的子控制器设置的。例如,假设您有:

  • 一个 100x50 的屏幕,顶部有一个 20 像素的状态栏。
  • 一个顶层视图控制器,覆盖整个窗口。它的 topLayoutGuide 为 20。
  • 顶视图内的嵌套视图控制器覆盖底部 95 个像素,例如。从屏幕顶部向下 5 个像素。此视图的 topLayoutGuide 应为 15,因为它的前 15 个像素被状态栏覆盖。

这是有道理的:这意味着嵌套视图控制器可以设置约束以防止不必要的重叠,就像顶级视图控制器一样。它不必关心它是嵌套的,或者它的父级在屏幕上的哪个位置显示它,并且父级视图控制器不需要知道子级想要如何与状态栏交互。

这似乎也是文档(或至少一些文档)所说的:

顶部布局指南指示视图控制器视图的顶部和覆盖视图的最底部栏的底部之间的距离(以磅为单位)

(https://developer.apple.com/library/ios/documentation/UIKit/Reference/UILayoutSupport_Protocol/Reference/Reference.html)

这并没有说明只为顶级视图控制器工作。

但是,我不知道这是否真的发生了。我肯定见过带有非零 topLayoutGuides 的子视图控制器,但我仍在找出其中的怪癖。 (在我的情况下,顶部指南应该为零,因为视图不在屏幕顶部,而这正是我现在正在努力解决的问题......)

【讨论】:

    【解决方案3】:

    虽然this answer 可能是正确的,但我仍然发现自己必须遍历包含树才能找到正确的父视图控制器并获得您所说的“真正的topLayoutGuide”。这样我可以手动实现automaticallyAdjustsScrollViewInsets

    这就是我的做法:

    在我的表格视图控制器(实际上是UIViewController 的子类)中,我有这个:

    - (void)viewWillLayoutSubviews {
        [super viewWillLayoutSubviews];
    
        _tableView.frame = self.view.bounds;
    
        const UIEdgeInsets insets = (self.automaticallyAdjustsScrollViewInsets) ? UIEdgeInsetsMake(self.ms_navigationBarTopLayoutGuide.length,
                                                                                                   0.0,
                                                                                                   self.ms_navigationBarBottomLayoutGuide.length,
                                                                                                   0.0) : UIEdgeInsetsZero;
        _tableView.contentInset = _tableView.scrollIndicatorInsets = insets;
    }
    

    注意UIViewController中的分类方法,我是这样实现的:

    @implementation UIViewController (MSLayoutSupport)
    
    - (id<UILayoutSupport>)ms_navigationBarTopLayoutGuide {
        if (self.parentViewController &&
            ![self.parentViewController isKindOfClass:UINavigationController.class]) {
            return self.parentViewController.ms_navigationBarTopLayoutGuide;
        } else {
            return self.topLayoutGuide;
        }
    }
    
    - (id<UILayoutSupport>)ms_navigationBarBottomLayoutGuide {
        if (self.parentViewController &&
            ![self.parentViewController isKindOfClass:UINavigationController.class]) {
            return self.parentViewController.ms_navigationBarBottomLayoutGuide;
        } else {
            return self.bottomLayoutGuide;
        }
    }
    
    @end
    

    希望这会有所帮助:)

    【讨论】:

    • 我希望这个答案被投票给 inifitum... 我去并删除了整个应用程序中由懒惰的开发人员编写的 10 个错误的硬编码值。错误已解决,不再有硬编码。
    • 很好的答案,谢谢。只是为了让事情变得纯粹: UIEdgeInsets 值是 CGFloat 而不是双重类型。我最好将它们设置为 0.f 或 0.0f 而不是 0.0 :)。
    • 这在 99% 的情况下都能完美运行。但如果你有一个 UINavigationController 作为 childViewController。
    • 谢谢。我必须添加一个 [_tableView setContentOffset:CGPointMake(0, 0 - _tableView.contentInset.top) animated:NO]; 才能让它正确滚动到顶部
    • 这让我很头疼:)
    【解决方案4】:

    文档说如果您使用 UIViewController 子类,则在 viewDidLayoutSubviews 中使用 topLayoutGuide,如果您使用 UIView 子类,则使用 layoutSubviews。

    如果你在这些方法中使用它,你应该得到一个适当的非零值。

    文档链接: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html#//apple_ref/occ/instp/UIViewController/topLayoutGuide

    【讨论】:

      【解决方案5】:

      您可以在故事板中添加约束并在 viewWillLayoutSubviews 中更改它

      类似这样的:

      - (void)viewWillLayoutSubviews
      {
          [super viewWillLayoutSubviews];
          self.topGuideConstraint.constant = [self.parentViewController.topLayoutGuide length];
      }
      

      【讨论】:

      • 如果你想在导航栏下扩展边缘是没有意义的。
      • 这个答案修复了我的子视图控制器中没有导航栏的顶部布局指南问题。 Swift 3 版本:override func viewWillLayoutSubviews() {self.topGuideConstraint.constant = (self.parent ?? self).topLayoutGuide.length}
      【解决方案6】:

      这已在 iOS 8 中得到解决。

      How to set topLayoutGuide position for child view controller

      本质上,容器视图控制器应该约束子视图控制器的(top|bottom|left|right)LayoutGuide,就像任何其他视图一样。 (在 iOS 7 中,它已经完全限制在所需的优先级上,所以这不起作用。)

      【讨论】:

      • 愿意为此分享一些代码吗?当谈到基于约束的布局时,我的头骨很厚:)
      • 实际上这在 iOS 7 中也可以使用,只要您在添加自己的约束之前先删除布局指南上的现有约束。
      • @Archaeopterasa 你能详细说明一下吗?您指的是布局指南中现有的哪些限制?
      • @Jesse Rusak 到底改变了什么?我们如何将相同的更改应用到运行 iOS 7 的应用程序以实现相同的行为。我为此提出了一个具体问题:stackoverflow.com/questions/28024425/…
      • @pe60t0 可以删除自动添加的约束(在调试输出中显示为 _UILayoutSupportConstraint 而不是 NSLayoutConstraint 的约束)。在我的代码中,我想约束一个子视图控制器的布局指南以匹配父视图控制器的,所以我首先浏览了约束列表并删除了设置其高度的那个,然后能够像普通视图一样约束它。 (问题在于,自动布局中始终存在一个问题:方向更改可能会造成混乱,并且需要仔细的操作顺序。)
      【解决方案7】:

      如果你有UIPageViewController 像 OP 一样,并且你有例如集合视图控制器作为孩子。结果发现内容插入的修复很简单,它适用于 iOS 8:

      - (void)viewWillLayoutSubviews {
          [super viewWillLayoutSubviews];
      
          UIEdgeInsets insets = self.collectionView.contentInset;
          insets.top = self.parentViewController.topLayoutGuide.length;
          self.collectionView.contentInset = insets;
          self.collectionView.scrollIndicatorInsets = insets;
      }
      

      【讨论】:

        【解决方案8】:

        这是已知引导长度的方法。创建约束不是针对指南,而是使用固定常量(假设指南距离为)查看顶部。

        【讨论】:

          【解决方案9】:

          @NachoSoto 的快速实现回答:

          extension UIViewController {
          
              func navigationBarTopLayoutGuide() -> UILayoutSupport {
                  if let parentViewController = self.parentViewController {
                      if !parentViewController.isKindOfClass(UINavigationController) {
                          return parentViewController.navigationBarTopLayoutGuide()
                      }
                  }
          
                  return self.topLayoutGuide
              }
          
              func navigationBarBottomLayoutGuide() -> UILayoutSupport {
                  if let parentViewController = self.parentViewController {
                      if !parentViewController.isKindOfClass(UINavigationController) {
                          return parentViewController.navigationBarBottomLayoutGuide()
                      }
                  }
          
                  return self.bottomLayoutGuide
              }
          }
          

          【讨论】:

            【解决方案10】:

            不确定是否还有人对此有疑问,就像我几分钟前一样。
            我的问题是like this(来自https://knuspermagier.de/2014-fixing-uipageviewcontrollers-top-layout-guide-problems.html 的源gif)。
            简而言之,我的 pageViewController 有 3 个子视图控制器。第一个视图控制器很好,但是当我滑动到下一个时,整个视图错误地偏移到顶部(我猜大约 20 像素),但在我的手指离开屏幕后会恢复正常。
            我熬夜寻找解决方案,但仍然没有找到一个。 然后我突然想到了这个疯狂的想法:

            [pageViewController setViewControllers:@[listViewControllers[1]] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:^(BOOL finished) {
            
            }];
            
            [pageViewController setViewControllers:@[listViewControllers[0]] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:^(BOOL finished) {
            
            }];
            

            我的 listViewControllers 有 3 个子视图控制器。索引 0 的那个有问题,所以我首先将它设置为 pageviewcontroller 的根,然后立即将它设置回第一个视图控制器(如我所料)。 瞧,它奏效了!
            希望对您有所帮助!

            【讨论】:

              【解决方案11】:

              这是一个不幸的行为,似乎已在 iOS 11 中通过安全区域 API 改进得到纠正。也就是说,您将始终从根视图控制器中获得正确的值。例如,如果你想要 iOS 11 之前的安全区域高度上限:

              斯威夫特 4

              let root = UIApplication.shared.keyWindow!.rootViewController!
              let topLayoutGuideLength = root.topLayoutGuide.length
              

              【讨论】:

                猜你喜欢
                • 2013-11-04
                • 1970-01-01
                • 1970-01-01
                • 2013-06-05
                • 1970-01-01
                • 1970-01-01
                • 2016-10-22
                • 2016-03-22
                相关资源
                最近更新 更多