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

边缘计算医疗风险自查APP开发方案

核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

django filter 统计数量 按属性去重

在Django中&#xff0c;如果你想要根据某个属性对查询集进行去重并统计数量&#xff0c;你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求&#xff1a; 方法1&#xff1a;使用annotate()和Count 假设你有一个模型Item&#xff0c;并且你想…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

高危文件识别的常用算法:原理、应用与企业场景

高危文件识别的常用算法&#xff1a;原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件&#xff0c;如包含恶意代码、敏感数据或欺诈内容的文档&#xff0c;在企业协同办公环境中&#xff08;如Teams、Google Workspace&#xff09;尤为重要。结合大模型技术&…...

AI书签管理工具开发全记录(十九):嵌入资源处理

1.前言 &#x1f4dd; 在上一篇文章中&#xff0c;我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源&#xff0c;方便后续将资源打包到一个可执行文件中。 2.embed介绍 &#x1f3af; Go 1.16 引入了革命性的 embed 包&#xff0c;彻底改变了静态资源管理的…...

初探Service服务发现机制

1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能&#xff1a;服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源&#xf…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中&#xff0c;理解API&#xff08;应用程序接口&#xff09;和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能&#xff0c;使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...

深入理解Optional:处理空指针异常

1. 使用Optional处理可能为空的集合 在Java开发中&#xff0c;集合判空是一个常见但容易出错的场景。传统方式虽然可行&#xff0c;但存在一些潜在问题&#xff1a; // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...

CVPR2025重磅突破:AnomalyAny框架实现单样本生成逼真异常数据,破解视觉检测瓶颈!

本文介绍了一种名为AnomalyAny的创新框架&#xff0c;该方法利用Stable Diffusion的强大生成能力&#xff0c;仅需单个正常样本和文本描述&#xff0c;即可生成逼真且多样化的异常样本&#xff0c;有效解决了视觉异常检测中异常样本稀缺的难题&#xff0c;为工业质检、医疗影像…...