您需要了解的有关iOS UITableView的所有信息

尽管已经写了有关如何正确使用UITableView ,但新开发人员甚至是经验丰富的开发人员仍然偶尔会遇到一些问题。 关于如何使与UITableView相关的代码更短,更好和更易读的内容,有很多有趣的文章以及方便的示例和最佳实践,但是以某种方式,我还没有看到所有建议的总览。

因此,我决定在这里列出至少在使用UITableView时要遵循的所有原则,但是如果您有任何添加或分享的方式,请在下面的评论部分与我联系🙂

0.分成几节

假设您有一些要使用UITableView实现的Profile屏幕:

第一个建议很简单:将表分成几个部分,以使用不同类型的单元格。

  如果 indexPath.section == ProfileSectionIndex { 
//对个人资料进行处理
}
如果 indexPath.section == InfoSectionIndex {
整数索引= indexPath.row
//用info [index]做点什么
}
如果 indexPath.section == FriendsSectionIndex {
整数索引= indexPath.row
//与朋友做某事[index]
}

比这更有意义,更容易使用:

  如果 indexPath.row == 0 { 
//个人资料
}
如果 indexPath.row> 0 && indexPath.row <info.count + 1 {
int index = indexPath.row-1 //我看到了很多次T_T
//信息部分
}
如果 indexPath.row> info.count {
int index = indexPath.row-info.count-1
//朋友部分
}

即使将您分为两个部分(仅个人资料好友) ,它仍然会使您的代码“难以修改”。 假设您突然需要在indexPath.row — 2下添加另一行-您的下一个单元索引将变为indexPath.row — 2 ,经过进一步修改indexPath.row — 3 ,然后又返回indexPath.row — 1 ,依此类推。 很容易产生这样的错误。 因此,将表分成几部分-因此行索引将始终在0items.count之间。

1.正确识别您的部分/行

使用enum和常量正确标识节/行,以使代码可扩展和可读:

  枚举 ProfileSections: Int { 
case Profile = 0,
信息,
友人
}

因此,您的代码具有有意义的外观:

  如果 indexPath.section == ProfileSections.Profile.rawValue { 
//个人资料部分
}
如果 indexPath.section == ProfileSections.Info.rawValue {
//信息部分
}
如果 indexPath.section == ProfileSections.Friends.rawValue {
//朋友部分
}

另外,由于代码是通过这种方式编写的,因此您现在可以轻松修改UITableView部分,而无需更改现有代码:

  枚举 ProfileSections: Int { 
case Profile = 0,
生物, //新部分
信息,
友人
}
// ...
如果 indexPath.section == ProfileSection.Bio.rawValue {
//我们新的生物部分
}

现在,您可以轻松添加和/或删除部分。

2.使用单元格的类名作为重用标识符

明智的做法是将类的名称连接到与UITableView相关的代码,如下所示:

  tableView.register(ProfileCell。self, 
forCellReuseIdentifier :“ \( ProfileCell。self )”)
tableView.register(ProfileInfoCell。self,
forCellReuseIdentifier :“ \(ProfileInfoCell。self)”)
tableView.register(ProfileContactCell。self,
forCellReuseIdentifier :“ \( ProfileContactCell。self )”)
// ...
如果 indexPath.section == ProfileSections.Profile.rawValue {
profileCell = tableView.dequeueReusableCell(
withIdentifier :“ \( ProfileCell。self )”,
for :indexPath)
// ...
}
如果 indexPath.section == ProfileSections.Info.rawValue {
profileInfoCell = tableView.dequeueReusableCell(
withIdentifier:“ \(ProfileInfoCell。self)”,
用于:indexPath)
// ...
}
如果 indexPath.section == ProfileSections.Friends.rawValue {
profileContactCell = tableView.dequeueReusableCell(
withIdentifier :“ \( ProfileContactCell。self )”,
for :indexPath)
// ...
}

因此,无论何时替换 / 删除 / 添加新的单元格类型,编译器都会突出显示代码中您注册或使用此单元格的所有位置。

3.尝试使更新代码远离cellForRow方法

通常,此方法是与UITableView相关的代码中最重的方法,因为它不仅应为您提供不同类型的单元格,而且还应提供自定义功能。 因此,您可以通过两种方式减轻重量:

3.1。 通过将更新代码保留在单元实现中

