ios - SwiftUI 动画形状变得疯狂
问题描述
首先,我对这篇文章的非传统标题表示歉意,但我不知道有什么方法可以比这更好地描述这种行为。
要重现此问题,请在 xCode 12 中创建一个新项目,指定任何名称的 IOS App 和 **Interface:**SwiftUI, Life Cycle: SwiftUI App, Language: Swift。然后将所有内容视图替换为此处列出的代码。
在实时预览中,或在运行应用程序时,单击多边形将触发动画。它应该移动一小部分像素,然后转 1/3 圈(因为角度以弧度为单位)。如第 37 行所述,问题在于,当我尝试移动或转动 Poly 时,它会发疯,将任何移动乘以更大的数量。颜色的动画效果很好,但形状中的 Animatable 属性却不行。该位置从 200,200 开始,角度为 0,如果您尝试将其移得很近,就像示例代码一样,它会反应过度。如果您尝试仅将其移动几个像素(例如 190,190),它将飞出屏幕。
我所做的任何其他动画都没有发生这种情况,也不知道为什么会这样。
在尝试对此进行调试时,我曾在 animatableData getter 和 setter 上放置了 print 语句,并且无法理解动画引擎对变量所做的事情。它似乎只是选择了一个离我要求它去的值的来源更远的数字。
我相信路径中的触发是正确的,并且怀疑问题出在以下之一:
- 我以某种方式声明 animatableData
- 手势中的 withAnimation 函数
- 动画 CGFloat 的问题
- SwiftUI 快疯了
我正在运行 Xcode 12.1 (12A7403) 和 Swift 5。经过数小时的尝试解决这个问题后,我在这里谦虚地提出我的问题。 帮助我 Obiwan Kenobi,你是我唯一的希望......
import SwiftUI
let twoPi:CGFloat = CGFloat.pi * 2
let pi = CGFloat.pi
class Poly: ObservableObject, Identifiable {
@Published var location:CGPoint
@Published var color:Color
var sides:CGFloat
var vertexRadius:CGFloat
var angle:CGFloat
init(at:CGPoint, color:Color, sides:CGFloat, radius:CGFloat, angle:CGFloat=0) {
self.location = at
self.color = color
self.sides = sides
self.vertexRadius = radius
self.angle = angle
}
}
struct ContentView: View {
@ObservedObject var poly:Poly = Poly(at: CGPoint(x:200,y:200), color: .green, sides: 6, radius: 100)
var body: some View {
PolyShape(poly: poly)
.fill(poly.color)
.gesture(
DragGesture(minimumDistance: 0, coordinateSpace: .local)
.onEnded { gesture in
withAnimation(.easeInOut(duration:15)) {
//This is what doesn't work.
//Try to nudge it a fraction of a pixel, and do only 1/3 of a turn, and it spins and moves much further.
poly.location.x = 200.4
poly.location.y = 200.2
poly.angle = twoPi / 3
poly.color = .red
}
}
)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView(poly: Poly(at: CGPoint(x:200,y:200), color: .blue, sides: 3, radius: 100))
}
}
}
struct PolyShape:Shape {
var poly:Poly
public var animatableData: AnimatablePair<CGFloat, AnimatablePair<CGFloat,CGFloat>> {
get { AnimatablePair(poly.angle, AnimatablePair(poly.location.x, poly.location.y))
}
set {
poly.angle = newValue.first
poly.location.x = newValue.second.first
poly.location.y = newValue.second.second
}
}
func path(in rect: CGRect) -> Path {
var path = Path()
var radial:CGFloat = 0.5
while radial < twoPi + 0.5 {
let radialAngle = twoPi / poly.sides * radial + poly.angle
let newX = poly.location.x + cos(radialAngle) * poly.vertexRadius
let newY = poly.location.y + sin(radialAngle) * poly.vertexRadius
if radial == 0.5 {
path.move(to: CGPoint(x: newX, y: newY))
} else {
path.addLine(to: CGPoint(x: newX, y: newY))
}
radial += 1
}
return path
}
}
解决方案
您需要将模型与视图模型分开,因为要PolyShape
在您的情况下正确工作,输入数据必须有一个值。
这是经过测试的解决方案(Xcode 12 / iOS 14)
- 分离模型和视图模型
class Poly: ObservableObject, Identifiable {
@Published var data:PolyData
init(data: PolyData) {
self.data = data
}
}
struct PolyData {
var location:CGPoint
var color:Color
var sides:CGFloat
var vertexRadius:CGFloat
var angle:CGFloat
init(at:CGPoint, color:Color, sides:CGFloat, radius:CGFloat, angle:CGFloat=0) {
self.location = at
self.color = color
self.sides = sides
self.vertexRadius = radius
self.angle = angle
}
}
- 使形状值依赖
struct PolyShape:Shape {
var poly:PolyData // << here !!
// ... other code no changes
}
- 更新依赖演示
struct ContentView: View {
@ObservedObject var poly:Poly = Poly(data: PolyData(at: CGPoint(x:200,y:200), color: .green, sides: 6, radius: 100))
var body: some View {
PolyShape(poly: poly.data) // << pass data only here !!
.fill(poly.data.color)
.gesture(
DragGesture(minimumDistance: 0, coordinateSpace: .local)
.onEnded { gesture in
withAnimation(.easeInOut(duration:15)) {
poly.data.location = CGPoint(x: 200.4, y: 200.2)
poly.data.angle = twoPi / 3
poly.data.color = .red
}
}
)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView(poly: Poly(data: PolyData(at: CGPoint(x:200,y:200), color: .blue, sides: 3, radius: 100)))
}
}
}
推荐阅读
- xcuitest - 如果 XCUITest 中的一项测试失败,如何使相关测试失败?
- c# - 我可以将 foreach 循环内的字典循环转换为 LINQ 语句吗
- javascript - Javascript - 查找字谜的更好解决方案 - 时间复杂度 O (n log n)
- ios - 在执行退出操作时,unwind segue 不会出现在界面构建中
- mysql - 通过 mysql 中的触发器更新已使用积分的奖励状态
- r - 创建只有 1 个 ID 列的宽数据
- javascript - 将 Suspense 与 React Router 一起使用时,Typescript 会引发显式函数返回类型错误
- angular - Angular routerLink 不会触发 ngOnInit
- python-3.x - 如何使用 pandas 将销售数据组织成 12 个月,并找到这 12 个月中最赚钱的 10 种产品?
- pandas - Pandas 0.24.0 用特殊的列标识符破坏了我的 pandas 数据框