UIScrollView使用详解
contentSize:子控件的大小, 限定滚动的范围
showsHorizontalScrollIndicator
showsVerticalScrollIndicator
bounces:弹簧效果,默认是YES,一般是不关闭
alwaysBounceHorizontal
alwaysBounceVertical
contentInset:拖动后,会停留在设置的内边距的位置
contentOffset:偏移量,滚动到了某个位置
setContentOffset: animated:
userInteractionEnabled
scrollEnabled
了解更多:
实现滚动翻页效果,用自动布局,以便页面能够上下拖动
在multiScrollView上添加多个子View,用自动布局,实现左右翻页
在autolayout下,会在viewDidAppear之前根据subview的constraint重新计算scrollview的contentsize。
这就是为什么,在viewdidload里面手动设置了contentsize没用。因为在后面,会再重新计算一次,前面手动设置的值会被覆盖掉。
解决办法就是:
1.去除autolayout选项,自己手动设置contentsize;
2.如果要使用autolayout,要么自己设置完subview的constraint,然后让系统自动根据constraint计算出contentsize。 要么就在viewDidAppear里面自己手动设置contentsize。
scrollview布局的注意事项:
首先,我们需要添加一个UIView类型的控件成为UIScrollView的唯一子控件,并设置其上下左右间距都为0。这时候我们发现设置完四个约束后仍然会报错,这里我们就来解释一下UIScrollView的特殊性:其实UIScrollView要想实现滚,必须设置其滚动区域contentSize,而这里UIScrollView的滚动区域是由其子控件的内容决定的,所以这里我们先添加一个UIView成为UIScrollView的唯一子控件,并设置其上下左右约束均为0。
然后设置UIView的宽度为300(这个高度就是UIScrollView的内容宽度:contentSize.width),再设置垂直居中,然后运行程序就会看到UIScrollView可以左右滚动了。
如果要上下滚动就将UIView的高度约束设置成300(这个高度就是UIScrollView的内容高度: contentSize.height),水平居中就可以了。
如果要想上下左右都可以滚动,那么就将宽高约束都设置为300,不用设置水平和垂直居中。
接下来所有的控件都添加到UIView中即可利用自动布局来设置约束。
问题的关键在于如何给scrollView内部的子控件添加约束。
scrollView内部子控件约束的添加需要遵循两个原则:
1、scrollView内部子控件的尺寸不能以scrollView的尺寸为参照;
2、scrollView内部的子控件的约束必须完整;
首先,子控件的尺寸不能以scrollView的尺寸为参照,那么我们有两种选择:
提供一个具体值的约束(比如200);
子控件的尺寸可以参照scrollView以外其它的控件的尺寸(如控制器的view的尺寸);
其次,约束“完整”的意思是说:子控件在水平及竖直方向上的约束要把scrollView“撑满”;
也就是说,在水平方向上,我们需要设置:
- 子控件左侧与父控件的距离;
- 子控件自身的宽度;
- 子控件右侧距父控件的距离;
竖直方向上也一样,要设置:
- 子控件顶部距父控件的距离;
- 子控件的高度;
- 子控件底部距父控件的距离;
为什么scrollView如此隔路(特殊,与众不同)呢?
这是因为,scrollView需要根据添加在其内部的子控件的宽高及与四周的距离计算出它的contentSize。
举个栗子:
一个添加在scrollView内部的imageView的宽高为{80, 50}, imageView距离上左下右的距离分别为:100, 200, 300, 400,那么不需要用代码赋值contentSize,我们就可以打印出scrollView的contentSize为{680, 450}。
IB添加约束的原理
如果理解起来还是有困难,我们可以把scrollView的contentSize的范围想象成一块UIView(上图中的蓝色区域),暂且叫它Container(实际是没有这个东西的)。当我们在storyboard或xib中设置子控件与scrollView之间约束时,实际上设置的是子控件与container之间的约束。
也就是说,子控件的约束决定了container的尺寸(contentSize)。
这就说明了为什么我们要在水平和竖直方向用约束“撑满”。如果不撑满,container不知道它自己应该多大。
也正是因为container的尺寸由子控件的约束决定,所以子控件的尺寸不能再反过来参照container的尺寸。不然你等于我,我等于你,那到底是多少呢?如果你是Xcode你也会抓狂。(再次强调那个container不是真的,只是为了方便理解)
理论部分解释完毕,回到一开始的案例上。
imageView有了上下左右四个约束,还缺少宽高,我们再添加个固定宽高的约束(可以大一点,太小了看不到滚动效果),问题就可以解决了。
强调一下, 用代码给scrollView添加约束(包括使用Masonry的情况)是一样的。
[self.pageStackView removeAllSubviews];
[self.multiTableViewArr removeAllObjects];
[self.pageBtnArr removeAllObjects];
[self.multiTableBackViewArr removeAllObjects];
self.isMulti = YES;
WS(weakSelf);
UIView *lastBackView;
self.multiScrollView.contentSize = CGSizeMake(kFullScreenWidth * multiAnswerArr.count, 0);
for (NSInteger i = 0; i < multiAnswerArr.count; i ++) {
SSTCorrectHomeworkMultipageAnswerModel *multiModel = multiAnswerArr[i];
UITableView *multiTableView = [self getMultiTableView];
multiTableView.tag = MultiTableViewIndex + i;
[self.multiTableViewArr addObject:multiTableView];
// 用来显示页码的
UIButton *pageBtn = [self getPageBtnWithModel:multiModel];
pageBtn.tag = 100 + i;
pageBtn.layer.borderColor = UIColorFromHex(COLOR_778ADC).CGColor;
[self.pageBtnArr addObject:pageBtn];
UIView *backView = [[UIView alloc] init];
[self.multiScrollView addSubview:backView];
[self.multiTableBackViewArr addObject:backView];
if (lastBackView) { // 借助这个lastBackView来实现左右自动布局;
[backView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(lastBackView.mas_right);
make.top.mas_equalTo(weakSelf.multiScrollView.mas_top);
make.bottom.mas_equalTo(weakSelf.bgView.mas_bottom).offset(-40);
make.width.mas_equalTo(kFullScreenWidth);
}];
}else {
[backView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(weakSelf.multiScrollView.mas_left);
make.top.mas_equalTo(weakSelf.multiScrollView.mas_top);
make.bottom.mas_equalTo(weakSelf.bgView.mas_bottom).offset(-40);
make.width.mas_equalTo(kFullScreenWidth);
}];
}
[backView addSubview:multiTableView];
[multiTableView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(UIEdgeInsetsMake(0, 0, 0, 0));
}];
[self.pageStackView addArrangedSubview:pageBtn];
[pageBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(35, 12));
}];
if ([multiModel.pageName isEqualToString:@"当前页"]) {
// 选中当前页,刷新数据;
[self pageBtnAction:pageBtn];
}
lastBackView = backView;
}
-(void)pageBtnAction:(UIButton *)btn {
if (btn == nil || btn.tag < 100 || btn.tag > 102) {
return;
}
NSLog(@" --- 页码点击 --- tag: %ld",btn.tag);
self.oldPageBtn.selected = NO;
self.oldPageBtn.layer.borderColor = UIColorFromHex(COLOR_778ADC).CGColor;
btn.selected = YES;
self.oldPageBtn = btn;
self.oldPageBtn.layer.borderColor = UIColorFromHex(COLOR_FF6465).CGColor;
NSInteger index = btn.tag - 100;
self.multiScrollView.contentOffset = CGPointMake(kFullScreenWidth * index, 0);
UITableView *currentTableView = [self.multiTableViewArr objectAtIndexCheck:index];
[currentTableView reloadData]; // 刷新数据;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if ([scrollView isKindOfClass:[UITableView class]]) {
return;
}
NSInteger tag = scrollView.contentOffset.x/kFullScreenWidth;
UIButton *pageBtn = [self.pageBtnArr objectAtIndexCheck:tag];
[self pageBtnAction:pageBtn];
}
实现页面的拖拽
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(dragTableView:)];
[self.btnShow addGestureRecognizer:pan];
-(void)dragTableView:(UIPanGestureRecognizer *)panGestureRecognizer{
UIView *view = panGestureRecognizer.view.superview; // 当前view = SSTHomeworkCorrectAnswerListView
CGPoint translation = [panGestureRecognizer translationInView:view.superview]; //获取到的是手指移动后,在相对坐标中的偏移量 (x = 0, y = -5.333343505859375)
[panGestureRecognizer setTranslation:CGPointZero inView:view.superview];
CGPoint center = CGPointMake(view.center.x + translation.x, view.center.y + translation.y);
view.centerY = center.y;
//1、拖到最高 - 答案页面高度最大
if (view.frame.origin.y <= kFullScreenHeight*0.3) {
view.y = kFullScreenHeight*0.3;
view.height = kFullScreenHeight-BottomBar_H-BottomHeight-kFullScreenHeight*0.3;
self.bgView.hidden = NO;
self.isShow = YES;
self.btnShow.selected = YES;
//2、拖动变化 - 答案高度最高和最低之间
}else if (view.frame.origin.y > kFullScreenHeight*0.3 && view.frame.origin.y < (kFullScreenHeight-BottomBar_H-AnswerListView_H-BottomHeight)) {
CGFloat view_Height = kFullScreenHeight-BottomHeight-BottomBar_H-view.y;
view.height = view_Height;
self.bgView.hidden = NO;
self.isShow = YES;
self.btnShow.selected = YES;
//3、拖动最低高度范围 - 保持最低高度
}else if (view.frame.origin.y >= (kFullScreenHeight-BottomBar_H-AnswerListView_H-BottomHeight) && view.frame.origin.y < (kFullScreenHeight-BottomBar_H-AnswerShowBtnIcon_H-BottomHeight)){
view.height = AnswerListView_H; //有一个最小的高度;
self.bgView.hidden = NO;
self.isShow = YES;
self.btnShow.selected = YES;
//4、拖到最低 - 关闭答案页面
}else{
view.y = kFullScreenHeight-BottomBar_H-AnswerShowBtnIcon_H-BottomHeight;
view.height = AnswerShowBtnIcon_H;
self.bgView.hidden = YES;
self.btnShow.selected = NO;//答案批注收起
self.isShow = NO;
}
if ([self.delegate respondsToSelector:@selector(answerListView:isShow:)]) {
[self.delegate answerListView:self isShow:self.btnShow.selected];
}
}
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(moveAction:)];
self.pan = pan;
pan.enabled = NO;
[self addGestureRecognizer:pan];
-(void)moveAction:(UIPanGestureRecognizer *)panGestureRecognizer {
UIView *view = panGestureRecognizer.view; // 当前view
CGPoint translation = [panGestureRecognizer translationInView:view.superview]; //获取到的是手指移动后,在相对坐标中的偏移量
CGPoint center = CGPointMake(view.center.x + translation.x, view.center.y + translation.y);
view.xl_centerY = center.y;
[panGestureRecognizer setTranslation:CGPointZero inView:view.superview];
}