告别重复代码:MJRefresh与Swift泛型实现通用刷新控件封装
在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
该基类定义了刷新控件的核心属性和方法,包括状态管理、回调机制和滚动视图交互。基于这个基类,框架实现了三大类刷新控件:
- Header:顶部下拉刷新控件,如MJRefreshNormalHeader.h
- Footer:底部上拉加载控件,如MJRefreshAutoNormalFooter.h
- Trailer:侧边拖动刷新控件,如MJRefreshNormalTrailer.h
传统实现方式的痛点
在未封装的情况下,我们通常会在每个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泛型的结合,我们实现了刷新逻辑的高度复用。这种方案的优势包括:
- 代码复用:一次封装,多处使用,大幅减少重复代码
- 统一风格:所有列表页面的刷新样式保持一致,提升用户体验
- 易于维护:刷新逻辑集中管理,修改一处即可影响所有页面
- 灵活性高:支持通过继承和扩展实现自定义需求
建议在实际项目中进一步扩展这个基类,添加空数据页面、错误处理、加载状态显示等通用功能,打造一个功能完善的列表页面解决方案。完整的实现示例可参考项目中的Examples目录,特别是MJTableViewController.m和MJPinHeaderCollectionViewController.swift。
希望本文介绍的方案能帮助你提升iOS开发效率,写出更优雅、更易维护的代码。如果你有更好的封装思路,欢迎在项目的README.md中提出宝贵意见。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考




