抽取UITableView的DataSource代理方法及同一份View能接受不同模型数据

本人录制技术视频地址:https://edu.csdn.net/lecturer/1899 欢迎观看。

View controllers 通常是 iOS 项目中最大的文件,并且它们包含了许多不必要的代码。所以 View controllers 中的代码几乎总是复用率最低的。比如UITableView常规用法如下:

传统使用方法

1. 定义数据模型

@interface LFPhoto : NSObject

@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *title;

@end


2. 设计自定义LFPhotoCell

#import "LFPhoto.h"
@interface LFPhotoCell : UITableViewCell

@property (weak, nonatomic) IBOutlet UILabel *name;
@property (weak, nonatomic) IBOutlet UILabel *title;

@property (nonatomic,strong) LFPhoto *photo;

+ (instancetype) photoCell;

@end


@implementation LFPhotoCell

+ (instancetype) photoCell {
    return [[[NSBundle mainBundle] loadNibNamed:@"LFPhotoCell" owner:nil options:nil] lastObject];
}

- (void)setPhoto:(LFPhoto *)photo {
    self.name.text = photo.name;
    self.title.text = photo.title;
}
@end


3. 利用UITableView展示数据

@interface ViewController ()
@property (nonatomic,strong) NSArray *photos;
@end

static NSString *const identifer = @"LFPhotoCell";

@implementation ViewController

#pragma mark - Lazy Load
- (NSArray *)photos {
    if (!_photos) {
        LFPhoto *p1 = [[LFPhoto alloc] init];
        p1.name = @"Jason";
        p1.title = @"OK";
        
        LFPhoto *p2 = [[LFPhoto alloc] init];
        p2.name = @"Rose";
        p2.title = @"NO";
        
        LFPhoto *p3 = [[LFPhoto alloc] init];
        p3.name = @"Lucy";
        p3.title = @"Luck";
        
        _photos = @[p1,p2,p3];
    }
    return _photos;
}

- (void)viewDidLoad {
    [super viewDidLoad];
}

#pragma mark - Data Source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.photos.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    LFPhotoCell *cell = (LFPhotoCell *)[tableView dequeueReusableCellWithIdentifier:identifer];
    if(!cell) {
        cell = [LFPhotoCell photoCell];
    }
    
    cell.photo = self.photos[indexPath.row];
    
    return cell;
}

#pragma mark - Data Delegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 80;
}

在书写了N次UITableView的代理方法后,我们发现Data Source 方法几乎写法不变,改变的只是Cell和model的类型。所以我们可以考虑把 UITableViewDataSource 的代码提取出来放到一个单独的类中,为View Controller“瘦身”。


抽取UITableView的DataSource代理方法

要抽取数据源方法,我们定义一个遵守UITableViewDataSource协议的类CommonDataSource。这个类初始化的时候,接受模型数据集合及Cell重用的identifer。最终会将模型数据反馈到Cell上面,所以我们可以再定义一个block,用于回调操作。

// 处理cell与model之间的关系
typedef void (^cellConfig)(id cell, id model);
// 实现UITableViewDataSource协议
@interface CommonDataSource : NSObject <UITableViewDataSource>

// 自定义数据源
- (instancetype) initWithItem:(NSArray *)items cellIdentifer:(NSString *)identifer cellConfigBlock:(cellConfig)cellConfigBlock;

@end

@interface CommonDataSource()
/*数据集合*/
@property (nonatomic,strong) NSArray *items;
@property (nonatomic,copy) NSString *identifer;
@property (nonatomic,copy) cellConfig cellBlock;
@end

@implementation CommonDataSource

- (instancetype) initWithItem:(NSArray *)items cellIdentifer:(NSString *)identifer cellConfigBlock:(cellConfig)cellConfigBlock {
    if(self = [super init]){
        self.items = items;
        self.identifer = identifer;
        self.cellBlock = [cellConfigBlock copy];
    }
    return self;
}

#pragma mark - 自定义的类中实现 UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.items.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.identifer];
    // 这里没有判断cell是否为空,因为在 storyboard中设置了 “prototype cells” 为1,并且将对应的 cell的类型设置为LFPhotoCell了。所以肯定有值。然后在cell上面自定义自己想要的控件,与LFPhotoCell类进行连线。
    id item = self.items[indexPath.row];
    self.cellBlock(cell,item);
    
    return cell;
}


代码中dequeueReusableCellWithIdentifer取得的cell一定不为空,因为我在Storyboard中进行了相关属性的设置。



此时,我们就可以在View Controller直接使用自定义的CommonDataSource了,而UITableView的数据源代理方法完全可以取消掉了。 

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 数据源方法可以进行回调工作
    void (^cellBlock)(LFPhotoCell *, LFPhoto *) = ^(LFPhotoCell *cell, LFPhoto *photo){
        cell.photo = photo;
    };
    
    // 准备数据源相关数据
    self.myDataSource = [[CommonDataSource alloc] initWithItem:self.photos cellIdentifer:identifer cellConfigBlock:cellBlock];
    
    // 设置数据源
    // 当自定义的Data Source 赋值给tableView 的 Data Source的时候,就会调用CommonDataSource 中定义的实现 UITableViewDataSource 的数据源方法
    self.tableView.dataSource = self.myDataSource;
}

以后使用的时候,我们只需要更改Cell和model的类型就可以了。


同一份View能接受不同模型数据

在实际开发中,有可能有这样的需求:封装了一个View,用来实现某些功能,但有可能数据源有可能不同,美团App就有这样的例子:


“类别分类”与“区域分类”是使用的同一个封装好的View,但他们的数据源明显来自不同的地方。这时候我们该如何处理呢?

这个时候,我们可以考虑使用protocol。以上面的LFPhotoCell为例:

1. 我们定义一个LFPhotoCellItem协议,并且定义指定的实现方法。

/*一个Cell模型,能接受多种模型数据,那么那些模型数据应该遵守cell定义的协议:LFPhotoCellItem,并且实现的模型中要实现协议中定义的方式*/
@protocol LFPhotoCellItem <NSObject>
- (NSString *)name;
- (NSString *)title;
@end

2. 在LFPhotoCell.h中定义协议属性:

@property (assign, nonatomic) id<LFPhotoCellItem> item;
3. 在LFPhotoCell.m中重写协议属性:

- (void)setItem:(id<LFPhotoCellItem>)item {
    _item = item;
    self.name.text = [item name];
    self.title.text = [item title];
}

4. 每个想传递数据到LFPhotoCell中的模型,必须遵循LFPhotoCellItem协议。

#import "LFPhotoCell.h"

@interface LFPhoto : NSObject <LFPhotoCellItem>

@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *title;

@end

5. 实现遵守协议的方法

@implementation LFPhoto
// 实现LFPhotoCellItem方法
- (NSString *)name {
    return _name;
}

- (NSString *)title {
    return _title;
}
@end

至此,就完成了协议的自定义工作,现在如果你想更换数据模型的话,只需要将4,5两步的模型更换掉,并且实现协议中定义的方法即可。

最后,在Cell回调中,只需要将传递的具体photo模型更换为item就好了。

void (^cellBlock)(LFPhotoCell *, LFPhoto *) = ^(LFPhotoCell *cell, LFPhoto *photo){
        cell.item = photo;
    };

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋恨雪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

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

抵扣说明:

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

余额充值