iOS 7:自定义容器视图控制器和内容插图

我有一个包裹在导航控制器中的表视图控制器。 导航控制器似乎在通过presentViewController:animated:completion:呈现时自动将正确的内容插入应用于表格视图控制器presentViewController:animated:completion: (任何人都可以向我解释这是如何工作的?)

但是,只要将组合包装在自定义容器视图控制器中,并将表视图内容的最顶部隐藏在导航栏的后面。 有什么我可以做的,以保持自动内容插入行为在这个configuration? 我是否必须“通过”容器视图控制器中的东西,才能正常工作?

我想避免不得不手动或通过自动布局调整内容插入,因为我想继续支持iOS 5。

我有类似的问题。 所以在iOS 7上,在UIViewController上有一个名为UIViewController的新属性。 默认情况下,它被设置为YES 。 当你的视图层次结构比导航或标签栏控制器中的滚动/表视图更复杂的时候,这个属性似乎不适合我。

我所做的是这样的:我创build了一个基本的视图控制器类,所有我的其他视图控制器inheritance。 该视图控制器然后负责明确设置插入:

标题:

 #import <UIKit/UIKit.h> @interface SPWKBaseCollectionViewController : UICollectionViewController @end 

执行:

 #import "SPWKBaseCollectionViewController.h" @implementation SPWKBaseCollectionViewController - (void)viewDidLoad { [super viewDidLoad]; [self updateContentInsetsForInterfaceOrientation:self.interfaceOrientation]; } - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { [self updateContentInsetsForInterfaceOrientation:toInterfaceOrientation]; [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; } - (void)updateContentInsetsForInterfaceOrientation:(UIInterfaceOrientation)orientation { if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7) { UIEdgeInsets insets; if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { insets = UIEdgeInsetsMake(64, 0, 56, 0); } else if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { if (UIInterfaceOrientationIsPortrait(orientation)) { insets = UIEdgeInsetsMake(64, 0, 49, 0); } else { insets = UIEdgeInsetsMake(52, 0, 49, 0); } } self.collectionView.contentInset = insets; self.collectionView.scrollIndicatorInsets = insets; } } @end 

这也适用于networking浏览:

 self.webView.scrollView.contentInset = insets; self.webView.scrollView.scrollIndicatorInsets = insets; 

如果有更优雅而可靠的方法来做到这一点,请让我知道! 硬编码的插入值的味道非常糟糕,但是当你想保持iOS 5兼容性的时候,我并没有看到另一种方式。

如果任何人有类似的问题的FYI:似乎automaticallyAdjustsScrollViewInsets调整ScrollViewInsets只适用于您的滚动视图(或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; } 

感谢Johannes Fahrenkrug的提示,我想出了以下内容:当子视图控制器的根视图是一个UIScrollView时, automaticallyAdjustsScrollViewInsets视图UIScrollView的确只能像广告一样工作。 对我来说,这意味着以下的安排工作:

导航控制器内部的内容视图控制器内部自定义容器视图控

虽然这不:

内部视图控制器内部导航控制器内部自定义容器视图控制

然而,从逻辑的angular度来看,第二种select似乎更为合理。 第一个选项可能仅适用于自定义容器视图控制器用于将视图粘贴到内容的底部。 如果我想把导航栏和内容之间的视图,它不会那样工作。

UINavigationController在控制器之间的转换中调用未logging的方法_updateScrollViewFromViewController:toViewController来更新内容插入。 这是我的解决scheme:

UINavigationController的+ ContentInset.h

 #import <UIKit/UIKit.h> @interface UINavigationController (ContentInset) - (void) updateScrollViewFromViewController:(UIViewController*) from toViewController:(UIViewController*) to; @end 

UINavigationController的+ ContentInset.m

 #import "UINavigationController+ContentInset.h" @interface UINavigationController() - (void) _updateScrollViewFromViewController:(UIViewController*) from toViewController:(UIViewController*) to; @end @implementation UINavigationController (ContentInset) - (void) updateScrollViewFromViewController:(UIViewController*) from toViewController:(UIViewController*) to { if ([UINavigationController instancesRespondToSelector:@selector(_updateScrollViewFromViewController:toViewController:)]) [self _updateScrollViewFromViewController:from toViewController:to]; } @end 

在自定义容器视图控制器中的某处调用updateScrollViewFromViewController:toViewController

 [self addChildViewController:newContentViewController]; [self transitionFromViewController:previewsContentViewController toViewController:newContentViewController duration:0.5f options:0 animations:^{ [self.navigationController updateScrollViewFromViewController:self toViewController:newContentViewController]; } completion:^(BOOL finished) { [newContentViewController didMoveToParentViewController:self]; }]; 

您不需要调用任何未公开的方法。 所有你需要做的就是在你的UINavigationController上调用setNeedsLayout 。 看到这个其他答案: https : //stackoverflow.com/a/33344516/5488931

Swift解决scheme

这个解决scheme的目标是Swift中的iOS 8和更高版本。

我的应用程序的根视图控制器是一个导航控制器。 最初这个导航控制器在它的堆栈上有一个UIViewController ,我将调用父视图控制器。

当用户点击导航栏button时,父视图控制器使用包含API(在切换的映射结果和列出的结果之间)在其视图上的两个子视图控制器之间切换。 父视图控制器保存对两个子视图控制器的引用。 直到第一次用户点击切换button时,第二个子视图控制器才被创build。 第二个子视图控制器是一个表视图控制器,并展示了它在导航栏下面的问题,而不pipe如何设置其automaticallyAdjustsScrollViewInsets ScrollViewInsets属性。

为了解决这个问题,在创build子表视图控制器和父视图控制器的viewDidLayoutSubviews方法之后调用adjustChild:tableView:insetTop (如下所示)。

子视图控制器的表视图和父视图控制器的topLayoutGuide.length被传递给adjustChild:tableView:insetTop方法像这样…

 // called right after childViewController is created adjustChild(childViewController.tableView, insetTop: topLayoutGuide.length) 

adjustChild方法…

 private func adjustChild(tableView: UITableView, insetTop: CGFloat) { tableView.contentInset.top = insetTop tableView.scrollIndicatorInsets.top = insetTop // make sure the tableview is scrolled at least as far as insetTop if (tableView.contentOffset.y > -insetTop) { tableView.contentOffset.y = -insetTop } } 

语句tableView.scrollIndicatorInsets.top = insetTop调整表视图右侧的滚动指示器,使其从导航栏的下方开始。 这是微妙的,很容易被忽视,直到你意识到它。

以下是viewDidLayoutSubviews外观

 override func viewDidLayoutSubviews() { if let childViewController = childViewController { adjustChild(childViewController.tableView, insetTop: topLayoutGuide.length) } } 

请注意,上面的所有代码都出现在父视图控制器中。

我在Christopher Pickslay的回答 “iOS 7表格视图无法自动调整内容插入的问题”的帮助下计算了这一点。