【问题标题】:Content falls beneath navigation bar when embedded in custom container view controller.嵌入自定义容器视图控制器时,内容位于导航栏下方。
【发布时间】:2013-10-03 01:17:28
【问题描述】:

更新

根据 Tim 的回答,我在每个视图控制器中实现了以下内容,该控制器具有作为我的自定义容器一部分的滚动视图(或子类):

- (void)didMoveToParentViewController:(UIViewController *)parent
{
    if (parent) {
        CGFloat top = parent.topLayoutGuide.length;
        CGFloat bottom = parent.bottomLayoutGuide.length;

        // this is the most important part here, because the first view controller added 
        // never had the layout issue, it was always the second. if we applied these
        // edge insets to the first view controller, then it would lay out incorrectly.
        // first detect if it's laid out correctly with the following condition, and if
        // not, manually make the adjustments since it seems like UIKit is failing to do so
        if (self.collectionView.contentInset.top != top) {
            UIEdgeInsets newInsets = UIEdgeInsetsMake(top, 0, bottom, 0);
            self.collectionView.contentInset = newInsets;
            self.collectionView.scrollIndicatorInsets = newInsets;
        }
    }

    [super didMoveToParentViewController:parent];
}

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

我有一个名为 SegmentedPageViewController 的自定义容器视图控制器。我将其设置为UINavigationController's rootViewController

SegmentedPageViewController 的目的是允许一个UISegmentedControl,设置为 NavController 的 titleView,在不同的子视图控制器之间切换。

这些子视图控制器都包含滚动视图、表格视图或集合视图。

我们发现第一个视图控制器加载良好,正确定位在导航栏下方。但是当我们切换到一个新的 视图控制器,导航栏不受尊重,视图设置在导航栏下方。

我们正在使用自动布局和界面构建器。我们已经尝试了所有能想到的方法,但找不到一致的解决方案。

这是负责设置第一个视图控制器并在用户点击分段控件时切换到另一个视图控制器的主要代码块:

- (void)switchFromViewController:(UIViewController *)oldVC toViewController:(UIViewController *)newVC
{
    if (newVC == oldVC) return;

    // Check the newVC is non-nil otherwise expect a crash: NSInvalidArgumentException
    if (newVC) {

        // Set the new view controller frame (in this case to be the size of the available screen bounds)
        // Calulate any other frame animations here (e.g. for the oldVC)
        newVC.view.frame = self.view.bounds;

        // Check the oldVC is non-nil otherwise expect a crash: NSInvalidArgumentException
        if (oldVC) {
            // **** THIS RUNS WHEN A NEW VC IS SET ****
            // DIFFERENT FROM FIRST VC IN THAT WE TRANSITION INSTEAD OF JUST SETTING


            // Start both the view controller transitions
            [oldVC willMoveToParentViewController:nil];
            [self addChildViewController:newVC];

            // Swap the view controllers
            // No frame animations in this code but these would go in the animations block
            [self transitionFromViewController:oldVC
                              toViewController:newVC
                                      duration:0.25
                                       options:UIViewAnimationOptionLayoutSubviews
                                    animations:^{}
                                    completion:^(BOOL finished) {
                                        // Finish both the view controller transitions
                                        [oldVC removeFromParentViewController];
                                        [newVC didMoveToParentViewController:self];
                                        // Store a reference to the current controller
                                        self.currentViewController = newVC;
                                    }];
        } else {

            // **** THIS RUNS WHEN THE FIRST VC IS SET ****
            // JUST STANDARD VIEW CONTROLLER CONTAINMENT

            // Otherwise we are adding a view controller for the first time
            // Start the view controller transition
            [self addChildViewController:newVC];

            // Add the new view controller view to the view hierarchy
            [self.view addSubview:newVC.view];

            // End the view controller transition
            [newVC didMoveToParentViewController:self];

            // Store a reference to the current controller
            self.currentViewController = newVC;
        }
    }

}

【问题讨论】:

  • 高度限制是否设置正确?
  • 我假设是这样。奇怪的是,总是第一个加载正常,第二个加载不正确。如果我切换它以便在应用程序加载时选择第二段,则加载正常,并且第一段加载不正确,与我在这里用作示例的图像相反。
  • 虽然我没有明确的表视图(在 vc 1 中)或集合视图(在 vc2 中)的高度约束。在笔尖中,它们只是设置为占据视野的全部高度。事实上,这两个 xib 没有明确应用任何约束。
  • 我遇到了同样的问题,这让我发疯了。感谢您花时间找出并发布您的解决方案。我无法自己解决这个问题。谢谢!
  • 很好的答案!只要确保在覆盖 didMoveToParentViewController 时调用 super。我还向 Apple 提交了雷达:openradar.appspot.com/16042338

标签: ios objective-c uinavigationbar ios7


【解决方案1】:

在我的 childcontrollers 上设置 edgesForExtendedLayout = [] 对我有用。

