How to Animate Changes to an @Observedobject

How can I animate changes to an @ObservedObject?

It looks as though using withAnimation inside an async closure causes the color not to animate, but instead to change instantly.

Either removing the wrapping asyncAfter, or removing the withAnimation call and adding an animation modifier in the body of your ContentView (as follows) should fix the issue:

Color(viewModel.color).onAppear {
self.viewModel.change()
}.animation(.easeInOut(duration: 1))

Tested locally (iOS 13.3, Xcode 11.3) and this also appears to dissolve/fade from blue to red as you intend.

How to use WithAnimation with @ObservedObject

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

SwiftUI Animation from @Published property changing from outside the View

The easiest option is to add a withAnimation block inside your timer closure:

withAnimation(.easeIn(duration: 0.5)) {
isOn.toggle()
}

If you don't have the ability to change the @ObservableObject closure, you could add a local variable to mirror the changes:

struct ContentView: View {
@ObservedObject var model: Model
@State var localIsOn = false
var body: some View {
VStack {
if localIsOn {
MyImage(color: .blue)
} else {
MyImage(color: .clear)
}
Spacer()
Toggle("switch", isOn: $model.isOn.animation(.easeIn(duration: 0.5)))
Spacer()
}.onChange(of: model.isOn) { (on) in
withAnimation {
localIsOn = on
}
}
}
}

You could also do a similar trick with a mirrored variable inside your ObservableObject:


struct ContentView: View {
@ObservedObject var model: Model
var body: some View {
VStack {
if model.animatedOn {
MyImage(color: .blue)
} else {
MyImage(color: .clear)
}
Spacer()
Toggle("switch", isOn: $model.isOn.animation(.easeIn(duration: 0.5)))
Spacer()
}
}
}

class Model: ObservableObject {
@Published var isOn: Bool = false
@Published var animatedOn : Bool = false

var cancellable : AnyCancellable?

var timer = Timer()
init() {
timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true, block: { [unowned self] _ in
isOn.toggle()
})
cancellable = $isOn.sink(receiveValue: { (on) in
withAnimation {
self.animatedOn = on
}
})
}
}

In SwiftUI how can I animate a Published View change?

Moving your view initialization to MasterView makes everything easier:

struct MasterView: View {

@State var playerLeft: Bool = false
@ObservedObject var viewModel: MasterViewModel

var body: some View {
ZStack {
VStack { //this represents WaitPlayerBackView
if viewModel.day { //here are the posibles views that self.viewModel.currentView could have
Text("DayView").transition(AnyTransition.opacity.animation(.easeInOut(duration: 1.0)))
} else {
Text("NightView").transition(AnyTransition.opacity.animation(.(duration: 1.0)))
}
}
}
}
}

class MasterViewModel: ObservableObject {
@Published var day: Bool = false

init () {
_ = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(changeView), userInfo: nil, repeats: true) //Only for testing
}
@objc func changeView() { //@objc is only for testing
day.toggle()
}
}

How to animate the removal of a view created with a ForEach loop getting its data from an ObservableObject in SwiftUI

It is not clear which effect do you try to achieve, but on remove you should animate not view internals, but view itself, ie. in parent, because view remove there and as-a-whole.

Something like (just direction where to experiment):

var body: some View {
ZStack {
ForEach(tagModel.tags, id: \.self) { label in
TagView(label: label)
.transition(.move(edge: .leading)) // << here !! (maybe asymmetric needed)
}
.onReceive(timer) { _ in
self.tagModel.addNextTag()
if tagModel.tags.count > 3 {
self.tagModel.removeOldestTag()
}
}
}
.animation(Animation.easeInOut(duration: 1)) // << here !! (parent animates subview removing)


Related Topics



Leave a reply



Submit