iOS自定义滚动条

  • 引言
最近一直在做数据通信相关的工作,导致了UI上的一些bug一直没有解决。这两天终于能腾出点时间大概看了一下Redmine上的bug,发现有很多bug都是与系统滚动条有关系的。所以索性就关注一下这个小小的滚动条。
  • 为什么要自定义ScrollIndictor
原有的ScrollIndictor当然是系统提供的滚动条,但是为什么会有bug出现呢。这和现有的的需求有关系,需求定义是当现有页面内容超过一屏高度的时候,滚动条需要常显示,不能消失。小于一屏就不需要显示了。这和系统滚动条的显示行为不太一致。所以起初我们单纯的考虑,直接修改系统滚动条,让他常显示不就OK了。但是经过几轮测试过后,发现系统定义成让其消失是为了弥补滚动条时常时短的bug。系统控件存在问题怎么办?洪荒之力--自定义吧。
  • 现有滚动条显示方案
在谈自定义方案之前,还想和大家分享下现有的解决方法--让系统滚动条常显。这个方法可谓是剑走偏锋,不过思路还是蛮新奇的。具体思路如下:
1.定义UIImageView的分类
2.重写setAlpha方法,从新定义UIImagView的隐藏行为,如果有tag值符合的View令其隐藏行为失效。
3.设置TableView,CollectionView, ScrollVIew的Tag值等于 noDisableVerticalScrollTag 或者 noDisableHorizontalScrollTag。
因为ScrollVIew的滚动条就是一个UIImageView,但是我们不能拿到这个滚动条的实例和隐藏掉用时机的。但是我们可以重新定义UIImageView的行为方法,控制起显示和隐藏的过程。具体代码如下。
#define noDisableVerticalScrollTag 836913
#define noDisableHorizontalScrollTag 836914
#import "UIImageView+ForScrollView.h"
@implementation UIImageView (ForScrollView)

- ( void) setAlpha:( CGFloat)alpha {
   
    if ( self. superview. tag == noDisableVerticalScrollTag) {
        if (alpha == 0 && self. autoresizingMask == UIViewAutoresizingFlexibleLeftMargin) {
            if ( self. frame. size. width < 10 && self. frame. size. height > self. frame. size. width) {
                UIScrollView *sc = ( UIScrollView*) self. superview;
                if (sc. frame. size. height < sc. contentSize. height) {
                    return;
                }
            }
        }
    }
   
    if ( self. superview. tag == noDisableHorizontalScrollTag) {
        if (alpha == 0 && self. autoresizingMask == UIViewAutoresizingFlexibleTopMargin) {
            if ( self. frame. size. height < 10 && self. frame. size. height < self. frame. size. width) {
                UIScrollView *sc = ( UIScrollView*) self. superview;
                if (sc. frame. size. width < sc. contentSize. width) {
                    return;
                }
            }
        }
    }
   
    [ super setAlpha:alpha];
}
 
@end
  • 现有方案存在的问题
上述的方案堪称是一劳永逸了,其他地方不需要修改任何代码就可以达到需求。但是测试时发现,在ScrollView滚动到底部的时候,滚动条会突然变长。而且快速改变滚动方向的时候,滚动条会出现闪烁的效果。这都会影响用户体验,必须要修改掉这样的问题。
  • 开始自定义之旅
由于系统中用的最多的就是CollectionView,所以就先从这个CollecitonView的ScrollIncidtor开始吧。自定义可以采用分类也可以采用基类的形式。都各有利弊。采用分类在可以避免与工程中其他的类出现耦合的情况,代码更加集中好管理。但是不能重写父类的方法,例如reloadData。如果强行重写的话可能导致系统时序的混乱。所以我这里才用的是基类的方案。
其中比较难理解的就是滚动条在滑动到顶端和底端的时候,都要有个弹力的效果。这个效果的计算方法当时想的比较复杂。但是最后实现的时候发现也不是很难。具体的说明参见注释,代码如下。
#import "BaseCollectionView.h"
#import "Constants.h"

@implementation BaseCollectionView{
    UIView *scrollIndicatorView;
    CGFloat contentSizeHeight;
}

- ( void)drawRect:( CGRect)rect {
    [ self enableCustomVerticalScrollIndicatorWithColor:[ UIColor lightGrayColor]];
}


- ( void)reloadData{
    [ super reloadData];
   
    [ self setNeedsDisplay];
}

