Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to work with bindings when using a view model vs using @Binding in the view itself?

Tags:

swift

swiftui

@State and @Binding work so well in SwiftUI, as long as you put all the view's data inside itself, like this:

struct ColorView: View {
    @Binding public var isBlue: Bool

    var body: some View {
        Rectangle()
            .foregroundColor(isBlue ? .blue : .red)
            .onTapGesture {
                self.isBlue.toggle()
        }
    }
}

struct TestView: View {
    @State var isBlue: Bool = false

    var body: some View {
        ColorView(isBlue: $isBlue)
    }
}

It works without a problem and it's really simple. But MVVM says you should put all of the view's data inside a view model class, to separate UI from the model. But then you lose @State and @Binding completely. You lose this 2-way binding it seems. Sure, you can do it manually with Combine or something but that should not be the correct way, right?

Whenever I try anything, SwiftUI is really easy and convenient when you don't use view models. Once you put everything inside a view model class, though, everything breaks down and nothing works as convenient anymore. This can't be the case, they have to had thought of that. So I'm missing something here. I'd really appreciate any help. How would you code the above's example using view models (without "hacking" anything manually)? I tried but it doesn't even compile:

struct ColorView: View {
    @ObservedObject var viewModel: ViewModel

    class ViewModel: ObservableObject {
        // Binding or Published? Both doesn't seem to work
        @Binding var isBlue: Bool

        init(isBlue: Binding<Bool>) { // Or type Bool? But then we lose the binding
            self.$isBlue = isBlue
        }
    }

    var body: some View {
        Rectangle()
            .foregroundColor(viewModel.isBlue ? .blue : .red)
            .onTapGesture {
                self.viewModel.isBlue.toggle()
        }
    }
}

struct TestView: View {
    @ObservedObject var viewModel: ViewModel

    class ViewModel: ObservableObject {
        @Published var isBlue: Bool = false // We would need a @State here, but then we lose @Published
    }

    var body: some View {
        ColorView(viewModel: .init(isBlue: /* ??? */)) // How to pass a binding here`
    }
}

Do I think of this the wrong way?

Thanks!

like image 588
Quantm Avatar asked Mar 31 '20 08:03

Quantm


Video Answer


2 Answers

Here is the way (assuming that somewhere you call TestView(viewModel: ViewModel())):

class ViewModel: ObservableObject {
    @Published var isBlue: Bool = false
}

struct TestView: View {
    @ObservedObject var viewModel: ViewModel

    var body: some View {
        ColorView(viewModel: self.viewModel)
    }
}

struct ColorView: View {
    @ObservedObject var viewModel: ViewModel

    var body: some View {
        Rectangle()
            .foregroundColor(viewModel.isBlue ? .blue : .red)
            .onTapGesture {
                self.viewModel.isBlue.toggle()
        }
    }
}
like image 84
Asperi Avatar answered Oct 12 '22 23:10

Asperi


Using MVVM is your choice, apple officially does not recommend to use any design pattern. They have given us below concepts and it truly depends on us how we can use them.

1- Property wrappers(@State, @Binding, @Environment)

2- SwiftUI replacement of Storyboards

3- Combine framework provides a declarative Swift API for processing values over time like RxSwift, RxJava, RxKotlin etc.

If you want to use MVVM you will still be using all of the above frameworks

View: Design and bind the view with SwiftUI.

ViewModel: Here you will use @Observable, @Published properties and bind it with View using SwiftUI binding concepts.

Model: Here we are using simple data models.

Network: Here we will be using Combine framework publisher/subscriber mechanism for handling network streams.

NOTE: You can combine all the above concepts and use it in another design pattern like MVP, MVC or VIPER.

like image 38
Muhammad Waqas Bhati Avatar answered Oct 13 '22 00:10

Muhammad Waqas Bhati