iOS开发——UI基础-UIScrollView

时间:2023-01-13 13:45:07

一、UIScrollView使用的步骤


1.创建UIScrollView
2.将需要展示的内容添加到UIScrollView中
3.设置UIScrollView的滚动范围 (contentSize)

 @interface ViewController ()
@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
@end
// 1.添加两个子控件到UIScrollView中
// 一个控件没有设置frame, 默认x/y就是0
UIButton *btn = [UIButton buttonWithType:UIButtonTypeContactAdd];
[self.scrollView addSubview:btn]; UISwitch *sw = [[UISwitch alloc] init];
CGRect tempFrame = sw.frame;
tempFrame.origin.y = ;
sw.frame = tempFrame;
[self.scrollView addSubview:sw]; // 添加一个按钮
UIButton *customBtn = [[UIButton alloc] init];
customBtn.frame = CGRectMake(, , , );
customBtn.backgroundColor = [UIColor redColor];
[customBtn setTitle:@"我是按钮" forState:UIControlStateNormal];
[customBtn setTitle:@"我是高亮" forState:UIControlStateHighlighted];
[customBtn setTitle:@"我是disabled状态" forState:UIControlStateDisabled];
//[customBtn addTarget:self action:@selector(customBtnClick) forControlEvents:UIControlEventTouchUpInside];
[self.scrollView addSubview:customBtn]; // 注意: 如果想让UIScrollView进行滚动, 必须设置可以滚动的范围
// 设置scrollView的滚动范围为, frame的宽高 + 100
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width + , self.scrollView.frame.size.height + );

二、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(, );

// 注意点: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 ..

 @interface ViewController ()<UIScrollViewDelegate>
@property (weak, nonatomic) IBOutlet UIScrollView *sc; @end self.sc.delegate = self; #pragma mark - UIScrollViewDelegate
// 只要成为了UIScrollView的代理, 遵守代理协议, 实现协议中的方法
// 当UIScrollView发生一些变化的时候, 系统就会自动调用这些代理方法 // scrollViewDidScroll方法什么时候调用?
// 只要UIScrollView滚动了, 系统就会自动调用
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSLog(@"%s", __func__);
} // 只要用户准备开始拖拽了就会调用
- (void)scrollViewWillBeginDragging:(nonnull UIScrollView *)scrollView
{
NSLog(@"%s", __func__);
} // 用户已经结束拖拽, 代表用户已经松手了
// 系统调用了该方法并不代表着,UIScrollView已经停止滚动了 // 每次调用 停止拖拽方法时 ,系统都会传入一个当前是否有惯性的参数
// 我们可以判断该参数是否为YES, 如果是YES代表当前UIScrollView有惯性, 停止拖拽并不会停止滚动, 需要在停止减速方法中监听什么时候真正的停止
- (void)scrollViewDidEndDragging:(nonnull UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
NSLog(@"%s", __func__);
if (decelerate == NO) {
// NSLog(@"没有惯性, 可以在当前方法监听UIScrollView是否停止滚动");
[self scrollViewDidEndDecelerating:scrollView];
}else{
// NSLog(@"有惯性, 需要在减速结束方法中监听UIScrollView是否停止滚动");
}
} // UIScrollView已经停止减速了
// 只有执行了这个方法才代表UIScrollView已经停止滚动了
- (void)scrollViewDidEndDecelerating:(nonnull UIScrollView *)scrollView
{
NSLog(@"UIScrollView停止滚动了");
}

注意:
如果想在UIScrollView停止滚动之后做一些操作, 有两种情况
1.没有惯性的情况: 只会调用 停止拖拽的方法, 不会调用停止减速的方法
2.有惯性的情况: 既会调用 停止拖拽的方法, 也会调用停止减速的方法
所以: 以后要判断UIScrollView是否停止滚动, 需要同时重写两个方法
  2.1scrollViewDidEndDragging
  2.2scrollViewDidEndDecelerating

四、缩放图片


要想缩放图片分为两步
  1.成为代理, 通过代理方法告诉UIScrollView要缩放哪一个子控件
  2.设最大置子控件和最小的缩放比例

 // 要想缩放, 除了告诉UISrollView要缩放哪一个控件以外, 还要告诉UISrollView最小能缩多小, 最大能放多大
