realm-swift icon indicating copy to clipboard operation
realm-swift copied to clipboard

SwiftUI Animations break with Realm Persisted properties

Open LilaQ opened this issue 3 years ago • 9 comments

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: /

LilaQ avatar Mar 14 '22 17:03 LilaQ

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)

dianaafanador3 avatar Mar 17 '22 18:03 dianaafanador3

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

LilaQ avatar Mar 17 '22 18:03 LilaQ

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.

dianaafanador3 avatar Mar 17 '22 18:03 dianaafanador3

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!

LilaQ avatar Mar 17 '22 19:03 LilaQ

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 avatar Mar 17 '22 20:03 dianaafanador3

@dianaafanador3 Awesome, thanks for the feedback! :) In case you remember, I'd love to hear about any updates!

LilaQ avatar Mar 17 '22 20:03 LilaQ

Hey @dianaafanador3 Is there any update to this? Thanks!

LilaQ avatar Jun 05 '22 16:06 LilaQ

@dianaafanador3 Trying again for updates on this

LilaQ avatar Aug 08 '22 14:08 LilaQ

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.

leemaguire avatar Aug 08 '22 15:08 leemaguire

@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,

  1. define @State variable in view with dummy initial value
  2. synchronize @State with Realm property in .onAppear { }
  3. use @State variable in body in place of Realm property
  4. update when Realm property changes in .onChange(of: ) { _ in }, using withAnimation

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

overlair avatar Oct 07 '22 09:10 overlair

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

andresitsu avatar Oct 29 '22 13:10 andresitsu