iOS自定义下拉刷新控件

自定义下拉刷新控件

概述

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

使用方式如下:

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

下边展示一下效果。

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

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

简单版本代码

swift 复制代码
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()
        }
    }
}

加强版本代码

swift 复制代码
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)
    }
}

参考地址

相关推荐
幸福回头1 天前
ms-swift 代码推理数据集
llm·swift
若水无华2 天前
fiddler 配置ios手机代理调试
ios·智能手机·fiddler
不二狗2 天前
每日算法 -【Swift 算法】Two Sum 问题:从暴力解法到最优解法的演进
开发语言·算法·swift
Aress"2 天前
【ios越狱包安装失败?uniapp导出ipa文件如何安装到苹果手机】苹果IOS直接安装IPA文件
ios·uni-app·ipa安装
Jouzzy2 天前
【iOS安全】Dopamine越狱 iPhone X iOS 16.6 (20G75) | 解决Jailbreak failed with error
安全·ios·iphone
瓜子三百克2 天前
采用sherpa-onnx 实现 ios语音唤起的调研
macos·ios·cocoa
左钦杨2 天前
IOS CSS3 right transformX 动画卡顿 回弹
前端·ios·css3
努力成为包租婆2 天前
SDK does not contain ‘libarclite‘ at the path
ios
安和昂3 天前
【iOS】Tagged Pointer
macos·ios·cocoa
I烟雨云渊T3 天前
iOS 阅后即焚功能的实现
macos·ios·cocoa