Can't Use @Observedobject on Real iPhone

Can't use @ObservedObject on real iPhone

This isn't a problem with not conforming to ObservableObject. The code you provided works in the canvas and in the simulator, and should also work on the device. I have already come across this issue with iOS 13 Beta 6 in my own project and have spent a lot of time troubleshooting.

Other things (such as calling self.presentationMode.value.dismiss() to dismiss a modal view) are also currently broken when running projects built with Xcode Beta 5 on devices running iOS 13 Beta 6. There have been issues with previous betas of Xcode not working on newer betas of iOS, and this may be the same issue.

I would suggest that you wait until Xcode Beta 6 is released to make any significant structural changes to your code, as iOS 13 Beta 6 may have been developed in anticipation of handling changes that will be made in Xcode Beta 6.

That being said, if you absolutely must make changes to workaround this issue in the meantime, I've found that using @EnvironmentObject instead of @ObservedObject fixes this issue. In your example, that would mean declaring your property like this:

@EnvironmentObject private var networkManager: NetworkManager

Then, when you create your view, you can pass a NetworkManager as an environment object like this:

ContentView()
.environmentObject(NetworkManager())

@StateObject vs @ObservedObject when passed externally but owned by the view

This is a really interesting question. There's some subtle behavior going on here.

First, notice that you can't just change @ObservedObject to @StateObject in NameView. It won't compile:

struct NameView: View {
@StateObject var viewModel: ViewModel

init(_ viewModel: ViewModel) {
self.viewModel = viewModel
// ^ Cannot assign to property: 'viewModel' is a get-only property
}
...
}

To make it compile, you have to initialize the underlying _viewModel stored property of type StateObject<ViewModel>:

struct NameView: View {
@StateObject var viewModel: ViewModel

init(_ viewModel: ViewModel) {
_viewModel = .init(wrappedValue: viewModel)
}
...
}

But there's something hidden there. StateObject.init(wrappedValue:) is declared like this:

public init(wrappedValue thunk: @autoclosure @escaping () -> ObjectType)

So the expression given as an argument (just viewModel above) is wrapped up in a closure, and is not evaluated right away. That closure is stored for later use, which is why it is @escaping.

As you might guess from the hoops we have to jump through to make it compile, this is a weird way to use StateObject. The normal use looks like this:

struct NormalView: View {
@StateObject var viewModel = ViewModel()

var body: some View {
Text(viewModel.name)
}
}

And doing it the weird way has some drawbacks. To understand the drawbacks, we need to look at the context in which makeView() or NormalView() is evaluated. Let's say it looks like this:

struct ContentView: View {
@Binding var count: Int

var body: some View {
VStack {
Text("count: \(count)")
NormalView()
ViewFactory().makeView()
}
}
}

When count's value changes, SwiftUI will ask ContentView for its body again, which will evaluate both NormalView() and makeView() again.

So body calls NormalView() during this second evaluation, which creates another instance of NormalView. NormalView.init creates a closure which calls ViewModel(), and passes the closure to StateObject.init(wrappedValue:). But StateObject.init does not evaluate this closure immediately. It stores it away for later use.

Then body calls makeView(), which does call ViewModel() immediately. It passes the new ViewModel to NameView.init, which wraps the new ViewModel in a closure and passes the closure to StateObject.init(wrappedValue:). This StateObject also doesn't evaluate the closure immediately, but the new ViewModel has been created regardless.

Some time after ContentView.body returns, SwiftUI wants to call NormalView.body. But before doing so, it has to make sure the StateObject in this NormalView has a ViewModel. It notices that this NormalView is replacing a prior NormalView at the same position in the view hierarchy, so it retrieves the ViewModel used by that prior NormalView and puts it in the StateObject of the new NormalView. It does not execute the closure given to StateObject.init, so it does not create a new ViewModel.

Even later, SwiftUI wants to call NameView.body. But before doing so, it has to make sure the StateObject in this NameView has a ViewModel. It notices that this NameView is replacing a prior NameView at the same position in the view hierarchy, so it retrieves the ViewModel used by that prior NameView and puts it in the StateObject of the new NameView. It does not execute the closure given to StateObject.init, and so it does not use the ViewModel referenced by that closure. But the ViewModel was created anyway.

So there are two drawbacks to the weird way in which you're using @StateObject:

  1. You're creating a new ViewModel each time you call makeView, even though that ViewModel may never be used. This may be expensive, depending on your ViewModel.
  2. You're creating the ViewModel while the ContentView.body getter is running. If creating the ViewModel has side effects, this may confuse SwiftUI. SwiftUI expects the body getter to be a pure function. In the NormalView case, SwiftUI is calling the StateObject's closure at a known time when it may be better prepared to handle side effects.

So, back to your original question:

Should it be @StateObject or @ObservedObject?

Well, ha ha, that's difficult to answer without seeing an example that's less of a toy. But if you do need to use @StateObject, you should probably try to initialize it in the ‘normal’ way.

ObservableObject is missing in Motherview

If you're not going to use it in parent view then just use locally as StateObject

struct RegistrationView: View {
@StateObject var progress = Progress() // << here !!

// ... other code

SwiftUI: How can I catch changing value from observed object when I execute function

It is used two different instances of SettingCodeTwo - one in NetworkNamager another in SettingsView, so they are not synchronised if created at same time.

Here is an approach to keep those two instances self-synchronised (it is possible because they use same storage - UserDefaults)

Tested with Xcode 11.4 / iOS 13.4

Modified code below (see also important comments inline)

extension UserDefaults { 
@objc dynamic var textKey2: String { // helper keypath
return string(forKey: "textKey2") ?? ""
}
}

class SettingCodeTwo: ObservableObject { // use capitalised name for class !!!

private static let userDefaultTextKey = "textKey2"
@Published var text: String = UserDefaults.standard.string(forKey: SettingCodeTwo.userDefaultTextKey) ?? ""

private var canc: AnyCancellable!
private var observer: NSKeyValueObservation!

init() {
canc = $text.debounce(for: 0.2, scheduler: DispatchQueue.main).sink { newText in
UserDefaults.standard.set(newText, forKey: SettingCodeTwo.userDefaultTextKey)
}
observer = UserDefaults.standard.observe(\.textKey2, options: [.new]) { _, value in
if let newValue = value.newValue, self.text != newValue { // << avoid cycling on changed self
self.text = newValue
}
}
}
}

class NetworkManager: ObservableObject {

var codeTwo = SettingCodeTwo() // no @ObservedObject needed here
...

SwiftUI with Core Location as ObservableObject crashes

First off I want to thank Fabian and graycampbell for their help.

Secondly, as for as I can tell @ObservableObject still does not work in iOS 13 beta 8 using XCode 11 beta 6.

Here is what worked for me:
1. I changed

@ObservedObject var location: MYLocationManager = MYLocationManager()

to:

@EnvironmentObject var location: MYLocationManager

2. In the SceneDelegate I added:

let myLocationManager = MYLocationManager()

and:

 window.rootViewController = UIHostingController(rootView: CoreLocationView_NeedsEnv()
.environmentObject(myLocationManager)

No more crash!!

P.S. I am using Fabian's updated code. Thanks again!

SwiftUI: ObservableObject does not persist its State over being redrawn

Finally, there is a Solution provided by Apple: @StateObject.

By replacing @ObservedObject with @StateObject everything mentioned in my initial post is working.

Unfortunately, this is only available in ios 14+.

This is my Code from Xcode 12 Beta (Published June 23, 2020)

struct ContentView: View {

@State var title = 0

var body: some View {
NavigationView {
VStack {
Button("Test") {
self.title = Int.random(in: 0...1000)
}

TestView1()

TestView2()
}
.navigationTitle("\(self.title)")
}
}
}

struct TestView1: View {

@ObservedObject var model = ViewModel()

var body: some View {
VStack {
Button("Test1: \(self.model.title)") {
self.model.title += 1
}
}
}
}

class ViewModel: ObservableObject {

@Published var title = 0
}

struct TestView2: View {

@StateObject var model = ViewModel()

var body: some View {
VStack {
Button("StateObject: \(self.model.title)") {
self.model.title += 1
}
}
}
}

As you can see, the StateObject Keeps it value upon the redraw of the Parent View, while the ObservedObject is being reset.

@Published property wrapper not working on subclass of ObservableObject

Finally figured out a solution/workaround to this issue. If you remove the property wrapper from the subclass, and call the baseclass objectWillChange.send() on the variable the state is updated properly.

NOTE: Do not redeclare let objectWillChange = PassthroughSubject<Void, Never>() on the subclass as that will again cause the state not to update properly.

I hope this is something that will be fixed in future releases as the objectWillChange.send() is a lot of boilerplate to maintain.

Here is a fully working example:

    import SwiftUI

class MyTestObject: ObservableObject {
@Published var aString: String = ""

}

class MyInheritedObject: MyTestObject {
// Using @Published doesn't work on a subclass
// @Published var anotherString: String = ""

// If you add the following to the subclass updating the state also doesn't work properly
// let objectWillChange = PassthroughSubject<Void, Never>()

// But if you update the value you want to maintain state
// of using the objectWillChange.send() method provided by the
// baseclass the state gets updated properly... Jaayy!
var anotherString: String = "" {
willSet { self.objectWillChange.send() }
}
}

struct MyTestView: View {
@ObservedObject var myTestObject = MyTestObject()
@ObservedObject var myInheritedObject = MyInheritedObject()

var body: some View {
NavigationView {
VStack(alignment: .leading) {
TextField("Update aString", text: self.$myTestObject.aString)
Text("Value of aString is: \(self.myTestObject.aString)")

TextField("Update anotherString", text: self.$myInheritedObject.anotherString)
Text("Value of anotherString is: \(self.myInheritedObject.anotherString)")
}
}
}
}

What is the difference between ObservedObject and StateObject in SwiftUI

@ObservedObject

When a view creates its own @ObservedObject instance it is recreated every time a view is discarded and redrawn:

struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
}

On the contrary a @State variable will keep its value when a view is redrawn.

@StateObject

A @StateObject is a combination of @ObservedObject and @State - the instance of the ViewModel will be kept and reused even after a view is discarded and redrawn:

struct ContentView: View {
@StateObject var viewModel = ViewModel()
}

Performance

Although an @ObservedObject can impact the performance if the View is forced to recreate a heavy-weight object often, it should not matter much when the @ObservedObject is not complex.

When to use @ObservedObject

It might appear there is no reason now to use an @ObservedObject, so when should it be used?

You should use @StateObject for any observable properties that you
initialize in the view that uses it. If the ObservableObject instance
is created externally and passed to the view that uses it mark your
property with @ObservedObject.

Note there are too many use-cases possible and sometimes it may be desired to recreate an observable property in your View. In that case it's better to use an @ObservedObject.

Useful links:

  • What’s the difference between @StateObject and @ObservedObject?
  • What’s the difference between @ObservedObject, @State, and @EnvironmentObject?
  • What is the @StateObject property wrapper?


Related Topics



Leave a reply



Submit