美文网首页
如何写UITableView解决项目中MVC架构存在的问题

如何写UITableView解决项目中MVC架构存在的问题

作者: 蔡少 | 来源:发表于2019-07-24 13:04 被阅读0次

    效果预览

    CXTableView.gif

    问题阐述

    • VC过于臃肿 业务稍微复杂点就代码量1000+
    • View与Model之间耦合性太强
    • bug不易定位
    • 业务更改维护成本很高

    基于这些问题我们就以一个UITableView的列表来进行阐述首先要弄明白 MVC 的核心:控制器(以下简称 C)负责模型(以下简称 M)和视图(以下简称 V)的交互。

    这里所说的 M,通常不是一个单独的类,很多情况下它是由多个类构成的一个层。最上层的通常是以 Model 结尾的类,它直接被 C 持有。

    项目版本

    在 C 中,我们创建 UITableView 对象,然后将它的数据源和代理设置为自己。也就是自己管理着 UI 逻辑和数据存取的逻辑。在这种架构下,主要存在这些问题:

    • 违背 MVC 模式,现在是 V 持有 C 和 M。
    • C 做了全部逻辑,耦合太严重。

    DataSource

    首先实现一个tableView 我们要实现两个数据源方法

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
    ```### Delegate
    这里包含一些点击代理,高度返回以及一系列的头部尾部视图的配置,以及`cell`绘制UI的操作
    ```objc
    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
    - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
    
    

    进阶版本

    首先的思路是单独把数据源方法抽离出去,单独实现
    这里我打算用到一个遵循UITableViewDataSource的协议和一个单独的数据源类(需要遵循自定义的协议)来实现
    结合前面数遇见的传统DataSource问题我们可以思考下这个协议api应该怎么设计

    @protocol CXTableViewDataSourceProtocol <UITableViewDataSource>
    @optional
    
    - (Class)tableView:(UITableView*)tableView cellClassForObject:(id)object;
    - (UITableViewCell *)registerTableView:(UITableView*)tableView cellClassForObject:(id)object;
    - (id)tableView:(UITableView *)tableView objectForRowAtIndexPath:(NSIndexPath *)indexPath;
    - (CGFloat)rowHeightForObject:(id)object;
    
    @end
    
    • 拿带数据源做对应的事
    • 注册cell类型支持多种样式的cell
    • 配置cell的高度

    再自定义一个遵循CXTableViewDataSourceProtocol的数据源类

    @interface CXTableViewDataSource : NSObject<CXTableViewDataSourceProtocol>
    
    /**
    包装setion 是个二维数组
    */
    @property (nonatomic, strong) NSMutableArray <CXTableViewSectionModel *>*sections;
    
    - (void)reamoveAllItems;
    
    - (void)addItem:(id)item;
    
    - (void)addItem:(id)item section:(NSInteger)section;
    
    - (id)loadFromXib:(NSString *)class_name;
    
    @end
    

    内部只需要做好协议实现,部分协议方法以让子类实现的方式暴露给给外界调用,这里我包含了一个设置高度的协议在数据源方法里,这主要是考虑到用户在使用的时候,很多的时候我们的高度并不是固定的,理论上应该配置高度的地方是cell它自己本身,因为涉及UI,目前同样开放了cell设置高度的接口,但协议的高度接口优先级要高于cell配置的接口,使用者只需要在CXTableViewDataSource的子类中去处理数据

    #pragma mark - CXTableViewDataSourceProtocol
    - (id)tableView:(UITableView *)tableView objectForRowAtIndexPath:(NSIndexPath *)indexPath {
        if (self.sections.count > indexPath.section) {
            CXTableViewSectionModel *tableViewSectionModel = [self.sections objectAtIndex:indexPath.section];
            if ([tableViewSectionModel.items count] > indexPath.row) {
                return [tableViewSectionModel.items objectAtIndex:indexPath.row];
            }
        }
        return nil;
    }
    
    - (UITableViewCell *)registerTableView:(UITableView*)tableView cellClassForObject:(id)object {
        //子类实现
        Class cellClass = [self tableView:tableView cellClassForObject:object];
        NSString *className = [NSString stringWithUTF8String:class_getName(cellClass)];
        return [[cellClass alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:className];
    }
    
    - (Class)tableView:(UITableView*)tableView cellClassForObject:(id)object {
        //子类实现
        return [CXBaseTableViewCell class];
    }
    
    - (CGFloat)rowHeightForObject:(id)object {
        //子类实现
        return 0;
    }
    
    

    具体的代理全部交由CXTableViewDataSource来处理

    #pragma mark - UITableViewDataSource Required
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        if (self.sections.count > section) {
            CXTableViewSectionModel *tableViewSectionModel =  [self.sections objectAtIndex:section];
            return tableViewSectionModel.items.count;
        }
        return 0;
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        //通过获得数据来确定Cell的样式
         id object = [self tableView:tableView objectForRowAtIndexPath:indexPath];
        Class cellClass = [self tableView:tableView cellClassForObject:object];
        NSString *className = [NSString stringWithUTF8String:class_getName(cellClass)];
        CXBaseTableViewCell* cell = (CXBaseTableViewCell*)[tableView dequeueReusableCellWithIdentifier:className];
        if (!cell) {
            cell = (CXBaseTableViewCell *)[self registerTableView:tableView cellClassForObject:object];
        }
        return cell;
    }
    
    #pragma mark - UITableViewDataSource Optional
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
        return self.sections ? self.sections.count : 0;
    }
    

    现在dataSource的数据源代理已全部交由CXTableViewDataSource来处理

    现在就只剩另外一个问题,那就是delegate的抽离,这里的处理方式稍微和dataSource的处理方式有些不同,dataSource的代理对象是一个我们自定义的CXTableViewDataSource对象,而delegate的代理对象,我们用CXTableView一个继承UITableView的子类,首先首先设置一个CXTableViewDelegateProtocol

    @protocol CXTableViewDelegateProtocol <UITableViewDelegate>
    
    @optional
    
    - (void)didSelectObject:(id)object atIndexPath:(NSIndexPath*)indexPath;
    
    @end
    

    CXTableViewDelegateProtocol协议是继承UITableViewDelegate,之说以这样是为了好让CXTableView作为一个中间桥接的作用,在系统的代理上层去处理自己的业务的前提下不影响系统的代理,和runtime交换方法有点异曲同工之妙

    //.h
    @interface CXTableView : UITableView<UITableViewDelegate>
    
    @property (nonatomic, weak) id<CXTableViewDataSourceProtocol> cxdataSource;
    @property (nonatomic, weak) id<CXTableViewDelegateProtocol> cxdelegate;
    
    @end
    
    //.m
    #pragma mark - UITableViewDelegate
    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
        id<CXTableViewDataSourceProtocol> dataSource = (id<CXTableViewDataSourceProtocol>)self.dataSource;
        id object = [dataSource tableView:tableView objectForRowAtIndexPath:indexPath];
        /*
         理论上现在已经知道了高度,但由于object是id类型
         需要子类提前异步计算好返回
         如果子类没有计算,则认为这里是固定高度,可由Cell自己配置
         */
        Class cls = [dataSource tableView:tableView cellClassForObject:object];
        return [dataSource rowHeightForObject:object] > 0?[dataSource rowHeightForObject:object]:[cls rowHeightForItem:object];
    }
    
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
        if (self.cxdelegate && [self.cxdelegate respondsToSelector:@selector(didSelectObject:atIndexPath:)]) {
            id<CXTableViewDataSourceProtocol> dataSource = (id<CXTableViewDataSourceProtocol>)self.dataSource;
            id object = [dataSource tableView:tableView objectForRowAtIndexPath:indexPath];
            [self.cxdelegate didSelectObject:object atIndexPath:indexPath];
        } else if([self.cxdelegate respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]) {
            [self.cxdelegate tableView:tableView didSelectRowAtIndexPath:indexPath];
        }
        [tableView deselectRowAtIndexPath:indexPath animated:YES];
    }
    
    - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
        id<CXTableViewDataSourceProtocol> dataSource = (id<CXTableViewDataSourceProtocol>)self.dataSource;
        id object = [dataSource tableView:tableView objectForRowAtIndexPath:indexPath];
        //给cell 绑定数据
        [(CXBaseTableViewCell *)cell setItem:object];
        if ([self.cxdelegate respondsToSelector:@selector(tableView:willDisplayCell:forRowAtIndexPath:)]) {
            [self.cxdelegate tableView:tableView willDisplayCell:cell forRowAtIndexPath:indexPath];
        }
    }
    
    - (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section {
        if([self.cxdelegate respondsToSelector:@selector(tableView:willDisplayHeaderView:forSection:)]) {
            [self.cxdelegate tableView:tableView willDisplayHeaderView:view forSection:section];
        }
    }
    
    - (void)tableView:(UITableView *)tableView willDisplayFooterView:(UIView *)view forSection:(NSInteger)section {
        if([self.cxdelegate respondsToSelector:@selector(tableView:willDisplayFooterView:forSection:)]) {
            [self.cxdelegate tableView:tableView willDisplayFooterView:view forSection:section];
        }
    }
    
    // 后续还可以继续添加代理 或者自己定义子类去实现 中转传递
    
    

    另外给cell 绑定数据的逻辑我放在了- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath 方法里,而并没有放在- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 方法中
    这是因为cellForRowAtIndexPath做的事情其实就在绑定Cell类型,而willDisplayCell方法中是Cell已经绘制出来了,这会添加数据再合适不过,包括前面提到的Cell设置高度接口这里也做了对应的说明
    至此,对CXTableView的基本封装就完成了,但远不仅仅是这些

    • 下拉刷新
    • 空白页
    • 带动画的loading页(知乎,简书那种)
    • 等等...

    这些东西都是业务开发中比较常见的场景 此篇文章中这里就没有进一步封装了(时间问题)思想最重要,要明白为什么这么去抽离,面向接口协议开发

    完结版本

    基于使用层面的考虑, 我们实现一个UIViewController的子类,并且把数据源和代理封装到 C 中

    @class CXTableViewDataSource;
    NS_ASSUME_NONNULL_BEGIN
    @protocol CXTableViewControllerDelegate <NSObject>
    
    @required
    - (void)configCXDataSource;
    @optional
    - (void)configCXDelegate;
    @end
    
    @interface CXTableViewController : UIViewController<CXTableViewDelegateProtocol,CXTableViewControllerDelegate>
    
    @property (nonatomic, strong) CXTableView *tableView;
    @property (nonatomic, strong) CXTableViewDataSource *tableViewDataSource;
    @property (nonatomic, assign) UITableViewStyle tableViewStyle;
    
    - (instancetype)init NS_UNAVAILABLE;
    + (instancetype)new NS_UNAVAILABLE;
    - (instancetype)initWithStyle:(UITableViewStyle)style;
    
    @end
    NS_ASSUME_NONNULL_END
    

    用户使用只要做几件事就行

    • 首先你需要创建一个继承自
      CXTableViewController 的视图控制器,并且调用它的
      initWithStyle## 方法。

    • 实现CXTableViewDataSource的子类

    @implementation CXDemoDataSource
    
    - (void)loadData {
        //业务数据处理  这里还可以抽离出一个p层
        NSArray *cellIdentifers = @[NSStringFromClass([CXDemo1TableViewCell class]),NSStringFromClass([CXDemoTableViewCell class])];
        NSMutableArray *items = [@[] mutableCopy];
        id<ContentViewAdapterProtocol> demoAdapter;
        for (NSInteger i = 0; i < 40; i ++) {
            if (i%5 > 2) {
                CXDemoItem *item = [CXDemoItem new];
                item.cellIdentifier = cellIdentifers[i%2];
                item.rowHeight = 150;
                item.name = item.cellIdentifier;
                item.subName = [NSString stringWithFormat:@"%zd",i];
                demoAdapter = [[CXDemoAdapter alloc] initWithData:item];
            } else{
                CXDemo1Item *item = [CXDemo1Item new];
                item.identifier = cellIdentifers[i%2];
                item.rowHeight = 70;
                item.contentName = item.identifier;
                item.titleName = [NSString stringWithFormat:@"%zd",i];
                demoAdapter = [[CXDemoAdapter alloc] initWithData:item];
            }
            [items addObject:demoAdapter];
        }
        CXTableViewSectionModel *sectionModel = [[CXTableViewSectionModel alloc] initWithItemArray:items];
        self.sections = [NSMutableArray arrayWithObject:sectionModel];
    }
    
    #pragma mark - CXTableViewDataSourceProtocol
    //注册cell类型
    - (UITableViewCell *)registerTableView:(UITableView*)tableView cellClassForObject:(id<ContentViewAdapterProtocol>)object {
        return [self loadFromXib:object.cellIdentifier];
    }
    
    //确立Cell的类型
    - (Class)tableView:(UITableView *)tableView cellClassForObject:(id<ContentViewAdapterProtocol>)object {
        return [NSClassFromString(object.cellIdentifier) class];
    }
    
    //异步计算好高度
    - (CGFloat)rowHeightForObject:(id<ContentViewAdapterProtocol>)object {
        return object.rowHeight;
    }
    
    

    VC的调用就更简单了 在这里我把 self.tableView.cxdelegate = self.demoTableViewDelegate;

    @interface CXDemoViewController ()
    
    @property (nonatomic, strong) UIActivityIndicatorView *activityIndicatorView ;
    @property (strong, nonatomic) CXDemoDataSource *demoDataSource;
    @property (strong, nonatomic) CXDemoTableViewDelegate *demoTableViewDelegate;
    
    @end
    
    @implementation CXDemoViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.navigationItem.title = @"CXTableView";
        UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithTitle:@"数据加载失败" style:UIBarButtonItemStylePlain target:self action:@selector(failClick)];
        [self.navigationItem setRightBarButtonItem:item];
        self.tableView.isNeedPullUpToRefresh = YES;
        self.tableView.isNeedPullDownToRefresh = YES;
        self.tableView.autoPullDownToRefresh = YES;
    }
    
    #pragma mark - CXTableViewControllerDelegate
    - (void)configCXDataSource {
        //设置数据源
        self.tableViewDataSource = self.demoDataSource;
    }
    
    - (void)configCXDelegate {
          //设置代理
        self.tableView.cxdelegate = self.demoTableViewDelegate;
    }
    
    #pragma mark - action
    - (void)failClick {
        [self.tableViewDataSource reamoveAllItems];
        [self.tableView reloadData];
    }
    
    #pragma mark - set&get
    - (CXDemoDataSource *)demoDataSource{
        if (!_demoDataSource) {
            _demoDataSource = [[CXDemoDataSource alloc] init];
        }
        return _demoDataSource;
    }
    
    - (CXDemoTableViewDelegate *)demoTableViewDelegate{
        if (!_demoTableViewDelegate) {
            _demoTableViewDelegate = [[CXDemoTableViewDelegate alloc] init];
            _demoTableViewDelegate.tableView = self.tableView;
            _demoTableViewDelegate.demoDataSource = self.demoDataSource;
        }
        return _demoTableViewDelegate;
    }
    
    @end
    
    

    self.demoTableViewDelegate 就比较贴近各自的项目逻辑

    到目前为止,我们实现了对UITableView以及相关协议、方法的封装,使它更容易使用,避免了很多重复、无意义的代码。
    M 只关心数据
    C 只负责调度 配置
    V 只负责展示数据

    self.demoTableViewDelegate 可以把一些复杂的业务逻辑,它直接和CXDemoDataSource通信 如果业务再复杂点,还可用self.demoTableViewDelegate的分类来处理业务分类

    补充版本

    • 下拉刷新的封装

    上次提到过的下拉刷新的封装,以及空白页的处理,因为这些从属性构造来讲,它们应该都属于TableView,所以我这边还是基于CXTableViewDelegateProtocol,给它增加协议方法

    @protocol CXTableViewDelegateProtocol <UITableViewDelegate>
    
    @optional
    
    /**
     cell点击的回调
    
     @param object 对象
     @param indexPath 索引
     */
    - (void)didSelectObject:(id)object atIndexPath:(NSIndexPath*)indexPath;
    
    /**
     空白占位
    
     @return 空白占位图
     */
    - (UIView *)registerEmptyView;
    
    /**
     下拉刷新触发的方法
     */
    - (void)pullDownToRefresh;
    
    /**
     上拉加载触发的方法
     */
    - (void)pullUpToRefresh;
    
    @end
    

    再给CXTableView 增加4个属性 两个关闭动画的方法

    @interface CXTableView : UITableView<UITableViewDelegate>
    
    @property (nonatomic, weak) id<CXTableViewDataSourceProtocol> cxdataSource;
    @property (nonatomic, weak) id<CXTableViewDelegateProtocol> cxdelegate;
    
    @property (nonatomic, assign) BOOL isNeedPullDownToRefresh;
    @property (nonatomic, assign) BOOL isNeedPullUpToRefresh;
    @property (assign, nonatomic) BOOL autoPullDownToRefresh;
    @property (assign, nonatomic) BOOL loadCompleted;
    
    - (void)stopRefreshingAnimation;
    - (void)triggerRefreshing;
    
    @end
    

    这边下拉刷新控件我选择的是SVPullToRefresh比较轻量级,其中内部有两个BUG,在源码层级上给它做了修改

    #import "UIScrollView+SVPullToRefresh.h"
    - (void)startAnimating{
        switch (self.position) {
            case SVPullToRefreshPositionTop:
                //bug 修复 设置了偏移量后 不能自动刷新的问题
                if(fequalzero(self.scrollView.contentOffset.y) + self.originalTopInset) {
                    [self.scrollView setContentOffset:CGPointMake(self.scrollView.contentOffset.x, - self.frame.size.height - self.originalTopInset) animated:YES];
                    self.wasTriggeredByUser = NO;
                }
                else
                    self.wasTriggeredByUser = YES;
    
                break;
            case SVPullToRefreshPositionBottom:
    
                if((fequalzero(self.scrollView.contentOffset.y) && self.scrollView.contentSize.height < self.scrollView.bounds.size.height)
                   || fequal(self.scrollView.contentOffset.y, self.scrollView.contentSize.height - self.scrollView.bounds.size.height)) {
                    [self.scrollView setContentOffset:(CGPoint){.y = MAX(self.scrollView.contentSize.height - self.scrollView.bounds.size.height, 0.0f) + self.frame.size.height} animated:YES];
                    self.wasTriggeredByUser = NO;
                }
                else
                    self.wasTriggeredByUser = YES;
    
                break;
        }
        self.state = SVPullToRefreshStateLoading;
    }
    
    #import "UIScrollView+SVInfiniteScrolling.h"
     id customView = [self.viewForState objectAtIndex:newState];
        BOOL hasCustomView = [customView isKindOfClass:[UIView class]];
    
        if(hasCustomView) {
            [self addSubview:customView];
            CGRect viewBounds = [customView bounds];
            CGPoint origin = CGPointMake(roundf((self.bounds.size.width-viewBounds.size.width)/2), roundf((self.bounds.size.height-viewBounds.size.height)/2));
            [customView setFrame:CGRectMake(origin.x, origin.y, viewBounds.size.width, viewBounds.size.height)];
            //bug 解决设置了自定义View SVInfiniteScrollingStateStopped状态后的 菊花不消失的问题
            switch (newState) {
                case SVInfiniteScrollingStateStopped:
                    [self.activityIndicatorView stopAnimating];
                    break;
            }
        }
    

    原有的下拉刷新箭头不精细,比较喜欢简书那样的下拉刷新,所有就画过了一个箭头

    #pragma mark - SVPullToRefreshArrow
    
    @implementation SVPullToRefreshArrow
    @synthesize arrowColor;
    
    - (UIColor *)arrowColor {
        if (arrowColor) return arrowColor;
        return [UIColor lightGrayColor]; // default Color
    }
    
    - (void)drawRect:(CGRect)rect {
        CGContextRef c = UIGraphicsGetCurrentContext();
        CGContextMoveToPoint(c, 11, 20);
        CGContextAddLineToPoint(c, 11, 35);
        CGContextMoveToPoint(c, 6, 30);
        CGContextAddLineToPoint(c, 11, 35);
        CGContextAddLineToPoint(c, 16, 30);
        CGContextSetLineWidth(c, 0.8);
        CGContextSetStrokeColorWithColor(c, self.arrowColor.CGColor);
        CGContextSetLineCap(c, kCGLineCapRound);
        CGContextDrawPath(c, kCGPathStroke);
    }
    
    @end
    

    使用层面很简单, 后续如果想换MJ也可以改CXTableView的实现,外界调用不用动

    - (void)pullDownToRefresh {
        //模拟网络请求
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self.demoDataSource loadData];
            [self.tableView setLoadCompleted:NO];
            [self.tableView stopRefreshingAnimation];
            [self.tableView reloadData];
        });
    }
    
    
    - (void)pullUpToRefresh {
        //模拟网络请求
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self.demoDataSource loadMoreData];
            [self.tableView setLoadCompleted:YES];
            [self.tableView triggerRefreshing];
            [self.tableView reloadData];
        });
    }
    
    • 空白页的封装

    主要是针对CXTableView实现了一个CXTableView+CXEmpty分类
    主要思路是针对CXTableView 刷新数据方法进行交换

    + (void)load {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            [[self class] cx_swizzleClassMethodWithOriginalSel:@selector(reloadData) newSel:@selector(cx_reloadData)];
            [[self class] cx_swizzleClassMethodWithOriginalSel:@selector(insertSections:withRowAnimation:) newSel:@selector(cx_insertSections:withRowAnimation:)];
            [[self class] cx_swizzleClassMethodWithOriginalSel:@selector(insertRowsAtIndexPaths:withRowAnimation:) newSel:@selector(cx_insertRowsAtIndexPaths:withRowAnimation:)];
            [[self class] cx_swizzleClassMethodWithOriginalSel:@selector(deleteSections:withRowAnimation:) newSel:@selector(cx_deleteSections:withRowAnimation:)];
            [[self class] cx_swizzleClassMethodWithOriginalSel:@selector(deleteRowsAtIndexPaths:withRowAnimation:) newSel:@selector(cx_deleteRowsAtIndexPaths:withRowAnimation:)];
        });
    }
    
    - (void)cx_reloadData {
        [self cx_reloadData];
        //忽略第一次加载
        if (![self isInitFinish]) {
            [self setIsInitFinish:YES];
            return;
        }
        [self checkData];
    }
    
    - (void)checkData {
        dispatch_async(dispatch_get_main_queue(), ^{
            if (!self.emptyView) {
                return;
            }
            NSInteger sections = 1;
            if ([self.cxdataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) {
                sections = [self.cxdataSource numberOfSectionsInTableView:self];
            }
            if (sections == 0){
                [self.emptyView removeFromSuperview];
                [self addSubview:self.emptyView];
            }else {
                if (sections == 1){
                    NSInteger rowNumber = [self.cxdataSource tableView:self numberOfRowsInSection:0];
                    if (rowNumber == 0){
                        [self.emptyView removeFromSuperview];
                        [self addSubview:self.emptyView];
                    } else {
                        [self.emptyView removeFromSuperview];
                    }
                }else {
                    [self.emptyView removeFromSuperview];
                }
            }
        });
    }
    
    static NSString *const CXRegisterEmptyViewKey = @"CXRegisterEmptyViewKey";
    static NSString *const CXTableViewPropertyInitFinishKey = @"CXTableViewPropertyInitFinishKey";
    
    - (UIView *)emptyView {
        if ([self.cxdelegate respondsToSelector:@selector(registerEmptyView)]) {
            if (!objc_getAssociatedObject(self, &CXRegisterEmptyViewKey)) {
                objc_setAssociatedObject(self, &CXRegisterEmptyViewKey, [self.cxdelegate registerEmptyView], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            }
            return objc_getAssociatedObject(self, &CXRegisterEmptyViewKey);
        }
        return nil;
    }
    
    - (void)setIsInitFinish:(BOOL)finish{
        objc_setAssociatedObject(self, &CXTableViewPropertyInitFinishKey, @(finish), OBJC_ASSOCIATION_ASSIGN);
    }
    
    - (BOOL)isInitFinish{
        id obj = objc_getAssociatedObject(self, &CXTableViewPropertyInitFinishKey);
        return [obj boolValue];
    }
    
    

    空白页的配置完全交给了外界,只要实现代理即可,这里还做了一个首次拿数据的时候不检测空白页,因为tableView在一开始不配置数据的时候,就会主动触发一次reloadData方法

    demo地址:轻量级UITableView的封装
    有不对的地方还请指正,觉得还不错点个小❤️❤️

    参照链接: 如何写好一个 UITableView

    相关文章

      网友评论

          本文标题:如何写UITableView解决项目中MVC架构存在的问题

          本文链接:https://www.haomeiwen.com/subject/rttukctx.html