您需要了解的有关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
,依此类推。 很容易产生这样的错误。 因此,将表分成几部分-因此行索引将始终在0和items.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.外部数据源/委托对象
将UITableViewDataSource
和UITableViewDelegate
实现移到单独的类(-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应用程序中看到的功能,有时让他们看到开发人员禁用它是令人沮丧的。
谢谢阅读! 我希望您觉得它有用或至少可以给您一些新鲜的想法。 力求简明扼要,但是如果您想要一些更具体的示例和/或有建议,请在下面的评论中让我知道!