我有以下用户界面:
Red Rectangle
旋转速度基于滑块的值。我使用 .linear(duration:).repeatForever(autoreverses: false)
(基于滑块值的持续时间)和 .id
修改器来实现此目的,以便在滑块值更改时停止当前动画。
struct ContentView: View {
@State private var level: Double = 0
@State private var rotation: Double = 0
var body: some View {
VStack(spacing: 16) {
Rectangle().fill(.red)
.frame(width: 100, height: 100)
.id(level)
.rotationEffect(Angle(degrees: rotation))
.animation(level > 0 ? .linear(duration: 2 / level).repeatForever(autoreverses: false) : .linear(duration: 2), value: rotation)
Spacer().frame(height: 12)
Slider(value: $level, in: 0...3, step: 1)
HStack {
Text("0")
.fontWeight(.semibold)
Spacer()
Text("1")
.fontWeight(.semibold)
Spacer()
Text("2")
.fontWeight(.semibold)
Spacer()
Text("3")
.fontWeight(.semibold)
}
}
.padding(16)
.onChange(of: level, perform: { _ in
rotation += 360
})
}
}
我当前的问题是,当用户更改滑块的值时,由于
Rectangle
,.id
重置为初始状态,因此会产生故障。
这是我的代码当前如何工作的视频链接:https://imgur.com/a/6bsUJzm
我的问题是当用户更改滑块的值时如何在状态之间实现平滑过渡?任何见解或建议将不胜感激!
解决方法之一是避免在动画中使用
.repeatForever
。相反:
自 iOS 17 起,使用
withAnimation
触发的动画可以有 completion
回调。所以这将是触发后续动画的一种方式。然而,这样做会产生相当断断续续的动画。
另一种技术是使用
Animatable
ViewModifier
来跟踪动画并在动画完成时执行操作。这样做工作起来会更顺利。 SwiftUI动画完成动画周期的答案提供了一个可用于此目的的通用修饰符(这是我的答案)。那篇文章也涉及连续轮换,所以这是一个非常相似的问题。
这是更新后的示例。更多注意事项:
应删除
.id
修饰符。
动画修改器出现在AnimationCompletionCallback
之后
非常重要。
.repeatForever
,因此可以简化动画修饰符(不需要三元运算符)。
.fontWeight
应用到
HStack
更简单,而不是应用到每个单独的标签。
.onChange
的已弃用版本。如果实际上您的目标是早于 iOS 17 的 iOS 版本,则只需删除参数内的
oldVal
即可。
var body: some View {
VStack(spacing: 16) {
Rectangle()
.fill(.red)
.frame(width: 100, height: 100)
.rotationEffect(Angle(degrees: rotation))
// See https://stackoverflow.com/a/76969841/20386264
.modifier(
AnimationCompletionCallback(animatedValue: rotation) {
if level > 0 {
rotation += 90
}
}
)
.animation(.linear(duration: 2 / max(1, level)), value: rotation)
Spacer().frame(height: 12)
Slider(value: $level, in: 0...3, step: 1)
HStack {
Text("0")
Spacer()
Text("1")
Spacer()
Text("2")
Spacer()
Text("3")
}
.fontWeight(.semibold)
}
.padding(16)
.onChange(of: level) { oldVal, newVal in
if oldVal == 0 {
rotation += 90
}
}
}