繁体   English   中英

UITableView,其控制器与ViewController分开

[英]UITableView with controller separate from ViewController

我是Swift和XCode的新手,今年夏天参加了iOS开发课程。 我们正在做的许多项目以及我看到的针对PickerViews,TableViews等UI元素的示例都在ViewController.swift文件中定义了充当主视图控制器的所有内容。 这很好用,但是我开始达到项目复杂性的地步,我真的希望我的所有代码都不会被挤到同一个Swift文件中。 我已经和一位在旁进行iOS开发的朋友交谈过,他说这是理智而合理的,并且与适当的面向对象的编程保持一致……但是我似乎无法使其正常工作。 经过反复试验,我得到了这种情况:该应用程序在模拟器中运行,出现了UITableView,但是我没有在其中填充条目。 当所有代码都在ViewController中时,我可以使它正常工作,但是一旦我开始尝试创建一个新的控制器类并将该类的实例作为UITableView的dataSource / delegate时,我什么都没得到。 我觉得我在这里缺少对Swift的一些核心了解,或者在XCode中使用Interface Builder做了一些错误。

我的最终结果应该是其中包含三个条目的UITableView。 目前,我正在获取没有条目的UITableView。 我将跟着一些我已经Google搜索过的不同示例,但主要是另一个SO问题: Swift的UITableView示例

ViewController.swift:

import UIKit

class ViewController: UIViewController{

    @IBOutlet var stateTableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        var viewController = StateViewController()
        self.stateTableView.delegate = viewController
        self.stateTableView.dataSource = viewController
    }
}

StateViewController.swift:

import UIKit

class StateViewController: UITableViewController{
    var states = ["Indiana", "Illinois", "Nebraska"]

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
    {
        return states.count;
    }

