Below is an approach with AnimatableModifier
. It only fades in the new value. If you want to fade out the old value as well, it won't be that hard to customize the modifier. Also since your display value is numeric, you can use that itself as the control variable with some minor modifications.
This approach can be used for not only fade but also other types of animations, in response to a change in the value of the view. You can have additional arguments passed to the modifier. You may also completely ignore the passed content
in the body
and create and return an entirely new view. Overlay
, EmptyView
etc too can be handy in such cases.
import SwiftUI
struct FadeModifier: AnimatableModifier {
// To trigger the animation as well as to hold its final state
private let control: Bool
// SwiftUI gradually varies it from old value to the new value
var animatableData: Double = 0.0
// Re-created every time the control argument changes
init(control: Bool) {
// Set control to the new value
self.control = control
// Set animatableData to the new value. But SwiftUI again directly
// and gradually varies it from 0 to 1 or 1 to 0, while the body
// is being called to animate. Following line serves the purpose of
// associating the extenal control argument with the animatableData.
self.animatableData = control ? 1.0 : 0.0
}
// Called after each gradual change in animatableData to allow the
// modifier to animate
func body(content: Content) -> some View {
// content is the view on which .modifier is applied
content
// Map each "0 to 1" and "1 to 0" change to a "0 to 1" change
.opacity(control ? animatableData : 1.0 - animatableData)
// This modifier is animating the opacity by gradually setting
// incremental values. We don't want the system also to
// implicitly animate it each time we set it. It will also cancel
// out other implicit animations now present on the content.
.animation(nil)
}
}
struct ExampleView: View {
// Dummy control to trigger animation
@State var control: Bool = false
// Actual display value
@State var message: String = "Hi" {
didSet {
// Toggle the control to trigger a new fade animation
control.toggle()
}
}
var body: some View {
VStack {
Spacer()
Text(message)
.font(.largeTitle)
// Toggling the control causes the re-creation of FadeModifier()
// It is followed by a system managed gradual change in the
// animatableData from old value of control to new value. With
// each change in animatableData, the body() of FadeModifier is
// called, thus giving the effect of animation
.modifier(FadeModifier(control: control))
// Duration of the fade animation
.animation(.easeInOut(duration: 1.0))
Spacer()
Button(action: {
self.message = self.message == "Hi" ? "Hello" : "Hi"
}) {
Text("Change Text")
}
Spacer()
}
}
}
struct ExampleView_Previews: PreviewProvider {
static var previews: some View {
ExampleView()
}
}