告别重复代码:MJRefresh与Swift泛型实现通用刷新控件封装

告别重复代码:MJRefresh与Swift泛型实现通用刷新控件封装

【免费下载链接】MJRefresh An easy way to use pull-to-refresh. 【免费下载链接】MJRefresh 项目地址: https://gitcode.com/gh_mirrors/mj/MJRefresh

在iOS开发中,下拉刷新和上拉加载是几乎每个应用都需要实现的功能。如果每个列表页面都写一套刷新逻辑,不仅代码冗余,维护成本也会直线上升。本文将展示如何利用MJRefresh的基础组件和Swift泛型特性,构建一套通用的刷新控件封装方案,让你从此告别重复代码。

MJRefresh核心组件解析

MJRefresh作为iOS最流行的刷新框架之一,其核心设计思想是通过组件化实现高复用性。从MJRefresh/Base/MJRefreshComponent.h的定义可以看出,所有刷新控件都继承自MJRefreshComponent基类:

@interface MJRefreshComponent : UIView
{
    UIEdgeInsets _scrollViewOriginalInset;
    __weak UIScrollView *_scrollView;
}
// 刷新状态控制
@property (assign, nonatomic) MJRefreshState state;
// 刷新回调
@property (copy, nonatomic, nullable) MJRefreshComponentAction refreshingBlock;
// 进入刷新状态
- (void)beginRefreshing;
// 结束刷新状态
- (void)endRefreshing;
@end

该基类定义了刷新控件的核心属性和方法,包括状态管理、回调机制和滚动视图交互。基于这个基类,框架实现了三大类刷新控件:

刷新控件类型

传统实现方式的痛点

在未封装的情况下,我们通常会在每个ViewController中直接使用MJRefresh:

// 传统实现方式
class DemoViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 设置下拉刷新
        tableView.mj_header = MJRefreshNormalHeader {
            self.loadNewData()
        }
        
        // 设置上拉加载
        tableView.mj_footer = MJRefreshAutoNormalFooter {
            self.loadMoreData()
        }
    }
    
    func loadNewData() {
        // 网络请求...
        tableView.mj_header?.endRefreshing()
    }
    
    func loadMoreData() {
        // 网络请求...
        tableView.mj_footer?.endRefreshing()
    }
}

这种方式存在明显缺点:每个页面都需要重复设置刷新控件,处理加载状态,当项目中有十几个列表页面时,维护将变得非常困难。

泛型封装方案设计

我们可以创建一个泛型的RefreshableViewController,将刷新逻辑抽象到父类中。首先定义一个数据源协议,规范数据加载的接口:

// 数据源协议定义
protocol RefreshableDataSource {
    associatedtype Item
    // 加载新数据
    func loadNewData(completion: @escaping ([Item]?, Error?) -> Void)
    // 加载更多数据
    func loadMoreData(completion: @escaping ([Item]?, Error?) -> Void)
}

然后实现泛型视图控制器基类,将MJRefresh的配置和状态管理封装起来:

// 泛型刷新控制器基类
class RefreshableViewController<DataSource: RefreshableDataSource>: UIViewController {
    var tableView: UITableView!
    var dataSource: DataSource!
    var dataList: [DataSource.Item] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupTableView()
        setupRefresh()
    }
    
    private func setupTableView() {
        tableView = UITableView(frame: view.bounds)
        tableView.delegate = self
        tableView.dataSource = self
        view.addSubview(tableView)
    }
    
    private func setupRefresh() {
        // 配置下拉刷新
        tableView.mj_header = MJRefreshNormalHeader { [weak self] in
            self?.loadNewData()
        }
        
        // 配置上拉加载
        tableView.mj_footer = MJRefreshAutoNormalFooter { [weak self] in
            self?.loadMoreData()
        }
    }
    
    private func loadNewData() {
        dataSource.loadNewData { [weak self] items, error in
            guard let self = self else { return }
            self.tableView.mj_header?.endRefreshing()
            
            if let items = items {
                self.dataList = items
                self.tableView.reloadData()
            }
        }
    }
    
    private func loadMoreData() {
        dataSource.loadMoreData { [weak self] items, error in
            guard let self = self else { return }
            self.tableView.mj_footer?.endRefreshing()
            
            if let items = items {
                self.dataList.append(contentsOf: items)
                self.tableView.reloadData()
            } else {
                self.tableView.mj_footer?.endRefreshingWithNoMoreData()
            }
        }
    }
}

