iOS开发常用控件之UIScrollView

hack_appList_result_failed

常用属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@property(nonatomic) CGPoint contentOffset;
// 这个属性用来表示UIScrollView滚动的位置

@property(nonatomic) CGSize contentSize;
// 这个属性用来表示UIScrollView内容的尺寸,滚动范围(能滚多远)

@property(nonatomic) UIEdgeInsets contentInset;
// 这个属性能够在UIScrollView的4周增加额外的滚动区域

@property(nonatomic) BOOL bounces;
// 设置UIScrollView是否需要弹簧效果

@property(nonatomic,getter=isScrollEnabled) BOOL scrollEnabled;
// 设置UIScrollView是否能滚动

@property(nonatomic) BOOL showsHorizontalScrollIndicator;
// 是否显示水平滚动条

@property(nonatomic) BOOL showsVerticalScrollIndicator;
// 是否显示垂直滚动条

UIScrollView的使用步骤

  • 创建UIScrollView
  • 给UIScrollView添加子控件
  • 要想UIScrollView可以滚动,必须设置UIScrollView的滚动范围
1
2
// 设置滚动范围
self.scrollview.contentSize = CGSizeMake(500, 500);

注意点

  • 千万不要使用subviews来获取UIScrollview中的子控件。

    • 因为subviews中包含了UIScrollview中的滚动条,而且滚动条的顺序是不确定的。
  • 默认情况下只要UIScrollview可以滚动, 就有弹簧效果

    • bounces属性用于设置UIScrollview是否有回弹效果
1
self.scrollview.bounces = NO;
  • 注意: alwaysBounceXXX属性主要用于设置UIScrollview没有设置contentSize的情况
    • 如果UIScrollview已经设置了contentSize,那么YES和NO没有区别
    • alwaysBounceXXX应用场景: 下拉刷新
1
2
self.scrollview.alwaysBounceVertical = YES;
self.scrollview.alwaysBounceHorizontal = YES;
  • 设置滚动条的样式和控制滚动条的显示
1
2
3
4
5
// 样式
self.scrollview.indicatorStyle = UIScrollViewIndicatorStyleWhite;
// 是否显示
self.scrollview.showsHorizontalScrollIndicator = NO;
self.scrollview.showsVerticalScrollIndicator = NO;

contentOffset

  • 只要点击了控制器的view系统就会自动调用下面方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// 1.修改UIScrollview的contentOffset属性
/*
// contentOffset就是用于设置内容的滚动偏移位
// 规律: 移动的距离 = "控件的左上角" - "内容的左上角"
// x等于负数 --> 右边移动
// x等于整数 --> 左边移动
// y等于负数 --> 下边移动
// y等于整数 --> 上边移动

// -100 = "控件的左上角" - "内容的左上角"
// 100 = "控件的左上角" - "内容的左上角"
// self.scrollview.contentOffset = CGPointMake(-100, 0);
// self.scrollview.contentOffset = CGPointMake(100, 0);
// self.scrollview.contentOffset = CGPointMake(0, -100);
*/
self.scrollview.contentOffset = CGPointMake(0, 100);
}

代理

hack_appList_result_failed

  • UIScrollView停止”拖拽”时调用
    • 注意:停止拖拽并不代表停止滚动,也就是说UIScrollView滚动是有惯性的\在scrollViewDidEndDragging方法中,是没有办法准确的监听到UIScrollView什么时候停止滚动。
    • decelerate等于YES代表有惯性, 会继续滚动; 如果等于NO代表没有惯性,会停止滚动。
1
2
3
4
5
6
7
8
9
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
NSLog(@"%s = %d", __func__, decelerate);

// 判断是否有惯性,如果没有惯性手动调用scrollViewDidEndDecelerating告知已经完全停止滚动
if (decelerate == NO) {
[self scrollViewDidEndDecelerating:scrollView];
}
}
  • UIScrollView停止”减速”时调用
    • scrollViewDidEndDecelerating不一定会调用, 只有scrollView有惯性的时候才会调用
1
2
3
4
5
6
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
NSLog(@"%s", __func__);
NSLog(@"停止滚动");
self.iv.alpha = 1.0;
}

UIScrollview缩放

  • 步骤:

    • 成为UIScrollview的代理, 通过代理方法告诉UIScrollview要缩放哪一个子控件
    • 设置子控件最大和最小的缩放比例
  • 常用方法

    • viewForZoomingInScrollView : 返回需要缩放的子控件
    • scrollViewDidZoom: 只要子控件被缩放了就会调用(调用频率非常高)
    • scrollViewDidEndZooming: 缩放完毕时掉用

如何保证被缩放的子控件永远居中

  • 只要子控件的contentSize小于UIScrollview的宽高时才需要缩放

第一种

1
2
3
4
5
6
7
8
9
10
11
12
// 1.判断contentSize的宽高, 是否小于UIScrollView的frame的size
if (scrollView.contentSize.width < scrollView.bounds.size.width) {

self.iv1.center = CGPointMake(scrollView.bounds.size.width * 0.5, scrollView.bounds.size.height * 0.5);

}

if (scrollView.contentSize.height < scrollView.bounds.size.height) {

self.iv1.center = CGPointMake(scrollView.bounds.size.width * 0.5, scrollView.bounds.size.height * 0.5);

}

