iosautolayout with UIScrollview:为什么滚动视图的内容视图不填充滚动视图



下面的代码(在viewDidLoad中调用)导致一个完全红色的屏幕。我希望它是一个全绿屏。为什么是红色的?我怎样才能把它变成绿色呢?

UIScrollView* scrollView = [UIScrollView new];
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
scrollView.backgroundColor = [UIColor redColor];
[self.view addSubview:scrollView];
UIView* contentView = [UIView new];
contentView.translatesAutoresizingMaskIntoConstraints = NO;
contentView.backgroundColor = [UIColor greenColor];
[scrollView addSubview:contentView];
NSDictionary* viewDict = NSDictionaryOfVariableBindings(scrollView,contentView);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics:0 views:viewDict]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:0 metrics:0 views:viewDict]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView]|" options:0 metrics:0 views:viewDict]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[contentView]|" options:0 metrics:0 views:viewDict]];

滚动视图的约束与其他视图的约束略有不同。contentView与其superview (scrollView)之间的约束是对scrollViewcontentSize,而不是对frame。这可能看起来令人困惑,但它实际上非常有用,这意味着您永远不必调整contentSize,但contentSize会自动调整以适应您的内容。此行为在技术说明TN2154中有描述。

如果你想定义contentView的屏幕大小或类似的东西,你必须在contentView和主视图之间添加一个约束,例如。不可否认,这与将内容放入滚动视图是对立的,所以我可能不会建议这样做,但这是可以做到的。


为了说明这个概念,contentView的大小将由其内容驱动,而不是由scrollViewbounds驱动,请在contentView上添加一个标签:

UIScrollView* scrollView = [UIScrollView new];
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
scrollView.backgroundColor = [UIColor redColor];
[self.view addSubview:scrollView];
UIView* contentView = [UIView new];
contentView.translatesAutoresizingMaskIntoConstraints = NO;
contentView.backgroundColor = [UIColor greenColor];
[scrollView addSubview:contentView];
UILabel *randomLabel = [[UILabel alloc] init];
randomLabel.text = @"this is a test";
randomLabel.translatesAutoresizingMaskIntoConstraints = NO;
randomLabel.backgroundColor = [UIColor clearColor];
[contentView addSubview:randomLabel];
NSDictionary* viewDict = NSDictionaryOfVariableBindings(scrollView, contentView, randomLabel);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics:0 views:viewDict]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:0 metrics:0 views:viewDict]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView]|" options:0 metrics:0 views:viewDict]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[contentView]|" options:0 metrics:0 views:viewDict]];
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[randomLabel]-|" options:0 metrics:0 views:viewDict]];
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[randomLabel]-|" options:0 metrics:0 views:viewDict]];

现在您将看到contentView(因此,scrollViewcontentSize)被调整以适合具有标准边距的标签。因为我没有指定标签的宽度/高度,它会根据你放入标签的文本进行调整。


如果你想让contentView也调整到主视图的宽度,你可以这样重新定义你的viewDict,然后添加这些额外的约束(除了所有其他的,上面):

UIView *mainView = self.view;
NSDictionary* viewDict = NSDictionaryOfVariableBindings(scrollView, contentView, randomLabel, mainView);
[mainView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[contentView(==mainView)]" options:0 metrics:0 views:viewDict]];

scrollviews中的多行标签有一个已知的问题(bug?),如果你想让它根据文本的数量来调整大小,你必须做一些技巧,比如:

dispatch_async(dispatch_get_main_queue(), ^{
    randomLabel.preferredMaxLayoutWidth = self.view.bounds.size.width;
});

我试过了。但是!如果您还启用了缩放,则此自动布局代码将成为阻碍。它在缩放时保持调整contentView的大小。也就是说,如果我放大(zoomScale> 1),我将无法滚动屏幕外的部分。

经过几天与Autolayout的斗争,遗憾的是我找不到任何解决方案。最后,它甚至不重要(什么?)好的,抱歉)。最后,我只是在contentView上删除自动布局(使用固定大小的contentView),然后在layoutSubviews中,我调整 self.scrollView.contentInset。(我使用的是Xcode5, iOS 7)

我知道这不是这个问题的直接解决方案。但我想指出一个简单的解决方法。它可以完美地在scrollView中居中固定大小的contentView,只需2行代码!希望它能帮助到一些人;)
- (void)layoutSubviews {
    [super layoutSubviews];
    // move contentView to center of scrollView by changing contentInset,
    // because I CANNOT set this with AUTOLAYOUT!!!
    CGFloat topInset = (self.frame.size.height - self.contentView.frame.size.height)/2;
    self.scrollView.contentInset = UIEdgeInsetsMake(topInset, 0, 0, 0);
}

一般情况下,Auto Layout会将视图的上、左、下、右边缘视为可见边缘。也就是说,如果你把一个视图固定在父视图的左边缘,你实际上是把它固定在父视图边界的最小x值上。修改父视图的边界原点不会改变视图的位置。

UIScrollView类通过改变边界的原点来滚动它的内容。为了使自动布局工作,滚动视图中的上、左、下、右边缘现在意味着其内容视图的边缘。

对滚动视图的子视图的约束必须产生要填充的大小,然后将其解释为滚动视图的内容大小。(这不应该与用于自动布局的intrinsicContentSize方法混淆。)要使用自动布局来调整滚动视图框架的大小,必须明确限制滚动视图的宽度和高度,或者将滚动视图的边缘与子树之外的视图绑定。

请注意,您可以通过在视图和滚动视图子树之外的视图(例如滚动视图的父视图)之间创建约束,使滚动视图的子视图看起来漂浮(而不是滚动)在其他滚动内容之上。

下面是如何配置滚动视图的两个示例,首先是混合方法,然后是纯方法

最新更新