我需要使用异构数据源构建一个UITableView示例两个模型:person和city。
一个选项是将person的数组和city数组合并到AnyObject数组中,并检查存储在单元格中的对象类型。
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let someobject = self.events[indexPath.row]
var test = self.events
if someobject is Person {
print ("I am a person")
}
if someobject is City {
print ("I am a city")
}
}
另一种选择是创建一个基类,并使Person和City继承自Base:
class Person: Base {}
class City: Base {}
然后代替[AnyObject]对tableView数据源使用[Base]。
我想知道是否有更好的方法可以做到这一点?
答案 0 :(得分:2)
我会这样做。你有你的模型类:
class Person {
// ...
}
class City {
// ...
}
我假设您的数据是Person
和City
的混合序列,其中序列中的每个元素可能是Person
或City
。
据推测,每个模型都有一个UITableViewCell
子类,如下所示:
class PersonCell: UITableViewCell {
var person: Person? {
didSet {
guard let person = person else { return }
// update appearance using properties of person
}
}
}
class CityCell: UITableViewCell {
var city: City? {
didSet {
guard let city = city else { return }
// update appearance using properties of city
}
}
}
我假设您已使用表格视图注册了每个单元格类型,方法是在故事板中创建单元格作为原型,或者在表格视图中调用registerNib:forCellReuseIdentifier:
或registerClass:forCellReuseIdentifier:
。
此时您需要的是将Person
个对象和City
对象放入数组中,并为每个数据对象获取适当类型的单元格,并配置单元格与数据对象。让我们制定一个协议:
protocol TableViewDatum: class {
/// Return a fully-configured cell for displaying myself in the table view.
func cell(inTableView tableView: UITableView, forIndexPath indexPath: NSIndexPath) -> UITableViewCell
}
然后我们扩展Person
以符合协议:
extension Person: TableViewDatum {
func cell(inTableView tableView: UITableView, forIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Person", forIndexPath: indexPath) as! PersonCell
cell.person = self
return cell
}
}
我们扩展City
以符合协议:
extension City: TableViewDatum {
func cell(inTableView tableView: UITableView, forIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("City", forIndexPath: indexPath) as! CityCell
cell.city = self
return cell
}
}
此时,实现所需的UITableViewDataSource
方法非常简单:
class MyTableViewDataSource: NSObject, UITableViewDataSource {
private var data = [TableViewDatum]()
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return data[indexPath.row].cell(inTableView: tableView, forIndexPath: indexPath)
}
}
您可以使用泛型减少Person
扩展名和City
扩展名之间的代码重复,但这可能不值得。它肯定会更难理解,也不会节省太多代码。
如果您没有拥有自定义单元格类,例如Person
,并且您只使用其中一种标准单元格样式,例如UITableViewCellStyle.Subtitle
,那么你仍然可以使用这种模式。您只需在Person
扩展名中配置单元格,而不是在自定义单元格类中。例如:
extension Person: TableViewDatum {
func cell(inTableView tableView: UITableView, forIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Person", forIndexPath: indexPath)
// Assume cell style is .Subtitle.
cell.textLabel?.text = self.name
cell.detailTextLabel?.text = self.title
return cell
}
}
响应评论者NRitH关注的是“我正在混合模型和观点”,“负责出售他们的桌面视图单元的人和城市模型类”:我不是在混合模型和观点。使Person
和City
符合TableViewDatum
并因此销售其单元格的扩展程序与Person
和City
类定义完全分开。
很容易想象来自某些第三方框架的Person
和City
类。例如,将Person
替换为CNContact
(来自iOS Contacts框架),将City
替换为CLLocation
(来自iOS CoreLocations框架)。
这种模式在这种情况下同样适用:框架提供的类实现对我的应用程序的用户界面一无所知。声称类实现正在出售他们的单元格是不合理的。它是出售细胞的扩展。
有关此实施方式的更多信息,请观看WWDC 2015 Session 408: Protocol-Oriented Programming in Swift。
关于“单元格使用传递给它们的Person或City对象进行自我配置会更有意义”,这正是我所做的!我设置person
PersonCell
属性以使PersonCell
配置自身,并设置city
CityCell
属性以使CityCell
自行配置
据推测,您只想消除模型对象本身的任何传递。但正如我所说,扩展与模型对象完全分开,所以我不认为它们存在问题。
然而,让我们看看你的样子。这就是我的意思:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
switch data[indexPath.row] {
case let person as Person:
let cell = tableView.dequeueReusableCellWithIdentifier("Person", forIndexPath: indexPath) as! PersonCell
cell.person = person
return cell
case let city as City:
let cell = tableView.dequeueReusableCellWithIdentifier("City", forIndexPath: indexPath) as! CityCell
cell.city = city
return cell
default: fatalError()
}
}
你有一个switch语句。 (如果您愿意,可以使用级联的if
语句,但它们是相同的基本模式。)如果您实现任何其他特定于行的UITableViewDataSource
或UITableViewDelegate
方法(例如tableView:canEditRowAtIndexPath:
},tableView:editActionsForRowAtIndexPath:
等),你可能需要在每一个中使用类似的switch语句。您是否添加了新的模型类但忘记更新其中一个委托方法?哎呀,直到运行时才会发现。在我的设计中,您向TableViewDatum
协议添加了相应的必需方法,数据源或委托方法调用该协议方法。如果您忘记在其中一个模型类中实现该方法,则编译器会标记错误。
这也是为什么我的设计没有与default: fatalError()
案例相对应的原因。编译器强制类型安全性在编译时对其进行规则。
答案 1 :(得分:0)
你可以做几件事:
只需[AnyObject]
,然后让cellForRowAtIndexPath
执行此操作:
let object = data[indexpath.row]
if let city = object as? City {
//do something
}else if let...