因为单元格通常表示某些数据实体,所以我们可以向其中添加一些update方法并在其中进行所有自定义:

  如果 indexPath.section == ProfileSections.Profile.rawValue { 
// ...
profileCell.update( profile :profile)
返回 profileCell
}
如果 indexPath.section == ProfileSections.Info.rawValue {
// ...
profileInfoCell.update( info :profile.info [indexPath.row])
返回 profileInfoCell
}
如果 indexPath.section == ProfileSections.Friends.rawValue {
// ...
profileContactCell.update(
联系人 :profile.friends [indexPath.row]

返回配置文件ContactCell
}

因此,在这种情况下,我们可以看到表结构没有每个单元的表示逻辑。

3.2。 通过使用单独的方法来获取和自定义单元格

如果显示单元格时严重依赖ViewController某些数据,则可以为每种单元格类型定义新方法,如下所示:

  func profileInfoCell( tableView :UITableView, indexPath :IndexPath) 
-> ProfileInfoCell {
//出队,自定义和返回单元格
}
  //因此在cellForRow:方法中,您可以像这样使用它: 
如果 indexPath.section == ProfileSections.Profile.rawValue {
返回 profileInfoCell(
tableView :tableView,
indexPath :indexPath

}

4.使用indexPathForSelectedRow(indexPathsForSelectedRows)方法

有时,您可以避免使用indexPathForSelectedRow将选定的行作为参数从didSelectRow方法传递给其他方法:

  func tableView(_ tableView :UITableView,didSelectRowAt indexPath :IndexPath){ 
如果 indexPath.section == ProfileSections.Friends.rawValue {
onFriendSelected()
}
}
  func onFriendSelected(){ 
index = tableView.indexPathForSelectedRow?.row
}

而不是使用额外的参数来传递选定的行索引,例如本示例:

  func tableView(_ tableView :UITableView,didSelectRowAt indexPath :IndexPath){ 
如果 indexPath.section == ProfileSections.Friends.rawValue {
onFriendSelected(indexPath.row)
}
}
  func onFriendSelected( index :Int){ 
//索引
}

5.外部数据源/委托对象

UITableViewDataSourceUITableViewDelegate实现移到单独的类(-es)中也是一个好习惯:

   ProfileViewController:UIViewController { 
var tableDataSource:ProfileTableDataSource
var tableDelegate:ProfileTableDelegate
  覆盖 func viewDidLoad(){ 
super.viewDidLoad()
  tableDataSource = ProfileTableDataSource( profile :profile) 
tableDelegate = ProfileTableDelegate()
  tableView.dataSource = tableDataSource 
tableView.delegate = tableDelegate
}
// ...

它可以提高代码的整体可读性,因为其中大多数–仅仅是tableview的实用程序方法。 同样,它将为您的代码增加更多的灵活性,因为您可以替换/删除/添加对象并使用它做一些其他事情。

当表具有逻辑上非常分开的部分时,您甚至可以定义多个DataSource对象,如下所示:

  枚举 ProfileSections: Int { 
case Profile = 0,
信息,
友人
}
   dataSources: 数组  = [ 
ProfileTableDataSource(),
ProfileInfoTableDataSource(),
ProfileFriendsTableDataSource()
]

但是在这种情况下,您的控制器应该自己实现相同的协议,并根据它的哪个部分将调用重定向到DataSource对象,或者您可以将所有这些逻辑移到将为您处理的单独对象上:

   multiDataSource = MultiDataSource([ 
ProfileTableDataSource(),
ProfileInfoTableDataSource(),
ProfileFriendsTableDataSource()
])

因此,尝试尝试一下。 对于我来说-多个数据源仅对我有利,而对其他数据则对我来说是两次-迫使我重写这一部分。

6.不要让您的表死于用户输入

在本文末尾,给您的一个小提示(对于用户应使用轻击手势进行交互的单元格),请确保未将UITableViewCellSelectionStyle设置为none ,并在didSelectRow方法的末尾调用tableView.deselectRow

  tableView.deselectRow( at :indexPath,animation:true) 

同样不要关闭表中的Bounce功能,因为该功能已经成为用户期望在iOS应用程序中看到的功能,有时让他们看到开发人员禁用它是令人沮丧的。

谢谢阅读! 我希望您觉得它有用或至少可以给您一些新鲜的想法。 力求简明扼要,但是如果您想要一些更具体的示例和/或有建议,请在下面的评论中让我知道!