当前位置: 首页 > 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;就…...

Qwen3字幕系统Linux部署指南:从安装到性能调优

Qwen3字幕系统Linux部署指南&#xff1a;从安装到性能调优 为视频内容自动生成精准字幕的时代已经到来 还记得手动为视频添加字幕的痛苦经历吗&#xff1f;一遍遍听写、校对、调整时间轴&#xff0c;几分钟的视频往往需要花费数小时。现在&#xff0c;基于Qwen3的智能字幕系统可…...

ESP8266-ESP32 物联网开发入门

ESP8266/ESP32物联网开发入门指南 在智能家居、远程监控和工业自动化等领域&#xff0c;物联网技术正快速改变我们的生活。作为物联网开发的热门选择&#xff0c;ESP8266和ESP32凭借低成本、高性能和丰富的开发资源&#xff0c;成为初学者和工程师的首选。本文将介绍如何快速入…...

s2-pro语音合成教程:参考音频采样率/格式/信噪比最佳实践

s2-pro语音合成教程&#xff1a;参考音频采样率/格式/信噪比最佳实践 1. 认识s2-pro语音合成工具 s2-pro是Fish Audio开源的专业级语音合成模型镜像&#xff0c;它不仅能将文本转换为自然流畅的语音&#xff0c;还能通过参考音频来复用特定的音色。这意味着你可以上传一段样本…...

Python 3.15 JIT为何在Docker中静默禁用?揭开musl libc与libffi-3.4.6 ABI不兼容的致命链

第一章&#xff1a;Python 3.15 JIT 的设计目标与 Docker 场景适配性Python 3.15 引入的实验性 JIT&#xff08;Just-In-Time&#xff09;编译器并非追求通用性能提升&#xff0c;而是聚焦于特定高价值场景——尤其是容器化微服务中反复执行的 CPU 密集型工作负载。其核心设计目…...

RWKV7-1.5B-g1a惊艳案例:将复杂段落压缩为三条逻辑闭环要点

RWKV7-1.5B-g1a惊艳案例&#xff1a;将复杂段落压缩为三条逻辑闭环要点 1. 模型能力展示&#xff1a;从复杂到简洁的文本处理 RWKV7-1.5B-g1a作为一款轻量级文本生成模型&#xff0c;在信息压缩和提炼方面展现出令人惊喜的能力。我们通过一个实际案例来展示它如何将复杂内容转…...

新手也能懂的RAIM算法:用Python复现GNSS完好性监测(附代码与数据)

新手也能懂的RAIM算法&#xff1a;用Python复现GNSS完好性监测&#xff08;附代码与数据&#xff09; 当你用手机导航时&#xff0c;是否想过这些定位信号有多可靠&#xff1f;RAIM&#xff08;Receiver Autonomous Integrity Monitoring&#xff09;算法就像GNSS系统的"质…...

Sqoop性能调优之 --fetch-size:小参数,大作用

Sqoop性能调优之 --fetch-size&#xff1a;小参数&#xff0c;大作用1. 引言&#xff1a;被忽视的"隐形冠军"2. 什么是 --fetch-size&#xff1f;2.1 基本定义2.2 核心作用3. 底层原理&#xff1a;从逐行到批量3.1 没有 --fetch-size 的情况&#xff08;逐行读取&…...

手把手教你用ThinkPHP6和Uniapp从零搭建一个物业设备巡检小程序(附完整源码)

从零构建物业设备巡检系统&#xff1a;ThinkPHP6与Uniapp全栈实战指南 物业设备巡检是保障设施安全运行的关键环节&#xff0c;传统纸质记录方式效率低下且难以追溯。本教程将带您从零开始&#xff0c;基于ThinkPHP6后端框架与Uniapp跨端方案&#xff0c;构建一个功能完整的移动…...

告别性能瓶颈:如何用NVIDIA Profile Inspector释放显卡90%潜能?

告别性能瓶颈&#xff1a;如何用NVIDIA Profile Inspector释放显卡90%潜能&#xff1f; 【免费下载链接】nvidiaProfileInspector 项目地址: https://gitcode.com/gh_mirrors/nv/nvidiaProfileInspector 为什么官方显卡控制面板永远像个"锁着的工具箱"&#…...

agent实习面经(十一)

来自网络&#xff0c;侵删 先完成&#xff0c;再完美 某东&#xff0c;某节1.LLM 为什么有幻觉&#xff0c;如何减少 LLM 幻觉&#xff1f;1.1概率生成机制&#xff1a;LLM 本质是基于统计概率预测下一个 token&#xff0c;而非检索事实数据库。当训练数据中缺乏确切信息或模…...