iOS自定义下拉刷新控件

news2025/2/27 1:38:03

自定义下拉刷新控件

概述

用了很多的别人的下拉刷新控件,想写一个玩玩,自定义一个在使用的时候也会比较有意思。使应用更加的灵动一些,毕竟谁不喜欢各种动画恰到好处的应用呢。

使用方式如下:

tableview.refreshControl = XRefreshControl.init(refreshingBlock: {
    DispatchQueue.main.asyncAfter(deadline: .now() + 5) { [weak self] in
        self?.table.endRefreshing()
    }
})

下边展示一下效果。

在这里插入图片描述

然后又搞了一个比较炫酷的版本~,效果图如下:

在这里插入图片描述

继承 UIRefreshControl,然后再其上直接添加view就能实现需要的加载效果,尝试发现自定义的类需要把背景色设置一下,要不然会有一下拉整体都显示出来的问题,而且最好在view上再加一个view整体给铺上,在设置一个背景色,把小菊花给盖上。

简单版本代码

import Foundation
import UIKit
import SnapKit

class XRefreshControl: UIRefreshControl {
    var observation: NSKeyValueObservation?
    var isLocalRefreshing: Bool = false
    let indicator = UIProgressView(progressViewStyle: .bar)
    var refreshingBlock: (()->Void)?
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        observation = observe(
            \.frame,
             options: .new
        ) { [weak self] object, change in
            if self?.isRefreshing == true {
                if self?.isLocalRefreshing == false {
                    if self?.refreshingBlock != nil {
                        self?.refreshingBlock!()
                    }
                }
                self?.isLocalRefreshing = true
            } else {
                let height = change.newValue!.height
                self?.indicator.progress = min(Float(abs(height / 60)), 1)
            }
        }
    }
    convenience init(refreshingBlock: @escaping ()->Void) {
        self.init(frame: .zero)
        
        self.refreshingBlock = refreshingBlock
        
        self.layer.masksToBounds = true
        self.backgroundColor = .red
        
        let v = UIView()
        v.backgroundColor = .red
        
        let center = UIView()
        v.addSubview(center)
        
        let title = UILabel()
        title.text = "加载中"
        title.textColor = .black
        center.addSubview(title)
        
        indicator.layer.masksToBounds = true
        center.addSubview(indicator)
        
        self.addSubview(v)
        v.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        center.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.equalToSuperview()
        }
        indicator.snp.makeConstraints { make in
            make.top.equalToSuperview()
            make.width.height.equalTo(32)
            make.centerX.equalToSuperview()
        }
        title.snp.makeConstraints { make in
            make.top.equalTo(indicator.snp.bottom)
            make.bottom.equalToSuperview()
            make.centerX.equalToSuperview()
        }
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    deinit {
        observation = nil
    }
    
    override func endRefreshing() {
        super.endRefreshing()
        
        self.isLocalRefreshing = false
        indicator.progress = 0
    }
}

extension UITableView {
    func endRefreshing() {
        if ((self.refreshControl?.isKind(of: XRefreshControl.self)) != nil) {
            self.refreshControl?.endRefreshing()
        }
    }
}

加强版本代码

class XRefreshControl: UIRefreshControl {
    var observation: NSKeyValueObservation?
    var isLocalRefreshing: Bool = false
    let indicator = UIProgressView(progressViewStyle: .bar)
    var refreshingBlock: (()->Void)?
    var displayLink: CADisplayLink?
    var targetDuration: CGFloat = 3
    var fireDate: Date = .now
    var endRefreshingDate: Date = .now
    var title = UILabel()
    var colors: [UIColor] = [
        UIColor(hex: "ffbe0b"),
        UIColor(hex: "fb5607"),
        UIColor(hex: "ff006e"),
        UIColor(hex: "8338ec"),
        UIColor(hex: "3a86ff"),
    ]
    var speedViews: [UIView] = []
    var blockViews: [UIView] = []
    
