当前位置: 首页 > news >正文

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里面去,从而减少重复代码和减少类的数量。

相关文章:

iOS自定义滚动条

引言 最近一直在做数据通信相关的工作&#xff0c;导致了UI上的一些bug一直没有解决。这两天终于能腾出点时间大概看了一下Redmine上的bug&#xff0c;发现有很多bug都是与系统滚动条有关系的。所以索性就关注一下这个小小的滚动条。 为什么要自定义ScrollIndictor 原有的Scrol…...

C++知识点2:把数据写进switch case结构,和写进json结构,在使用上有什么区别

将数据存储在Switch Case结构和JSON结构中有明显的区别&#xff0c;它们用于不同的目的和方式。以下是它们之间的主要区别&#xff1a; 1、用途和结构&#xff1a; Switch Case结构&#xff1a;Switch Case是一种条件语句&#xff0c;通常用于根据条件执行不同的代码块。它通常…...

肖sir__linux详解__003(vim命令)

linux 文本编辑命令 作用&#xff1a;用于编辑一个文件 用法&#xff1a;vim 文件名称 或者vi &#xff08;1&#xff09;编辑一个存在的文档 例子&#xff1a;编辑一个file1文件 vim aa &#xff08;2&#xff09;编辑一个文件不存在&#xff0c;会先创建文件&#xff0c;再…...

瑞芯微RK3588开发板:虚拟机yolov5模型转化、开发板上python脚本调用npu并部署 全流程

目录 0. 背景1. 模型转化1.1 基础环境1.2 创建python环境1.3 将yolov5s.pt转为yolov5s.onnx1.4 将yolov5s.onnx转为yolov5s.rknn 2. 开发板部署2.1. c版本2.1. python版本&#xff08;必须是python 3.9&#xff09; 3. 性能测试 0. 背景 全面国产化&#xff0c;用瑞芯微rk3588…...

【Redis专题】RedisCluster集群运维与核心原理剖析

目录 课程内容一、Redis集群架构模型二、Redis集群架构搭建&#xff08;单机搭建&#xff09;2.1 在服务器下新建各个节点的配置存放目录2.2 修改配置&#xff08;以redis-8001.conf为例&#xff09; 三、Java代码实战四、Redis集群原理分析4.1 槽位定位算法4.2 跳转重定位4.3 …...

我眼中的《视觉测量技术基础》

为什么会写这篇博客&#xff1a; 首先给大家说几点&#xff1a;看我的自我介绍对于学习这本书没有任何帮助&#xff0c;如果你是为了急切的想找一个视觉测量的解决方案那可以跳过自我介绍往下看或者换一篇博客看看&#xff0c;如果你是刚入门想学习计算机视觉的同学&#xff0…...

【Cisco Packet Tracer】管理方式,命令,接口trunk,VLAN

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …...

深入协议栈了解TCP的三次握手、四次挥手、CLOSE-WAIT、TIME-WAIT。

TCP网络编程的代码网上很多&#xff0c;这里就不再赘述&#xff0c;简单用一个图展示一下tcp网络编程的流程&#xff1a; 1、深入connect、listen、accept系统调用&#xff0c;进一步理解TCP的三次握手 这三个函数都是系统调用&#xff0c;我们可以分为请求连接方和被…...

接口自动化测试系列-yml管理测试用例

项目源码 目录结构及项目介绍 整体目录结构&#xff0c;目录说明参考 测试用例结构类似httprunner写法&#xff0c;可参考demo 主要核心函数 用例读取转换json import yaml import main import os def yaml_r():curpath f{main.BASE_DIR}/quality_management_logic/ops_ne…...

开源对象存储系统minio部署配置与SpringBoot客户端整合访问

文章目录 1、MinIO安装部署1.1 下载 2、管理工具2.1、图形管理工具2.2、命令管理工具2.3、Java SDK管理工具 3、MinIO Server配置参数3.1、启动参数&#xff1a;3.2、环境变量3.3、Root验证参数 4、MinIO Client可用命令 官方介绍&#xff1a; MinIO 提供高性能、与S3 兼容的对…...

Matlab之数组字符串函数汇总

一、前言 在MATLAB中&#xff0c;数组字符串是指由字符组成的一维数组。字符串可以包含字母、数字、标点符号和空格等字符。MATLAB提供了一些函数和操作符来创建、访问和操作字符串数组。 二、字符串数组具体怎么使用&#xff1f; 1、使用单引号或双引号括起来的字符序列 例…...

基于深度学习网络的火灾检测算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ................................................................................ load F…...

