[iOS] UIKit-UIScrollView

  • 当展示的内容较多,超出一个屏幕时,用户可通过滚动手势来查看屏幕以外的内容
  • 普通的UIView不具备滚动功能,不适合显示过多的内容
  • UIScrollView是一个能够滚动的视图控件,可以用来展示大量的内容,并且可以通过滚动查看所有的内容
  • UIScrollView是一个可以处理滚动操作的视图,UIScrollView在开发过程中使用很频繁,而且它也经常作为其他控件的子控件,例如UITableView就继承自UIScrollView。

常见属性

属性 说明
@property(nonatomic) CGPoint contentOffset; 内容偏移量,当前显示的内容的顶点相对此控件顶点的x、y距离,默认为CGPointZero
@property(nonatomic) CGSize contentSize; 控件内容大小,不一定在显示区域,如果这个属性不设置,此控件无法滚动,默认为CGSizeZero
@property(nonatomic) UIEdgeInsets contentInset; 控件四周边距,类似于css中的margin,注意边距不作为其内容的一部分,默认为UIEdgeInsetsZero
@property(nonatomic,assign) id<UIScrollViewDelegate> delegate; 控件代理,一般用于事件监听,在iOS中多数控件都是通过代理进行事件监听的
@property(nonatomic) BOOL bounces; 是否启用弹簧效果,启用弹簧效果后拖动到边缘可以看到内容后面的背景,默认为YES
@property(nonatomic,getter=isPagingEnabled) BOOL pagingEnabled; 是否分页,如果分页的话每次左右拖动则移动宽度是屏幕宽度整数倍,默认为NO
@property(nonatomic,getter=isScrollEnabled) BOOL scrollEnabled; 是否启用滚动,默认为YES
@property(nonatomic) BOOL showsHorizontalScrollIndicator; 是否显示横向滚动条,默认为YES
@property(nonatomic) BOOL showsVerticalScrollIndicator; 是否显示纵向滚动条,默认为YES
@property(nonatomic) CGFloat minimumZoomScale; 最小缩放倍数,默认为1.0
@property(nonatomic) CGFloat maximumZoomScale; 最大缩放倍数(注意只有maximumZoomScale大于minimumZoomScale才有可能缩放),默认为1.0
@property(nonatomic,readonly,getter=isTracking) BOOL tracking; (状态)是否正在被追踪,手指按下去并且还没有拖动时是YES,其他情况均为NO
@property(nonatomic,readonly,getter=isDragging) BOOL dragging; 是否正在被拖拽
@property(nonatomic,readonly,getter=isDecelerating) BOOL decelerating; 是否正在减速
@property(nonatomic,readonly,getter=isZooming) BOOL zooming; 是否正在缩放
  • scrollView不能滚动的几种情况
    1.没有设置contentSize
    2.scrollEnabled属性 = NO
    3.userInteractionEnabled属性 = NO

  • enabled和userInteractionEnabled的区别

    • enabled: 代表控件不可用
    • userInteractionEnabled: 代表控件不可以和用户交互,也就是不能响应用户的操作
contentSize、contentInset、contentOffset

contentInset相当于在内容大小contentSize的基础上增加了一块额外的滚动区域,是额外的,所以和contentSize、contentOffset都没有直接的关系。

contentOffset

下面用图来表达contentOffset,其中,绿色矩形是scrollview,透明红矩形是scrollview的内容

图1 - 内容在右上角,contentOffset = (100,100)
图2 -内容在左下角,contentOffset = (-100,-100)
  • 如图1,内容左上角是(-100, -100),则
    contentOffset = (0, 0) - (-100, -100) = (0 - -100, 0 - -100) = (100, 100)
  • 如图2,内容的左上角是(100, 100),则
    contentOffset = (0, 0) - (100, 100) = (0 - 100, 0 - 100) = (-100, -100)
  • 所以,contentOffset = 控件左上角(0, 0) - 内容左上角

记住!
contentOffset = 控件左上角(0, 0) - 内容左上角
contentOffset.x > 0 ,内容左移
contentOffset.x < 0 ,内容右移
contentOffset.y > 0 ,内容上移
contentOffset.y < 0 ,内容下移

常用方法

方法 说明
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated; 设置滚动位置,第二个参数表示是否启用动画效果
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated; 滚动并显示指定区域的内容,第二个参数表示是否启用动画效果

常见代理方法

代理方法 说明
- (void)scrollViewDidScroll:(UIScrollView *)scrollView; 滚动事件方法,滚动过程中会一直循环执行(滚动中…)
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView; 开始拖拽事件方法
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate; 拖拽操作完成事件方法
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView; 即将停止滚动事件方法(拖拽松开后开始减速时执行)
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView; 滚动停止事件方法(滚动过程中减速停止后执行)
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view NS_AVAILABLE_IOS(3_2); 开始缩放事件方法
- (void)scrollViewDidZoom:(UIScrollView *)scrollView NS_AVAILABLE_IOS(3_2); 缩放操作完成事件方法
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView; 返回缩放视图,注意只有实现这个代理方法才能进行缩放,此方法返回需要缩放的视图
方法的调用顺序
拖动UIScrollview