    // 背景
    var contentView = UIView()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        observation = observe(
            \.frame,
             options: .new
        ) { [weak self] object, change in
            if self?.isRefreshing == true {
                if self?.isLocalRefreshing == false {
                    if self?.refreshingBlock != nil {
                        self?.refreshingBlock!()
                    }
                    self?.startAnimation()
                }
                self?.isLocalRefreshing = true
            } else {
                let height = change.newValue!.height
                self?.dragEffect(distance: height)
            }
        }
    }
    
    convenience init(refreshingBlock: @escaping ()->Void) {
        self.init(frame: .zero)
        
        self.refreshingBlock = refreshingBlock
        
        self.layer.masksToBounds = true
        self.backgroundColor = .white
        
        contentView.backgroundColor = .red
        self.addSubview(contentView)
        
        let center = UIView()
        contentView.addSubview(center)
        
        title.text = "下拉加载"
        title.textColor = .black
        center.addSubview(title)
        
        center.addSubview(indicator)
        
        for _ in 0...6 {
            let v = UIView()
            v.backgroundColor = .white
            speedViews.append(v)
            contentView.addSubview(v)
        }
        
        for _ in 0..<10 {
            let v = UIView()
            v.backgroundColor = .white
            blockViews.append(v)
            contentView.addSubview(v)
        }
        
        contentView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        center.snp.makeConstraints { make in
            make.center.equalToSuperview()
        }
        indicator.snp.makeConstraints { make in
            make.left.top.right.equalToSuperview()
            make.width.equalTo(120)
            make.height.equalTo(6)
        }
        title.snp.makeConstraints { make in
            make.top.equalTo(indicator.snp.bottom).offset(10)
            make.bottom.equalToSuperview()
            make.centerX.equalToSuperview()
        }
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    deinit {
        observation = nil
        self.displayLink?.remove(from: RunLoop.current, forMode: .common)
    }
    
    func dragEffect(distance: CGFloat) {
        let diff = abs(endRefreshingDate.timeIntervalSinceNow)
        if diff < 0.5 {
            return
        }
        
        let precent = min(abs(distance/140),1)
        let value = precent * 8 * CGFloat.pi
        self.indicator.progress = 1
        let opacity = Float(sin(value))
//        print("opacity \(opacity)")
        self.indicator.layer.opacity = opacity
        self.title.text = "下拉加载"
        
        for i in 0..<3 {
            let xx = (self.frame.size.width / 12.0) * CGFloat(i+1)
            var yy = abs(distance/2)-2
            yy += sin(distance/10 + CGFloat(i+1)*10)*6
            speedViews[i].frame = .init(x: xx, y: yy, width: 2, height: 4)
        }
        for i in 3..<6 {
            var x = (self.frame.size.width / 12.0) * CGFloat(i+1-3)
            x += self.frame.width * 2.0 / 3.0
            var yy = abs(distance/2)-2
            yy += sin(distance/10 + CGFloat(i+1-3)*10)*6
            speedViews[i].frame = .init(x: x, y: yy, width: 2, height: 4)
        }
        for i in 0..<blockViews.count {
            blockViews[i].frame = .init(x: 0, y: 0, width: 0, height: 0)
        }
    }
    
    func startAnimation() {
        displayLink = CADisplayLink(target: self, selector: #selector(update))
        displayLink?.add(to: RunLoop.current, forMode: .common)
        fireDate = .now
        self.indicator.layer.opacity = 1
        self.indicator.progress = 1
        self.title.text = "加载中"
        
        let width = self.frame.width
        for i in 0..<blockViews.count {
            let size = CGFloat.random(in: 4...8)
            let x = CGFloat.random(in: 0...width)
            blockViews[i].frame = .init(x: x, y: 0, width: size, height: size)
        }
    }
    
    @objc func update(_ displayLink: CADisplayLink) {
        let diff = abs(fireDate.timeIntervalSinceNow)
        var precent = diff / targetDuration
        precent = min(precent, 1)
        self.indicator.progress = Float(precent)
        contentView.backgroundColor = colors[Int(diff*3)%colors.count]
        
        for i in 0..<3 {
            var xx = (self.frame.size.width / 12.0) * CGFloat(i+1)
            var yy = self.frame.height/2-12
            if i == 1 {
                yy += sin(CGFloat(diff)*6) * 2
                xx += sin(CGFloat(diff)*6)
            } else {
                yy += sin(CGFloat(diff)*6) * 4
                xx += sin(CGFloat(diff)*6 + CGFloat(i+1))
            }
            speedViews[i].frame = .init(x: xx, y: yy, width: 2, height: 24)
        }
        for i in 3..<6 {
            var xx = (self.frame.size.width / 12.0) * CGFloat(i+1-3)
            xx += self.frame.width * 2.0 / 3.0
            var yy = self.frame.height/2-12
            if i == 4 {
                yy += sin(CGFloat(diff)*6) * 2
                xx += sin(CGFloat(diff)*6)
            } else {
                yy += sin(CGFloat(diff)*6) * 4
                xx += sin(CGFloat(diff)*6 + CGFloat(i+1-3))
            }
            
            speedViews[i].frame = .init(x: xx, y: yy, width: 2, height: 24)
        }
        
        for i in 0..<self.blockViews.count {
            var x = self.blockViews[i].frame.origin.x
            var y = self.blockViews[i].frame.origin.y + self.blockViews[i].frame.width / 4
            if y > self.contentView.frame.height {
                y = 0
                x = CGFloat.random(in: 0...self.contentView.frame.width)
            }
            self.blockViews[i].frame = .init(origin: .init(x: x, y: y), size: self.blockViews[i].frame.size)
        }
    }
    
    override func endRefreshing() {
        super.endRefreshing()
        
        self.isLocalRefreshing = false
        self.displayLink?.remove(from: RunLoop.current, forMode: .common)
        endRefreshingDate = .now
        self.title.text = "加载完毕"
    }
}
extension UITableView {
    func endRefreshing() {
        if ((self.refreshControl?.isKind(of: XRefreshControl.self)) != nil) {
            self.refreshControl?.endRefreshing()
        }
    }
}
extension UIColor {
    /// 使用 #FFFFFF 来初始化颜色
    convenience init(hex: String, alpha: CGFloat = 1.0) {
        var hexFormatted: String = hex.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).uppercased()

        if hexFormatted.hasPrefix("#") {
            hexFormatted = String(hexFormatted.dropFirst())
        }
        
        if hexFormatted.hasPrefix("0x") {
            hexFormatted = String(hexFormatted.dropFirst())
            hexFormatted = String(hexFormatted.dropFirst())
        }

        assert(hexFormatted.count == 6, "Invalid hex code used.")

        var rgbValue: UInt64 = 0
        Scanner(string: hexFormatted).scanHexInt64(&rgbValue)

        self.init(red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
                  green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
                  blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
                  alpha: alpha)
    }
}