【Linux】高级IO和多路转接 | select/poll/epoll

多路转接和高级IO 咳咳&#xff0c;写的时候出了点问题&#xff0c;标点符号全乱了&#xff08;批量替换了几次&#xff09;&#xff0c;干脆就把全文的逗号和句号都改成英文的了&#xff08;不然代码块里面的代码都是中文标点就跑不动了&#xff09; 1.高级IO 1.1 五种IO模型…...

el-select 支持多选 搜索远程数据 组件抽取

el-select 支持多选 搜索远程数据 组件抽取 使用方式 import selectView from ./components/selectView<el-form><el-form-item label"选择器"><selectView v-model"selValue" change"handleChange"></el-form-item> …...

el-table纵向垂直表头

参考&#xff1a;https://www.jianshu.com/p/1f38eaffd070 <el-tablestyle"width: 100%":data"getValues":show-header"false"border:cell-style"cellStyle" ><el-table-columnv-for"(item, index) in getHeaders"…...

Pinyin4j介绍和简单使用

前言 Pinyin4j是一个Java库&#xff0c;用于将汉字转换为拼音。它是由中国清华大学的Tsinghua University和中国科学院计算技术研究所的研究人员开发的。Pinyin4j可以用于Java应用程序中&#xff0c;以便在需要时将汉字转换为拼音。例如&#xff0c;它可以用于中文输入法、文本…...

【数据结构】查找

【数据结构】查找 数据结构中&#xff0c;有顺序查找、二分查找、散列查找、插值查找、斐波那契额查找 1.顺序查找 条件&#xff1a;待查找的元素与数组中的元素按顺序排列。算法&#xff1a;从数组的第一个元素开始&#xff0c;逐个比较&#xff0c;直到找到目标元素或遍历完…...

第一次面试

1.多态的原理 2.编译原理 3.HTTPS的加密原理 4.说一说C11新特性 5.平时用过哪些STL容器 6.STL的比较器 原来就是自定义工具类hhhhhh 7.函数指针用过吗 8.I/O多路复用 9.Redis 问的基本都背过&#xff0c;但是一紧张啥都忘了hhhhhhhhh...

Nacos配置文件更新+热更新+多环境配置共享+集群搭建

对服务配置文件 场景&#xff1a; 如果多个服务对应的配置文件都需要更改时&#xff0c;可以利用配置管理&#xff0c;方便对配置文件进行更新&#xff0c;而且是在本地配置前先读取nacos的配置文件&#xff0c;优先级大于本地配置文件 配置步骤 1.首先在Nacos中的配置列表中增…...

李宏毅-机器学习hw4-self-attention结构-辨别600个speaker的身份

一、慢慢分析学习pytorch中的各个模块的参数含义、使用方法、功能&#xff1a; 1.encoder编码器中的nhead参数&#xff1a; self.encoder_layer nn.TransformerEncoderLayer( d_modeld_model, dim_feedforward256, nhead2) 所以说&#xff0c;这个nhead的意思&#xff0c;就…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻

在如今就业市场竞争日益激烈的背景下&#xff0c;越来越多的求职者将目光投向了日本及中日双语岗位。但是&#xff0c;一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧&#xff1f;面对生疏的日语交流环境&#xff0c;即便提前恶补了…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

python如何将word的doc另存为docx

将 DOCX 文件另存为 DOCX 格式&#xff08;Python 实现&#xff09; 在 Python 中&#xff0c;你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是&#xff0c;.doc 是旧的 Word 格式&#xff0c;而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

Robots.txt 文件

什么是robots.txt&#xff1f; robots.txt 是一个位于网站根目录下的文本文件&#xff08;如&#xff1a;https://example.com/robots.txt&#xff09;&#xff0c;它用于指导网络爬虫&#xff08;如搜索引擎的蜘蛛程序&#xff09;如何抓取该网站的内容。这个文件遵循 Robots…...

前端开发面试题总结-JavaScript篇(一)

文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包&#xff08;Closure&#xff09;&#xff1f;闭包有什么应用场景和潜在问题&#xff1f;2.解释 JavaScript 的作用域链&#xff08;Scope Chain&#xff09; 二、原型与继承3.原型链是什么&#xff1f;如何实现继承&a…...

Caliper 配置文件解析:config.yaml

Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...

Element Plus 表单(el-form)中关于正整数输入的校验规则

目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入&#xff08;联动&#xff09;2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...

laravel8+vue3.0+element-plus搭建方法

创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

排序算法总结(C++)

目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指&#xff1a;同样大小的样本 **&#xff08;同样大小的数据&#xff09;**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...