第二种

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 1.判断contentSize的宽高, 是否小于UIScrollView的frame的size
if (scrollView.contentSize.width < scrollView.bounds.size.width) {

// 计算X的值
CGFloat x = (scrollView.contentSize.width * 0.5) + ((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5);

// 重新设置center
self.iv1.center = CGPointMake(x, self.iv1.center.y);
}

if (scrollView.contentSize.height < scrollView.bounds.size.height) {

// 计算Y的值
CGFloat y = (scrollView.contentSize.height * 0.5) + ((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5);

// 重新设置center
self.iv1.center = CGPointMake(self.iv1.center.x, y);
}

第三种 (对第二种方法进行简化)

1
2
3
CGFloat x = scrollView.contentSize.width < scrollView.bounds.size.width ? (scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5 : 0.0;
CGFloat y = scrollView.contentSize.height < scrollView.bounds.size.height ? (scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5 : 0.0;
self.iv1.center = CGPointMake(scrollView.contentSize.width * 0.5 + x, scrollView.contentSize.height * 0.5 + y);

图片轮播器

  • 如何分页 : pagingEnabled = YES
  • 分页的原理: 是根据UIScrollview的宽度或者高度来分页
  • UIPageControl
    • numberOfPages : 设置总页码
    • currentPage: 设置当前页码
    • pageIndicatorTintColor: 设置其它页码的颜色
    • currentPageIndicatorTintColor : 设置当前页码的颜色

自定义页码

  • 利用KVC给UIPageControl设置页码图片(私有属性)
1
2
[pageControl setValue:[UIImage imageNamed:@"current"] forKeyPath:@"_currentPageImage"];
[pageControl setValue:[UIImage imageNamed:@"other"] forKeyPath:@"_pageImage"];
  • 监听UIPageControl的点击
    • 由于UIPageControl继承于UIControl, 所以通过addTargt来监听
1
[pageControl addTarget:self action:@selector(nextPage) forControlEvents:UIControlEventValueChanged];

切换页码

  • 滚动完毕之后再切换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
// 判断是否有惯性, 如果没有就手动调用scrollViewDidEndDecelerating
if (NO == decelerate) {
[self scrollViewDidEndDecelerating:scrollView];
}
}

// 只有有惯性才会调用
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
// 1.动态计算当前的页码
// 页码 = UIScrollView的偏移位 / UIScrollView的宽度
int page = scrollView.contentOffset.x / scrollView.bounds.size.width;
NSLog(@"page = %d", page);
// 2.设置当前的页码
self.pageControl.currentPage = page;

}
  • 实时切换 (原理相同 实现方式不同的方法)
  • 方式1
1
2
3
4
5
6
7
8
9
10
11
12
// 只要滚动就会调用
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
// NSLog(@"%s", __func__);
// 1.动态计算当前的页码
// 页码 = UIScrollView的偏移位 / UIScrollView的宽度
CGFloat page = scrollView.contentOffset.x / scrollView.bounds.size.width;

NSLog(@"page = %f", page + 0.5);
// 2.设置当前的页码
self.pageControl.currentPage = (int)(page + 0.5);
}
  • 方式2
1
2
3
4
5
6
7
8
9
// 当正在滚动的时候就会调用代理的方法
- (void)scrollViewDidScroll:(UIScrollView *)imageScrollView{

// 根据滚动的位置计算出当前的页数
CGFloat scrollViewW = self.imageScrollView.frame.size.width;
int page = (imageScrollView.contentOffset.x + scrollViewW * 0.5) / scrollViewW;
self.pageControl.currentPage = page;

}

NSTimer的使用

  • 作用: 可以让系统每隔一段时间执行指定对象的指定方法
  • 注意:
    • 间隔时间是不准确的
    • 只要通过scheduledTimerWithTimeInterval创建出来的Timer,就会被RUNLOOP强引用, 所以如果通过属性保存使用weak
    • 只要调用了NSTimer的invalidate方法, 那么定时器就不能使用了, 想要使用必须重新创建
    • 如何主线程正在处理其它操作, 那么NSTimer不会执行
      • 默认NSTimer是NSDefaultRunLoopMode模式
      • 要想在主线程处理其它操作的时候也处理NSTimer, 那么必须把NSTiemr在RunLoop中的模式改为NSRunLoopCommonModes

        开启定时器

1
2
3
4
5
6
7
8
@property (nonatomic, weak) NSTimer *timer;

// 返回一个自动开始执行任务的定时器
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(nextPage:) userInfo:@"123" repeats:YES];

// 修改NSTimer在NSRunLoop中的模式:NSRunLoopCommonModes
// 主线程不管在处理什么操作,都会抽时间处理NSTimer
[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
1
2
3
4
5
    // 开启定时器(方式2)
// 设置定时器的开始时间为过去的某一个时间, 也就意味着立刻开始
// [self.timer setFireDate:[NSDate distantPast]];
// 设置定时器从当前时间之后两秒才开始执行
[self.timer setFireDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];

关闭定时器(销毁)

  • 这种的性能不好 每次都要重启
1
[self.timer invalidate];
  • 暂停定时器(可以采用这种)
1
2
3
    // 设置timer的开始时间为 遥远的未来 4001
// NSLog(@"%@", [NSDate distantFuture]);
[self.timer setFireDate:[NSDate distantFuture]];

封装的思想

  • 只要发现控制器知道得太多,就要考虑重构代码
  • 只要发现一个效果很多地方都需要使用, 就要考虑封装
要不要鼓励一下😘