问题描述
如何访问此动画属性 scaleAmount
的当前值?
struct ContentView: View {
@State private var scaleAmount: CGFloat = 1
var body: some View {
vstack {
Stepper("Scale amount",value: $scaleAmount.animation(),in: 1...5)
Circle()
.fill(Color.red)
.frame(width: 50,height: 50)
.scaleEffect(scaleAmount)
.onChange(of: scaleAmount) { value in
print(value)
}
}
}
}
输出:
2.0
3.0
4.0
5.0
这些是可动画属性的最终值 - 而不是用于动画的内插值。这些就是我需要的。
可以这样做吗?我还尝试将 .onChange
块替换为
.onChange(of: $scaleAmount.animation()) { value in
print(value)
}
但是编译器说 .animation()
调用返回的 Binding 必须符合 Equatable。
我还尝试将 Circle 视图分解为自定义视图,并只为其提供一个 CGFloat 来使用,然后动画就会发生。但是绘制圆圈的视图中没有绑定。这给出了与第一个代码片段相同的输出。
struct ContentView: View {
@State private var scaleAmount: CGFloat = 1
var body: some View {
vstack {
Stepper("Scale amount",in: 1...5)
MyCircle(scaleAmount: scaleAmount)
}
}
}
struct MyCircle: View {
var scaleAmount: CGFloat
var body: some View {
print(scaleAmount)
return Circle()
.fill(Color.red)
.frame(width: 50,height: 50)
.scaleEffect(scaleAmount)
}
}
那么,问题又来了:是否可以在动画中获取 scaleAmount
属性的当前值?
解决方法
//
// Created by Eric Lightfoot on 2021-06-24.
//
import SwiftUI
/// Use .onBody(observedValue:onBody:) by passing an
/// animatable property along with a closure to execute
/// whenever that property is changed throughout the
/// animation
/// ---
// struct ContentView: View {
// @State private var scaleAmount: CGFloat = 1
// @State var scaleText: String = "1.000"
//
// var body: some View {
// VStack {
// Stepper("Scale amount",value: $scaleAmount.animation(),in: 1...5)
// ZStack {
// Circle()
// .fill(Color.red)
// .frame(width: 50,height: 50)
// Text(scaleText)
// .onBody(for: scaleAmount,onBody: { scaleText = "\($0)" })
// }
// .scaleEffect(scaleAmount)
// }
// }
// }
/// ---
struct AnimationProgressObserverModifier<Value>: AnimatableModifier where Value: VectorArithmetic {
/// Some experimenting led to observing the behaviour
/// that wrapping the observedValue with animatableData
/// (to conform to the protocol) caused this property
/// to be set continuously during the animation,instead
/// of only at the end
var animatableData: Value {
get {
observedValue
}
set {
observedValue = newValue
runCallbacks()
}
}
/// The animatable property to be observed
/// passed in at init
private var observedValue: Value
/// The block to execute on each animation progress
/// update
private var onBody: (Value) -> Void
init (observedValue: Value,onBody: @escaping (Value) -> Void) {
self.onBody = onBody
self.observedValue = observedValue
}
/// Run the onBody block provided at init
/// specifically passing animatable data,/// NOT observedValue
private func runCallbacks () {
DispatchQueue.main.async {
onBody(animatableData)
}
}
func body (content: Content) -> some View {
/// No modification to view content
content
}
}
extension View {
/// Convenience method on View
func onBody<Value: VectorArithmetic> (for value: Value,onBody: @escaping (Value) -> Void)
-> ModifiedContent<Self,AnimationProgressObserverModifier<Value>> {
modifier(AnimationProgressObserverModifier(observedValue: value,onBody: onBody))
}
}