ScrollView详解
创建方式
1:StoryBoard/Xib
这里StoarBoard就不多说,直接拖就可以,说太多没意思,如果连这个都不会我只能先给你跪了!
2:代码:
CGRect bounds = [ [ UIScreen mainScreen ] applicationFrame ] ; UIScrollView* scrollView = [ [UIScrollView alloc ] initWithFrame:bounds ];
- 当你创建完滚动视图后,你可以将另一个视图的内容粘合到滚动视图的空白页上。这回创建一个滚动的内容窗口:
1 [ scrollView addSubview:myView];
UIScrollView使用的步骤
- 1.创建UIScrollView
- 2.将需要展示的内容添加到UIScrollView中
- 3.设置UIScrollView的滚动范围 (contentSize)
基本属性
scrollView不能滚动的几种情况
- 1.没有设置contentSize
- 2.scrollEnabled属性 = NO
- 3.userInteractionEnabled属性 = NO
self.scrollView.scrollEnabled = NO;
self.scrollView.userInteractionEnabled = NO;
enabled和userInteractionEnabled的区别
- enabled: 代表控件不可用
- userInteractionEnabled: 代表控件不可以和用户交互, 也就是不能响应用户的操作
注意: 如果想让UIScrollView进行滚动, 必须设置可以滚动的范围
注意: 如果想让UIScrollView进行滚动, 必须设置可以滚动的范围
设置scrollView的滚动范围为, frame的宽高 + 100
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width + 100, self.scrollView.frame.size.height + 100);
2.设置滚动范围
self.scrollView.contentSize = iv.image.size;
// 如何去掉滚动条
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.showsVerticalScrollIndicator = NO;
滚动条也是scrollView的子控件的一部分
滚动条可能在子控件的前面, 也可能在子控件的后面
正是因为这个原始, 所以以后在开发中不推荐大家通过subviews获取子控件的方式来操作子控件
[self.scrollView.subviews lastObject];
设置滚动条的样式
self.scrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
默认情况下UIScrollView有一个回弹效果
只要设置了contentSize就有回弹效果
self.scrollView.bounces = YES;
设置默认是否有回弹效果 (默认就是没有设置contentSize的情况)
垂直方向可以回弹
下拉刷新
哪怕没有设置contentSize也可以有回弹效果
- self.scrollView.alwaysBounceVertical = YES;
- self.scrollView.alwaysBounceHorizontal = YES;
3.设置边距
self.scrollView.contentInset = UIEdgeInsetsMake(10, 20, 30, 40);
注意点:contentOffset移动的位置是一个临时的位置, 只要轻轻拖拽一下就会回到默认的位置
- CGFloat offsetX = self.sc.contentSize.width - self.sc.frame.size.width;
- CGFloat offsetY = self.sc.contentSize.height - self.sc.frame.size.height;
- [self.sc setContentOffset:CGPointMake(offsetX, offsetY) animated:YES];
-----------------------代理:----------------------------
如何监听一个控件的变化/状态
1. 首先需要查看该控件的头文件, 看它继承于谁
- 1.1如果继承于UIControl, 那么就可以通过addTarget来监听
- 1.2如果继承于UIView, 那么必须通过代理来监听
——————————————————————————————————————————————————————
代理协议的规律:
- 以控件的类名开头, 后面加上delegate
代理协议中的方法名的规律:
- 一般以控件名称去掉类前缀开头
代理协议中的方法参数的规律:
- 谁触发事件, 就将谁传递进来
如何监听UIScrollView的变化
- 1.成为UIScrollView的代理
- 2.遵守UIScrollView的协议
- 3.实现UIScrollView协议中的方法
代理作用:
- 当A对象想监听B对象的变化 , 那么可以让A成为B的代理
- 当B对象发生一些变化想通知A对象, 那么可以让A成为B的代理
/**************************代理方法***********************************/
#pragma mark - UIScrollViewDelegate
// 只要成为了UIScrollView的代理, 遵守代理协议, 实现协议中的方法
// 当UIScrollView发生一些变化的时候, 系统就会自动调用这些代理方法
// scrollViewDidScroll方法什么时候调用?
// 只要UIScrollView滚动了, 系统就会自动调用
1 - (void)scrollViewDidScroll:(UIScrollView *)scrollView 2 3 { 4 5 NSLog(@"%@", NSStringFromCGPoint(self.sc.contentOffset)); 6 7 } 8 9 10 11 // 只要用户准备开始拖拽了就会调用 12 13 - (void)scrollViewWillBeginDragging:(nonnull UIScrollView *)scrollView 14 15 { 16 17 }
// 用户已经结束拖拽, 代表用户已经松手了
// 系统调用了该方法并不代表着,UIScrollView已经停止滚动了
// 每次调用 停止拖拽方法时 ,系统都会传入一个当前是否有惯性的参数
// 我们可以判断该参数是否为YES, 如果是YES代表当前UIScrollView有惯性, 停止拖拽并不会停止滚动, 需要在停止减速方法中监听什么时候真正的停止
1 - (void)scrollViewDidEndDragging:(nonnull UIScrollView *)scrollView willDecelerate:(BOOL)decelerate 2 3 { 4 5 NSLog(@"decelerate = %i", decelerate); 6 7 if (decelerate == NO) { 8 9 NSLog(@"没有惯性, 可以在当前方法监听UIScrollView是否停止滚动"); 10 11 [self scrollViewDidEndDecelerating:scrollView]; 12 13 }else{ 14 15 NSLog(@"有惯性, 需要在减速结束方法中监听UIScrollView是否停止滚动"); 16 17 } 18 19 } 20 21 // UIScrollView已经停止减速了 22 23 // 只有执行了这个方法才代表UIScrollView已经停止滚动了 24 25 - (void)scrollViewDidEndDecelerating:(nonnull UIScrollView *)scrollView 26 27 { 28 29 NSLog(@"UIScrollView停止滚动了"); 30 31 }
结合前面MVC中的九宫格案例使用UIScrollView实现,并且设置滚动到对应的位置:
//6.设置shopsView的滚动范围
1 //添加滚动范围 2 3 if (self.row > 2) { 4 5 self.shopsView.contentSize = CGSizeMake(0, (self.row + 1) * (shopHeight + RowMargin)); 6 7 //开始滚动 8 9 10 11 CGPoint offset = CGPointMake(0, (self.row - 2) * (shopHeight + RowMargin)); 12 13 [self.shopsView setContentOffset:offset animated:YES]; 14 15 }
注意:
如果想在UIScrollView停止滚动之后做一些操作, 有两种情况
- 1.没有惯性的情况: 只会调用 停止拖拽的方法, 不会调用停止减速的方法
- 2.有惯性的情况: 既会调用 停止拖拽的方法, 也会调用停止减速的方法
所以: 以后要判断UIScrollView是否停止滚动, 需要同时重写两个方法
- 2.1scrollViewDidEndDragging
- 2.2scrollViewDidEndDecelerating
缩放:
要想缩放图片分为两步
- 1.成为代理, 通过代理方法告诉UIScrollView要缩放哪一个子控件
- 2.设最大置子控件和最小的缩放比例
}
// 因为UISrollView中可能有多个子控件
// 那么UISrollView就搞不清楚到底要缩放哪一个子控件
// 想要缩放, 必须明确的告诉UISrollView要缩放哪一个控件
/**************************缩放代理方法***********************************/
#pragma mark - UIScrollViewDelegate
1 // 在此方法中告诉UISrollView要缩放哪一个控件 2 3 - (nullable UIView *)viewForZoomingInScrollView:(nonnull UIScrollView *)scrollView 4 5 { 6 7 return self.iv; 8 9 } 10 11 // 缩放的过程中调用 12 13 // 和上午学习的scrollViewDidScroll一样, 只要缩放一点点就会调用 14 15 - (void)scrollViewDidZoom:(nonnull UIScrollView *)scrollView 16 17 { 18 19 NSLog(@"%s", __func__); 20 21 } 22 23 // 缩放结束时调用 24 25 - (void)scrollViewDidEndZooming:(nonnull UIScrollView *)scrollView withView:(nullable UIView *)view atScale:(CGFloat)scale 26 27 { 28 29 NSLog(@"%s", __func__); 30 31 }
综合案例:
在iOS开发中UIScrollView使用最多的地方也就是结合pageControl实现页面的轮播,比如新特性或者广告还有一些就是关于图片浏览什么的。
1:在StoryBoard中搭建界面并且设置输出口
2:ViewDidiLoad方法中初始化子控件(UIImageView)设置相应的图片
// Do any additional setup after loading the view, typically from a nib.
1 CGFloat width = self.sc.frame.size.width; 2 3 CGFloat height = self.sc.frame.size.height; 4 5
// 1.初始化子控件, 添加图片
1 for (int i = 0; i < count; i++) { 2 3 // 1.创建UIImageView 4 5 UIImageView *iv = [[UIImageView alloc] init]; 6 7 // 2.创建图片 8 9 UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"img_%02i", i + 1]]; 10 11 // 3.设置每个UIImageView的frame 12 13 iv.frame = CGRectMake(i * width, 0, width, height); // 按照宽度分页 14 15 iv.image = image; 16 17 // 4.添加到父控件 18 19 [self.sc addSubview:iv]; 20 21 }
3:设置UIScrollView和pageControl对应的属性
1 self.sc.showsHorizontalScrollIndicator = NO; 2 3 self.sc.showsVerticalScrollIndicator = NO; 4 5
// 2.设置滚动范围
1 self.sc.contentSize = CGSizeMake(count * width, height); 2 3 self.sc.bounces = NO; 4 5 self.sc.pagingEnabled = YES; 6 7
// 3.监听PageControl的点击事件
[self.pageControl addTarget:self action:@selector(pageControlClick:) forControlEvents:UIControlEventValueChanged]; self.pageControl.numberOfPages = count;
//:设置pageControl对应每个点显示的图片,在这里因为是私有属性所以使用KVC来实现,正好用到了前面我们刚学到的KVC,如果不懂情查看相关文章()
4.通过KVC给UIPageControl的私有属性赋值, 设置自定义图片
1 [self.pageControl setValue:[UIImage imageNamed:@"current"] forKeyPath:@"_currentPageImage"]; 2 3 [self.pageControl setValue:[UIImage imageNamed:@"other"] forKeyPath:@"_pageImage"]; 4 5 6 7
5:设置代理并且实现代理方法,在代理方法里面实现UIScrollView的与PageControl同步滚动(使用四舍五入法处理中线变化的bug)
#pragma mark - UIScrollViewDelegate
// 只要滚动就会调用
1 - (void)scrollViewDidScroll:(nonnull UIScrollView *)scrollView 2 3 { 4 5 // 1.计算页码 6 7 // 当前页码 = 偏移位 / UIScrollView的宽度 8 9 CGFloat page = scrollView.contentOffset.x / scrollView.frame.size.width; 10 11 int currnetPage = page + 0.5; 12 13 14 15 // 2.修改页码 16 17 self.pageControl.currentPage = currnetPage; 18 19 }
6:当我们有时候可能会的点击pageControl上面的点,所以有的时候也会希望能点击对应的点直接到对应的界面,可以直接使用UIScrollView的属性contentOfSet设置。
在StoryBoard中为pageControl设置一个Action并且实现一下代码
#pragma mark - 内部监听
1 - (IBAction)pageControlClick:(UIPageControl *)sender { 2 3 4 5 self.sc.contentOffset = CGPointMake(sender.currentPage * self.sc.frame.size.width , 0); 6 7 }
7:既然是做一个完整的小项目,我们就要根据大部分App的需求,比如我们经常见到的广告会自动滚动,新特性同样也可以实现这个功能,而且在用户能够看完上面的内容的情况下自动到下一张效果会跟好,这样用户就不需要操作什么,下面就来实现一下自动滚动的效果,
这里使用的是NSTimer定时器
1)声明一个NSTimer属性
// 注意:NSTimer应该是weak
// 为什么NSTimer应该是weak, 因为只要创建一个NSTimer对象, 该对象就会被主线程强引用
1 @property (weak, nonatomic) NSTimer *timer;
2)定义一个公共开启定时器的方法,这里使用的是NSRunLoop实现一个常见的bug(用户做其他操作图片还是同意滚动),关于NSRunLoop后面的关于多线程的文章中将会详细介绍。
#pragma mark - 定时器相关
1 - (void)startTimer 2 3 { 4 5 // 打开定时器 6 7 self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(nextPage:) userInfo:@"lnj" repeats:YES]; 8 9 10 11 // 主线程在处理其它事件的时候, 分一点时间来处理NSTimer 12 13 // 1.0 0.1 14 15 [[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes]; 16 17 }
在ViewDidLoad中调用这个方法,是的应用程序进来就会实现滚动,
NStimer实现定时的时候会自定定时的调用我们下面的方法实现页面的跳转:
// 切换到下一页
1 - (void)nextPage:(NSTimer *)timer 2 3 { 4 5 // NSLog(@"%@", timer.userInfo); 6 7 8 9 // 1.获取下一页的页码 10 11 NSUInteger page = self.pageControl.currentPage + 1; 12 13 // 2.判断页码是否越界 14 15 if (page >= IMAGE_COUNT) { 16 17 // 如果越界就回到第0页 18 19 self.pageControl.currentPage = 0; 20 21 }else 22 23 { 24 25 // 如果没有越界, 就进入到下一页 26 27 self.pageControl.currentPage = page; 28 29 } 30 31 32 33 [self pageControlClick:self.pageControl]; 34 35 }
3)同样实现一个关闭定时器的公共方法。
1 - (void)stopTimer 2 3 { 4 5 // 关掉定时器 6 7 #warning 注意:NSTimer是一次性的, 只要invalidate之后就不能使用了 8 9 // 只要调用invalidate方法, 系统就会将NSTimer从主线程移除, 并且销毁NSTimer对象 10 11 NSLog(@"销毁前: %@", self.timer); 12 13 [self.timer invalidate]; 14 15 self.timer = nil; 16 17 NSLog(@"销毁后: %@", self.timer); 18 19 }
4)最后有两个地方需要注意的是,可能甚至一定回去对界面有一些相应的操作,而我们要做的就是在用户操作的同时实现图片的滚动不受任何影响除非是对UIScrolView做一些操作,所以我们要根据用户相应的操作实现定时器的关闭与打开,这里我只是在拖拽或者触摸的时候实现这个功能,
1 // 开始拖拽 2 3 - (void)scrollViewWillBeginDragging:(nonnull UIScrollView *)scrollView 4 5 { 6 7 // NSLog(@"%s", __func__); 8 9 [self stopTimer]; 10 11 } 12 13 14 15 // 结束拖拽 16 17 - (void)scrollViewDidEndDragging:(nonnull UIScrollView *)scrollView willDecelerate:(BOOL)decelerate 18 19 { 20 21 // NSLog(@"%s", __func__); 22 23 [self startTimer]; 24 25 26 27 } 28 29 30 31 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 32 33 { 34 35 36 37 [self stopTimer]; 38 39 } 40 41 42 43 44 45 -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 46 47 { 48 49 50 51 [self startTimer]; 52 53 }
最后的效果
在后面的文章中我们会讲到三个UI中最难(其实也不难,只是相对)的控件:UITableView,UICollection,UIStackView,其中后面两个可以非常简单的实现上面的功能,几行代码就搞定。
属性与方法总结:
属性:
1 // 监控目前滚动的位置(默认CGPointZero) 2 3 CGPoint contentOffset; 4 - (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated; 5 // 滚动范围的大小(默认CGSizeZero) 6 7 CGSize contentSize; 8 // 视图在scrollView中的位置(UIEdgeInsetsZero) 9 10 UIEdgeInsets contentInset; 11 // 设置协议 12 13 id<UIScrollViewDelegate> delegate; 14 // 指定控件是否只能在一个方向上滚动(默认为NO) 15 16 BOOL directionalLockEnabled; 17 // 控制控件遇到边框是否反弹(默认为YES) 18 19 BOOL bounces; 20 // 控制垂直方向遇到边框是否反弹(默认为NO,如果为YES,bounces也是YES) 21 22 BOOL alwaysBounceVertical; 23 // 控制水平方向遇到边框是否反弹(默认为NO,如果为YES,bounces也是YES) 24 25 BOOL alwaysBounceHorizontal; 26 // 控制控件是否整页翻动(默认为NO) 27 28 BOOL pagingEnabled; 29 // 控制控件是否能滚动 30 31 BOOL scrollEnabled; 32 // 控制是否显示水平方向的滚动条 33 34 BOOL showsHorizontalScrollIndicator; 35 // 控制是否显示垂直方向的滚动条 36 37 BOOL showsVerticalScrollIndicator; 38 // 指定滚动条在scrollerView中的位置 39 40 UIEdgeInsets scrollIndicatorInsets; 41 // 设定滚动条的样式 42 43 UIScrollViewIndicatorStyle indicatorStyle;
UIScrollViewDelegate详解
1 //scrollView滚动时,就调用该方法。任何offset值改变都调用该方法。即滚动过程中,调用多次 2 - (void)scrollViewDidScroll:(UIScrollView *)scrollView{ 3 4 NSLog(@"scrollViewDidScroll"); 5 CGPoint point=scrollView.contentOffset; 6 NSLog(@"%f,%f",point.x,point.y); 7 // 从中可以读取contentOffset属性以确定其滚动到的位置。 8 9 // 注意:当ContentSize属性小于Frame时,将不会出发滚动 10 11 12 } 13 // 当scrollView缩放时,调用该方法。在缩放过程中,回多次调用 14 - (void)scrollViewDidZoom:(UIScrollView *)scrollView{ 15 16 NSLog(@"scrollViewDidScroll"); 17 float value=scrollView.zoomScale; 18 NSLog(@"%f",value); 19 20 21 } 22 // 当开始滚动视图时,执行该方法。一次有效滑动(开始滑动,滑动一小段距离,只要手指不松开,只算一次滑动),只执行一次。 23 - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{ 24 25 NSLog(@"scrollViewWillBeginDragging"); 26 27 } 28 // 滑动scrollView,并且手指离开时执行。一次有效滑动,只执行一次。 29 // 当pagingEnabled属性为YES时,不调用,该方法 30 - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{ 31 32 NSLog(@"scrollViewWillEndDragging"); 33 34 } 35 // 滑动视图,当手指离开屏幕那一霎那,调用该方法。一次有效滑动,只执行一次。 36 // decelerate,指代,当我们手指离开那一瞬后,视图是否还将继续向前滚动(一段距离),经过测试,decelerate=YES 37 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ 38 39 NSLog(@"scrollViewDidEndDragging"); 40 if (decelerate) { 41 NSLog(@"decelerate"); 42 }else{ 43 NSLog(@"no decelerate"); 44 45 } 46 47 CGPoint point=scrollView.contentOffset; 48 NSLog(@"%f,%f",point.x,point.y); 49 50 } 51 // 滑动减速时调用该方法。 52 - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView{ 53 54 NSLog(@"scrollViewWillBeginDecelerating"); 55 // 该方法在scrollViewDidEndDragging方法之后。 56 57 58 } 59 // 滚动视图减速完成,滚动将停止时,调用该方法。一次有效滑动,只执行一次。 60 - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{ 61 62 NSLog(@"scrollViewDidEndDecelerating"); 63 64 [_scrollView setContentOffset:CGPointMake(0, 500) animated:YES]; 65 66 } 67 // 当滚动视图动画完成后,调用该方法,如果没有动画,那么该方法将不被调用 68 - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView{ 69 70 NSLog(@"scrollViewDidEndScrollingAnimation"); 71 // 有效的动画方法为: 72 // - (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated 方法 73 // - (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated 方法 74 75 76 } 77 // 返回将要缩放的UIView对象。要执行多次 78 - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView{ 79 80 NSLog(@"viewForZoomingInScrollView"); 81 return self.imgView; 82 83 } 84 // 当将要开始缩放时,执行该方法。一次有效缩放,就只执行一次。 85 - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view{ 86 87 NSLog(@"scrollViewWillBeginZooming"); 88 89 } 90 // 当缩放结束后,并且缩放大小回到minimumZoomScale与maximumZoomScale之间后(我们也许会超出缩放范围),调用该方法。 91 - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale{ 92 93 NSLog(@"scrollViewDidEndZooming"); 94 95 } 96 // 指示当用户点击状态栏后,滚动视图是否能够滚动到顶部。需要设置滚动视图的属性:_scrollView.scrollsToTop=YES; 97 - (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView{ 98 99 return YES; 100 101 102 } 103 // 当滚动视图滚动到最顶端后,执行该方法 104 - (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView{ 105 106 NSLog(@"scrollViewDidScrollToTop"); 107 }
判断uiscrollview是向上滚动还是向下滚动
int _lastPosition; //A variable define in headfile
1 - (void)scrollViewDidScroll:(UIScrollView *)scrollView{ 2 int currentPostion = scrollView.contentOffset.y; 3 if (currentPostion - _lastPosition > 25) { 4 _lastPosition = currentPostion; 5 NSLog(@"ScrollUp now"); 6 } 7 else if (_lastPosition - currentPostion > 25) 8 { 9 _lastPosition = currentPostion; 10 NSLog(@"ScrollDown now"); 11 } 12 } 13 // 25 可以是任意数字,可根据自己的需要来设定。 14 // 升级版:到达顶部或底部时不会反弹 15 - (void)scrollViewDidScroll:(UIScrollView *)scrollView 16 { 17 int currentPostion = scrollView.contentOffset.y; 18 19 if (currentPostion - _lastPosition > 20 && currentPostion > 0) { //这个地方加上 currentPostion > 0 即可) 20 _lastPosition = currentPostion; 21 22 NSLog(@"ScrollUp now"); 23 } 24 else if ((_lastPosition - currentPostion > 20) && (currentPostion <= scrollView.contentSize.height-scrollView.bounds.size.height-20) ){ 25 _lastPosition = currentPostion; 26 27 NSLog(@"ScrollDown now"); 28 } 29 }