【讨论】:

    【解决方案2】:

    这似乎比人们想象的要简单得多。

    UINavigationController 只会在布局子视图时设置滚动视图插入。 addChildViewController: 不会导致布局,但是,在调用它之后,你只需要在你的 navigationController 上调用 setNeedsLayout。以下是我在自定义选项卡式视图中切换视图时所做的操作:

    [self addChildViewController:newcontroller];
    [self.view insertSubview:newview atIndex:0];
    [self.navigationController.view setNeedsLayout];
    

    最后一行将导致为新视图控制器的内容重新计算滚动视图插图。

    【讨论】:

    • 如你所说!
    • 这行得通,它真的救了我。但我想知道为什么self.navigationController.view 有效而self.view 无效。
    • 布局 self.view 只布局它及其子视图。设置插图的导航控制器是一个超级视图,因此它的布局方法永远不会被调用。 (它设置 insets 的视图是一个子视图,这很令人困惑,但实际完成工作的代码在导航控制器中。)
    • 这与确保添加的子视图位于堆栈顶部一起工作。
    • 大概和我的原始代码对[self.view insertSubview:newview atIndex:0]; 所做的一样。确保添加的视图控制器的视图是视图列表中的第一个。
    【解决方案3】:

    我找到了一个更好的解决方案,使用 UINavigationController 的未记录方法。

    #import <UIKit/UIKit.h>
    
    @interface UINavigationController (ContentInset)
    
    
    - (void) computeAndApplyScrollContentInsetDeltaForViewController:(UIViewController*) controller;
    
    @end

    #import "UINavigationController+ContentInset.h"
    
    @interface UINavigationController()
    
    
    - (void)_computeAndApplyScrollContentInsetDeltaForViewController:(id)arg1; 
    
    @end
    
    
    @implementation UINavigationController (ContentInset)
    
    - (void) computeAndApplyScrollContentInsetDeltaForViewController:(UIViewController*) controller
    {
        if ([UINavigationController instancesRespondToSelector:@selector(_computeAndApplyScrollContentInsetDeltaForViewController:)])
            [self _computeAndApplyScrollContentInsetDeltaForViewController:controller];
    }
    
    @end

    那么,就这样吧

    - (void) cycleFromViewController: (UIViewController*) oldC
                    toViewController: (UIViewController*) newC
    {
        [oldC willMoveToParentViewController:nil];                        
        [self addChildViewController:newC];
      
        [self transitionFromViewController: oldC toViewController: newC   
                                  duration: 0.25 options:0
                                animations:^{
                                    newC.view.frame = oldC.view.frame;                                    
                                    [self.navigationController computeAndApplyScrollContentInsetDeltaForViewController:newC];
                                }
                                completion:^(BOOL finished) {
                                    [oldC removeFromParentViewController];                  
                                    [newC didMoveToParentViewController:self];
                                }];
    }

    【讨论】:

      【解决方案4】:

      仅供参考,以防有人遇到类似问题:即使没有嵌入式视图控制器,也可能出现此问题。看来automaticallyAdjustsScrollViewInsets 仅适用于您的滚动视图(或 tableview/collectionview/webview)是其视图控制器层次结构中的第一个视图。

      我经常在我的层次结构中首先添加一个 UIImageView 以获得背景图像。如果这样做,则必须在 viewDidLayoutSubviews 中手动设置滚动视图的边缘插图:

      - (void) viewDidLayoutSubviews {
          CGFloat top = self.topLayoutGuide.length;
          CGFloat bottom = self.bottomLayoutGuide.length;
          UIEdgeInsets newInsets = UIEdgeInsetsMake(top, 0, bottom, 0);
          self.collectionView.contentInset = newInsets;
      
      }
      

      【讨论】:

      • 这是一个很好的解决方案——可能类似于 UIKit 自动执行的操作。
      • 请注意,如果您在 UITableViewController 中执行此操作,这将阻止控制器在显示键盘时自动更新内容插入。
      【解决方案5】:

      您的自定义容器视图控制器将需要根据您已知的导航栏高度调整第二个视图控制器的contentInset,尊重子视图控制器的automaticallyAdjustsScrollViewInsets 属性。 (您可能还对容器的 topLayoutGuide 属性感兴趣 - 确保它在视图切换期间和之后返回正确的值。)

      UIKit 在应用这个逻辑的方式上非常不一致(并且有问题);有时您会看到它通过到达层次结构中的多个视图控制器自动为您执行此调整,但通常在自定义容器切换之后您需要自己完成工作。

      【讨论】:

      • 我不清楚如何执行您的建议,主要是因为我不想假设我的孩子有滚动视图。我在我的应用程序的各个地方使用我的 SegmentedPageController,我不想假设我的父母知道关于孩子的一切。如果父母应该这样做,我不清楚我是如何做到的。我尝试过的一件事:在我的子视图控制器中,我覆盖了 -willMoveToParentVC: 并设置 self.tableView.contentInset = UIEdgeInsetsMake(parent.topLayoutGuide.length, 0, 0, 0); 如果这是第二个加载的孩子,那很好,但如果是第一个,它仍然像原来一样不正确。
      • 嗯 - 你说得很好。在您的孩子中,尝试将该逻辑移至-viewWillLayoutSubviews - 我发现topLayoutGuide 并不总是与-willMoveToParentViewController: 一致。
      • 如果这不起作用,您可能需要建立某种滚动视图搜索方法,您可以在其中遍历孩子的视图层次结构,寻找可以调整其contentInset 的东西。 (在我自己的自定义容器中,我让子项符合一个协议,该协议声明了一个返回滚动视图作为主要内容视图的方法,如果存在这样的东西,以简化搜索。)
      • 我更新了我的问题。原来这不是关于使用 viewWillLayout.. 或 willMoveToParentVC。这是关于意识到 UIKit 第一次做对了,然后失败了。将调整包装在一个条件中,首先检查它是否需要调整是票。另外,通过这个解决方案,孩子们自己负责,容器 vc 不用担心它显示的是谁。如果这很糟糕,您可以在容器中的 viewWillLayout... 中执行相同的操作,并在使用您建议的任何一种方法找到滚动视图后设置插图。
      • 您对 UIKit 处理自定义容器的解释正是我想要确认的。谢谢!
      猜你喜欢
      • 2015-10-30
      • 1970-01-01
      • 1970-01-01
      • 2017-08-12
      • 1970-01-01
      • 2015-01-13
      • 1970-01-01
      • 2011-05-15
      相关资源
      最近更新 更多