一、UIScrollView使用的步骤
1.创建UIScrollView
2.将需要展示的内容添加到UIScrollView中
3.设置UIScrollView的滚动范围 (contentSize)
1 @interface ViewController () 2 @property (weak, nonatomic) IBOutlet UIScrollView *scrollView; 3 @end 4 // 1.添加两个子控件到UIScrollView中 5 // 一个控件没有设置frame, 默认x/y就是0 6 UIButton *btn = [UIButton buttonWithType:UIButtonTypeContactAdd]; 7 [self.scrollView addSubview:btn]; 8 9 UISwitch *sw = [[UISwitch alloc] init]; 10 CGRect tempFrame = sw.frame; 11 tempFrame.origin.y = 150; 12 sw.frame = tempFrame; 13 [self.scrollView addSubview:sw]; 14 15 // 添加一个按钮 16 UIButton *customBtn = [[UIButton alloc] init]; 17 customBtn.frame = CGRectMake(0, 0, 100, 100); 18 customBtn.backgroundColor = [UIColor redColor]; 19 [customBtn setTitle:@"我是按钮" forState:UIControlStateNormal]; 20 [customBtn setTitle:@"我是高亮" forState:UIControlStateHighlighted]; 21 [customBtn setTitle:@"我是disabled状态" forState:UIControlStateDisabled]; 22 //[customBtn addTarget:self action:@selector(customBtnClick) forControlEvents:UIControlEventTouchUpInside]; 23 [self.scrollView addSubview:customBtn]; 24 25 // 注意: 如果想让UIScrollView进行滚动, 必须设置可以滚动的范围 26 // 设置scrollView的滚动范围为, frame的宽高 + 100 27 self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width + 100, self.scrollView.frame.size.height + 100);
二、scrollView的基本属性
scrollView不能滚动的几种情况
1.没有设置contentSize
2.scrollEnabled属性 = NO
3.userInteractionEnabled属性 = NO
self.scrollView.scrollEnabled = NO;
self.scrollView.userInteractionEnabled = NO;
enabled和userInteractionEnabled的区别
enabled: 代表控件不可用
userInteractionEnabled: 代表控件不可以和用户交互, 也就是不能响应用户的操作
如何去掉滚动条
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;
设置内容偏移位(contentOffset)
// 其实就是设置scrollView滚动到什么地方
// 告诉scrollView x方向要移动多少, y方向要移动多少
// 如果x是正数: 图片往左边移动
// 如果x是负数: 图片往右边移动
// 同理y是正数: 图片往上移动
// 同理y是负数: 图片往下移动
// 计算公式: 永远都是以 控件的左上角 – 内容的左上角
sc.contentOffset = CGPointMake(100, 0);
// 注意点:contentOffset移动的位置是一个临时的位置, 只要轻轻拖拽一下就会回到默认的位置
// 个人理解: 以图片左上角为原点,sc.contentOffset即UIScrollView相对于图片的偏移量
三、如何监听一个控件的变化/状态
1. 首先需要查看该控件的头文件, 看它继承于谁
1.1如果继承于UIControl, 那么就可以通过addTarget来监听
1.2如果继承于UIView, 那么必须通过代理来监听
2. 代理协议的规律:
以控件的类名开头, 后面加上delegate
3. 代理协议中的方法名的规律:
一般以控件名称去掉类前缀开头
4. 代理协议中的方法参数的规律:
谁触发事件, 就将谁传递进来
5. 如何监听UIScrollView的变化
1.成为UIScrollView的代理
2.遵守UIScrollView的协议
3.实现UIScrollView协议中的方法
6.代理作用:
当A对象想监听B对象的变化 , 那么可以让A成为B的代理
当B对象发生一些变化想通知A对象, 那么可以让A成为B的代理
@property (weak, nonatomic) IBOutlet UIScrollView *sc;
7.为什么代理要用weak
原因: 为了防止循环引用
控制器 -强引用-> 控制器的View -强引用-> subViews数组 -强引用-> UIScrollView -弱引用-> 控制器
如果只有一个控制器的情况, 程序一启动就创建的这个控制器是不会被释放的
strong
对象, 强指针, 强引用
weak
对象, 控件/代理
copy
对象, 字符串, 为了防止外界修改内部的属性的值
assign
基本数据类型 int/float/doble/bool ..
1 @interface ViewController ()<UIScrollViewDelegate> 2 @property (weak, nonatomic) IBOutlet UIScrollView *sc; 3 4 @end 5 6 self.sc.delegate = self; 7 8 9 #pragma mark - UIScrollViewDelegate 10 // 只要成为了UIScrollView的代理, 遵守代理协议, 实现协议中的方法 11 // 当UIScrollView发生一些变化的时候, 系统就会自动调用这些代理方法 12 13 // scrollViewDidScroll方法什么时候调用? 14 // 只要UIScrollView滚动了, 系统就会自动调用 15 - (void)scrollViewDidScroll:(UIScrollView *)scrollView 16 { 17 NSLog(@"%s", __func__); 18 } 19 20 // 只要用户准备开始拖拽了就会调用 21 - (void)scrollViewWillBeginDragging:(nonnull UIScrollView *)scrollView 22 { 23 NSLog(@"%s", __func__); 24 } 25 26 27 // 用户已经结束拖拽, 代表用户已经松手了 28 // 系统调用了该方法并不代表着,UIScrollView已经停止滚动了 29 30 // 每次调用 停止拖拽方法时 ,系统都会传入一个当前是否有惯性的参数 31 // 我们可以判断该参数是否为YES, 如果是YES代表当前UIScrollView有惯性, 停止拖拽并不会停止滚动, 需要在停止减速方法中监听什么时候真正的停止 32 - (void)scrollViewDidEndDragging:(nonnull UIScrollView *)scrollView willDecelerate:(BOOL)decelerate 33 { 34 NSLog(@"%s", __func__); 35 if (decelerate == NO) { 36 // NSLog(@"没有惯性, 可以在当前方法监听UIScrollView是否停止滚动"); 37 [self scrollViewDidEndDecelerating:scrollView]; 38 }else{ 39 // NSLog(@"有惯性, 需要在减速结束方法中监听UIScrollView是否停止滚动"); 40 } 41 } 42 43 // UIScrollView已经停止减速了 44 // 只有执行了这个方法才代表UIScrollView已经停止滚动了 45 - (void)scrollViewDidEndDecelerating:(nonnull UIScrollView *)scrollView 46 { 47 NSLog(@"UIScrollView停止滚动了"); 48 }
注意:
如果想在UIScrollView停止滚动之后做一些操作, 有两种情况
1.没有惯性的情况: 只会调用 停止拖拽的方法, 不会调用停止减速的方法
2.有惯性的情况: 既会调用 停止拖拽的方法, 也会调用停止减速的方法
所以: 以后要判断UIScrollView是否停止滚动, 需要同时重写两个方法
2.1scrollViewDidEndDragging
2.2scrollViewDidEndDecelerating
四、缩放图片
要想缩放图片分为两步
1.成为代理, 通过代理方法告诉UIScrollView要缩放哪一个子控件
2.设最大置子控件和最小的缩放比例
1 // 要想缩放, 除了告诉UISrollView要缩放哪一个控件以外, 还要告诉UISrollView最小能缩多小, 最大能放多大 2 self.sc.maximumZoomScale = 2.0; 3 self.sc.minimumZoomScale = 0.5; 4 5 6 // 因为所有的子控件都是我们添加进去的, 所以要缩放哪一个我们最清楚 7 // 所以只要让控制器成为UISrollView的代理, 当UISrollView不清楚要缩放哪一个控件的时候 8 // UISrollView就会调用它的代理方法, 问问代理到底要缩放哪一个 9 self.sc.delegate = self; 10 11 12 // 因为UISrollView中可能有多个子控件 13 // 那么UISrollView就搞不清楚到底要缩放哪一个子控件 14 // 想要缩放, 必须明确的告诉UISrollView要缩放哪一个控件 15 // 在此方法中告诉UISrollView要缩放哪一个控件 16 - (nullable UIView *)viewForZoomingInScrollView:(nonnull UIScrollView *)scrollView 17 { 18 return self.iv; 19 } 20 21 // 缩放的过程中调用 22 // 和scrollViewDidScroll一样, 只要缩放一点点就会调用 23 - (void)scrollViewDidZoom:(nonnull UIScrollView *)scrollView 24 { 25 NSLog(@"%s", __func__); 26 } 27 28 // 缩放结束时调用 29 - (void)scrollViewDidEndZooming:(nonnull UIScrollView *)scrollView withView:(nullable UIView *)view atScale:(CGFloat)scale 30 { 31 NSLog(@"%s", __func__); 32 }
五、设置分页
1 #import "ViewController.h" 2 3 #define IMAGE_COUNT 5 4 @interface ViewController () 5 @property (weak, nonatomic) IBOutlet UIScrollView *sc; 6 7 @end 8 9 @implementation ViewController 10 11 - (void)viewDidLoad { 12 [super viewDidLoad]; 13 14 self.sc.showsHorizontalScrollIndicator = NO; 15 self.sc.showsVerticalScrollIndicator = NO; 16 17 CGFloat width = self.sc.frame.size.width; 18 CGFloat height = self.sc.frame.size.height; 19 20 // 1.初始化子控件, 添加图片 21 for (int i = 0; i < IMAGE_COUNT; i++) { 22 // 1.创建UIImageView 23 UIImageView *iv = [[UIImageView alloc] init]; 24 // 2.创建图片 25 UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"img_%02i", i + 1]]; 26 // 3.设置每个UIImageView的frame 27 // iv.frame = CGRectMake(i * width, 0, width, height); // 按照宽度分页 28 iv.frame = CGRectMake(0, i * height, width, height); // 按照高度分页 29 iv.image = image; 30 // 4.添加到父控件 31 [self.sc addSubview:iv]; 32 } 33 34 // 2.设置滚动范围 35 // self.sc.contentSize = CGSizeMake(IMAGE_COUNT * width, height); 36 self.sc.contentSize = CGSizeMake(width, IMAGE_COUNT * height); 37 self.sc.bounces = NO; 38 self.sc.pagingEnabled = YES; 39 // pagingEnabled实现分页的本质, 是按照UIScrollView的宽度或者高度来分页的 40 // UIScrollView的宽度就是一页的宽度 41 } 42 @end
要实现动态修改页码, 有两种方式
1.实时计算
// 只要滚动就会调用 - (void)scrollViewDidScroll:(nonnull UIScrollView *)scrollView { // 1.计算页码 // 当前页码 = 偏移位 / UIScrollView的宽度 CGFloat page = scrollView.contentOffset.x / scrollView.frame.size.width; int currnetPage = page + 0.5; // 2.修改页码 self.pageControl.currentPage = currnetPage; }
2.翻页之后再计算
2.1停止拖拽
2.2停止减速
// 停止拖拽 - (void)scrollViewDidEndDragging:(nonnull UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { if (decelerate == NO) { [self scrollViewDidEndDecelerating:scrollView]; } } // 停止减速 - (void)scrollViewDidEndDecelerating:(nonnull UIScrollView *)scrollView { // 1.计算页码 // 当前页码 = 偏移位 / UIScrollView的宽度 int page = scrollView.contentOffset.x / scrollView.frame.size.width; NSLog(@"page = %i", page); // 2.修改页码 self.pageControl.currentPage = page; }
点击UIpageControl进行翻页
// 监听PageControl的点击事件 [self.pageControl addTarget:self action:@selector(pageControlClick:) forControlEvents:UIControlEventValueChanged]; - (IBAction)pageControlClick:(UIPageControl *)sender { NSLog(@"%lu", sender.currentPage); self.sc.contentOffset = CGPointMake(sender.currentPage * self.sc.frame.size.width , 0); }
让UIScrollView每隔一段事件就切换一页
// scheduledTimerWithTimeInterval: 创建一个定时器, 并且立即可是计时 // TimeInterval: 间隔时间 // target: 调用谁的方法 // selector: 调用什么方法 // userInfo: 需要传递什么参数 // repeats: 是否重复 // 每隔2.0秒调用一次self的nextPage方法, 并且不传递任何参数 [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(nextPage) userInfo:nil repeats:YES]; // 切换到下一页 - (void)nextPage { // 1.获取下一页的页码 NSUInteger page = self.pageControl.currentPage + 1; NSLog(@"%lu", self.pageControl.currentPage); // 2.判断页码是否越界 if (page >= IMAGE_COUNT) { // 如果越界就回到第0页 self.pageControl.currentPage = 0; }else { // 如果没有越界, 就进入到下一页 self.pageControl.currentPage = page; } [self pageControlClick:self.pageControl]; } // 如果给userInfo赋值, 那么定时器调用的方法就必须接受参数, 并且接受的参数就是NSTimer // 只要调用scheduled方法创建一个NSTimer对象, 系统就会自动将NSTimer添加到主线程中
如果是单线程,并且有多个任务,比如说添加一个Text View,在点击Text View时定时器会停止工作,那么需要做以下操作让主线程空出时间来执行定时器
- (void)startTimer { // 打开定时器 self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(nextPage:) userInfo:@"lnj" repeats:YES]; // 主线程在处理其它事件的时候, 分一点时间来处理NSTimer // 1.0 0.1 [[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes]; } - (void)stopTimer { // 关掉定时器 #warning 注意:NSTimer是一次性的, 只要invalidate之后就不能使用了 // 只要调用invalidate方法, 系统就会将NSTimer从主线程移除, 并且销毁NSTimer对象 [self.timer invalidate]; }
五、图片轮播器
实现代码如下
1 #import "ViewController.h" 2 #import "XMGPageView.h" 3 4 @interface ViewController ()<UIScrollViewDelegate> 5 6 @property(nonatomic, strong)XMGPageView *pageView; 7 @end 8 9 @implementation ViewController 10 11 - (void)viewDidLoad 12 { 13 [super viewDidLoad]; 14 /* 15 1.利用UIScrollView实现商品展示 16 2.用纯代码封装图片轮播器 17 */ 18 19 // 1.创建图片轮播器 20 XMGPageView *pageView = [XMGPageView pageView]; 21 // 2.设置图片轮播器的frame 22 pageView.imageNames = @[@"img_01", @"img_02", @"img_03", @"img_04", @"img_05"]; 23 pageView.frame = CGRectMake(27, 97, 320, 128); 24 // pageView.frame = CGRectMake(0, 97, 330, 200); 25 [self.view addSubview:pageView]; 26 self.pageView = pageView; 27 28 } 29 30 @end 31 32 /***************华丽的分割线*******************/ 33 34 #import <UIKit/UIKit.h> 35 36 @interface XMGPageView : UIView 37 38 + (instancetype)pageView; 39 40 /** 所有需要展示的图片名称*/ 41 @property (nonatomic, strong)NSArray *imageNames; 42 @end 43 44 45 46 #import "XMGPageView.h" 47 48 @interface XMGPageView ()<UIScrollViewDelegate> 49 50 @property (weak, nonatomic) IBOutlet UIScrollView *sc; 51 @property (weak, nonatomic) IBOutlet UIPageControl *pageControl; 52 53 // 注意:NSTimer应该是weak 54 @property (weak, nonatomic) NSTimer *timer; 55 @end 56 57 @implementation XMGPageView 58 59 60 + (instancetype)pageView 61 { 62 return [[[NSBundle mainBundle] loadNibNamed:@"XMGPageView" owner:nil options:nil] lastObject]; 63 } 64 /* 65 自定义View的步骤: 66 1.重写初始化方法 (在里面进行一次性的初始化) 67 xib :awakeFromNib 68 纯代码:initWithFrame 69 2.重写layoutSubviews, 在里面布局子控件 70 3.接收外界传入的数据, 重写set方法 71 */ 72 73 - (void)awakeFromNib 74 { 75 76 self.sc.delegate = self; 77 // 1.隐藏滚动条 78 self.sc.showsHorizontalScrollIndicator = NO; 79 self.sc.showsVerticalScrollIndicator = NO; 80 81 // 2.设置UIScrollView的其它属性 82 self.sc.bounces = NO; 83 self.sc.pagingEnabled = YES; 84 85 // 3.监听PageControl的点击事件 86 [self.pageControl addTarget:self action:@selector(pageControlClick:) forControlEvents:UIControlEventValueChanged]; 87 88 // 4.通过KVC给UIPageControl的私有属性赋值, 设置自定义图片 89 [self.pageControl setValue:[UIImage imageNamed:@"current"] forKeyPath:@"_currentPageImage"]; 90 [self.pageControl setValue:[UIImage imageNamed:@"other"] forKeyPath:@"_pageImage"]; 91 92 // 5.让UIScrollView每隔一段事件就切换一页 93 [self startTimer]; 94 } 95 96 #pragma mark - 内部监听 97 - (IBAction)pageControlClick:(UIPageControl *)sender 98 { 99 100 self.sc.contentOffset = CGPointMake(sender.currentPage * self.sc.frame.size.width , 0); 101 } 102 103 #pragma mark - 定时器相关 104 - (void)startTimer 105 { 106 // 打开定时器 107 self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(nextPage:) userInfo:@"lnj" repeats:YES]; 108 109 // 主线程在处理其它事件的时候, 分一点时间来处理NSTimer 110 [[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes]; 111 } 112 113 // 切换到下一页 114 - (void)nextPage:(NSTimer *)timer 115 { 116 // 1.获取下一页的页码 117 NSUInteger page = self.pageControl.currentPage + 1; 118 // 2.判断页码是否越界 119 if (page >= _imageNames.count) { 120 // 如果越界就回到第0页 121 self.pageControl.currentPage = 0; 122 }else 123 { 124 // 如果没有越界, 就进入到下一页 125 self.pageControl.currentPage = page; 126 } 127 128 [self pageControlClick:self.pageControl]; 129 } 130 131 - (void)stopTimer 132 { 133 // 关掉定时器 134 [self.timer invalidate]; 135 } 136 137 #pragma mark - UIScrollViewDelegate 138 // 只要滚动就会调用 139 - (void)scrollViewDidScroll:(nonnull UIScrollView *)scrollView 140 { 141 // 1.计算页码 142 // 当前页码 = 偏移位 / UIScrollView的宽度 143 CGFloat page = scrollView.contentOffset.x / scrollView.frame.size.width; 144 int currnetPage = page + 0.5; 145 146 // 2.修改页码 147 self.pageControl.currentPage = currnetPage; 148 } 149 150 // 开始拖拽 151 - (void)scrollViewWillBeginDragging:(nonnull UIScrollView *)scrollView 152 { 153 [self stopTimer]; 154 } 155 156 // 结束拖拽 157 - (void)scrollViewDidEndDragging:(nonnull UIScrollView *)scrollView willDecelerate:(BOOL)decelerate 158 { 159 [self startTimer]; 160 161 } 162 163 164 - (void)setImageNames:(NSArray *)imageNames 165 { 166 _imageNames = imageNames; 167 168 // 0.每次重新设置图片, 都需要清空以前的图片 169 for (UIView *subView in self.sc.subviews) { 170 [subView removeFromSuperview]; 171 } 172 173 // 1.初始化子控件, 添加图片 174 for (int i = 0; i < _imageNames.count; i++) { 175 176 // 1.创建UIImageView 177 UIImageView *iv = [[UIImageView alloc] init]; 178 179 // 2.创建图片 180 NSString *imageName = _imageNames[i]; 181 UIImage *image = [UIImage imageNamed:imageName]; 182 iv.image = image; 183 184 // 3.添加到父控件 185 [self.sc addSubview:iv]; 186 } 187 188 // 2.设置pageControl的页码数量 189 self.pageControl.numberOfPages = _imageNames.count; 190 191 } 192 193 - (void)layoutSubviews 194 { 195 [super layoutSubviews]; 196 197 CGFloat width = self.sc.frame.size.width; 198 CGFloat height = self.sc.frame.size.height; 199 NSUInteger imageCount = self.imageNames.count; 200 // 1.设置每个UIImageView的frame 201 for (int i = 0; i < imageCount; i++) { 202 UIImageView *iv = self.sc.subviews[i]; 203 iv.frame = CGRectMake(i * width, 0, width, height); 204 } 205 206 // 2.设置滚动范围 207 self.sc.contentSize = CGSizeMake(imageCount * width, height); 208 } 209 210 @end