swift项目自定义MJRefresh刷新动画

3,029 阅读2分钟

swift项目中对自定义上拉下拉刷新(对MJRefresh的封装)

我不知道官方自定义用法是怎么用的,看了下自己团队中项目的实现方式,觉得挺有意思的,特此记录一下 整体思路,从中间层下手,替换掉MJ_header方法,实现自己的header方法,同时使用自定义的headerrefreshview

好处是可以非常方便的在自定义的refreshview中添加自己所需要的动画或者其他视图

extra: 附带简单讲解下mjrefresh的逻辑

  • MJRefresh基类MJRefreshComponent
定义刷新状态
定义子类需要覆盖的方法
**
重要的是对其父类添加了scrollview的位移监听,核心就在这里
监听到位移之后会通知子类去掉响应的事件
️**
  • MJRefreshStateHeader继承自MJRefreshHeader
中间这一层MJRefreshHeader就不多说了,就是定义了一个header里面该有的东西,继承自MJRefreshComponent,同时在收到父类的位移回掉的时候回去调用自己的子类的方法

接下来是自定义时要使用的MJRefreshStateHeader,他继承自MJRefreshHeader
其具有刷新状态,可以更具刷新状态处理响应事件

  • tableview.mj_header
平时调用的时候一般就是调用mj_header,传入一个header 和回掉
这个过程等下会模仿他重写掉,以此替换掉mj的mj_header方法,
转而使用我们自己的添加刷新方法

自定义实现

  1. 新建自己的header继承自MJRefreshStateHeader,重写 prepare
/** 写一个类继承自MJRefreshStateHeader, 我的取名叫GQRefreshHeader
把默认的时间label和状态label隐藏掉
*/
override func prepare() {
        super.prepare()
        self.lastUpdatedTimeLabel.isHidden = true
        self.stateLabel.isHidden = true
        self.mj_h = 56
    }
  1. 重写placeSubviews方法,放上自己的动画view,这里用的lottie加载了一个刷新的圈圈
lazy var gifView: AnimationView? = {
        let animationView = AnimationView(name: "refresh")
        animationView.loopMode = .loop
        animationView.isUserInteractionEnabled = false
        addSubview(animationView)
        return animationView
    }()
    
override func placeSubviews() {
        super.placeSubviews()
        if self.gifView?.constraints.count ?? 0 > 0 {
            return
        }
        self.gifView?.frame = CGRect.init(x: (self.bounds.size.width)/2, y: self.bounds.size.height/2, width: 24, height: 24)
    }    
  1. OC里面是重写setstate,swift里面就是把state属性的didset方法设置一下
/**
d
*/
override var state: GQRefreshHeader{
        didSet {
            guard let gifV =  gifView else {
                return
            }
            if state == .pulling || state == .refreshing{
                gifV.stop()
                gifV.play()
            }else if state == .idle{
                gifV.stop()
            }
            
        }
    }
  1. 关键骚操作在这里
/**
扩展UIScrollView 添加一个 GQRefreshHeader类型的关联属性
*/

struct GQRefreshKey {
    static var headerKey = 0
}

extension UIScrollView {
    /// 关联属性 (扩展中添加)
    private var gqHeader: GQRefreshHeader? {
        get {
            return objc_getAssociatedObject(self, &GQRefreshKey.headerKey) as? GQRefreshHeader
        }
        set {
            objc_setAssociatedObject(self, &GQRefreshKey.headerKey, newValue,objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
  }
  1. 在该扩展中添加一个 addRefreshHeader 方法(mj 的setRefreshheader 原理也是一样的): 注意这一句 self.insertSubview 实际上就是把我们传进来的header 加到了图层里,
 /// 添加下拉刷新时调用的方法
    @discardableResult /// discardableResult目的是为了让返回值没被调用者接收到的时候不报警告(这句加不加无所谓 加了更优雅些)
    func addRefreshHeader(refreshHeader:GQRefreshHeader?,refreshBlock:@escaping ()->Void) -> UIScrollView {
        if gqHeader != refreshHeader {
            gqHeader?.removeFromSuperview()
            
            if let header: GQRefreshHeader = refreshHeader {
                header.refreshingBlock = refreshBlock
                self.insertSubview(header, at: 0)
                self.gqHeader = header
            }
        }
        return self
    }
    
//触发头部上拉刷新
    final public func triggerRefreshing(){
        self.gqHeader?.beginRefreshing()
    }
    
    final public func triggerDelayRefreshing() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) {
            self.triggerRefreshing()
        }
    }    
    
    func endRefreshing() {
        self.gqHeader?.endRefreshing()
    }    

6.外部调用的时候

self.tableView.addRefreshHeader(refreshHeader: GQRefreshHeader()) {
            DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2) {
                self.tableView.endRefreshing()///模拟刷新完成后结束动画
            }
        }