iOS开发——UI基础-UIScrollView

一、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
将来的你会感谢今天如此努力的你! 版权声明:本文为博主原创文章,未经博主允许不得转载。
原文地址:https://www.cnblogs.com/chglog/p/4652716.html