программно переворачивайте tableView в пользовательском представлении - теряя ссылку на делегат контроллеров и источник данных - PullRequest
0 голосов
/ 05 июля 2018

Я пытаюсь изучить паттерн MVVM и пишу все свои виды программно, используя Snapkit. Я создаю меню гамбургера, которое состоит из простого tableView, и у меня есть проблема, что мой tableView в обычном представлении теряет ссылки на делегаты и источники данных на контроллере представления. Я также пытался использовать UITableViewController, но результат тот же, вот мой код:

ViewModel:

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)
        }
    }

}

И мой контроллер просмотра:

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")
    }

}

Симулятор после инициализации

Симулятор после прокрутки

Результат нажатия кнопки DELEGATE

Я учусь на нескольких уроках, и у них не было этой проблемы, но все они использовали построители интерфейсов, которых я хочу избежать. Пожалуйста, дайте мне знать, если я делаю что-то действительно неправильно, спасибо.

РЕШЕНИЕ

Я обнаружил, что совершил огромную ошибку за пределами этого показанного кода, я инициализировал SideMenuController в функции и не сохранял ссылку на него, поэтому, естественно, он автоматически деинициализировался после завершения функции. Это была действительно плохая ошибка. Спасибо за все ответы, код здесь работает, но я реорганизовал его в соответствии с ответом.

1 Ответ

0 голосов
/ 05 июля 2018

Полагаю, вы уже давно этим занимались, и похоже, что код немного закончился.

Если вы собираетесь следовать MVVM, вам нужно подумать о роли каждого компонента.

  • Модель - Массив SideMenuItem
  • ViewModel - в этом случае она совпадает с вашей моделью, поэтому вы можете обойтись без модели и просто использовать ViewModel. В более сложных примерах ViewModel сопоставляется с моделью, предоставляя данные, необходимые для представления, и выполняя любые необходимые переводы
  • Вид - фактические визуальные элементы; В этом случае просто просмотр таблицы (хотя у вас также есть кнопка для отладки)

  • Наконец, у вас все еще есть контроллер вида, который объединяет все это

ViewModel

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

View

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)
    }
}

ViewController

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


}
...