参考地址

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

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

相关文章

【CTF-web】buuctf-[CISCN2019 华北赛区 Day2 Web1]Hack World(sql盲注)

题目链接 根据上图可知&#xff0c;页面中已经告诉我们要从flag表中的flag列取出flag&#xff0c;思考是sql注入。经过抓包发现发post包中的id字段是注入点。 经测试当输入id1时&#xff0c;结果为Hello, glzjin wants a girlfriend.&#xff0c;当id2时&#xff0c;结果为Do y…

Azure共享映像库构建VM镜像

什么是Azure共享映像库 Azure共享映像库是一项在Microsoft Azure中以共享方式存储和管理映像的服务。映像是预配置的虚拟机操作系统和应用程序的快照&#xff0c;可以用来创建多个虚拟机实例。通过将映像存储在共享映像库中&#xff0c;用户可以轻松地共享映像给其他Azure订阅…

MES管理系统如何帮助制造企业打造透明化工厂

在制造型企业的运营中&#xff0c;车间现场管理至关重要。然而&#xff0c;面临着信息传递速度慢、跨部门协作困难、生产进度无法及时掌握、制造品质不良、设备故障不能及时处理等困境&#xff0c;企业需要寻求有效的解决方案。MES生产管理系统作为针对制造企业车间生产过程控制…

REC 系列 Visual Grounding with Transformers 论文阅读笔记

REC 系列 Visual Grounding with Transformers 论文阅读笔记 一、Abstract二、引言三、相关工作3.1 视觉定位3.2 视觉 Transformer 四、方法4.1 基础的视觉和文本编码器4.2 定位编码器自注意力的文本分支文本引导自注意力的视觉分支 4.3 定位解码器定位 query 自注意力编码器-解…

教你手机摄影要知道的技巧

手机摄影已经成为人们记录生活、分享瞬间的重要方式之一。随着手机摄像头技术的不断提升&#xff0c;我们每个人都有机会成为优秀的手机摄影师。然而&#xff0c;要想在手机摄影领域脱颖而出&#xff0c;掌握一些关键的技巧是必不可少的。 1. 了解你的手机摄像头&#xff1a; …

使用percona-xtrabackup备份MySQL数据

xtrabackup备份分为两种 本文参考链接1 本文参考链接2 全量备份 1.备份数据 要创建备份&#xff0c;请xtrabackup使用xtrabackup --backup option. 您还需要指定一个xtrabackup --target-dir选项&#xff0c;即备份的存储位置&#xff0c;如果InnoDB数据或日志文件未存储在同…

Electron入门,项目启动。

electron 简单介绍&#xff1a; 实现&#xff1a;HTML/CSS/JS桌面程序&#xff0c;搭建跨平台桌面应用。 electron 官方文档&#xff1a; [https://electronjs.org/docs] 本文是基于以下2篇文章且自行实践过的&#xff0c;可行性真实有效。 文章1&#xff1a; https://www.cnbl…

