如何在UITableViewController中分配异构数据源?

时间:2016-05-29 03:22:33

标签: ios swift uitableview

我需要使用异构数据源构建一个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]。

我想知道是否有更好的方法可以做到这一点?

2 个答案:

答案 0 :(得分:2)

我会这样做。你有你的模型类:

class Person {
    // ...
}

class City {
    // ...
}

我假设您的数据是PersonCity的混合序列,其中序列中的每个元素可能是PersonCity

据推测,每个模型都有一个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)
    }

}

注1。

您可以使用泛型减少Person扩展名和City扩展名之间的代码重复,但这可能不值得。它肯定会更难理解,也不会节省太多代码。

注2。

如果您没有拥有自定义单元格类,例如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
    }
}

注3。

响应评论者NRitH关注的是“我正在混合模型和观点”,“负责出售他们的桌面视图单元的人和城市模型类”:我不是在混合模型和观点。使PersonCity符合TableViewDatum并因此销售其单元格的扩展程序与PersonCity类定义完全分开。

很容易想象来自某些第三方框架的PersonCity类。例如,将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语句,但它们是相同的基本模式。)如果您实现任何其他特定于行的UITableViewDataSourceUITableViewDelegate方法(例如tableView:canEditRowAtIndexPath: },tableView:editActionsForRowAtIndexPath:等),你可能需要在每一个中使用类似的switch语句。您是否添加了新的模型类但忘记更新其中一个委托方法?哎呀,直到运行时才会发现。在我的设计中,您向TableViewDatum协议添加了相应的必需方法,数据源或委托方法调用该协议方法。如果您忘记在其中一个模型类中实现该方法,则编译器会标记错误。

这也是为什么我的设计没有与default: fatalError()案例相对应的原因。编译器强制类型安全性在编译时对其进行规则。

答案 1 :(得分:0)

你可以做几件事:

  1. 制定协议并使它们都符合协议,因此您没有超类问题,并且仍然具有相同的类型。
  2. 只需[AnyObject],然后让cellForRowAtIndexPath执行此操作:

    let object = data[indexpath.row]
    if let city = object as? City {
        //do something
    }else if let...