如果我们拖动一个UIScrollView中的子控件移动的时候它的执行顺序如下:

  1. 开始拖拽,
  2. 滚动...,
  3. 停止拖拽,
  4. 将要停止滚动,
  5. 滚动...
  6. 停止滚动

其中步骤4、5和6有可能执行也有可能不执行,关键看你拖拽的停止的时候是突然停止(没有惯性)还是有一段惯性让他继续执行(有惯性)。但是不管怎么样滚动事件会一直执行,因此如果在这个事件中进行某种操作一定要注意性能。

  • 如果想在UIScrollView停止滚动之后做一些操作, 有两种情况
  1. 没有惯性的情况:只会调用 停止拖拽的方法,不会调用停止减速的方法
  2. 有惯性的情况:既会调用 停止拖拽的方法,也会调用停止减速的方法
    所以:以后要判断UIScrollView是否停止滚动,需要同时重写两个方法
    2.1 scrollViewDidEndDragging
    2.2 scrollViewDidEndDecelerating
  • 优化停止方法,使得只需写一次滚动停止的操作
// 结束拖拽
    -(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
        if (decelerate == NO) {  // 如果不是减速,也就是没有惯性 
        // 主动调用结束减速方法,所有的滚动停止后操作都在那个方法中执行
        [self scrollViewDidEndDecelerating:scrollView];
        }
    }
// 结束减速
    - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
        // 在这里写停止滚动后的相关操作
    }
缩放UIScrollview
  • 如果我们缩放UIScrollView的子控件的时候它的执行顺序如下:
    1. 开始缩放
    2. 滚动…
    3. 停止缩放
  • 同样在这个过程中滚动事件会一直调用(当然如果缩放过程中手指有别的动作也可能会触发其他事件,这个大家可以自己体会)。
// 1.设置缩放大小
  srcollView.maximumZoomScale = 2.0;
  srcollView.minimumZoomScale = 1;
// 2.设置需要缩放的控件
-(UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
    return self.imageView;
}

无限循环轮播器

  • 在UIScrollView中如果放置其他控件后,只要设置contentSize之后这些图片就可以滚动。如果要让图片无限循环那么只有两种办法,一种是无限循环叠加图片,另一种就是如果最后一张图片浏览完立即显示第一张图片。显然第一种明显不合适,考虑第二种。

  • 使用第二种原理其实很简单,就是前后各加一张(n+2)。比如有5张图片那么久需要7个UIImageView,图片分别是图片5,图片1,图片2,图片3,图片4,图片5,图片0。

  • 当从图片1滚动到图片5时由于最后一张是图片1就给用户一种无限循环的感觉,当这张图完全显示后我们迅速将UIScrollView的contentOffset设置到第二个UIImageView,也就是图片1,接着用户可以继续向后滚动。当然向前滚动原理完全一样,当滚动到第一张图片(图片5)就迅速设置UIScrollView的contentOffset显示第6张图(图片5)。

  • 性能优化

  • 用上面的方法的话,需要过多这些图片,而这样势必全部加载到内存,这是我们不愿意看到的,此时需要优化。其实只需要3个UIImageView(2个也可以)同样可以实现上面的效果。只是每次滚动后,都需要重新设置图片。例如开始的时候,是图片5,图片1,图片2。向后滚动,那么三种图分别的序号就是1,2,3,然后通过contentOffset设置显示中间的UIImageView。当然,向前滚动原理完全一样,如此就给用户一种循环错觉,而且不占用过多内存。

在程序中,读取写入了图片信息的plist文件并加载对应的图片,图片按顺序依次命名:page0.jpg、page1.jpg…page4.jpg。我们的程序主要集中于自定义的ViewController.m中,在这里我们声明1个UIScrollView和3个UIImageView用于显示图片,同时声明一个UIPageControl来显示当前图片页数,具体代码如下:

#import "ViewController.h"

@interface ViewController ()<UIScrollViewDelegate>

/** 所有图片*/
@property (nonatomic, strong) NSDictionary *images;
/** 图片个数*/
@property (nonatomic, assign) int imageCount;

/** 轮播器*/
@property (nonatomic, weak) UIScrollView *cycleScrollView;
/** 左边的图控件*/
@property (nonatomic, weak) UIImageView *leftImageView;
@property (nonatomic, weak) UIImageView *midImageView;
@property (nonatomic, weak) UIImageView *rightImageView;

/** 图片索引*/
@property (nonatomic, assign) int imageIndex;

// 包装view需要装下scrollview和pageControl
/** 包装view*/
@property (nonatomic, weak) UIView *backGroundView;

/** 分页控件*/
@property (nonatomic, weak) UIPageControl *pageC;

@end

@implementation ViewController

#define SCREEN_WIDTH self.view.bounds.size.width
#define SCREEN_HEIGHT self.view.bounds.size.height
#define IMAGEVIEW_COUNT 3
#define ScrollView_HEIGHT (200 * (SCREEN_WIDTH) / 320)

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 加载图片数据
    [self loadImageData];
    
    // 添加滚动图
    [self createScrollView];
    
    // 给滚动图添加图片控件
    [self addImageViewToScrollView];
    
    // 添加分页控件
    [self addPageControl];
    
    // 加载默认图片
    [self setDefaultImages];
    
    
}

