UIScrollView本质上是简单的,通过frame指定了视窗的位置大小,通过contentSize指定了内容的大小(可滚动的区域)。
Autolayout本质上也是简单的,通过指定相对和绝对量,控制UI组件的位置和大小。
这两个本质上简单的东西碰撞到一起后,产生了可怕的火花。
首先,contentSize的概念隐藏了,我们也不愿意去计算实际的size去指定scrollview的contentSize。
所以,我们最常见的做法是用一个空的view,通常命名为contentView,去包含原本属于scrollView的一切子视图。然后将contentView插入到scrollView中,并且用contentView去对scrollView进行约束:
[_contentView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(_scrollView);
}];
contentView的size会撑开scrollView的contentSize,然后用实际的view去撑开contentView的size,看上去似乎更简单了。
愿景是美好的,需求是复杂的,iOS6是坑爹的。iOS6有一个bug,在更新约束时,无耻的假定了scrollView的contentOffset是(0,0)。所以如果有需求是根据scrollview的滚动动态的调整scrollView的宽度或高度,杯具就发生了。
- (void)switchToIndex:(NSInteger)index
{
if (index == [_views indexOfObject:_activeView]) {
return;
}
UIView *targetView = _views[index];
self.activeView = targetView;
[_scrollView setContentOffset:CGPointMake(targetView.left, 0) animated:YES];
[self.view setNeedsUpdateConstraints]; //对于iOS6,此时更新约束,会发生杯具,表现为contentView的frame会跑到屏幕之外
}
所以,不得已,需要写一些恶心的代码解决iOS6的问题:
- (void)switchToIndex:(NSInteger)index
{
if (index == [_views indexOfObject:_activeView]) {
return;
}
UIView *targetView = _views[index];
self.activeView = targetView;
if( floor(NSFoundationVersionNumber) < NSFoundationVersionNumber_iOS_7_0 ) {
[_scrollView setContentOffset:CGPointZero animated:NO]; //iOS7之前的版本,更新约束之前,滚动转到(0,0)位置,禁用动画效果
_offsetX = targetView.left; //记录下真正想要滚动到的位置
} else {
[_scrollView setContentOffset:CGPointMake(targetView.left, 0) animated:YES];
}
[self.view setNeedsUpdateConstraints];
}
-(void) viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
if( floor(NSFoundationVersionNumber) < NSFoundationVersionNumber_iOS_7_0 &&
_offsetX > 0 && fabs((_scrollView.contentOffset.x - _offsetX )) > 0.1 ) {
[self.scrollView setContentOffset:CGPointMake(_offsetX, 0) animated:NO]; //约束更新完毕,如果之前记录了要跳转到的位置,此时开始滚动
_offsetX = -1;
}
}
最后,为了适当的改善胃部的不适,用RAC美化一下代码形式:
- (void)switchToIndex:(NSInteger)index
{
if (index == [_views indexOfObject:_activeView]) {
return;
}
UIView *targetView = _views[index];
self.activeView = targetView;
if( floor(NSFoundationVersionNumber) < NSFoundationVersionNumber_iOS_7_0 ) {
[_scrollView setContentOffset:CGPointZero animated:NO];
CGFloat offsetX = targetView.left;
[[[self rac_signalForSelector:@selector(viewDidLayoutSubviews)] take:1 ] subscribeNext:^(id x) {
if( fabs((_scrollView.contentOffset.x - offsetX )) > 0.1 ) {
[self.scrollView setContentOffset:CGPointMake(offsetX, 0) animated:NO];
}
}];
} else {
[_scrollView setContentOffset:CGPointMake(targetView.left, 0) animated:YES];
}
[self.view setNeedsUpdateConstraints];
}