ios - 以编程方式自定义 View 中的 swift tableView - 丢失对 Controller 委托(delegate)和数据源的引用

标签 ios swift mvvm weak

我正在尝试学习 MVVM 模式并使用 Snapkit 以编程方式编写我的所有 View 。我正在创建由简单的 tableView 组成的汉堡菜单,但我有一个问题,即自定义 View 中的 tableView 正在丢失 View Controller 上的委托(delegate)和数据源引用。我也尝试使用 UITableViewController,但结果是相同的,这是我的代码:

View 模型:

class SideMenuViewModel {

    let cellId = "SideMenuCellId"
    weak var delegate: SideMenuViewModelDelegate?
    private let cells: [SideMenuItemStruct] = [SideMenuItemStruct(type: .allDogs, title: "ALL DOGOS"),
                                           SideMenuItemStruct(type: .randomDog, title: "RANDOM DOGO")]

    init(delegate: SideMenuViewModelDelegate) {
        self.delegate = delegate
    }

    var numberOfRows: Int {
        return cells.count
    }

    func selectedMenuItem(indexPath: IndexPath) {
        switch SideMenuItemsEnum(rawValue: indexPath.row) {
        case .allDogs?:
            delegate?.selectedMenuItem(selectedItem:        SideMenuItemsEnum.allDogs)
        case .randomDog?:
            delegate?.selectedMenuItem(selectedItem: SideMenuItemsEnum.randomDog)
        default:
            print("error when choosing menu item")
        }
    }

    func cellForRow(_ tableView: UITableView, indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as? SideMenuCell else {
            fatalError("could not deque Side menu cell")
        }

        cell.selectionStyle = .none
        cell.setUpCell(sideMenuItem: cells[indexPath.row])
        return cell
        }
}

查看:

class SideMenuView: UIView {

    var sideMenuTableView = UITableView()

    let sideMenuButton = UIButton(type: .system)

    weak var delegate: UITableViewDelegate? {
        get {
            return sideMenuTableView.delegate
        }
        set {
            sideMenuTableView.delegate = newValue
        }
    }