// 实现UITableView代理方法
extension RefreshableViewController: UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataList.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
        let item = dataList[indexPath.row]
        // 配置cell...
        return cell
    }
}

实战应用:快速创建列表页面

有了泛型基类后,创建新的列表页面变得异常简单。只需定义具体的数据源和视图控制器:

// 具体数据源实现
class ProductDataSource: RefreshableDataSource {
    typealias Item = Product
    
    func loadNewData(completion: @escaping ([Product]?, Error?) -> Void) {
        // 网络请求实现...
    }
    
    func loadMoreData(completion: @escaping ([Product]?, Error?) -> Void) {
        // 网络请求实现...
    }
}

// 商品列表页面
class ProductListViewController: RefreshableViewController<ProductDataSource> {
    override func viewDidLoad() {
        super.viewDidLoad()
        dataSource = ProductDataSource()
        // 额外的页面特定配置...
    }
    
    // 可以重写父类方法,实现自定义行为
    override func setupTableView() {
        super.setupTableView()
        tableView.register(UINib(nibName: "ProductCell", bundle: nil), forCellReuseIdentifier: "Cell")
    }
}

通过这种方式,我们彻底解决了代码重复问题,新增列表页面只需关注业务逻辑,无需重复编写刷新相关代码。

高级扩展:自定义刷新样式

MJRefresh提供了丰富的自定义选项,我们可以在基类中添加配置方法,实现统一的样式管理:

// 扩展刷新样式配置
extension RefreshableViewController {
    /// 配置默认刷新样式
    func configureDefaultRefreshStyle() {
        // 配置header样式
        let header = MJRefreshNormalHeader { [weak self] in
            self?.loadNewData()
        }
        header.setTitle("下拉刷新", for: .idle)
        header.setTitle("释放立即刷新", for: .pulling)
        header.setTitle("正在刷新...", for: .refreshing)
        header.lastUpdatedTimeLabel.isHidden = true
        tableView.mj_header = header
        
        // 配置footer样式
        let footer = MJRefreshAutoNormalFooter { [weak self] in
            self?.loadMoreData()
        }
        footer.setTitle("上拉加载更多", for: .idle)
        footer.setTile("正在加载...", for: .refreshing)
        footer.setTile("没有更多数据了", for: .noMoreData)
        tableView.mj_footer = footer
    }
}

项目中提供了多种预定义的刷新控件,如MJRefreshGifHeader支持GIF动画效果,MJRefreshBackGifFooter提供回弹式加载动画,你可以根据需求在基类中灵活配置。

总结与最佳实践

通过MJRefresh和Swift泛型的结合,我们实现了刷新逻辑的高度复用。这种方案的优势包括:

  1. 代码复用:一次封装,多处使用,大幅减少重复代码
  2. 统一风格:所有列表页面的刷新样式保持一致,提升用户体验
  3. 易于维护:刷新逻辑集中管理,修改一处即可影响所有页面
  4. 灵活性高:支持通过继承和扩展实现自定义需求

建议在实际项目中进一步扩展这个基类,添加空数据页面、错误处理、加载状态显示等通用功能,打造一个功能完善的列表页面解决方案。完整的实现示例可参考项目中的Examples目录,特别是MJTableViewController.mMJPinHeaderCollectionViewController.swift

希望本文介绍的方案能帮助你提升iOS开发效率,写出更优雅、更易维护的代码。如果你有更好的封装思路,欢迎在项目的README.md中提出宝贵意见。

【免费下载链接】MJRefresh An easy way to use pull-to-refresh. 【免费下载链接】MJRefresh 项目地址: https://gitcode.com/gh_mirrors/mj/MJRefresh

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值