【问题标题】:Where to put views creation in MVVM and MVC patterns?在 MVVM 和 MVC 模式中将视图创建放在哪里?
【发布时间】:2017-09-11 11:33:23
【问题描述】:

如题重复请见谅。

我通常写我的应用程序没有故事板,并将视图创建放入“viewDidLoad”,例如:

class LoginVC: UIViewController {

    var view1: UIView!
    var label1: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        loadStaticViews()
    }

    func loadStaticViews() {
        view1 = UIView()
        label1 = UILabel()
        view.addSubview(view1)
        view1.addSubview(label1)
        // constraints...
    }
}

现在我想在我的下一个应用程序中尝试 MVVM 模式,只是不确定在哪里创建视图。 现在我想到了类似的事情:

class LoginVCViews {
    static func loadViews<T, T1, T2>(superview: UnsafeMutablePointer<T>, view: UnsafeMutablePointer<T1>, label: UnsafeMutablePointer<T2>) {
        guard let superview = superview.pointee as? UIView else { return }
        let v = UIView()
        let l = UILabel()
        superview.addSubview(v)
        v.addSubview(l)

        // constraints...

        view.pointee = v as! T1
        label.pointee = l as! T2
    }
}

class LoginVC: UIViewController {

    private var view1: UIView!
    private var label1: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        LoginVCViews.loadViews(superview: &view, view: &view1, label: &label1)
    }
}

你怎么看?我对 UnsafeMutablePointer 不太熟悉,并且不确定不会有一些问题。 它有多丑?

【问题讨论】:

  • 我认为实现是正确的,因为对 UI 的任何更新都必须根据 MVVM 在 View 部分下完成。我猜需要添加哪些视图的业务逻辑可以在View-Model中决定

标签: ios objective-c swift mvvm unsafemutablepointer


【解决方案1】:

也许您应该尝试完全面向对象的路径。视图组合看起来像这样:

// 可复用的协议集

protocol OOString: class {
    var value: String { get }
}

protocol Executable: class {
    func execute()
}

protocol Screen: class {
    var ui: UIViewController { get }
}

protocol ViewRepresentation: class {
    var ui: UIView { get }
}

// 可重用功能(无 uikit 依赖)

final class ConstString: OOString {

    init(_ value: String) {
        self.value = value
    }

    let value: String

}

final class ExDoNothing: Executable {

    func execute() { /* do nothing */ }

}

final class ExObjCCompatibility: NSObject, Executable {

    init(decorated: Executable) {
        self.decorated = decorated
    }

    func execute() {
        decorated.execute()
    }

    private let decorated: Executable

}

// 可复用的 UI(uikit 依赖)

final class VrLabel: ViewRepresentation {

    init(text: OOString) {
        self.text = text
    }

    var ui: UIView {
        get {
            let label = UILabel()
            label.text = text.value
            label.textColor = UIColor.blue
            return label
        }
    }

    private let text: OOString

}

final class VrButton: ViewRepresentation {

    init(text: OOString, action: Executable) {
        self.text = text
        self.action = ExObjCCompatibility(decorated: action)
    }

    var ui: UIView {
        get {
            let button = UIButton()
            button.setTitle(text.value, for: .normal)
            button.addTarget(action, action: #selector(ExObjCCompatibility.execute), for: .touchUpInside)
            return button
        }
    }

    private let text: OOString
    private let action: ExObjCCompatibility

}

final class VrComposedView: ViewRepresentation {

    init(first: ViewRepresentation, second: ViewRepresentation) {
        self.first = first
        self.second = second
    }

    var ui: UIView {
        get {
            let view = UIView()
            view.backgroundColor = UIColor.lightGray
            let firstUI = first.ui
            view.addSubview(firstUI)
            firstUI.translatesAutoresizingMaskIntoConstraints = false
            firstUI.topAnchor.constraint(equalTo: view.topAnchor, constant: 100).isActive = true
            firstUI.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
            firstUI.widthAnchor.constraint(equalToConstant: 100).isActive = true
            firstUI.heightAnchor.constraint(equalToConstant: 40).isActive = true
            let secondUI = second.ui
            view.addSubview(secondUI)
            secondUI.translatesAutoresizingMaskIntoConstraints = false
            secondUI.topAnchor.constraint(equalTo: firstUI.topAnchor).isActive = true
            secondUI.leadingAnchor.constraint(equalTo: firstUI.trailingAnchor, constant: 20).isActive = true
            secondUI.widthAnchor.constraint(equalToConstant: 80).isActive = true
            secondUI.heightAnchor.constraint(equalToConstant: 40).isActive = true
            return view
        }
    }

    private let first: ViewRepresentation
    private let second: ViewRepresentation

}

// 一个视图控制器

final class ContentViewController: UIViewController {

    convenience override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)   {
        self.init()
    }

    convenience required init(coder aDecoder: NSCoder) {
        self.init()
    }

    convenience init() {
        fatalError("Not supported!")
    }

    init(content: ViewRepresentation) {
        self.content = content
        super.init(nibName: nil, bundle: nil)
    }

    override func loadView() {
        view = content.ui
    }

    private let content: ViewRepresentation

}

//现在是一个屏幕的业务逻辑(不可重用)

final class ScStartScreen: Screen {

    var ui: UIViewController {
        get {
            return ContentViewController(
                content: VrComposedView(
                    first: VrLabel(
                        text: ConstString("Please tap:")
                    ),
                    second: VrButton(
                        text: ConstString("OK"),
                        action: ExDoNothing()
                    )
                )
            )
        }
    }

}

在 AppDelegate 中的使用:

window?.rootViewController = ScStartScreen().ui

注意:

  • 它遵循面向对象编码的规则(干净的编码、优雅的对象、装饰器模式……)
  • 每个类的构造都非常简单
  • 类之间通过协议进行通信
  • 所有的依赖都尽可能的通过依赖注入来给出
  • 一切(最后的业务屏幕除外)都是可重用的 -> 事实上:可重用代码的组合随着您编码的每一天而增长
  • 应用的业务逻辑集中在 Screen 对象的实现中
  • 在对协议使用假实现时,单元测试非常简单(在大多数情况下甚至不需要模拟)
  • 保留周期问题较少
  • 避免 Null、nil 和 Optional(它们会污染您的代码)
  • ...

在我看来,这是最好的编码方式,但大多数人不会那样做。

【讨论】:

  • 有意思,TableView Delegate/DataSource 放在哪里?
  • 如果需要,可以通过 VrComposedView 方法与一些视图元素进行交互?
  • A UITableView 有一个从 ViewRepresentation 派生的等效类,例如简单表视图表示。此类获取数据源和委托对象(两个协议实现都是自己的对象!)作为 init 中的依赖注入,并在构造 UITableView 时将它们设置在 get 实现中。委托和数据源的实现再次变得非常简单,并且再次从 ScStartScreen 实现中获取它们的依赖关系。注意:TableViews 通常有更复杂的业务规则,所以当 ScStartScreen 导致超过 100 行代码时不要感到震惊。
  • 顺便说一句:我印象深刻的是我还没有得到任何反对票。大多数人对面向对象的编码不感兴趣:(
  • 视图之间的交互通常是注入可执行对象,这些对象与另一个视图有依赖关系但隐藏了该依赖关系。
猜你喜欢
  • 1970-01-01
  • 2015-01-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多