- ( UIView *)createIndicatorViewWithFrame:( CGRect) frame{
    UIView *indicator = [[ UIView alloc] initWithFrame:frame];
    indicator. layer. cornerRadius = ScrollIndicatorWidth/ 2.0f;
    //    viewScrollIndicator.alpha = 0.0f;
    //    viewScrollIndicator.layer.borderWidth = 1.0f;
    //    viewScrollIndicator.layer.borderColor = indicatorColor.CGColor;
   
    [ self addSubview:indicator];

    return indicator;
}


//Calculate the real height of scroll indictor accroding to the content size.
- ( CGFloat)getNormalScrollIndictorHeight{
    CGFloat percent = self. frame. size. height / self. contentSize. height;
    float normalHeight = MAX( 0.0f,(percent * self.frame.size.height));
    return normalHeight;
}

- ( void)enableCustomVerticalScrollIndicatorWithColor:( UIColor *)indicatorColor
{
    self. showsVerticalScrollIndicator = NO;
   
    float height = [ self getNormalScrollIndictorHeight];
    CGRect frame = CGRectMake( self. frame. size. width - ScrollIndicatorWidth - ScrollIndicatorRightSpace, 0.0f, ScrollIndicatorWidth, height);
   
    if( scrollIndicatorView == nil){
        scrollIndicatorView = [ self createIndicatorViewWithFrame:frame];
        [ self addKVOObservers];
    }
    else{
        scrollIndicatorView. frame = frame;
    }
   
    [ scrollIndicatorView setBackgroundColor:[indicatorColor colorWithAlphaComponent: 0.75]];
   
    //If content size is larger than frame size, the indictor will be displayed.
    if( self. frame. size. height >= self. contentSize. height){
        scrollIndicatorView. alpha = 0;
    }
    else{
        scrollIndicatorView. alpha = 1.0;
    }
   
    [ self refreshVerticalScrollIndicator];
}

- ( void)refreshVerticalScrollIndicator
{
    if ( self. contentSize. height <= 0) {
        return;
    }
   
    //Get the current frame of scroll indicator
    CGRect rect = scrollIndicatorView. frame;
   
    //Get the normal height of Indicator
    float normalHeight = [ self getNormalScrollIndictorHeight];
   
    //Calculate the real content offset ratio.
    CGFloat maxConentOffset = self. contentSize. height - self. frame. size. height;
    CGFloat offsetRatio = self. contentOffset. y / maxConentOffset;
   
    //Calculate the indictor offset
    CGFloat maxIndicatorOffset = ( self. frame. size. height - normalHeight);
    CGFloat indicatorOffset = offsetRatio * maxIndicatorOffset;
   
    //if scrolling out of top limitation, the scroll indictor will be compressed.
    if (indicatorOffset < 0) {
        rect. size. height = normalHeight + indicatorOffset;
    }
    //if scrolling out of bottom limitation, the scroll indictor will be compressed again.
    else if(indicatorOffset > self. frame. size. height - normalHeight){
        rect. size. height = normalHeight- (indicatorOffset - maxIndicatorOffset);
       
        //        indicatorOffset = self.frame.size.height - normalHeight;
    }
    else{
        rect. size. height = normalHeight;
    }
   
    rect. origin. yself. contentOffset. y + MAX( 0.0f,indicatorOffset);
   
    if (rect. size. height < ScrollIndicatorMinHeight) {
        rect. size. height = ScrollIndicatorMinHeight;
    }
   
    scrollIndicatorView. frame = rect;
}

- ( void)dealloc
{
    [ self removeKVOObservers];
}

#pragma mark - KVO

