0

I want to decouple my ViewModel and View layer to increase testability of my Views. Therefore, I want to keep my property states inside view and only init them as I needed. But I cannot initialize my @Binding or @States with @Published properties. Is there a way to couple them inside init function?

I just add example code below to

instead of

import SwiftUI

class ViewModel: ObservableObject {
    @Published var str: String = "a"
    @Published var int: Int = 1 { didSet { print("ViewModel int = \(int)")} }
    
    init() {
        print("ViewModel initialized")
    }
}

struct ContentView: View {
    
    @ObservedObject vM = ViewModel()
    
    var body: some View {
        Button(action: { vM.int += 1; print(int) }, label: {
        Text("Button")
        })
    }
}

I want to achieve this without using @ObservedObject inside my view.

import SwiftUI

class ViewModel: ObservableObject {
    @Published var str: String = "a"
    @Published var int: Int = 1 { didSet { print("ViewModel int = \(int)")} }
    
    init() {
        print("ViewModel initialized")
    }
}

struct ContentView: View {
    
    @Binding var str: String
    @Binding var int: Int
    
    var body: some View {
        Button(action: { int += 1; print(int) }, label: {
        Text("Button")
        })
    }
}

extension ContentView {
    
        init(viewModel:ObservedObject<ViewModel> = ObservedObject(wrappedValue: ViewModel())) {
        // str: Binding<String> and viewModel.str: Published<String>.publisher 
        // type so that I cannot bind my bindings to viewModel. I must accomplish
        // this by using @ObservedObject but this time my view couples with ViewModel
        _str = viewModel.wrappedValue.$str 
        _int = viewModel.wrappedValue.$int
        print("ViewCreated")
    }
    
}



// Testing Init
ContentView(str: Binding<String>, int: Binding<Int>)

// ViewModel Init
ContentView(viewModel: ViewModel)

This way I can't bind them each other, I just want to bind my binding or state properties to published properties.

OguzYuksel
  • 88
  • 6
  • What do you mean bind them? `@Binding` is meant to be binded to values originating in other SwiftUI views, not reference types – Sergio Bost Apr 26 '21 at 16:59
  • @SergioBost I edit my question, I want to totally separate my view and ViewModel so that I want to design my view states inside my view, not by using observed object, but same time I want to have opportunity to init my view with ViewModel. – OguzYuksel Apr 26 '21 at 17:53

1 Answers1

0

I have realized that by Binding(get:{}, set{}), I can accomplish that. if anyone want to separate their ViewModel and View layer, they can use this approach:

import SwiftUI

class ViewModel: ObservableObject {
    @Published var str: String = "a"
    @Published var int: Int = 1 { didSet { print("ViewModel int = \(int)")} }

    init() {
        print("ViewModel initialized")
    }
}

struct ContentView: View {

    @Binding var str: String
    @Binding var int: Int

    var body: some View {
        Button(action: { int += 1; print(int) }, label: {
            Text("Button")
        })
    }
}

extension ContentView {

    init(viewModel:ViewModel = ViewModel()) {
        _str = Binding ( get: { viewModel.str }, set: { viewModel.str = $0 } )
        _int = Binding ( get: { viewModel.int }, set: { viewModel.int = $0 } )

        print("ViewCreated")
    }

}
OguzYuksel
  • 88
  • 6