更新
根据Tim的回答,我在每个视图控制器中实现了以下内容,该控制器具有作为自定义容器一部分的scrollview(或子类):
- (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
的目的是允许设置为NavController的titleView的UISegmentedControl
在不同的子视图控制器之间切换。
这些子视图控制器都包含scrollview,tableview或集合视图。
我们发现第一个视图控制器加载正确,正确定位在导航栏下方。但是当我们切换到新的时候 视图控制器,导航栏未被遵守,视图设置在导航栏下方。
我们正在使用自动布局和界面构建器。我们已经尝试了我们能想到的一切,但找不到一致的解决方案。
这是主要代码块,负责设置第一个视图控制器并在用户点击分段控件时切换到另一个视图控制器:
- (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;
}
}
}
答案 0 :(得分:26)
您的自定义容器视图控制器需要根据您已知的导航栏高度调整第二个视图控制器的contentInset
,并尊重子视图控制器的automaticallyAdjustsScrollViewInsets
属性。 (您可能也对容器的topLayoutGuide
属性感兴趣 - 确保它在视图切换期间和之后返回正确的值。)
UIKit在如何应用这种逻辑方面存在显着的不一致(和错误);有时您会看到它通过到达层次结构中的多个视图控制器自动为您执行此调整,但通常在自定义容器切换后您需要自己完成工作。
答案 1 :(得分:22)
这似乎比人们做出的要简单得多。
UINavigationController仅在布局子视图时设置scrollview insets。但是,addChildViewController:
不会导致布局,因此在调用它之后,您只需要在navigationController上调用setNeedsLayout
。这是我在自定义选项卡式视图中切换视图时所做的事情:
[self addChildViewController:newcontroller];
[self.view insertSubview:newview atIndex:0];
[self.navigationController.view setNeedsLayout];
最后一行将导致为新视图控制器的内容重新计算scrollview insets。
答案 2 :(得分:9)
如果有人遇到类似问题,即使没有嵌入式视图控制器,也会出现此问题。看来automaticallyAdjustsScrollViewInsets
仅在滚动视图(或tableview / collectionview / webview)是其视图控制器层次结构中的第一个视图时应用。
我经常在我的层次结构中添加UIImageView以获得背景图像。如果这样做,您必须在viewDidLayoutSubviews中手动设置scrollview的边缘插入内容:
- (void) viewDidLayoutSubviews {
CGFloat top = self.topLayoutGuide.length;
CGFloat bottom = self.bottomLayoutGuide.length;
UIEdgeInsets newInsets = UIEdgeInsetsMake(top, 0, bottom, 0);
self.collectionView.contentInset = newInsets;
}
答案 3 :(得分:0)
我找到了一个更好的解决方案,使用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 :(得分:0)
在我的儿童控制器上设置edgesForExtendedLayout = []
为我工作。