- ( void)addKVOObservers
{
    [ self addObserver: self forKeyPath: @"contentSize" options:( NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context: NULL];
    [ self addObserver: self forKeyPath: @"contentOffset" options:( NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context: NULL];
}

- ( void)removeKVOObservers
{
    [ self removeObserver: self forKeyPath: @"contentSize"];
    [ self removeObserver: self forKeyPath: @"contentOffset"];
}

- ( void) observeValueForKeyPath: ( NSString *) keyPath ofObject: ( id) object change: ( NSDictionary *) change context: ( void *) context
{
    if ( self. contentSize. width > 0.0f) {
        [ self refreshVerticalScrollIndicator];
       
        /*
         UIView *viewScrollIndicator = [self getViewForHorizontalScrollIndicator];
         CGRect rect =  self.frame;
         CGFloat pourcent = self.contentOffset.x / self.contentSize.width;
         viewScrollIndicator.hidden = self.contentSize.width < self.frame.size.width;
         rect.size.width = self.frame.size.width * (self.frame.size.width / self.contentSize.width);
         rect.origin.x = pourcent * self.frame.size.width;
         viewScrollIndicator.frame = rect;
         */

    }
}
 
@end
  • 有待完善和改进的地方
由于当时写的时候没有考虑有很多类型的控件都有这样的需求,例如CollectionView,TableView,TextView等等带滚动条的控件。所以导致需要创建大量的类以应对同的类型的需要。以后的改进方向就是将滚动条的主控逻辑抽象到ScrollVIew里面去,从而减少重复代码和减少类的数量。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/73323.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

百望云亮相服贸会 重磅发布业财税融Copilot

小望小望&#xff0c;我要一杯拿铁&#xff01; 好的&#xff0c;已下单成功&#xff0c;请问要开具发票嘛&#xff1f; 在获得确认的指令后&#xff0c; 百小望AI智能助手 按用户要求成功开具了一张电子发票&#xff01; 这是2023年服贸会国家会议中心成果发布现场&#x…

搭建hadoop集群的常见问题及解决办法

问题一: namenode -format重复初始化 出现问题的原因是重复初始化时会重新生成集群ID&#xff0c;而dn还是原先的集群ID&#xff0c;两者不匹配时无法启动相应的dn进程。 怎么查找问题原因&#xff1a;在logs目录下找到对应节点的.log文件&#xff0c;使用tail -200 文件名来查…

2023国赛C题解题思路代码及图表:蔬菜类商品的自动定价与补货决策

2023国赛C题&#xff1a;蔬菜类商品的自动定价与补货决策 C题表面上看上去似乎很简单&#xff0c;实际上23题非常的难&#xff0c;编程难度非常的大&#xff0c;第二题它是一个典型的动态规划加仿真题目&#xff0c;我们首先要计算出销量与销售价格&#xff0c;批发价格之间的…

视频集中存储/直播点播平台EasyDSS点播文件分类功能新升级

视频推拉流EasyDSS视频直播点播平台&#xff0c;集视频直播、点播、转码、管理、录像、检索、时移回看等功能于一体&#xff0c;可提供音视频采集、视频推拉流、播放H.265编码视频、存储、分发等视频能力服务。 TSINGSEE青犀视频的EasyDSS平台具有点播文件分类展示方法&#xf…

与面试相关的redis

这里写自定义目录标题 &#x1f4dd; redis的知识点数据结构及其特性&#xff0c;用途和操作方法持久化高可用分布式锁发布订阅性能优化安全性数据分片缓存策略键过期删除策略内存淘汰策略 &#x1f917; 总结归纳&#x1f4ce; 参考文章 &#x1f600; 这里写文章的前言&#…

Android屏幕录制

这里使用Java语言编写实现&#xff0c;完整代码如下&#xff1a; 文件 AndroidMainfest.xml 的主要配置 <?xml version"1.0" encoding"utf-8"?> <manifest xmlns:android"http://schemas.android.com/apk/res/android"package"…

第5章:传输层

文章目录 一、传输层概述1.传输层功能2.传输层的寻址与端口1.端口2.端口号3.套接字二、UDP协议1.UDP概述2.UDP首部格式3.UDP校验三、TCP协议1.TCP协议的特点2.TCP报文段的首部格式3.TCP连接管理(1)TCP连接的建立 (三次握手)(2)TCP连接的释放 (四次挥手)4.TCP可靠传输5.TCP流量控…

Halcon不改变目标大小尺寸及坐标,放大图像或区域

如果需要检测一张图像中很细小的物体&#xff0c;计算机可能可以观察到&#xff0c;但人的肉眼很难判别。这时候就需要放大图像中目标所在区域进行观测&#xff0c;放大观察可能会导致图像被放大&#xff0c;目标相对原始图像的坐标大小信息会被改变。如下&#xff0c;分割图中…

CloudCompare 二次开发(13)——点云投影到圆柱

目录 一、概述二、代码集成三、结果展示一、概述 不依赖任何第三方点云相关库,使用CloudCompare编程实现点云投影到指定圆柱,具体计算原理见:PCL 点云投影到圆柱 二、代码集成 1、mainwindow.h文件public中添加: void doActionProjectToCylinder(); // 投影到圆柱2、mai…

rt-thread系列文章目录

rt-thread系列文章目录 本文是rt-thread系列文章目录&#xff0c;后续关于rt-thread的更新都会添加到此文内。 实战篇 rt-thread------生成工程模板 rt-thread------fal移植 内核篇 rt-thread-------内存管理&#xff08;内存堆&#xff09; 设备和驱动篇 C语言面向对象…

单例模式(饿汉式单例 VS 懒汉式单例)

所谓的单例模式就是保证某个类在程序中只有一个对象 一、如何控制只产生一个对象&#xff1f; 1.构造方法私有化&#xff08;保证对象的产生个数&#xff09; 创建类的对象&#xff0c;要通过构造方法产生对象 构造方法若是public权限&#xff0c;对于类的外部&#xff0c;可…

JAR will be empty - no content was marked for inclusion!

现象 在对自建pom依赖组件打包时&#xff0c;出现JAR will be empty - no content was marked for inclusion!错误。 方案 在pom中怎么加packaging标签内容为pom&#xff0c;标识只打包pom文件 <?xml version"1.0" encoding"UTF-8"?> ...<grou…

科技互联网领域,怎样才算突破性创新?

在科技互联网领域&#xff0c;一个突破性的创新应该具备以下特征&#xff1a; 革命性&#xff1a;突破性创新应该能够彻底改变行业或市场&#xff0c;引起根本性的变革。前瞻性&#xff1a;突破性创新应该有前瞻性&#xff0c;能够在未来产生重大影响。这意味着创新应该不仅考…

基于VUE3+Layui从头搭建通用后台管理系统(前端篇)十二:通用详情组件封装实现

一、本章内容 本章实现通用详情组件,自动识别实体配置信息,并自动生成对应组件,填充组件数据,并完成数据自动加载等过程。 1. 详细课程地址: 待发布 2. 源码下载地址: 待发布 二、界面预览 三、开发视频 3.1 B站视频地址࿱

对比学习与simclr详解

对比学习&#xff08;Contrastive Learning&#xff09;是一种机器学习方法&#xff0c;旨在通过比较数据样本之间的相似性和差异性来学习有用的表示&#xff08;特征&#xff09;。其核心思想是通过将相似的样本映射到相邻的位置&#xff0c;将不相似的样本映射到远离的位置&a…

微信小程序——简易复制文本

在微信小程序中&#xff0c;可以使用wx.setClipboardData()方法来实现复制文本内容的功能。以下是一个示例代码&#xff1a; // 点击按钮触发复制事件 copyText: function() {var that this;wx.setClipboardData({data: 要复制的文本内容,success: function(res) {wx.showToa…

ARM指令集

CPU是计算机的中央处理器&#xff0c;负责执行组成计算机程序的指令。为此&#xff0c;CPU必须能够解释机器码即计算机程序的最低级别表示形式&#xff0c;以让CPU可以直接执行。机器码是一系列的二进制指令&#xff0c;每个指令都代表CPU可以执行的特定操作。指令由一系列1和0…

微信小程序onReachBottom事件使用

在微信小程序中&#xff0c;onReachBottom事件用于监听页面滚动到页面底部的时候触发的事件。当用户滑动页面到底部时&#xff0c;可以通过监听该事件来执行相应的操作。 要使用onReachBottom事件&#xff0c;需要在对应的页面或组件中定义一个函数&#xff0c;并在Page或Comp…

vue3 将 HTML 网页内容生成为 PDF 文件

使用 html2pdf.js 实现 npmjs 地址&#xff1a;html2pdf.js github 仓库地址&#xff1a;html2pdf.js 使用 html2pdf.js 的方法非常简单&#xff0c;只需要能获取到要生成 PDF 的 HTML 元素即可 安装 npm i html2pdf.js引入 先在需要使用导出的页面引入 import html2pdf fr…

操作系统 --- 计算机系统引论

&#xff08;一&#xff09;操作系统的目的和作用 概念&#xff08;定义&#xff09; 操作系统 &#xff08; Operating System &#xff0c; OS &#xff09;是指控制和 管理 整个计算机系统的 硬件和软件 资源&#xff0c;并合理地组织调度计算机的工作和资源的分配&#…