    func tableView(cellForRowAttableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    {
        let cell = UITableViewCell(style:UITableViewCellStyle.default, reuseIdentifier:"cell")
        cell.textLabel?.text = states[indexPath.row]
        return cell
    }
}

在XCode中,我将UITableView连接到了View Controller; 出口设置为dataSourcedelegate ,引用出口为stateTableView

我没有任何错误; 我确实在ViewController.swift中的`var viewController = StateViewController()'语句上得到警告,在该语句中我希望我使用一个常量,但是将其切换为常量不会改变行为(这是应该的,我假设)。

最初,我以为该错误是在StateViewController.swift文件中,我没有在该文件中创建符合UITableViewDataSource或UITableViewDelegate协议的对象,但是如果我将它们添加到类语句中,我会立即收到诸如“冗余一致性'StateViewController'到协议'UITableViewDataSource''“-我正在读这是因为从UITableViewController继承也自动继承了其他协议。

我尝试的最后一件事是在StateViewController的tableView函数中引用self.states ,但是我敢肯定,Swift中的self的工作方式与Python中的self.states相同,并且感觉就像我只是在尝试添加魔术字一样点。

就我目前有限的知识斯威夫特能带我调查过,所以任何回答,解释我做错了什么 ,而不是仅仅告诉我要解决什么将是非常赞赏。

您的问题是由内存管理问题引起的。 您有以下代码:

override func viewDidLoad() {
    super.viewDidLoad()
    var viewController = StateViewController()
    self.stateTableView.delegate = viewController
    self.stateTableView.dataSource = viewController
}

考虑一下viewController变量的生命周期。 当到达viewDidLoad的末尾时,它结束。 并且由于表视图的dataSourcedelegate属性weak ,因此在viewDidLoad结束后,没有强有力的参考来使StateViewController保持活动状态。 由于引用弱,结果是在到达viewDidLoad的末尾后,表视图的dataSourcedelegate属性恢复为nil

解决方案是创建对StateViewController的强引用。 通过向视图控制器类添加属性来执行此操作:

class ViewController: UIViewController{
    @IBOutlet var stateTableView: UITableView!
    let viewController = StateViewController()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.stateTableView.delegate = viewController
        self.stateTableView.dataSource = viewController
    }
}

现在您的代码可以使用了。

一旦完成该工作,请查看Ahmed F的答案。绝对没有理由将StateViewController类用作视图控制器。 从任何意义上说,它都不是视图控制器。 它只是一个实现表视图数据源和委托方法的类。

虽然我觉得它更易于阅读和理解以实现在同一视图 - 控制数据源/委托方法,什么是你想才达到同样有效。 然而, StateViewController并不一定是一个子类UITableViewController (我认为这是你误解它的一部分),例如(改编自另一个答案对我来说):

import UIKit

// ViewController File
class ViewController: UIViewController {
    var handler: Handler!

    @IBOutlet weak var tableView: UITableView!
    override func viewDidLoad() {
        super.viewDidLoad()

        handler = Handler()
        tableView.dataSource = handler
    }
}

处理程序类:

import UIKit

class Handler:NSObject, UITableViewDataSource {
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("myCell")

        cell?.textLabel?.text = "row #\(indexPath.row + 1)"

        return cell!
    }
}

您试图将数据源和UITableView的委托设置为UITableViewController 正如@Ahmad在同一个类(即ViewController)中提到的那样,它更易于理解,您可以采用清晰的方法将数据源和UITableView委托与UIViewController分开。 您可以优先使用NSObject的子类,并将其用作UITableView数据源和delgate类。

您还可以使用container view并嵌入UITableViewController 您所有的表格视图代码都将移至UITableViewController子类,因此将表格视图逻辑与View Controller分开

希望能帮助到你。 快乐编码!

您还可以使用适配器通过超级干净的代码来解决此问题,并且易于理解,例如

protocol MyTableViewAdapterDelegate: class {
    func myTableAdapter(_ adapter:MyTableViewAdapter, didSelect item: Any)
}

class MyTableViewAdapter: NSObject {
    private let tableView:UITableView
    private weak var delegate:MyTableViewAdapterDelegate!

    var items:[Any] = []

    init(_ tableView:UITableView, _ delegate:MyTableViewAdapterDelegate) {
        self.tableView = tableView
        self.delegate = delegate
        super.init()
        tableView.dataSource = self
        tableView.delegate = self
        tableView.rowHeight = UITableViewAutomaticDimension
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
    }

    func setData(data:[Any]) {
        self.items = data
        reloadData()
    }

    func reloadData() {
        tableView.reloadData()
    }
}

extension MyTableViewAdapter: UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = "Hi im \(indexPath.row)"
        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        delegate?.myTableAdapter(self, didSelect: items[indexPath.row])
    }
}

使用 即插即用

class ViewController: UIViewController, MyTableViewAdapterDelegate {

    @IBOutlet var stateTableView: UITableView!
    var myTableViewAdapter:MyTableViewAdapter!

    override func viewDidLoad() {
        super.viewDidLoad()
        myTableViewAdapter = MyTableViewAdapter(stateTableView, self)
    }

    func myTableAdapter(_ adapter: MyTableViewAdapter, didSelect item: Any) {
        print(item)
    }
}

在项目中分离这些问题的方式是,通过创建一个类来跟踪应用程序的状态并对数据执行所需的操作。 此类负责获取实际数据(创建硬编码或从持久性存储获取数据)。 这是一个真实的例子:

import Foundation

class CountriesStateController {

    private var countries: [Country] = [
        Country(name: "United States", visited: true),
        Country(name: "United Kingdom", visited: false),
        Country(name: "France", visited: false),
        Country(name: "Italy", visited: false),
        Country(name: "Spain", visited: false),
        Country(name: "Russia", visited: false),
        Country(name: "Moldova", visited: false),
        Country(name: "Romania", visited: false)
    ]

    func toggleVisitedCountry(at index: Int) {
        guard index > -1, index < countries.count else {
            fatalError("countryNameAt(index:) - Error: index out of bounds")
        }
        let country = countries[index]
        country.visited = !country.visited
    }

    func numberOfCountries() -> Int {
        return countries.count
    }

    func countryAt(index: Int) -> Country {
        guard index > -1, index < countries.count else {
            fatalError("countryNameAt(index:) - Error: index out of bounds")
        }
        return countries[index]
    }
}

然后,我创建实现UITableViewDataSource和UITableViewDelegate协议的单独的类:

import UIKit

class CountriesTableViewDataSource: NSObject {

    let countriesStateController: CountriesStateController
    let tableView: UITableView

    init(stateController: CountriesStateController, tableView: UITableView) {
        countriesStateController = stateController
        self.tableView = tableView
        self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "UITableViewCell")
        super.init()
        self.tableView.dataSource = self
    }
}

extension CountriesTableViewDataSource: UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // return the number of items in the section(s)
        return countriesStateController.numberOfCountries()
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // return a cell of type UITableViewCell or another subclass
        let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell", for: indexPath)
        let country = countriesStateController.countryAt(index: indexPath.row)
        let countryName = country.name
        let visited = country.visited
        cell.textLabel?.text = countryName
        cell.accessoryType = visited ? .checkmark : .none
        return cell
    }
}

import UIKit

protocol CountryCellInteractionDelegate: NSObjectProtocol {

    func didSelectCountry(at index: Int)
}

class CountriesTableViewDelegate: NSObject {

    weak var interactionDelegate: CountryCellInteractionDelegate?

    let countriesStateController: CountriesStateController
    let tableView: UITableView

    init(stateController: CountriesStateController, tableView: UITableView) {
        countriesStateController = stateController
        self.tableView = tableView
        super.init()
        self.tableView.delegate = self
    }
}

extension CountriesTableViewDelegate: UITableViewDelegate {

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("Selected row at index: \(indexPath.row)")
        tableView.deselectRow(at: indexPath, animated: false)
        countriesStateController.toggleVisitedCountry(at: indexPath.row)
        tableView.reloadRows(at: [indexPath], with: .none)
        interactionDelegate?.didSelectCountry(at: indexPath.row)
    }
}

这就是现在从ViewController类中使用它们很容易:

import UIKit

class ViewController: UIViewController, CountryCellInteractionDelegate {

    public var countriesStateController: CountriesStateController!
    private var countriesTableViewDataSource: CountriesTableViewDataSource!
    private var countriesTableViewDelegate: CountriesTableViewDelegate!
    private lazy var countriesTableView: UITableView = createCountriesTableView()

    func createCountriesTableView() -> UITableView {
        let tableViewOrigin = CGPoint(x: 0, y: 0)
        let tableViewSize = view.bounds.size
        let tableViewFrame = CGRect(origin: tableViewOrigin, size: tableViewSize)
        let tableView = UITableView(frame: tableViewFrame, style: .plain)
        return tableView
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        guard countriesStateController != nil else {
            fatalError("viewDidLoad() - Error: countriesStateController was not injected")
        }

        view.addSubview(countriesTableView)
        configureCountriesTableViewDelegates()
    }

    func configureCountriesTableViewDelegates() {
        countriesTableViewDataSource = CountriesTableViewDataSource(stateController: countriesStateController, tableView: countriesTableView)
        countriesTableViewDelegate = CountriesTableViewDelegate(stateController: countriesStateController, tableView: countriesTableView)
        countriesTableViewDelegate.interactionDelegate = self
    }

    func didSelectCountry(at index: Int) {
        let country = countriesStateController.countryAt(index: index)
        print("Selected country: \(country.name)")
    }
}

请注意,ViewController并未创建countryStateController对象,因此必须将其注入。 我们可以从Flow Controller,Coordinator或Presenter等中进行操作。我是从AppDelegate进行的,如下所示:

class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    let countriesStateController = CountriesStateController()

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        if let viewController = window?.rootViewController as? ViewController {

            viewController.countriesStateController = countriesStateController
        }

        return true
    }
    /* ... */
}

如果从来没有注入过它-我们会发生崩溃时间崩溃,因此我们知道我们必须立即修复它。

这是国家(地区)类:

import Foundation

class Country {

    var name: String
    var visited: Bool

    init(name: String, visited: Bool) {
        self.name = name
        self.visited = visited
    }
}

注意ViewController类的干净程度和苗条程度。 它少于50行,并且如果从Interface Builder创建表视图-它将变小8-9行。

上面的ViewController可以完成它应该做的事情,并且可以作为View和Model对象之间的中介。 该表是否显示一种类型还是多种类型的单元格并不重要,因此用于注册单元格的代码属于CountrysTableViewDataSource类,该类负责根据需要创建每个单元格。

有些人将CountrysTableViewDataSource和CountrysTableViewDelegate结合在一个类中,但是我认为这违反了单一职责原则。 这两个类都需要访问相同的DataProvider / State Controller对象,而ViewController也需要访问该对象。

请注意,View Controller现在可以知道何时调用didSelectRowAt,因此我们需要在UITableViewDelegate内部创建一个附加协议:

protocol CountryCellInteractionDelegate: NSObjectProtocol {

    func didSelectCountry(at index: Int)
}

而且我们还需要一个委托属性来使通信成为可能:

weak var interactionDelegate: CountryCellInteractionDelegate?

请注意,无论是CountrysTableViewDataSource还是CountrysTableViewDelegate类都不知道ViewController类的存在。 使用面向协议的编程-我们甚至可以消除这两个类与CountrysStateController类之间的紧密耦合。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM