在 SwiftUI 中使用 animatableData 设置动画时出现问题
Problem animating with animatableData in SwiftUI
SwiftUI 不仅让我们获得自动动画……它还让我们使用 animatableData
属性 控制动画的发生方式。太酷了!
只是我无法让它工作。以下代码在屏幕上显示一个钟面(数字 0…11 )并显示一个切换开关。切换切换开关将数字旋转 180 度,动画。或者这就是我的意图。但是,不是每个数字都围绕数字圈采用动画路径,而是每个数字沿着直线移动到其新位置……这样数字圈就会折叠到圆心点,然后重新爆炸成新的配置:
SwiftUI 未引用 animatableData
属性。知道为什么吗?
(请注意,使用标准旋转动画是行不通的,因为那样会使所有数字颠倒过来。)
import SwiftUI
struct ContentView: View {
@State var state: Bool = false
var body: some View {
VStack {
CircleView(hoursOffset: CGFloat(state ? 6 : 0 ))
.aspectRatio(contentMode: .fit)
Toggle(isOn: $state, label: { Text("Rotate 180°?") })
.frame(maxWidth: 200).padding()
}
}
}
extension CGPoint {
static func onCircle(hours: CGFloat, size: CGFloat) -> CGPoint {
let radians = (3 - hours) * CGFloat.pi / CGFloat (6)
let hypotenuse = size / 2
return CGPoint(x: size / 2 + 0.8 * hypotenuse * cos(radians),
y: size / 2 - 0.8 * hypotenuse * sin(radians))
}
}
struct CircleView: View {
var hoursOffset: CGFloat
public var animatableData: CGFloat {
get { hoursOffset }
set { self.hoursOffset = newValue }
}
var body: some View {
GeometryReader() { geo in
ForEach(0..<12) { hour in
ZStack {
Text("\(hour)")
.position(CGPoint.onCircle(hours: CGFloat(hour) + self.hoursOffset, size: geo.size.width))
.animation(Animation.easeInOut(duration: 2.0))
}
}
}
}
}
问题出在邪恶的数学上……你在计算端点。 Apple 只是将步骤从一个位置添加到另一个位置(作为一条线),所以这就是您所看到的。如果你从 0 切换到 1,它 "looks" 就像一个圆形移动,但它也是线性的 ;)
解决方案是,根据您的小时偏移值手动计算圆上的 x、y 位置
这是可能的方法。使用 Xcode 11.4 / iOS 13.4 进行测试。视图中的 animatableData
不起作用 - 您必须将其放入 AnimatableModifier
.
struct CircleMoving: AnimatableModifier {
var hoursOffset: CGFloat
var hour: CGFloat
var size: CGFloat
public var animatableData: CGFloat {
get { hoursOffset }
set { self.hoursOffset = newValue }
}
func body(content: Content) -> some View {
content
.position(CGPoint.onCircle(hours: hour + hoursOffset, size: size))
}
}
struct CircleView: View {
var hoursOffset: CGFloat
var body: some View {
GeometryReader() { geo in
ForEach(0..<12) { hour in
ZStack {
Text("\(hour)")
.modifier(CircleMoving(hoursOffset: self.hoursOffset, hour: CGFloat(hour), size: geo.size.width))
}
}
}
.animation(.easeInOut(duration: 3.0), value: hoursOffset) // !!!
}
}
可选:我也建议您将点值四舍五入以获得更好的完整性,但这并不重要。
extension CGPoint {
static func onCircle(hours: CGFloat, size: CGFloat) -> CGPoint {
let radians = (3 - hours) * CGFloat.pi / CGFloat (6)
let hypotenuse = size / 2
return CGPoint(x: (size / 2 + 0.8 * hypotenuse * cos(radians)).rounded(.up),
y: (size / 2 - 0.8 * hypotenuse * sin(radians)).rounded(.up))
}
}
SwiftUI 不仅让我们获得自动动画……它还让我们使用 animatableData
属性 控制动画的发生方式。太酷了!
只是我无法让它工作。以下代码在屏幕上显示一个钟面(数字 0…11 )并显示一个切换开关。切换切换开关将数字旋转 180 度,动画。或者这就是我的意图。但是,不是每个数字都围绕数字圈采用动画路径,而是每个数字沿着直线移动到其新位置……这样数字圈就会折叠到圆心点,然后重新爆炸成新的配置:
SwiftUI 未引用 animatableData
属性。知道为什么吗?
(请注意,使用标准旋转动画是行不通的,因为那样会使所有数字颠倒过来。)
import SwiftUI
struct ContentView: View {
@State var state: Bool = false
var body: some View {
VStack {
CircleView(hoursOffset: CGFloat(state ? 6 : 0 ))
.aspectRatio(contentMode: .fit)
Toggle(isOn: $state, label: { Text("Rotate 180°?") })
.frame(maxWidth: 200).padding()
}
}
}
extension CGPoint {
static func onCircle(hours: CGFloat, size: CGFloat) -> CGPoint {
let radians = (3 - hours) * CGFloat.pi / CGFloat (6)
let hypotenuse = size / 2
return CGPoint(x: size / 2 + 0.8 * hypotenuse * cos(radians),
y: size / 2 - 0.8 * hypotenuse * sin(radians))
}
}
struct CircleView: View {
var hoursOffset: CGFloat
public var animatableData: CGFloat {
get { hoursOffset }
set { self.hoursOffset = newValue }
}
var body: some View {
GeometryReader() { geo in
ForEach(0..<12) { hour in
ZStack {
Text("\(hour)")
.position(CGPoint.onCircle(hours: CGFloat(hour) + self.hoursOffset, size: geo.size.width))
.animation(Animation.easeInOut(duration: 2.0))
}
}
}
}
}
问题出在邪恶的数学上……你在计算端点。 Apple 只是将步骤从一个位置添加到另一个位置(作为一条线),所以这就是您所看到的。如果你从 0 切换到 1,它 "looks" 就像一个圆形移动,但它也是线性的 ;) 解决方案是,根据您的小时偏移值手动计算圆上的 x、y 位置
这是可能的方法。使用 Xcode 11.4 / iOS 13.4 进行测试。视图中的 animatableData
不起作用 - 您必须将其放入 AnimatableModifier
.
struct CircleMoving: AnimatableModifier {
var hoursOffset: CGFloat
var hour: CGFloat
var size: CGFloat
public var animatableData: CGFloat {
get { hoursOffset }
set { self.hoursOffset = newValue }
}
func body(content: Content) -> some View {
content
.position(CGPoint.onCircle(hours: hour + hoursOffset, size: size))
}
}
struct CircleView: View {
var hoursOffset: CGFloat
var body: some View {
GeometryReader() { geo in
ForEach(0..<12) { hour in
ZStack {
Text("\(hour)")
.modifier(CircleMoving(hoursOffset: self.hoursOffset, hour: CGFloat(hour), size: geo.size.width))
}
}
}
.animation(.easeInOut(duration: 3.0), value: hoursOffset) // !!!
}
}
可选:我也建议您将点值四舍五入以获得更好的完整性,但这并不重要。
extension CGPoint {
static func onCircle(hours: CGFloat, size: CGFloat) -> CGPoint {
let radians = (3 - hours) * CGFloat.pi / CGFloat (6)
let hypotenuse = size / 2
return CGPoint(x: (size / 2 + 0.8 * hypotenuse * cos(radians)).rounded(.up),
y: (size / 2 - 0.8 * hypotenuse * sin(radians)).rounded(.up))
}
}