    weak var dataSource: UITableViewDataSource? {
        get {
            return sideMenuTableView.dataSource
        }
        set {
            sideMenuTableView.dataSource = newValue
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        initUI()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func awakeFromNib() {
        super.awakeFromNib()
    }

    private func initUI() {
        addSubview(sideMenuButton)
        addSubview(sideMenuTableView)

        setUpSideMenuButton()
        setUpSideMenuTableView()
    }

    private func setUpSideMenuButton() {
        sideMenuButton.setTitle("DELEGATE", for: .normal)
        sideMenuButton.addTarget(self, action: #selector(buttonPrint), for: .touchUpInside)
        sideMenuButton.snp.makeConstraints { (make) in
            make.top.equalTo(self)
            make.centerX.equalTo(self)
        }
    }

    @objc func buttonPrint() {
        print("delegate: \(String(describing: sideMenuTableView.delegate)), data source: \(String(describing: sideMenuTableView.dataSource))")
    }

    private func setUpSideMenuTableView() {
        sideMenuTableView.snp.makeConstraints { (make) in
            make.top.equalTo(sideMenuButton.snp.bottom)
            make.bottom.equalTo(self)
            make.left.equalTo(self)
            make.right.equalTo(self)
        }
    }

}

还有我的 View Controller :

class SideMenuController: UIViewController {

    fileprivate let viewModel: SideMenuViewModel

    fileprivate var sideMenuView: SideMenuView {
        return view as! SideMenuView
    }

    init(viewModel: SideMenuViewModel) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }

    override func loadView() {
        let sideMenuView = SideMenuView()
        sideMenuView.sideMenuTableView.delegate = self
        sideMenuView.sideMenuTableView.dataSource = self
        view = sideMenuView
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        sideMenuView.sideMenuTableView.register(SideMenuCell.self, forCellReuseIdentifier: viewModel.cellId)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

extension SideMenuController: UITableViewDelegate, UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return viewModel.numberOfRows
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        return viewModel.cellForRow(tableView, indexPath: indexPath)
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        viewModel.selectedMenuItem(indexPath: indexPath)
        print("awd")
    }

}

Simulater after init

Simulator after scroll

DELEGATE button tapped result

我从一些教程中学习,他们没有遇到这个问题,但他们都使用界面构建器,我想避免这种情况。如果我做错了什么,请告诉我,谢谢。

解决方案

我发现,除了显示的代码之外,我犯了一个非常大的错误,我在函数中初始化了 SideMenuController 并且没有保留对它的引用,所以自然地它在函数结束后自动取消初始化。这是一个非常严重的错误。感谢您的所有答案,这里的代码可以正常工作,但我根据答案重构了它。

最佳答案

我猜你已经对此进行了一段时间的黑客攻击,看起来代码已经变得有点乱了。

如果您要遵循 MVVM,那么您需要考虑每个组件的角色。

  • 模型 - SideMenuItem 数组
  • ViewModel - 在这种情况下,它与您的模型相同,因此您可以省去模型而只使用 ViewModel。在更复杂的示例中,ViewModel 映射回模型,公开 View 所需的数据并执行任何所需的转换
  • View - 实际的视觉元素;在本例中只是一个表格 View (尽管您还有一个用于调试的按钮)

  • 最后,您仍然拥有将所有内容组合在一起的 View Controller

ViewModel

struct SideMenuViewModel {
    let items = [SideMenuItemStruct(type: .allDogs, title: "ALL DOGOS"),                                           
                 SideMenuItemStruct(type: .randomDog, title: "RANDOM DOGO")]
}

查看

class SideMenuView: UIView { 

    weak var viewModel: SideMenuViewModel?
    weak var delegate: SideMenuViewDelegate? // Was SideMenuViewModelDelegate

    private let sideMenuButton = UIButton(type: .system)
    private var sideMenuTableView = UITableView()
    private let cellId = "YourCellID"

    override init(frame: CGRect) {
        super.init(frame: frame)
        initUI()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func awakeFromNib() {
        super.awakeFromNib()
    }

    private func initUI() {
        addSubview(sideMenuButton)
        addSubview(sideMenuTableView)

        setUpSideMenuButton()
        setUpSideMenuTableView()
    }

    private func setUpSideMenuButton() {
        sideMenuButton.setTitle("DELEGATE", for: .normal)
        sideMenuButton.addTarget(self, action: #selector(buttonPrint), for: .touchUpInside)
        sideMenuButton.snp.makeConstraints { (make) in
            make.top.equalTo(self)
            make.centerX.equalTo(self)
        }
    }

    @objc func buttonPrint() {
        print("delegate: \(String(describing: sideMenuTableView.delegate)), data source: \(String(describing: sideMenuTableView.dataSource))")
    }

    private func setUpSideMenuTableView() {
        sideMenuTableView.snp.makeConstraints { (make) in
            make.top.equalTo(sideMenuButton.snp.bottom)
            make.bottom.equalTo(self)
            make.left.equalTo(self)
            make.right.equalTo(self)
        }
        sideMenuTableView.datasource = self
        sideMenuTableView.delegate = self
        sideMenuTableView.register(SideMenuCell.self, forCellReuseIdentifier: cellId)
    }

}

extension SideMenuView: UITableViewDelegate, UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return viewModel?.numberOfRows ?? 0
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as? SideMenuCell else {
            fatalError("could not deque Side menu cell")
        }

        cell.selectionStyle = .none
        cell.setUpCell(sideMenuItem: self.viewModel!.items[indexPath.row])
        return cell

    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let menuItem = self.viewModel!.items[indexPath.row]
        self.delegate?.didSelect(menuItem)
    }
}

View Controller

class SideMenuController: UIViewController {

    fileprivate let viewModel: SideMenuViewModel

    fileprivate var sideMenuView: SideMenuView {
        return view as! SideMenuView
    }

    override func loadView() {
        let sideMenuView = SideMenuView()
        sideMenuView.delegate = self
        sideMenuView.viewModel = viewModel
        view = sideMenuView
    }

    init(viewModel: SideMenuViewModel) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

extension SideMenuController: SideMenuViewDelegate {


    // TODO: Implement delegate method for menu selection


}

关于ios - 以编程方式自定义 View 中的 swift tableView - 丢失对 Controller 委托(delegate)和数据源的引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51198657/

相关文章:

应用缩放变换时的 ios 生涩动画

swift - 如何快速将单词分解为字符

c - 访问 View 中的 DataContext 属性

c# - 如何绑定(bind)到文本框的 CaretIndex aka 光标位置

ios - 从 url 获取的图像以及如何在 iPhone 中减小图像大小

ios - 背景图像显示在模拟器上但不显示在设备上?

ios - 打印说明时更改日期格式

swift - 每次呈现新场景时,SpriteKit 内存都会增加

ios - 当应用程序通过通知启动时使用推送通知负载

c# - slider 的值滞后于其拇指的当前位置