self.sc.maximumZoomScale = 2.0;
self.sc.minimumZoomScale = 0.5; // 因为所有的子控件都是我们添加进去的, 所以要缩放哪一个我们最清楚
// 所以只要让控制器成为UISrollView的代理, 当UISrollView不清楚要缩放哪一个控件的时候
// UISrollView就会调用它的代理方法, 问问代理到底要缩放哪一个
self.sc.delegate = self; // 因为UISrollView中可能有多个子控件
// 那么UISrollView就搞不清楚到底要缩放哪一个子控件
// 想要缩放, 必须明确的告诉UISrollView要缩放哪一个控件
// 在此方法中告诉UISrollView要缩放哪一个控件
- (nullable UIView *)viewForZoomingInScrollView:(nonnull UIScrollView *)scrollView
{
return self.iv;
} // 缩放的过程中调用
// 和scrollViewDidScroll一样, 只要缩放一点点就会调用
- (void)scrollViewDidZoom:(nonnull UIScrollView *)scrollView
{
NSLog(@"%s", __func__);
} // 缩放结束时调用
- (void)scrollViewDidEndZooming:(nonnull UIScrollView *)scrollView withView:(nullable UIView *)view atScale:(CGFloat)scale
{
NSLog(@"%s", __func__);
}

五、设置分页


 #import "ViewController.h"

 #define IMAGE_COUNT 5
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIScrollView *sc; @end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad]; self.sc.showsHorizontalScrollIndicator = NO;
self.sc.showsVerticalScrollIndicator = NO; CGFloat width = self.sc.frame.size.width;
CGFloat height = self.sc.frame.size.height; // 1.初始化子控件, 添加图片
for (int i = ; i < IMAGE_COUNT; i++) {
// 1.创建UIImageView
UIImageView *iv = [[UIImageView alloc] init];
// 2.创建图片
UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"img_%02i", i + ]];
// 3.设置每个UIImageView的frame
// iv.frame = CGRectMake(i * width, 0, width, height); // 按照宽度分页
iv.frame = CGRectMake(, i * height, width, height); // 按照高度分页
iv.image = image;
// 4.添加到父控件
[self.sc addSubview:iv];
} // 2.设置滚动范围
// self.sc.contentSize = CGSizeMake(IMAGE_COUNT * width, height);
self.sc.contentSize = CGSizeMake(width, IMAGE_COUNT * height);
self.sc.bounces = NO;
self.sc.pagingEnabled = YES;
// pagingEnabled实现分页的本质, 是按照UIScrollView的宽度或者高度来分页的
// UIScrollView的宽度就是一页的宽度
}
@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 , );
}

让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 + ;
NSLog(@"%lu", self.pageControl.currentPage);
// 2.判断页码是否越界
if (page >= IMAGE_COUNT) {
// 如果越界就回到第0页
self.pageControl.currentPage = ;
}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]; }

五、图片轮播器


iOS开发——UI基础-UIScrollView   iOS开发——UI基础-UIScrollView

实现代码如下

 #import "ViewController.h"