// 加载图片数据
-(void)loadImageData {
    NSString *path = [[NSBundle mainBundle] pathForResource:@"CycleScrollImages" ofType:@"plist"];
    self.images = [NSDictionary dictionaryWithContentsOfFile:path];
    self.imageCount = (int)self.images.count;
}

// 添加滚动图
-(void)createScrollView {
    // 创建包含的view
    UIView *bV = [[UIView alloc] init];
    bV.frame = CGRectMake(0, 20, SCREEN_WIDTH, ScrollView_HEIGHT);
    [self.view addSubview:bV];
    self.backGroundView = bV;
    
    UIScrollView *scrollView = [[UIScrollView alloc] init];
    scrollView.frame = CGRectMake(0, 0, SCREEN_WIDTH, ScrollView_HEIGHT);
    scrollView.backgroundColor = [UIColor redColor];
    scrollView.contentSize = CGSizeMake(SCREEN_WIDTH * IMAGEVIEW_COUNT, 0);
    scrollView.pagingEnabled = YES;
    scrollView.delegate = self;
    scrollView.bounces = NO;
    [scrollView setContentOffset:CGPointMake(SCREEN_WIDTH, 0)];
    [self.backGroundView addSubview:scrollView];
    
    self.cycleScrollView = scrollView;
}

// 给滚动图添加图片控件
-(void)addImageViewToScrollView {

    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, ScrollView_HEIGHT)];
    imageView.contentMode = UIViewContentModeScaleAspectFit;
    [self.cycleScrollView addSubview:imageView];
    self.leftImageView = imageView;
    
    imageView = [[UIImageView alloc] initWithFrame:CGRectMake(SCREEN_WIDTH, 0, SCREEN_WIDTH, ScrollView_HEIGHT)];
    [self.cycleScrollView addSubview:imageView];
    self.midImageView = imageView;
    
    imageView = [[UIImageView alloc] initWithFrame:CGRectMake(SCREEN_WIDTH * 2, 0, SCREEN_WIDTH, ScrollView_HEIGHT)];
    [self.cycleScrollView addSubview:imageView];
    self.rightImageView = imageView;
}

// 添加分页控件
-(void)addPageControl {
    
    int count = self.imageCount;
    UIPageControl *pageC = [[UIPageControl alloc] init];
    CGSize size = [pageC sizeForNumberOfPages:count];
    pageC.bounds = CGRectMake(0, 0, size.width, size.height);
    pageC.center = CGPointMake(SCREEN_WIDTH * 0.5, ScrollView_HEIGHT - size.height - 10);
    //设置颜色
    pageC.pageIndicatorTintColor=[UIColor colorWithRed:193/255.0 green:219/255.0 blue:249/255.0 alpha:1];
    //设置当前页颜色
    pageC.currentPageIndicatorTintColor=[UIColor colorWithRed:0 green:150/255.0 blue:1 alpha:1];
    //设置总页数
    pageC.numberOfPages = count;
    
    pageC.backgroundColor = [UIColor redColor];
    [self.backGroundView addSubview:pageC];
    
    self.pageC = pageC;
}

// 加载默认图片
-(void)setDefaultImages {
    self.leftImageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"page%d.jpg",self.imageCount - 1]];
    self.midImageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"page%d.jpg",0]];
    self.rightImageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"page%d.jpg",1]];
    
    
    self.imageIndex = 0;
    [self.pageC setCurrentPage:0];
    
}

// 重新读取图片
-(void)reloadImages {
    int leftImageIndex, rightImageIndex;
    CGPoint currentP = [self.cycleScrollView contentOffset];
    if (currentP.x < SCREEN_WIDTH) { // 滚到左边
        self.imageIndex = (self.imageIndex + self.imageCount -1) % self.imageCount;
    } else if(currentP.x > SCREEN_WIDTH) { // 滚到右边
        self.imageIndex = (self.imageIndex + 1) % self.imageCount;
    }
    leftImageIndex = (self.imageIndex + self.imageCount -1) % self.imageCount;
    rightImageIndex = (self.imageIndex + 1) % self.imageCount;
    
    
    self.midImageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"page%d.jpg",self.imageIndex]];
    self.leftImageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"page%d.jpg",leftImageIndex]];
    self.rightImageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"page%d.jpg",rightImageIndex]];
}

#pragma mark - UIScrollViewDelegate

-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    if (decelerate == NO) {
        [self scrollViewDidEndDecelerating:scrollView];
    }
}

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    // 滚完之后
    // 重新读取图片
    [self reloadImages];
    
    // 滚回中间
    [scrollView setContentOffset:CGPointMake(SCREEN_WIDTH, 0)];
    
    // 设置分页控件的序数
    [self.pageC setCurrentPage:self.imageIndex];
}

@end
效果图1

注意:这里只是简单实现思路,没有封装好。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容