实施不同的单元格类型

时间:2018-05-05 17:29:56

标签: ios swift uitableview mvvm

我需要一些帮助来弄清楚我如何能够为UITableView的每一行提供一个自定义单元格,即第一个单元格有一个" Name"标题,第二个单元格有一个" Email"标题等。

如何使用协议并同时跟踪MVVM来实现这一目标?

我目前的解决方案如下:

我有1个ViewModel,1个Controller和1个UITableViewCell atm。

struct ProfileNameViewModel
{

}

extension ProfileNameViewModel: TextPresentable
{
    var text: String
    {
        return "Name"
    }
}


import UIKit

class ProfileController: UIViewController
{
    // VARIABLES
    let profileView = ProfileView()
    let profileTableView: UITableView =
    {
        let tableView = UITableView()
        tableView.tableFooterView = UIView(frame: .zero)
        tableView.translatesAutoresizingMaskIntoConstraints = false

        return tableView
    }()

    private var profileCellViewControllers = [UIViewController]()
    let testVC = UIViewController()

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

        view.backgroundColor = UIColor.white

        navigationItem.title = "Profile"

        // profileTableView initialization
        setupProfileTableView()
    }

    private func setupProfileTableView()
    {
        profileTableView.delegate = self
        profileTableView.dataSource = self

        profileTableView.register(ProfileTableViewCell<ProfileNameViewModel>.self, forCellReuseIdentifier: ProfileTableViewCell<ProfileNameViewModel>.reuseIdentifier)

        view.addSubview(profileTableView)

        profileTableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
        profileTableView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
        profileTableView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
        profileTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
    }

    private func setupProfileViewLayout()
    {
        profileView.setupAnchors(top: view.safeAreaLayoutGuide.topAnchor,
                                 bottom: view.safeAreaLayoutGuide.bottomAnchor,
                                 left: view.leftAnchor,
                                 right: view.rightAnchor)
    }
}

extension ProfileController: UITableViewDataSource, UITableViewDelegate
{
    // Table view header setup
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
    {
        if (section == 0)
        {
            return "Profile"
        }

        return "Foo"
    }

    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
    {
        return 50
    }

    // Table view sections setup
    func numberOfSections(in tableView: UITableView) -> Int
    {
        return 2
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
    {
        if (section == 1)
        {
            return 3
        }

        return 5
    }

    // Table view cell setup
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    {
        let cell = tableView.dequeueReusableCell(withIdentifier: ProfileTableViewCell<ProfileNameViewModel>.reuseIdentifier, for: indexPath) as! ProfileTableViewCell<ProfileNameViewModel>

        let viewModel = ProfileNameViewModel()
        cell.configure(withDelegate: viewModel)

        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            print(indexPath.row)
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
    {
        return 50
    }
}


import UIKit

class ProfileTableViewCell<T>: UITableViewCell where T: TextPresentable
{

    // VARIABLES
    let containerView = UIView()
    var arrowImageView = UIImageView()
    var cellTitle: UILabel!
    var userInformationText: UILabel!

    private var delegate: T?

    // FUNCTIONS
    func configure(withDelegate viewModel: T)
    {
        // Setup relavant views
        cellTitle = UILabel()
        userInformationText = UILabel()
        arrowImageView = setupImageView(imageName: "testImage", contentMode: .scaleAspectFit, imageTintColor: .black)
        setupContainer()

        delegate = viewModel

        cellTitle?.text = viewModel.text
        cellTitle.font = viewModel.textFont
        cellTitle.textColor = viewModel.textColor

        userInformationText.text = "Test"
    }

    private func setupContainer()
    {
        addSubview(containerView)
        containerView.addSubview(cellTitle)
        containerView.addSubview(userInformationText)
        containerView.addSubview(arrowImageView)

        // Container view for each cell
        containerView.setupAnchors(top: topAnchor,
                               bottom: bottomAnchor,
                               left: leftAnchor,
                               right: rightAnchor,
                               padding: .init(top: 3, left: 0, bottom: 3, right: 0))

        // Views inside the container view
        cellTitle.setupAnchors(top: nil,
                           bottom: containerView.bottomAnchor,
                           left: containerView.leftAnchor,
                           right: nil,
                           padding: .init(top: 0, left: 15, bottom: 7, right: 0))

        arrowImageView.setupAnchors(top: nil,
                                bottom: containerView.bottomAnchor,
                                left: nil,
                                right: containerView.rightAnchor,
                                padding: .init(top: 0, left: 0, bottom: 13, right: 5),
                                size: .init(width: 10, height: 10))

        userInformationText.setupAnchors(top: nil,
                                     bottom: containerView.bottomAnchor,
                                     left: nil,
                                     right: arrowImageView.leftAnchor,
                                     padding: .init(top: 0, left: 0, bottom: 7, right: 10))
    }

    private func setupImageView(imageName: String, contentMode: UIViewContentMode, imageTintColor: UIColor) -> UIImageView
    {
        let arrowImage: UIImage = UIImage(named: imageName)!
        let arrowImageView: UIImageView = UIImageView(image: arrowImage)
        arrowImageView.contentMode = contentMode
        arrowImageView.imageTintColor(color: imageTintColor)

        return arrowImageView
    }
}

我试图按照this链接提供的示例来实现正确的解决方案,但是,只添加了一个单元格,我无法理解如何在后续实现第二个单元格行。我是否应该为我想要使用的每个单独的单元格创建一个新的ViewModel?如果是这种情况,请您提供一个例子吗?

1 个答案:

答案 0 :(得分:0)

尝试使用“框”来包含您的单元格生成代码。在需要单元格之前不要注册单元ID。提出一个工作示例有点困难所以请以下面的代码为例:

import UIKit

protocol CellBox {

    func getCell(for tableView: UITableView) -> UITableViewCell
}

struct MyCellBox: CellBox {

    private let id = "my_cell_id"

    func getCell(for tableView: UITableView) -> UITableViewCell {
        if let cell = tableView.dequeueReusableCell(withIdentifier: id) {
            return cell
        } else {
            return UITableViewCell(style: .default, reuseIdentifier: id)
        }
    }
}

class MyDataSource: NSObject, UITableViewDataSource {

    var boxes: [CellBox] = []
    // This is just a hack for compiling...
    // ...the idea is that you need to have a reference to your real table view somewhere
    let tableView = UITableView()

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        return boxes.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        return boxes[indexPath.row].getCell(for: tableView)
    }
}

我建议您将问题缩小为更易于管理的示例,但是作为上述MyCellBox的改进,有可能接近您想要的内容:

struct MyCellBox: CellBox {

    private let id = "my_cell_id"

    func getCell(for tableView: UITableView) -> UITableViewCell {
        if let cell = tableView.dequeueReusableCell(withIdentifier: id) {
            return cell
        } else {
            let viewModel = MyViewModel(text: "WOW", font: UIFont(name: "Whatever", size: 10)!, color: UIColor.black)
            let cell = ProfileTableViewCell<MyViewModel>()
            cell.configure(withDelegate: viewModel)
            return UITableViewCell(style: .default, reuseIdentifier: id)
        }
    }
}