在当 UITableView
或者 UICollectionView
有多个 section,或者有多个静态的 row 时,想要在其中插入或删除某个 section 或 row,简直壮观。对此,本文将针对这一问题来讨论讨论。
环境信息
macOS 10.12.1
Xcode 8.2.1
iOS 10.2
在迭代公司项目时,遇到一个要在几年前的 UITableView
中插入一个新 section 的需求。当我打开那个文件的时候,一千多行且杂乱无章的代码就不说了,遍地的 section == 1/2/3 这样的判断简直就是 bug 的聚集地,防不胜防。
首先我们来看一下 section 需要判断的地方,至少都有以下方法:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section; - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
更别说还有 UITableViewDelegate
中的 height
、 size
、 didSelected
等方法,甚至其他和业务相关的逻辑,例如跳到某个指定的 indexPath,或者点击判断等等。
如此之多的 section,没有任何一行注释标明 0、1、2、3 代表什么,对新需求或者平时维护,都是很大的挑战。
和 section 的 0、1、2、3 很像,默认的枚举值也是有序的,并且,枚举也有自己的名字,不用再用到 section 的地方都标上注释。所以,首先考虑的是枚举:
// 定义视频详情页面中,每个 section 的含义 typedef NS_ENUM(NSInteger, VideoDetailSectionIndex) { VideoDetailSectionIndexVideoInfo = 0, ///< 视频信息 section VideoDetailSectionIndexProductList, ///< 推荐购买链接 section VideoDetailSectionIndexRecommendVideo, ///< 推荐视频 section VideoDetailSectionIndexPraiseComments, ///< 最赞评论 VideoDetailSectionIndexLatestComments, ///< 最新评论 };
对应的 delegate 或者 dataSource 方法的实现就可以写为:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { switch (indexPath.section) { case VideoDetailSectionIndexVideoInfo: return 100; case VideoDetailSectionIndexProductList: // 如果有购买链接,则返回具体高度 if (self.productList.count > 0) { return 20 * self.productList.count; // 如果没有购买链接,则返回 0 } else { return 0; } ... default: break; } }
这样以来,当需求需要在某个 section 下插入一栏的时候,直接在枚举中插入值,然后在对应的方法实现中,进行实现即可。尽可能的减少了直接出现下标、不清楚 section 含义的问题。
但还有一个比较棘手的问题,即 UITableViewDataSource
中返回 section 个数的方法。当需要新增或者删除 section 时,对应 numberOfSectionsInTableView:
返回的 section 个数也应该变化,那么是否意味着,每新增一个 section,除了要改枚举值以外,还要记得改 numberOfSectionInTableView:
的返回值,并且,该方法返回值,还会直接出现数字。
如果是 Swift,我们可以直接给枚举一个方法,返回当前成员个数。但 Objective-C 其实也不赖,有一种很取巧的方式:
// 定义视频详情页面中,每个 section 的含义 typedef NS_ENUM(NSInteger, VideoDetailSectionIndex) { VideoDetailSectionIndexVideoInfo = 0, ///< 视频信息 section VideoDetailSectionIndexProductList, ///< 推荐购买链接 section VideoDetailSectionIndexRecommendVideo, ///< 推荐视频 section VideoDetailSectionIndexPraiseComments, ///< 最赞评论 VideoDetailSectionIndexLatestComments, ///< 最新评论 VideoDetailSectionIndexCount ///< section 总个数 };
可以除了有含义的 section 定义以外,再加一个成员,而最后一个成员,刚好就是 section 的个数,所以,就可以优雅的返回 section 个数了:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return VideoDetailSectionIndexCount; }
这种实现方式当然也有利有弊:
弊端: