Appearance
自定义下拉刷新控件
概述
用了很多的别人的下拉刷新控件,想写一个玩玩,自定义一个在使用的时候也会比较有意思。使应用更加的灵动一些,毕竟谁不喜欢各种动画恰到好处的应用呢。
使用方式如下:
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)
}
}