使用 Swift 的带有 MVVM 的 UITableView

Posted

技术标签:

【中文标题】使用 Swift 的带有 MVVM 的 UITableView【英文标题】:UITableView with MVVM using Swift 【发布时间】:2017-12-25 15:29:48 【问题描述】:

我正在使用UITableView 在 Swift 中开发 MVVM 架构。为此,我创建了示例表视图。

谁能建议我是否正确或需要做任何其他改进?

以下是此架构的类。

    ViewController - 包含 UITableView 及其 delegatedatasource 方法。

    class ViewController: UIViewController 
        let PRODUCT_CELL_IDENTIFIER = "ProductCellIdentifier"
    
        @IBOutlet weak var productTableView: UITableView!
    
        var productViewModel: ProductViewModel = ProductViewModel()
    
    
    //UITableView Delegate Methods
    extension ViewController 
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
            return productViewModel.numberOfRowsInSection()
        
    
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
            let cell = tableView.dequeueReusableCell(withIdentifier: PRODUCT_CELL_IDENTIFIER) as! ProductTableViewCell
            let product = productViewModel.productsArray[indexPath.row]
            cell.productName.text = product.name
            cell.productQuantity.text = "\(product.quantity)"
            return cell
        
    
    

    ProductViewModel: - 这是 ViewModel 类。

    class ProductViewModel: NSObject 
        var productsArray = Array<Product>()
    
        override init() 
            let product1 = Product(name: "Prodcut1", image_url: "", quantity: 2)
            let product2 = Product(name: "Prodcut2", image_url: "", quantity: 3)
            productsArray.append(product1)
            productsArray.append(product2)
        
    
        func numberOfRowsInSection() -> Int 
            return productsArray.count
        
    
    

    Product - 这是模型类

    class Product: NSObject 
        var name: String
        var image_url: String
        var quantity: Int
    
        init(name: String, image_url: String, quantity: Int) 
            self.name = name
            self.image_url = image_url
            self.quantity = quantity
        
    
    

    ProductTableViewCell - 这是 UITableViewCell 类

    class ProductTableViewCell: UITableViewCell 
        @IBOutlet weak var productQuantity: UILabel!
        @IBOutlet weak var productName: UILabel!
        @IBOutlet weak var productImageView: UIImageView!
    
    

【问题讨论】:

您正在考虑在 View/ViewController 中获取与 MVVM 概念相矛盾的模型数组,View 不应访问模型,它应该从视图模型中获取所需的一切。 【参考方案1】:

您做得很好,但您甚至可以通过添加以下功能来改进您的产品模型以获取直接模型数组。当您从 Web Api 响应创建数组时,它非常有用。

class Product : NSObject
    

        var imgUrl : String!
        var name : String!
        var quantity : Int!

        init(dictionary: [String:Any])
        
            imgUrl = dictionary["img_url"] as? String
            name = dictionary["name"] as? String
            quantity = dictionary["quantity"] as? Int
        
        init(name: String, image_url: String, quantity: Int)
        
        self.name = name
        self.imgUrl = image_url
        self.quantity = quantity
        
        public class func modelsFromArray(array:[[String:Any]]) -> [Product]
        
            var models:[Product] = []
            for item in array
            
                models.append(Product.init(dictionary:item))
            
            return models
        
    

像用法一样

let product1 = Product(name: "Prodcut1", image_url: "", quantity: 2) //Normal Case

    let productList:[[String:Any]] =
        [
        ["name":"Jaydeep","img_url":"xyz","quantity":1],
        ["name":"Jaydeep","img_url":"xyz","quantity":2],
        ["name":"Jaydeep","img_url":"xyz","quantity":3],
        ["name":"Jaydeep","img_url":"xyz","quantity":4],
        ["name":"Jaydeep","img_url":"xyz","quantity":5],
        ["name":"Jaydeep","img_url":"xyz","quantity":6]
        ]
    //Assign Direct Dictionary to Get Array Of Models
/* Very useful when productList is dictionary from server response*/
    let productArray:[Product] = Product.modelsFromArray(array: productList)

而且你的 Cell 类也被改进了

class ProductTableViewCell: UITableViewCell 

@IBOutlet weak var productQuantity: UILabel!
@IBOutlet weak var productName: UILabel!
@IBOutlet weak var productImageView: UIImageView!
func setProductData(product:Product)

 self.productName.text = product.name
 self.productQuantity.text = "\(product.quantity)"




用法:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
    let cell = tableView.dequeueReusableCell(withIdentifier: PRODUCT_CELL_IDENTIFIER) as! ProductTableViewCell
    let product = productViewModel.productsArray[indexPath.row]
    cell.setProductData(product:product)
    return cell
  

【讨论】:

非常感谢。 modelsFromArray 方法有点混乱。 您正在使用两个数组。一个是使用中,另一个是Product模型类。 第一个数组是用来演示你的,它可能是服务器响应而不是你可以直接传递响应来生成模型数组。另请注意,我也为您的单元格更新了代码 非常感谢杰迪普。它真的很有帮助。我会更新我的代码。 还有一个疑问。你已经使用了implicit uwrapped 变量。例如var quantity : Int!。如果我想从ViewController 类访问它,则值为Options(1). AS per my knowledge Implicit Unwrapped` 对象不能强制解包。你能告诉我为什么吗?【参考方案2】:

ios 中的 MVVM 可以轻松实现,无需使用第三方依赖项。对于数据绑定,我们可以使用 Closure 和 didSet 的简单组合来避免第三方依赖。

public final class Observable<Value> 

    private var closure: ((Value) -> ())?

    public var value: Value 
        didSet  closure?(value) 
    

    public init(_ value: Value) 
        self.value = value
    

    public func observe(_ closure: @escaping (Value) -> Void) 
        self.closure = closure
        closure(value)
    

来自 ViewController 的数据绑定示例:

final class ExampleViewController: UIViewController 

    private func bind(to viewModel: ViewModel) 
        viewModel.items.observe(on: self)  [weak self] items in
            self?.tableViewController?.items = items
            // self?.tableViewController?.items = viewModel.items.value // This would be Momory leak. You can access viewModel only with self?.viewModel
        
        // Or in one line:
        viewModel.items.observe(on: self)  [weak self] in self?.tableViewController?.items = $0 
    

    override func viewDidLoad() 
        super.viewDidLoad()

        bind(to: viewModel)
        viewModel.viewDidLoad()
    


protocol ViewModelInput 
    func viewDidLoad()


protocol ViewModelOutput 
    var items: Observable<[ItemViewModel]>  get 


protocol ViewModel: ViewModelInput, ViewModelOutput 
final class DefaultViewModel: ViewModel   
  let items: Observable<[ItemViewModel]> = Observable([])

  // Implmentation details...

以后可以用 SwiftUI 和 Combine 替换(当您的应用的最低 iOS 版本为 13 时)

在这篇文章中,对 MVVM 有更详细的描述 https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3

【讨论】:

以上是关于使用 Swift 的带有 MVVM 的 UITableView的主要内容,如果未能解决你的问题,请参考以下文章

我想通过在 Swift 中以编程方式将 UINavigationController 与 UITableViewController 嵌入 UITableViewController 这是 UITab

防止表视图被重用(MVVM)

在应用程序中使用两个不同的 uitab bar

iOS中的UITab冻结

如何使用MVVM + Swift4实现注册屏幕

谁应该在 Swift 上将 DisposeBag 保留在 MVVM(+controller) 中