如何将正确的viewModel注入单个viewController

时间:2018-04-09 11:52:12

标签: ios swift mvvm storyboard swinject

我正在使用Swinject作为我的DI解决方案,并使用SwinjectStoryboard扩展名进行扩展。

我正在努力将正确的viewModel动态注入特定的viewContoller。具体方案如下:

MyViewController有一个名为var viewModel: ViewModeling的属性。

有两种不同的视图模型符合ViewModeling协议,我们可以调用它们:firstViewModelsecondViewModel。 我的故事板只包含一个控制器及其MyViewController

问题

动态地将右视图模型注入MyViewController的依赖项(因此只有在运行时我才会知道是注入第一个还是第二个)

我能够在服务级别上执行此操作(2个服务符合的协议,以及2个不同的viewModel,每个使用不同的服务可以使用特定名称解析所需的服务)

我正在努力在viewController级别上执行此操作,尝试向同一视图控制器注入特定的viewModel(两者都符合相同协议的提及)。

目前我的预感是SwinjectStoryboard不允许我使用其故事板ID实例化视图控制器(就像我通常那样),此外还定义了几个不同的名称,这些名称将在运行时解析。

我错过了什么吗?

2 个答案:

答案 0 :(得分:1)

你没有错过任何东西 - 你正在寻找的行为目前无法通过SwinjectStoryboard实现。

您可以有多个storyboardInitCompleted使用不同的names,但它们对应于在storyboard参数swinjectRegistrationName中输入的名称(有关详情,请参阅docs) - 按顺序要使用它,您需要在故事板中拥有视图控制器的多个副本。

从我的观点来看,理想的解决方案是注册参数,即您将enum ViewModelType {}使用storyboardInitCompleted来解析正确的视图模型。很遗憾,此功能尚未最终确定(请参阅this PR)。

在当前状态下,我可能会尝试将视图模型的选择从注入逻辑移动到应用程序逻辑 - 即你可以有一些

protocol ViewModelProvider {
    var viewModel: ViewModeling { get }
}

将注入到视图控制器中,并根据某些应用程序状态提供正确的视图模型。

规避问题的另一种方法是抛弃SwinjectStoryboard注册,并使用基本的Swinject来实例化视图控制器:

container.register(MyViewController.self, name: "name") {
    let vc = SwinjectStoryboard.create(name: "MyStoryboard", bundle: nil).instantiateViewController(withIdentifier: "identifier")
    vc.viewModel = $0.resolve(ViewModeling.self)
    return viewModel
}

let vc = SwinjectStoryboard.defaultContainer.resolve(MyViewController.self, name: "name")

答案 1 :(得分:1)

我的模式,如何向ViewModel注入ViewController

// ------------------------------------------------------------------------------------------
// Option 1, when you use Storyboards
// ------------------------------------------------------------------------------------------

import Foundation

class MyViewController {

    var viewModel: MyViewModel!

    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var titleLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        bindViewModel()
    }

    func bindViewModel() {

    }

    // Then, you can inject ViewModel in prepare function
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        super.prepare(for: segue, sender: sender)

        guard let myViewController = segue.destination as? MyViewController else { return }
        myViewController.viewModel = MyViewModel(myDependency: myDependency)
    }
}

// ------------------------------------------------------------------------------------------
// Option 2, when you use xibs
// ------------------------------------------------------------------------------------------

import Foundation

class MyViewController {

    let viewModel: MyViewModel

    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var titleLabel: UILabel!

    init(viewModel: MyViewModel) {
        self.viewModel = viewModel

        super.init(nibName: nil, bundle: nil)
    }

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

    override func viewDidLoad() {
        super.viewDidLoad()

        bindViewModel()
    }

    func bindViewModel() {

    }

    // Then, you can inject ViewModel in constructor:
    func showMyViewController() {
        let vm = MyViewModel(myDependency: myDependency)
        let vc = MyViewController(viewModel: vm)
        self.present(vc, animated: true, completion: nil)
    }
}