iOS自定义下拉刷新控件

2023-10-26

自定义下拉刷新控件

概述

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

使用方式如下:

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)
    }
}

参考地址

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

iOS自定义下拉刷新控件 的相关文章

  • Swift 中 EXCEL 的 RATE 函数提供不同的结果

    func RATE nper Double pmt Double pv Double fv Double type Double guess Double gt Double var rate guess var y Double 0 va
  • 使用故事板视图控制器创建编程选项卡栏?

    我有一个以编程方式创建的选项卡栏 并且在初始化与视图关联的故事板时遇到困难 我能够在没有故事板的情况下在选项卡栏中成功加载视图 请参阅下面的代码 但视图仅部分显示 因为某些 UI 组件位于故事板中 我的故事板的名称是 Main Storyb
  • SWRevealViewController 右侧菜单

    我正在尝试将 UITableView 插入第一个示例的 RightViewController 中 该示例位于https github com John Lluch SWRevealViewController tree master Re
  • SwiftUI |选择器 - 更改选定行的颜色

    我想更改所选行的颜色 正如您所看到的 默认情况下它具有浅灰色 我不知道该怎么做 因为我根本找不到访问这一行的方法 有什么办法可以做到这一点吗 演示代码 struct ContentView View var data Array 0 20
  • xib 文件中的自定义类和文件所有者设置有什么区别?

    In a custom xib file what s the difference between the two following setting methods shown in the images below 请检查此链接 文件
  • 加载 XCTest 时出错,未找到合适的图像。

    我最近更新了 Xcode 并开始出现此错误 2014 11 03 15 03 54 222 App 13141 60b Error loading private var mobile Applications 677127BD 5230
  • 在 swift 中将参数传递给 #selector 方法[重复]

    这个问题在这里已经有答案了 我想在单击图像时将多个参数传递给函数 这是我的代码 var param1 120 var param2 hello var param3 world let image UIImage UIImage named
  • Alamofire:处理错误并返回成功响应

    嗨 我对 Alamofire 和 json 还很陌生 我只是想问如何处理成功响应的错误 下面是 api 对 post 请求的响应 data cabinet id 3289146811 created at 2018 04 11T04 53
  • 从弹出视图控制器传递数据

    我有两个视图控制器 我首先打开 当我按下按钮时 第二个视图控制器被推到导航控制器堆栈上 在这里 在第二个视图控制器中 我有一个表视图 当我点击某些行时 它们被选中 如复选框 并且与该行相关的一些数据被添加到数组中 现在 当我完成选择后 我想
  • Objective C:如何从 appdelegate 呈现模态视图控制器?

    我在我的应用程序的 appdelegate 中 如何在 didfinishlaunching 方法中添加模式视图控制器 我尝试了以下方法但没有成功 SomeViewController vc SomeViewController alloc
  • Flutter IOS 使用连接或 wifi 插件读取 wifi 名称

    这个问题是类似的这个问题 https stackoverflow com questions 52498906 how to get the wifi namessid of the currently connected wifi in
  • 如何在 IOS 中以编程方式使 TTTAttributedLabel 居中对齐

    我正在开发一个应用程序 其中有一个由带有前缀 的标签组成的字符串 我正在使用 TTTAttribute Label 添加指向给定字符串中具有前缀 的单词的链接 当我添加 TTTAttribute 标签的链接时 它已成功添加 当单击它时 我可
  • 好的 iPad SplitViewController 教程吗? [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我是 iOS 开发新手 正在尝试学习如何使用 iPad 的 splitViewController 我在
  • Swift NSSet 和 CoreData

    我正在尝试将目标 C 和 CoreData 应用程序移动到 Swift 和 iOS 但在迭代 NSSet 对象时遇到了困难 Xcode 已生成这些类 class Response NSManagedObject NSManaged var
  • 如何从 Swift NSURLSession 获取数据?

    例如 我有以下代码 let task NSURLSession sharedSession dataTaskWithURL url completionHandler data response error gt Void in var d
  • Xcode 省略块的参数名称

    我有一个块属性 如下所示 property nonatomic copy void indexChangeBlock NSInteger index 当我尝试设置此属性的值时 Xcode 自动完成将省略参数名称 留下如下所示的内容 self
  • AFNetworking 可以处理请求队列吗?

    我在 iOS 6 上的示例 需要 按顺序 向服务器发送 10 个 Multi Part 请求 所以请求形成一个队列 应显示进展情况 如果一个请求失败 则所有后续请求都会失败 请求队列应该是可取消的 AFNetworking 可以帮我解决这个
  • 快速循环中的并行异步执行

    我的快速代码如下所示 var jsFriendlyFreinds JSObject for friend in friends let jsFriend await FriendsPlugin createFriendResult frie
  • Firebase ios sdk 异步和线程

    有谁知道使用 Firebase iOS SDK 远程调用 Firebase 是使用主线程还是后台线程 我在文档中找不到任何内容 Firebase 并没有完全消除对线程的担忧 由于 Firebase 回调始终在主线程上返回 因此从后台线程调用
  • React 本机应用程序卡在设备上的启动屏幕上,但可以在模拟器中运行

    我的 React Native 应用程序在 XCode 模拟器中运行没有任何问题 但是当我在物理设备 我的 iPhone 中运行时 就会出现问题 应用程序启动并卡在 React Native 启动屏幕上 10 15 秒后应用程序崩溃 关闭

随机推荐

  • 四行代码制作你的esp8266天气时钟——基于NodeMCU、OLED模块

    OLED 开学了 好闲呀 炸鸡 给你找个无休的工作 怎么样 ESP8266 物料 0 96OLED屏幕 esp8266 NodeMCU 开发板 杜邦线 可以自制PCB美化硬件组合 配置方法 四行代码 1 填上wifi或者热点的名称和密码 2
  • Apollo代码学习(三)—车辆动力学模型

    Apollo代码学习 车辆动力学模型 前言 车辆动力学模型 横向动力学 方向盘控制模型 总结 补充 2018 11 27 前言 接上一篇 Apollo代码学习 二 车辆运动学模型 主要参考资料仍是这三个 1 Rajamani R Vehic
  • Java学习心得

    Java学习心得 一 Java入门 Java是一门面向对象编程语言 不仅吸收了C 语言的各种优点 还摒弃了C 里难以理解的多继承 指针等概念 我初次接触java时 发现它和c语言有一些不同 不仅要定义类 还要搭建环境 我也是在同学的帮助下才
  • MySQL常见面试题(2023年最新)

    目录 前言 1 char和varchar的区别 2 数据库的三大范式 3 你了解sql的执行顺序吗 4 索引是什么 5 索引的优点和缺点 6 索引的类型 7 索引怎么设计 优化 8 怎么避免索引失效 也属于sql优化的一种 9 索引的数据类
  • JAVA多线程-线程安全问题

    一 CPU多核缓存架构 CPU分为三级缓存 每个CPU都有L1 L2缓存 但是L3缓存是多核公用的 CPU查找数据的顺序为 CPU gt L1 gt L2 gt L3 gt 内存 gt 硬盘 进一步优化 CPU每次读取一个数据 并不是仅仅读
  • 【NLP】使用 BERT 和 PyTorch Lightning 进行多标签文本分类

    大家好 我是Sonhhxg 柒 希望你看完之后 能对你有所帮助 不足请指正 共同学习交流 个人主页 Sonhhxg 柒的博客 CSDN博客 欢迎各位 点赞 收藏 留言 系列专栏 机器学习 ML 自然语言处理 NLP 深度学习 DL fore
  • html网页小插件,Html 小插件2

    调用google的JS翻译插件实现页面自动翻译功能 设置自己需要的配置生成如下代码放到自己站的页面头部 代码 开源Unity小插件CheatConsole 我们在开发游戏的过程中 通常都需要一些快捷的方式来进行一些非常规的测试 这些功能一般
  • SpringBoot启动Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactor

    SpringBoot启动Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactor异常原因总结 废话少说 上干货 原因一
  • 时代选出最重要 AI 100人,多位华人上榜

    来源 Time 链接 https time com collection time100 ai 编译 芯芯 编辑 靖宇 这是 AI 的时代 2022 年 没人能预料到 以大模型为代表的人工智能技术 成为这个时代独撑科技行业上行的力量 在这一
  • kubectl exec

    文章目录 kubectl exec 通过bash获得pod中某个容器的TTY 相当于登录容器 命令行 创建一个test文件 kubectl exec exec命令同样类似于docker的exec命令 为在一个已经运行的容器中执行一条shel
  • 模型的“参数”与“超参数”

    目录 前言 一 参数 与 超参数 二 模型训练与最终模型 三 参考文献 前言 起初由于团队项目临时需要 花了个一两天的时间直接仓促上手Machine Learning 最近回顾机器学习的模型评估与选择方面的内容时 才幡然发现在初识机器学习阶
  • 小程序发布后图片不显示问题

    本地图片能正常显示 发布后图片不显示 http 127 0 0 1 49287 pageframe static img apply 20 11 png 修改后 http 127 0 0 1 49287 pageframe static i
  • 获取table中的所有行中的td值,包括input值和select值等

    首先获取表对象document getElementById onceFeeTableId 然后通过循环可以获取所有的行 tableObj rows i 如果有需要可在循环里面再加一个循环 用来循环所有的列 HTML table class
  • 【Mysql 存储过程 Or 存储函数 傻傻分不清? 】

    MySQL的存储函数 自定义函数 和存储过程都是用于存储SQL语句的 但是什么时候用什么呢 是不是总是傻傻的分不清 本文来详细的讲一下存储函数 和存储过程 以后再也不会迷糊 存储函数 存储过程 一 异同点 二 存储函数 语法 三 存储过程
  • Ubuntu 20.04上安装和配置MySql5.7

    此博客作为学习笔记使用 仅供用于学习 勿用于任何非法用途 Ubuntu换源 ubuntu 20 04系统自带源直接安装是MySQL 8 0 我要安装MySQL 5 7的版本的所以先进行换源 1 1 备份原来的sorce文件 sudo cp
  • 深度之眼(四)——Python基本数据类型

  • 使用NPM发布uni-app,Vue自定义组件

    使用NPM发布uni app以及vue自定义组件 使用npm install 安装他人发布的组件平时用的比较多 操作也比较简单 最近自己捣鼓了一下 如何发布自己的自定义组件方便自己和他人使用npm install 安装 编写组件 第一步是本
  • 多线程进阶(下)

    目录 一 JUC 二 线程安全的集合类 三 死锁 一 JUC 这里的juc指的是java util concurrent 并发 多线程相关的 一个标准库中的类 下面是JUC里面的常见类 Callable 这是一个interface 也是一种
  • 样本选择模型 & 处理效应模型

    一 样本选择偏差与自选择偏差 样本选择偏差 样本选择偏差的非随机选择机制在于对样本的选择不随机 在样本数据的采集过程中 只对某部分群体进行调查 但这部分群体与其他群体在某些方面的特征差异较大 因此根据这样的样本做回归得到的普适性结论并不可信
  • iOS自定义下拉刷新控件

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