realm-swift
realm-swift copied to clipboard
SwiftUI Animations break with Realm Persisted properties
How frequently does the bug occur?
All the time
Description
withAnimation wrapped changes to @Persisted properties of an @ObservableRealmObject are not being animated, contrary to doing the same thing on @Published properties of an @ObservedObject.
Sample code:
struct ContentView: View {
@ObservedObject var obObject: ObObject
@ObservedRealmObject var rlmObject: RlmObject
var body: some View {
if obObject.showSomething {
Text("ObObject before change")
.background(.red)
} else {
Text("ObOjbect after change")
.background(.blue)
}
if rlmObject.showSomething {
Text("rlmObject before change")
.background(.red)
} else {
Text("rlmObject after change")
.background(.blue)
}
Text("Change ObObject")
.onTapGesture {
withAnimation {
obObject.showSomething.toggle()
}
}
Text("Change RlmObject")
.onTapGesture {
withAnimation {
let realm = try! Realm()
try! realm.write {
$rlmObject.wrappedValue.showSomething.toggle()
}
}
}
}
}
with the Realm model and ObservableObject looking simple, like this:
class ObObject: ObservableObject {
@Published var showSomething: Bool = false
}
class RlmObject: Object, Identifiable {
@Persisted(primaryKey: true) var id = 1
@Persisted var showSomething: Bool = false
}
When you press on the “button” for the ObservableObject you will see the according field blend over, animating. When you do the same for the RealmObject, it will just snap over instantly, ignoring any animation (same when thawing the object first).
Stacktrace & log output
None
Can you reproduce the bug?
Yes, always
Reproduction Steps
Use the sample code above.
Version
latest, haven't seen it working in any version yet
What SDK flavour are you using?
Local Database only
Are you using encryption?
No, not using encryption
Platform OS and version(s)
iOS 13-15, several sims and devices
Build environment
Xcode version: up to 13.2.1 Dependency manager and version: /
hi @LilaQ unfortunately the animation is not settled automatically , but if you wrap the conditional content within a View and add .animation, this will animate the view when the realm object change value.
VStack {
if rlmObject.showSomething {
Text("rlmObject before change")
.background(.red)
} else {
Text("rlmObject after change")
.background(.blue)
}
}
.animation(.easeInOut, value: rlmObject.showSomething)
Hi @dianaafanador3 Thanks so much for the workaround! Do you know what causes this?
Additionally, I'm a bit confused. I can't make my own sample code work anymore. Speaking, even the Realm object changes are animated - did something change in that regard in the last few updates? (Tried a fresh project, with 10.24 installing by CocoaPods). I really can't make any sense of this right now...
This is probably caused by SwiftUI setting a default animation for @Published state changes on ObservableObjects , which is not happening in our case.
The value change on the realm object is triggering a SwiftUI state change and because the identity of the views (Different Text() views in the conditional) are different this is generating the simple change you see without adding .animate, but because there is no animation for that view (which will be something similar to having .animation(nil, value: rlmObject.showSomething)) it is not animating.
What error are you getting after the update, I'm testing everything on our latest release (v10.24.0) using SPM and it is working fine.
Nevermind, I messed up when setting up the project.
Your workaround seems to be working great, thank you so much for that. Are there any plans to make it not necessary to add the animation() e.g. handle it like SwiftUI does with @Published? I could see more SwiftUI people being irritated when their animations stop working when shifting a project to Realm.
Thanks a lot for your quick help! Very much appreciated!
Ok I see it now, I was wrong is not is not SwiftUI setting a default animation, when you use withAnimation it sets the default animation, which in case of a realm object we are observing any changes to the object and triggering a SwiftUI refresh which is reading the new value, and not modifying directly the published value, which is not triggering the animation.
I'll take this to the team and check if we can get to work with our property wrappers. Thanks for the suggestion.
@dianaafanador3 Awesome, thanks for the feedback! :) In case you remember, I'd love to hear about any updates!
Hey @dianaafanador3 Is there any update to this? Thanks!
@dianaafanador3 Trying again for updates on this
Hi @LilaQ we don't have any updates yet. Wrapping each View in a parent View with .animation(..., value: ...) is still the best way to get around this for now. As a plus with that approach you can control animations individually for each element.
@dianaafanador3 Trying again for updates on this
If you are still looking for a work around, you can define an @State variable on the view, and send animations to this object. Using the .onChange(of: rlmObject.property) { newValue in } modifier, you can update the @State variable to match the corresponding Realm property, using withAnimation here to customize the animation.
The caveats are that you should use the @State variable in place of the Realm property in the view body, and that you should need to declare an initial value for @State, which can be synchronized with the Realm property in the .onAppear { } modifier
In all,
- define
@Statevariable in view with dummy initial value - synchronize
@Statewith Realm property in.onAppear { } - use
@Statevariable inbodyin place of Realm property - update when Realm property changes in
.onChange(of: ) { _ in }, usingwithAnimation
Below is an example:
struct ContentView: View {
@ObservedRealmObject var rlmObject: RlmObject
@State var isShowing: Bool = false
var body: some View {
VStack {
if isShowing {
Text("rlmObject before change")
.background(.red)
} else {
Text("rlmObject after change")
.background(.blue)
}
Text("Change RlmObject")
.onTapGesture {
let realm = try! Realm()
try! realm.write {
$rlmObject.wrappedValue.showSomething.toggle()
}
}
}
.onAppear {
withAnimation(.none) {
self.isShowing = rlmObject.showSomething
}
}
.onChange(of: rlmObject.showSomething) { newShowSomething in
withAnimation(.easeInOut) {
self.isShowing = newShowSomething
}
}
}
}
I have a very strange bug.
When I use SwiftUI's .animation that is based a realm property's change, the app sometime crash and notifying me "realm accessed from incorrect thread".
Is there anyone also facing this problem