presentViewController:animated:completion:
呈现时,导航控制器似乎会自动向表视图控制器应用正确的内容插入。(有人能否准确解释一下这是如何工作的?)然而,一旦我将它们组合在自定义容器视图控制器中并进行演示,表视图内容的顶部部分就会隐藏在导航栏后面。在这种配置中,我是否可以做些什么来保留自动内容插入行为?我必须在容器视图控制器中“透过”某些东西才能正常工作吗?
我想避免手动调整内容插入或通过自动布局来实现,因为我希望继续支持iOS 5。
presentViewController:animated:completion:
呈现时,导航控制器似乎会自动向表视图控制器应用正确的内容插入。(有人能否准确解释一下这是如何工作的?)我遇到了一个类似的问题。在 iOS 7 上,UIViewController
有一个名为 automaticallyAdjustsScrollViewInsets
的新属性。默认情况下,它被设置为 YES
。但是当你的视图层次结构比滚动/表格视图在导航或选项卡栏控制器中更复杂时,这个属性似乎对我不起作用。
我所做的是:创建一个基本的视图控制器类,所有其他视图控制器都从该类继承。那个视图控制器会明确地设置插图:
Header:
#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
这也适用于Web视图:
self.webView.scrollView.contentInset = insets;
self.webView.scrollView.scrollIndicatorInsets = insets;
如果有更加优雅可靠的方法,请告诉我!硬编码的插入值很不好,但是当您想要保持iOS 5的兼容性时,我真的看不到其他方法。
提供参考,如果有人遇到类似问题:看起来只有在滚动视图(或表格视图/集合视图/网页视图)是其视图控制器层次结构中的第一个视图时,automaticallyAdjustsScrollViewInsets
才会生效。
通常我会先在层次结构中添加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;
}
automaticallyAdjustsScrollViewInsets
只有当子视图控制器的根视图是UIScrollView
时才能正常工作。对于我来说,这意味着以下布置可行:
内容视图控制器内部包含导航控制器内部包含自定义容器视图控制器。在控制器之间的过渡中,UINavigationController调用未公开的方法_updateScrollViewFromViewController:toViewController来更新内容插入。这是我的解决方案:
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://dev59.com/YWMk5IYBdhLWcg3wtQFd#33344516
这个解决方案针对iOS 8及以上版本的Swift。
我的应用程序根视图控制器是一个导航控制器。最初,这个导航控制器有一个UIViewController
在其堆栈上,我称之为父视图控制器。
当用户点击导航栏按钮时,父视图控制器在其视图中使用容器API在两个子视图控制器之间切换(在映射结果和列表结果之间切换)。父视图控制器持有对这两个子视图控制器的引用。第二个子视图控制器直到用户第一次点击切换按钮才会被创建。第二个子视图控制器是一个表视图控制器,存在以下问题:无论其 automaticallyAdjustsScrollViewInsets
属性设置如何,都会低于导航栏。
为了解决这个问题,在子表视图控制器创建后以及在父视图控制器的 viewDidLayoutSubviews
方法中,我调用 adjustChild:tableView:insetTop
方法(如下所示)。
像这样将子视图控制器的表视图和父视图控制器的 topLayoutGuide.length
传递给 adjustChild:tableView:insetTop
方法...
// called right after childViewController is created
adjustChild(childViewController.tableView, insetTop: topLayoutGuide.length)
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)
}
}