• 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
    将来的你会感谢今天如此努力的你! 版权声明:本文为博主原创文章,未经博主允许不得转载。
  • 相关阅读:
    添加arcgis portal数据存储bad login user
    使用python从地图服务中提取数据
    山体
    也能用高德输入点击初始结果
    从源代码构建Qt6开发工具
    rust组件安装
    ubuntu apt-get 安装指定版本软件
    Ubuntu上如何查询和安装指定版本的软件
    gnutls not found using pkg-config
    Package not found
  • 原文地址:https://www.cnblogs.com/chglog/p/4652716.html
Copyright © 2020-2023  润新知