在iOS App中构建可重用的通用UITableViewController

TableView Controller是一个必不可少的UIKit组件,几乎每个iOS应用程序都使用TableView Controller组件来显示列表中的数据。 当我们要在UITableViewController显示不同类型的数据时,大多数时候我们会创建一个新的子类来显示相关的数据类型。 这种方法有效,但是如果我们的应用程序中有许多不同类型的数据,则可能导致重复和维护困难。

我们如何解决这个问题? 一种方法是,我们可以使用Swift通用抽象数据类型使用简单抽象来创建通用UITableViewController子类,该子类可用于使用Swift通用约束配置和显示不同种类的数据。

您可以在GitHub存储库中找到并构建源项目:

alfianlosari / GenericTableViewController

通用UITableViewController实现的示例– alfianlosari / GenericTableViewController

github.com

我们创建一个名为GenericTableViewControllerUITableViewController子类,并添加2种类型的Generic TCell 。 我们添加了约束,即Cell必须是UITableViewCell子类。 T将用作数据的抽象,而Cell将被注册到UITableView并出队以将每行的数据显示为UITableViewCell

   GenericTableViewController :UITableViewController { 变量 :[T] 
var configure:(Cell,T)-> Void
var selectHandler:(T)-> Void init (items:[T],​​配置: @转义 (Cell,T)-> Void,selectHandler: @转义 (T)-> Void){
自我 .items =项目
自我 .configure =配置
自我 .selectHandler = selectHandler
超级 .init(样式:.plain)
self .tableView.register(Cell。self,forCellReuseIdentifier:“ Cell”)
} ...
}

让我们看一下初始化程序,它接受3个参数:

  1. T泛型的数组:这将被分配为驱动UITableViewDataSource的实例变量。
  2. 配置闭包:当表视图使要在每行中显示的单元格出队时,将通过T数据和Cell来调用此配置闭包。 在这里,我们设置如何使用数据显示UITableViewCell 。 (通过在参数中明确声明Cell的类型,编译器将能够隐式推断Cell的类型,只要它是UITableViewCell的子类即可)
  3. 选定的处理程序关闭。 当用户选择/点击单元格中的行时,将调用闭包并传递所选内容。 在这里,我们可以添加当用户点击一行时将被调用的逻辑或动作。

