Search code examples
iosswiftui

How to use WithAnimation with @ObservedObject


Let's say, I have a simple profile screen like that:

class Model: ObservableObject {
    @Published var isSignedIn = false
    
    init() {}
    
    func login() {
        //Some networking here
        isSignedIn = true
    }
    func logout() {
        //Some networking here
        isSignedIn = false
    }
}

struct ContentView: View {
    @ObservedObject var model = Model()

    var body: some View {
        ZStack {
            //ProfileView
            VStack {
                //Some Views with WithAnimation inside
                // ...
 
                Text("Hello, Dear User!")
                Button(action: {
                    self.model.logout()
                }) {
                    Text("Sign Out")
                }
            }
            .opacity(model.isSignedIn ? 1 : 0)

            //LoginView
            VStack {
                Text("Hello, Stranger")
                Button(action: {
                    self.model.login()
                }) {
                    Text("Sign In")
                }
            }
            .opacity(model.isSignedIn ? 0 : 1)
        }
    }
}

And I want to apply animation to opacity changing.

The first approach is to use .animation modifier. But it has certain drawbacks: it does not work properly if inner view has WithAnimation - it overrides animation that was set with WithAnimation.

My second approach to use .onReceive:

class Model: ObservableObject {
    @Published var isSignedIn = false
    
    init() {}
    
    func login() {
        isSignedIn = true
    }
    func logout() {
        isSignedIn = false
    }
}

struct ContentView: View {
    @ObservedObject var model = Model()
    
    @State var isSignedIn = false

    var body: some View {
        ZStack {
            //ProfileView
            VStack {
                Text("Hello, Dear User!")
                Button(action: {
                    self.model.logout()
                }) {
                    Text("Sign Out")
                }
            }
            .opacity(model.isSignedIn ? 1 : 0)

            //LoginView
            VStack {
                Text("Hello, Stranger")
                Button(action: {
                    self.model.login()
                }) {
                    Text("Sign In")
                }
            }
            .opacity(model.isSignedIn ? 0 : 1)
        }
        .onReceive(self.model.$isSignedIn) { value in
            withAnimation(Animation.easeIn) {
                self.isSignedIn = value
            }
        }
    }
}

There are some problems (in my opinion):

  • Another @State var is required to handle changing in the model
  • Each WithAnimation block requires separate .onReceive

So the question is: is it a correct way to apply WithAnimation to @ObservedObject, or is there a better solution?


Solution

  • You can specify the animation directly inside withAnimation. This way it will be specific for this change only:

    Button(action: {
        withAnimation(.easeInOut) { // add animation
            self.model.logout()
        }
    }) {
        Text("Sign Out")
    }