以编程方式在UITableViewController中切换UITableView

时间:2017-12-28 20:46:19

标签: ios swift uitableview uikit

注意:我知道这是重新发布的;我几天前发布了同样的问题sans-code,可以理解的是,它已经关闭了。我编辑了问题以添加一些相关的片段,但问题没有重新打开,所以我在这里重新发布。如果这不是正确的方法,请告诉我!

我有一个具有两种模式的应用程序,并且每种模式的设置屏幕略有不同(其中一种模式中的一个附加部分,行数,不同UserDefaults键等的一些差异)。在过去,我使用switch和if语句实现了这一点,但为了使事情更易于维护,我正在研究将这两种模式分解为各自不同类别的方法。最初我考虑过制作两个单独的UITableViewController子类,但是我很难想到如何使用故事板等。然后我考虑使用两个单独的UITableView子类,并根据viewDidLoad中的模式选择要显示的子类。

但是,我遇到了这种方法的问题。我进行了设置,以便控制器的cellForRow方法调用TableView的cellForRow方法,但事情就是这样。在尝试执行dequeueReusableCell时,应用程序会因为模糊的问题而崩溃,EXC_BAD_INSTRUCTION"那条线上的错误。

以下是一些相关代码:

ViewController.swift

...
override func viewDidLoad()
{
    super.viewDidLoad()

    ...

    tableView = SRScaleSettingsTableView()
}
...
override func tableView(_ tableView: UITableView?, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
    return (tableView?.cellForRow(at: indexPath))!
}

SRScaleSettingsTableView.swift

override func cellForRow(at indexPath: IndexPath) -> UITableViewCell?
{
    ...
        switch indexPath.section
        {
        case 0:
            ...
            let switchCell = dequeueReusableCell(withIdentifier: "SwitchCell") as! SRSwitchCell     
            ^ Debugger breaks on that line with EXC_BAD_INSTRUCTION
            ...

            return switchCell
        ...
        }
}

关于会导致这种情况的任何想法?我的方法是否正确;有更好的方法吗?

2 个答案:

答案 0 :(得分:1)

您可以保留单个UITableView类(您可能根本不需要子类化UITableView)和单个UIViewController子类。创建两个实现UITableViewDataSource协议的类(也可能是UITableViewDelegate)。这两个类可以以完全不同的方式实现各种委托/数据源方法(例如cellForRowAtIndexPath,numberOfRowsInSection,didSelectRow),以适应您的应用程序需要运行的不同模式。

protocol SettingsSource: UITableViewDelegate, UITableViewDataSource {
}

class SettingsSourceForModeA: NSObject, SettingsSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int)...
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)...
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)...
}

class SettingsSourceForModeB: NSObject, SettingsSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int)...
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)...
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)...
}

class SettingsViewController: UIViewController {
    @IBOutlet tableView: UITableView!

    var source: SettingsSource! {
        didSet {
            tableView.dataSource = source
            tableView.delegate = source
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // whatever logic is appropriate to determine the mode
        if appIsRunningInModeA() {
            source = SettingsSourceForModeA()
        } else {
            source = SettingsSourceForModeB()
        }
    }
}

上面代码中的关键细节是SettingsViewController中的source变量 - source的值基于应用程序运行的模式,并确定将使用哪个类作为表视图的数据源。

故事板设置很简单:一个场景SettingsViewController,以及该场景中的单个股票UITableView。

请注意,上面的SettingsViewController是UIViewController子类,而不是UITableViewController,因为数据源和委托协议是在单独的类中实现的,并在运行时确定。这将要求您手动连接故事板中的tableView插座。但是,您不会在故事板中连接UITableView的dataSource和委托插座。相反,它在运行时完成,如上面的示例代码所示。

请注意,您可能不需要实现UITableViewDelegate,在这种情况下,您可以忽略上面示例代码中对UITableViewDelegate及其方法的引用。或者,如果UITableViewDelegate实现(例如didSelectRow方法)对于您的应用程序可以运行的两种模式是相同的,您可以在视图控制器类中实现它,在这种情况下,您可以连接{{1您的表的出口直接在故事板中查看您的视图控制器。

答案 1 :(得分:0)

您对UITableView和UITableViewController如何协同工作有误解。 UITableView需要一个UITableViewDataSource来为它提供底层数据的细节(部分数量,行数和实际单元格等)。这就是UITableViewController的作用(它符合UITableViewDataSource)。因此,如果为tableView调用cellForRow,那么它将调用它的数据源cellForRow方法来获取它。

所以在你的代码中执行此操作时:     return(tableView?.cellForRow(at:indexPath))!

您的表视图调用其数据源,即UITableViewController并调用表视图cellForRow,依此类推。您刚刚进入了一个递归循环,最终会因您看到的错误而被终止。

至于你的整体方法,我会选择两条UITableViewControllers路由,因为它分隔了两者之间的不同逻辑,使得理解和维护更容易,并且还允许更多的重用。

至于如何使用故事板,它在很大程度上取决于你如何在两种模式之间切换,但实质上你可以设置segue在两个控制器之间切换。