I have a custom ViewModifier
which simply returns the same content attached with a onReceive
modifier, the onReceive
is not triggered, here is a sample code that you can copy, paste and run in XCode
:
import SwiftUI
import Combine
class MyViewModel: ObservableObject {
@Published var myProperty: Bool = false
}
struct ContentView: View {
@ObservedObject var viewModel: MyViewModel
var body: some View {
Text("Hello, World!")
.modifier(MyOnReceive(viewModel: viewModel))
.onTapGesture {
self.viewModel.myProperty = true
}
}
}
struct MyOnReceive: ViewModifier {
@ObservedObject var viewModel: MyViewModel
func body(content: Content) -> some View {
content
.onReceive(viewModel.$myProperty) { theValue in
print("The Value is \(theValue)") // <--- this is not executed
}
}
}
is SwiftUI
designed to disallow onReceive
to execute inside a ViewModifier
or is it a bug ? I have a view in my real life project that gets bigger with some business logic put inside onReceive
, so I need to clean that view by separating it from onReceive
.
A modifier that you apply to a view or another view modifier, producing a different version of the original value. Adopt the ViewModifier protocol when you want to create a reusable modifier that you can apply to any view.
However, thanks to SwiftUI’s highly composable design, it’s often quite easy to split a view’s body up into separate pieces, which might not even require any new View types to be created.
Often when working with interactive SwiftUI views, we’re using closures to define the actions that we wish to perform when various events occur. For example, the following AddItemView has two interactive elements, a TextField and a Button, that both enable the user to add a new text-based Item to our app:
ok, this works for me:
func body(content: Content) -> some View {
content
.onAppear() // <--- this makes it work
.onReceive(viewModel.$myProperty) { theValue in
print("-----> The Value is \(theValue)") // <--- this will be executed
}
}
ObservableObject
and @Published
are part of the Combine framework if you aren't using Combine then you shouldn't be using a class for your view data. Instead, you should be using SwiftUI as designed and avoid heavy objects and either put the data in the efficient View data struct or make a custom struct as follows:
import SwiftUI
struct MyConfig {
var myProperty: Bool = false
mutating func myMethod() {
myProperty = !myProperty
}
}
struct ContentView: View {
@State var config = MyConfig()
var body: some View {
Text("Hello, World!")
.onTapGesture {
config.myMethod()
}
}
}
Old answer:
Try onChange
instead
https://developer.apple.com/documentation/swiftui/scrollview/onchange(of:perform:)
.onChange(of: viewModel.myProperty) { newValue in
print("newValue \(newValue)")
}
But please don't use the View Model object pattern in SwiftUI, try to force yourself to use value types like structs for all your data as SwiftUI was designed. Property wrappers like @State
will give you the reference semantics you are used to with objects.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With