初始化程序将3个参数中的每一个分配为该类的实例变量,然后使用可重用的标识符将Cell注册到UITableView ,该标识符可用于使UITableViewCell为数据源出队。

   GenericTableViewController :UITableViewController {.... // 1 
覆盖 func tableView( _ tableView:UITableView,numberOfRowsInSection部分:Int)-> Int {
返回 items.count
} // 2
覆盖 func tableView( _ tableView:UITableView,cellForRowAt indexPath:IndexPath)-> UITableViewCell {
cell = tableView.dequeueReusableCell(withIdentifier:“ Cell”,for:indexPath) ! 细胞
item = items [indexPath.row]
配置(单元格,项)
返回单元
} // 3
覆盖 func tableView( _ tableView:UITableView,didSelectRowAt indexPath:IndexPath){
tableView.deselectRow(at:indexPath,animation: true
item = items [indexPath.row]
selectHandler(项目)
}
}

这是我们需要覆盖的UITableViewDataSourceUITableViewDelegate方法:

  1. tableView:numberOfRowsInSection:这里我们只是返回T对象数组中的数据数
  2. tableView:cellForRowAtIndexPath:我们使用可重用的标识符使UITableViewCell出队,然后将其强制转换为Cell 。 然后,我们使用索引路径行从T数组中获取数据。 之后,我们调用配置闭包,以传递该单元格以及要在显示之前对其进行自定义的数据。
  3. tableView:didSelectRowAtIndexPath:这里,我们只是使用索引路径行从数组中获取数据,然后调用选定的处理程序闭包并传递数据。

要尝试使用不同类型的对象尝试GenericTableViewController ,我们创建两个简单的结构PersonFilm 。 在每个结构内,我们创建一个静态计算变量,该变量将为每个结构返回一个硬编码的存根对象数组。

  struct Person { 

命名:字符串

静态 var stubPerson:[人员] {
返回 [
人(姓名:“马克·哈米尔”),
人(姓名:“哈里森·福特”),
人(姓名:“嘉莉·费舍尔”),
人(姓名:“ Hayden Christensen”),
人(姓名:“ Ewan McGregor”),
人(姓名:“ Natalie Portman”),
人(姓名:“利亚姆·尼森”)
]
}
} struct Film { 标题:String
releaseYear:Int 静态 var stubFilms:[Film] {
返回 [
电影(标题:“星球大战:新希望”,发行年份:1978),
电影(标题:“星球大战:帝国反击”,发行年份:1982),
电影(标题:“星球大战:绝地归来”,发行年份:1984),
电影(标题:“星球大战:幻影威胁”,发行年份:1999),
电影(标题:“星球大战:克隆人战争”,发行年份:2003),
电影(标题:“星球大战:西斯的复仇”,发行时间:2005年)]
}
}

设置Person GenericTableViewController

   personVC = GenericTableViewController(items:Person.stubPerson,配置:{(单元格:UITableViewCell,person)  
cell.textLabel?.text = person.name
}){(person) in
打印(person.name)
}

我们将使用标准的UITableViewCell Basic样式显示“ Person ”列表。 在这里,我们实例化GenericTableViewController传递了Person对象的数组。 完成闭包将标准UITableViewCell用作Cell的类型,在配置内部,我们仅使用人员名称分配textLabel文本属性。 对于所选的处理程序关闭,我们只需将所选人员的姓名打印到控制台即可。 您可以在这里看到Swift隐式类型引用的强大功能,编译器将自动用Person结构替换T泛型。

设置电影GenericTableViewController

   SubtitleTableViewCell:UITableViewCell { 
覆盖 init (样式:UITableViewCell.CellStyle,reuseIdentifier:字符串?){
超级 .init(样式:.subtitle,复用标识符: nil
}
...
}

对于Film ,我们将使用带有字幕样式的UITableViewCell来显示它。 为了做到这一点,我们需要创建覆盖默认样式的子类以使用我们称为SubtitleTableViewCell的Subtitle样式。

   filmsVC = GenericTableViewController(items:Film.stubFilms,配置:{(单元格:SubtitleTableViewCell,电影)  
cell.textLabel?.text = film.title
cell.detailTextLabel?.text =“ \(film.releaseYear)”
}){(电影)
打印(film.title)
}

我们实例化了传递Film对象数组的GenericTableViewController 。 对于配置闭包,我们将Cell参数的单元格类型显式设置为SubtitleTableViewCell ,然后在闭包内部,我们仅使用影片的标题和发行年份设置单元格textLabeldetailTextLabel文本属性。 对于所选的处理程序闭包,我们只需将所选电影的标题打印到控制台

使用UITabBarController作为容器视图控制器的最终集成

  @UIApplicationMain  AppDelegate:UIResponder,UIApplicationDelegate {变量窗口:UIWindow?  func application( _ application:UIApplication,didFinishLaunchingWithOptions launchOptions:[UIApplication.LaunchOptionsKey: Any ]?)-> Bool {//实例化人物和电影表视图控制器 
... tabVC = UITabBarController(nibName: nil ,bundle: nil
tabVC.setViewControllers([
UINavigationController(rootViewController:personVC),
UINavigationController(rootViewController:filmsVC)
],动画: false

窗口= UIWindow(框架:UIScreen.main.bounds)
窗口?.rootViewController = tabVC
窗口?.makeKeyAndVisible()

返回
}
}

为了在iOS项目中显示它,我们将使用一个UITabBarController ,其中包含GenericTableViewControllerPersonFilm实例作为ViewController。 我们将标签栏控制器设置为UIWindow根视图控制器,并将每个通用表视图控制器嵌入UINavigationController

我们终于设法使用Swift泛型为UITableViewController创建了一个abstract容器类。 这种方法确实可以帮助我们将相同的UITableViewController重用为不同类型的数据源,但仍然可以使用符合UITableViewCell的通用Cell进行自定义。 Swift泛型是一个非常了不起的范例,我们可以用来创建非常强大的抽象。 雨后春笋般快乐,Crusty long万岁。