How to Get Data from Observedobject with Onreceive in Swiftui

How can I get data from ObservedObject with onReceive in SwiftUI?

First in your view you need to request the HeadingProvider to start updating heading. You need to listen to objectWillChange notification, the closure has one argument which is the new value that is being set on ObservableObject.

I have changed your Compass a bit:

struct Compass: View {

@StateObject var headingProvider = HeadingProvider()
@State private var angle: CGFloat = 0

var body: some View {
VStack {
Image("arrow")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 300, height: 300)
.modifier(RotationEffect(angle: angle))
.onReceive(self.headingProvider.objectWillChange) { newHeading in
withAnimation(.easeInOut(duration: 1.0)) {
self.angle = newHeading
}
}

Text(String("\(angle)"))
.font(.system(size: 20))
.fontWeight(.light)
.padding(.top, 15)
} .onAppear(perform: {
self.headingProvider.updateHeading()
})
}
}

I have written an example HeadingProvider:

public class HeadingProvider: NSObject, ObservableObject {

public let objectWillChange = PassthroughSubject<CGFloat,Never>()

public private(set) var heading: CGFloat = 0 {
willSet {
objectWillChange.send(newValue)
}
}

private let locationManager: CLLocationManager

public override init(){
self.locationManager = CLLocationManager()
super.init()
self.locationManager.delegate = self
}

public func updateHeading() {
locationManager.startUpdatingHeading()
}
}

extension HeadingProvider: CLLocationManagerDelegate {

public func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
DispatchQueue.main.async {
self.heading = CGFloat(newHeading.trueHeading)
}
}
}

Remember you need to handle asking for permission to read user's location and you need to call stopUpdatingHeading() at some point.

onReceive not getting called in SwiftUI View when ObservedObject changes

Well, here it is - your MenuView and MenuItemView use different instances of view model

1)

struct MenuView: View {

@ObservedObject private var viewModel: MenuViewModel
@State var showPicker = false
@State private var menu: Menu = Menu.mockMenu()


init(viewModel: MenuViewModel = MenuViewModel()) { // 1st one created

2)

struct MenuItemView: View {

var item: MenuItem
@ObservedObject private var viewModel: MenuViewModel = MenuViewModel() // 2nd one

thus, you modify one instance, but subscribe to changes in another one. That's it.

Solution: pass view model via .environmentObject or via argument from MenuView to MenuItemView.

SwiftUI: ViewModifier doesn't listen to onReceive events

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
}
}

Why is onReceive block on SwiftUI View called when Publisher argument doesn't have any new values to emit?

You use Publishers.Sequence in your code, which publishes the elements in the sequence one by one.

And with your code, [model.greeting].publisher is re-evaluated and the publisher is re-created at each time body is evaluated, which means the published element reset to the first.

With the following code, you can find that x is printed out, at each time body is evaluated:

struct ContentView: View {
@ObservedObject var model: Model = Model()
let pickerTitles = ["One", "Two", "Three"]

var body: some View {
VStack {
return Picker("Options", selection: $model.selectedIndex) {
ForEach(0 ..< pickerTitles.count) { index in
Text(self.pickerTitles[index])
}

}.pickerStyle(SegmentedPickerStyle())
}
.onReceive(["x"].publisher){str in
print(str)
}
}
}

If you want to receive the changes only on greeting, you can write something like this:

struct ContentView: View {
@ObservedObject var model: Model = Model()
let pickerTitles = ["One", "Two", "Three"]

var body: some View {
VStack {
return Picker("Options", selection: $model.selectedIndex) {
ForEach(0 ..< pickerTitles.count) { index in
Text(self.pickerTitles[index])
}

}.pickerStyle(SegmentedPickerStyle())
}
.onReceive(model.$greeting){str in
print(str)
}
}
}

onReceive is not triggered after published value has changed


but why does it work?

Most probably you use AddChallengeView in NavigationView (or another container, which recreates content during workflow), so having

@ObservedObject var viewModel = AddChallengeViewModel()

creates new instance of AddChallengeViewModel class on each such view re-creation, so any previous changes are lost. However

@StateObject var viewModel = AddChallengeViewModel()

preserves instance of model (created at first time) and injects it into new view of same type re-created in same place of view hierarchy. Moreover, new view is notified about all changes of that same model.

Actually @StateObject property wrapper gives same behaviour for ObservableObject as @State gives for value types.



Related Topics



Leave a reply



Submit