#import "XMGPageView.h" @interface ViewController ()<UIScrollViewDelegate> @property(nonatomic, strong)XMGPageView *pageView;
@end @implementation ViewController - (void)viewDidLoad
{
[super viewDidLoad];
/*
1.利用UIScrollView实现商品展示
2.用纯代码封装图片轮播器
*/ // 1.创建图片轮播器
XMGPageView *pageView = [XMGPageView pageView];
// 2.设置图片轮播器的frame
pageView.imageNames = @[@"img_01", @"img_02", @"img_03", @"img_04", @"img_05"];
pageView.frame = CGRectMake(, , , );
// pageView.frame = CGRectMake(0, 97, 330, 200);
[self.view addSubview:pageView];
self.pageView = pageView; } @end /***************华丽的分割线*******************/ #import <UIKit/UIKit.h> @interface XMGPageView : UIView + (instancetype)pageView; /** 所有需要展示的图片名称*/
@property (nonatomic, strong)NSArray *imageNames;
@end #import "XMGPageView.h" @interface XMGPageView ()<UIScrollViewDelegate> @property (weak, nonatomic) IBOutlet UIScrollView *sc;
@property (weak, nonatomic) IBOutlet UIPageControl *pageControl; // 注意:NSTimer应该是weak
@property (weak, nonatomic) NSTimer *timer;
@end @implementation XMGPageView + (instancetype)pageView
{
return [[[NSBundle mainBundle] loadNibNamed:@"XMGPageView" owner:nil options:nil] lastObject];
}
/*
自定义View的步骤:
1.重写初始化方法 (在里面进行一次性的初始化)
xib :awakeFromNib
纯代码:initWithFrame
2.重写layoutSubviews, 在里面布局子控件
3.接收外界传入的数据, 重写set方法
*/ - (void)awakeFromNib
{ self.sc.delegate = self;
// 1.隐藏滚动条
self.sc.showsHorizontalScrollIndicator = NO;
self.sc.showsVerticalScrollIndicator = NO; // 2.设置UIScrollView的其它属性
self.sc.bounces = NO;
self.sc.pagingEnabled = YES; // 3.监听PageControl的点击事件
[self.pageControl addTarget:self action:@selector(pageControlClick:) forControlEvents:UIControlEventValueChanged]; // 4.通过KVC给UIPageControl的私有属性赋值, 设置自定义图片
[self.pageControl setValue:[UIImage imageNamed:@"current"] forKeyPath:@"_currentPageImage"];
[self.pageControl setValue:[UIImage imageNamed:@"other"] forKeyPath:@"_pageImage"]; // 5.让UIScrollView每隔一段事件就切换一页
[self startTimer];
} #pragma mark - 内部监听
- (IBAction)pageControlClick:(UIPageControl *)sender
{ self.sc.contentOffset = CGPointMake(sender.currentPage * self.sc.frame.size.width , );
} #pragma mark - 定时器相关
- (void)startTimer
{
// 打开定时器
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(nextPage:) userInfo:@"lnj" repeats:YES]; // 主线程在处理其它事件的时候, 分一点时间来处理NSTimer
[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
} // 切换到下一页
- (void)nextPage:(NSTimer *)timer
{
// 1.获取下一页的页码
NSUInteger page = self.pageControl.currentPage + ;
// 2.判断页码是否越界
if (page >= _imageNames.count) {
// 如果越界就回到第0页
self.pageControl.currentPage = ;
}else
{
// 如果没有越界, 就进入到下一页
self.pageControl.currentPage = page;
} [self pageControlClick:self.pageControl];
} - (void)stopTimer
{
// 关掉定时器
[self.timer invalidate];
} #pragma mark - UIScrollViewDelegate
// 只要滚动就会调用
- (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;
} // 开始拖拽
- (void)scrollViewWillBeginDragging:(nonnull UIScrollView *)scrollView
{
[self stopTimer];
} // 结束拖拽
- (void)scrollViewDidEndDragging:(nonnull UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
[self startTimer]; } - (void)setImageNames:(NSArray *)imageNames
{
_imageNames = imageNames; // 0.每次重新设置图片, 都需要清空以前的图片
for (UIView *subView in self.sc.subviews) {
[subView removeFromSuperview];
} // 1.初始化子控件, 添加图片
for (int i = ; i < _imageNames.count; i++) { // 1.创建UIImageView
UIImageView *iv = [[UIImageView alloc] init]; // 2.创建图片
NSString *imageName = _imageNames[i];
UIImage *image = [UIImage imageNamed:imageName];
iv.image = image; // 3.添加到父控件
[self.sc addSubview:iv];
} // 2.设置pageControl的页码数量
self.pageControl.numberOfPages = _imageNames.count; } - (void)layoutSubviews
{
[super layoutSubviews]; CGFloat width = self.sc.frame.size.width;
CGFloat height = self.sc.frame.size.height;
NSUInteger imageCount = self.imageNames.count;
// 1.设置每个UIImageView的frame
for (int i = ; i < imageCount; i++) {
UIImageView *iv = self.sc.subviews[i];
iv.frame = CGRectMake(i * width, , width, height);
} // 2.设置滚动范围
self.sc.contentSize = CGSizeMake(imageCount * width, height);
} @end