Tomcat 为什么要破坏 Java 双亲委派机制?

大家好&#xff0c;我是锋哥!&#xff01; 我们分为4个部分来探讨: 什么是类加载机制&#xff1f;什么是双亲委任模型&#xff1f;如何破坏双亲委任模型&#xff1f;Tomcat 的类加载器是怎么设计的&#xff1f; 我想&#xff0c;在研究tomcat 类加载之前&#xff0c;我们复习…

java+springboot+mysql银行管理系统

项目介绍&#xff1a; 使用javaspringbootmysql开发的银行管理系统&#xff0c;系统包含超级管理员、管理员、客户角色&#xff0c;功能如下&#xff1a; 超级管理员&#xff1a;管理员管理&#xff1b;客户管理&#xff1b;卡号管理&#xff08;存款、取款、转账&#xff09…

自动化安装系统(三)

Cobbler 简介 Cobbler是一款Linux生态的自动化运维工具&#xff0c;基于Python2开发&#xff0c;用于自动化批量部署安装操作系 统&#xff1b;其提供基于CLI的管理方式和WEB配置界面&#xff0c;其中WEB配置界面是基于Python2和Django框架开发。另外&#xff0c;cobbler还提…

Go语言基础之运算符

运算符用于在程序运行时执行数学或逻辑运算。 运算符 Go 语言内置的运算符有&#xff1a; 算术运算符关系运算符逻辑运算符位运算符赋值运算符

【Java】Spring——Bean对象的作用域和生命周期

文章目录 前言一、引出Bean对象的作用域1.普通变量的作用域2.Bean对象的作用域 二、Bean对象的作用域1.Bean对象的6种作用域2.设置Bean对象的作用域 三、Bean对象的生命周期总结 前言 本人是一个普通程序猿!分享一点自己的见解,如果有错误的地方欢迎各位大佬莅临指导,如果你也…

window安裝python2.7.0

官网下载安装 https://www.python.org/downloads/release/python-270/ 选中所有用户&#xff0c;然后点击next 切换安装位置&#xff0c;最好不要选择c盘 点击next 等待安装 安装完成 配置环境变量 将python安装路径添加到系统环境变量 cmd窗口输入python,会打开应用商…

unity Dropdown默认选择不选择任何选项

当我们使用Dropdown下拉框时&#xff0c;有时不需要有默认选项&#xff0c;把 value设置为-1就可以了&#xff0c; 但是用代码设置value-1是没有效果的&#xff0c;

Stochastic: Distribution-Expectation-Inequalities

见&#xff1a;https://www.math.hkust.edu.hk/~makchen/MATH5411/Chap1Sec2.pdf

创意灵感网站都有哪些?推荐这8个

设计师最痛苦的事情不是&#xff1a;改变草稿&#xff01;加班吧&#xff01;但创造力已经耗尽&#xff0c;没有灵感。对于创意设计师来说&#xff0c;浏览创意网站是寻找灵感创意的关键途径。但当你寻找灵感和创造力时&#xff0c;你会发现一些著名的创意网站只是展示了热门图…

利用console提高写bug的效率

前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 自从入坑前端后&#xff0c;日常写bug就没离开过console。 要说用得多&#xff0c;不如说是console.log用得多&#xff0c;console.warn和console.erro…

msvcp140dll下载方法,msvcp140dll丢失的修复教程

msvcp140.dll是Microsoft Visual C Redistributable组件中的一个动态链接库文件。它是Microsoft Visual C 2015版本和更高版本所需的一个重要文件。这个文件主要用于支持C编译的应用程序&#xff0c;以提供与C相关的函数和功能。 为了解决msvcp140.dll文件丢失或损坏的问题&am…

[Go版]算法通关村第十二关黄金——字符串冲刺题

目录 题目&#xff1a;最长公共前缀解法1&#xff1a;纵向对比-循环内套循环写法复杂度&#xff1a;时间复杂度 O ( n ∗ m ) O(n*m) O(n∗m)、空间复杂度 O ( 1 ) O(1) O(1)Go代码 解法2&#xff1a;横向对比-两两对比&#xff08;类似合并K个数组、合并K个链表&#xff09;复…

antd5源码调试环境搭建(window系统)

将antd源码克隆至本地 $ git clone gitgithub.com:ant-design/ant-design.git $ cd ant-design $ npm install $ npm start前提安装python3、安装node版本18版本 不然后续安装依赖会报python3相关的错误。 项目需要使用git 初始化 不然会报husky相关的错误 git init重新安…