在 SwiftUI 的动画期间有条件地更新第二个 属性
Conditionally updating a second property during animation in SwiftUI
我在 SwiftUI 中绘制了一个动画 Arc
,表示来自两个设备的数据。当弧向左移动时,“仪表”表示左侧设备的数据高于右侧,反之亦然。 “顶”为零,绘制 Arc
形状时为 270 度。正因为如此,我在 clockwise
属性 上设置了一个条件,这样动画就会出现在零 (270) 的左侧或右侧:
var clockwise: Bool {
get { endAngle.degrees > 270 ? false : true }
}
当 属性 endAngle
从小于 270 变为大于 270 时,或相反,圆弧未正确绘制并显示为圆形,因为顺时针不是将动画的一部分通过 270 设置为新的 endAngle
.
有没有办法延迟 clockwise
的变化,直到动画经过 270 度?
我在下面的视图中添加了一些注释代码。为了动画 Angle
,它必须符合 VectorArithmetic
这就是扩展的原因。
struct AverageGauge: View {
@State var endAngle = Angle(degrees: 271.0)
// Property I'd like to update during the animation
var clockwise: Bool {
get { endAngle.degrees > 270 ? false : true }
}
var body: some View {
VStack {
Arc(startAngle: .degrees(270), endAngle: endAngle,
clockwise: clockwise)
.stroke(.red, style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round))
.frame(width: 100, height: 100)
// Tap gesture simulates changing data
.onTapGesture {
withAnimation(Animation.easeIn(duration: 2.0)) {
// endAngle animated here
endAngle = Angle(degrees: Double.random(in: 180...360))
}
}
Text(String(describing: endAngle.degrees))
Text("\(String(clockwise))")
}
}
}
struct Arc: Shape {
var startAngle: Angle
var endAngle: Angle
var clockwise: Bool
// var startAngleAnimatable: Angle {
// get { startAngle }
// set {startAngle = Angle(degrees: 270.0) }
// }
// Required to animate endAngle
var animatableData: Angle {
get { endAngle }
set { endAngle = newValue }
}
// var clockwiseAnimatable: Bool {
// get { clockwise }
// set { clockwise = newValue }
// }
func path(in rect: CGRect) -> Path {
var path = Path()
path.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: rect.width / 2, startAngle: startAngle, endAngle: endAngle, clockwise: clockwise)
return path
}
}
extension Angle: VectorArithmetic {
public static var zero = Angle(degrees: 0.0)
public static func + (lhs: Angle, rhs: Angle) -> Angle {
Angle(degrees: lhs.degrees + rhs.degrees)
}
public static func - (lhs: Angle, rhs: Angle) -> Angle {
Angle(degrees: lhs.degrees - rhs.degrees)
}
public static func += (lhs: inout Angle, rhs: Angle) {
lhs = Angle(degrees: lhs.degrees + rhs.degrees)
}
public static func -= (lhs: inout Angle, rhs: Angle) {
lhs = Angle(degrees: lhs.degrees - rhs.degrees)
}
public mutating func scale(by rhs: Double) {
self.degrees = self.degrees * rhs
}
public var magnitudeSquared: Double {
get { 0.0 }
}
}
// Preview in case you want to paste it.
struct AverageGauge_Previews: PreviewProvider {
static var previews: some View {
AverageGauge()
}
}
这是动画的 gif。可以看到当新值和270在同一边的时候看起来很正常,但是当动画遍历零(270)的时候就出现了一个圆,因为clockwise
设置错误
我用修剪过的圆简化了它,导致了相同的结果。
如果值从 + 更改为 - 或相反,我会等待第一个动画完成后再开始第二个动画。
struct ContentView: View {
@State var gaugeValue: Double = 0.8 // now in values -1 (-45°) to +1 (+45°)
var body: some View {
VStack {
let fraction: CGFloat = abs(gaugeValue) * 0.25
let rotation = gaugeValue < 0 ? (gaugeValue * 90) - 90 : -90
Circle()
.trim(from: 0, to: fraction )
.rotation(Angle(degrees: rotation), anchor: .center)
.stroke(.red, style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round))
.frame(width: 100, height: 100)
// Tap gesture simulates changing data
Button {
let newGaugeValue = CGFloat.random(in: -1 ... 1)
if newGaugeValue * gaugeValue < 0 { // if change of +/-
withAnimation(Animation.easeOut(duration: 1.0)) {
gaugeValue = 0
}
// delay for reaching 0 point
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
withAnimation(Animation.easeIn(duration: 1.0)) {
gaugeValue = newGaugeValue
}
}
} else { // no change of +/-
withAnimation(Animation.easeIn(duration: 1.0)) {
gaugeValue = newGaugeValue
}
}
} label: {
Text("New Data")
}
Text("\(gaugeValue)")
}
}
}
我在 SwiftUI 中绘制了一个动画 Arc
,表示来自两个设备的数据。当弧向左移动时,“仪表”表示左侧设备的数据高于右侧,反之亦然。 “顶”为零,绘制 Arc
形状时为 270 度。正因为如此,我在 clockwise
属性 上设置了一个条件,这样动画就会出现在零 (270) 的左侧或右侧:
var clockwise: Bool {
get { endAngle.degrees > 270 ? false : true }
}
当 属性 endAngle
从小于 270 变为大于 270 时,或相反,圆弧未正确绘制并显示为圆形,因为顺时针不是将动画的一部分通过 270 设置为新的 endAngle
.
有没有办法延迟 clockwise
的变化,直到动画经过 270 度?
我在下面的视图中添加了一些注释代码。为了动画 Angle
,它必须符合 VectorArithmetic
这就是扩展的原因。
struct AverageGauge: View {
@State var endAngle = Angle(degrees: 271.0)
// Property I'd like to update during the animation
var clockwise: Bool {
get { endAngle.degrees > 270 ? false : true }
}
var body: some View {
VStack {
Arc(startAngle: .degrees(270), endAngle: endAngle,
clockwise: clockwise)
.stroke(.red, style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round))
.frame(width: 100, height: 100)
// Tap gesture simulates changing data
.onTapGesture {
withAnimation(Animation.easeIn(duration: 2.0)) {
// endAngle animated here
endAngle = Angle(degrees: Double.random(in: 180...360))
}
}
Text(String(describing: endAngle.degrees))
Text("\(String(clockwise))")
}
}
}
struct Arc: Shape {
var startAngle: Angle
var endAngle: Angle
var clockwise: Bool
// var startAngleAnimatable: Angle {
// get { startAngle }
// set {startAngle = Angle(degrees: 270.0) }
// }
// Required to animate endAngle
var animatableData: Angle {
get { endAngle }
set { endAngle = newValue }
}
// var clockwiseAnimatable: Bool {
// get { clockwise }
// set { clockwise = newValue }
// }
func path(in rect: CGRect) -> Path {
var path = Path()
path.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: rect.width / 2, startAngle: startAngle, endAngle: endAngle, clockwise: clockwise)
return path
}
}
extension Angle: VectorArithmetic {
public static var zero = Angle(degrees: 0.0)
public static func + (lhs: Angle, rhs: Angle) -> Angle {
Angle(degrees: lhs.degrees + rhs.degrees)
}
public static func - (lhs: Angle, rhs: Angle) -> Angle {
Angle(degrees: lhs.degrees - rhs.degrees)
}
public static func += (lhs: inout Angle, rhs: Angle) {
lhs = Angle(degrees: lhs.degrees + rhs.degrees)
}
public static func -= (lhs: inout Angle, rhs: Angle) {
lhs = Angle(degrees: lhs.degrees - rhs.degrees)
}
public mutating func scale(by rhs: Double) {
self.degrees = self.degrees * rhs
}
public var magnitudeSquared: Double {
get { 0.0 }
}
}
// Preview in case you want to paste it.
struct AverageGauge_Previews: PreviewProvider {
static var previews: some View {
AverageGauge()
}
}
这是动画的 gif。可以看到当新值和270在同一边的时候看起来很正常,但是当动画遍历零(270)的时候就出现了一个圆,因为clockwise
设置错误
我用修剪过的圆简化了它,导致了相同的结果。
如果值从 + 更改为 - 或相反,我会等待第一个动画完成后再开始第二个动画。
struct ContentView: View {
@State var gaugeValue: Double = 0.8 // now in values -1 (-45°) to +1 (+45°)
var body: some View {
VStack {
let fraction: CGFloat = abs(gaugeValue) * 0.25
let rotation = gaugeValue < 0 ? (gaugeValue * 90) - 90 : -90
Circle()
.trim(from: 0, to: fraction )
.rotation(Angle(degrees: rotation), anchor: .center)
.stroke(.red, style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round))
.frame(width: 100, height: 100)
// Tap gesture simulates changing data
Button {
let newGaugeValue = CGFloat.random(in: -1 ... 1)
if newGaugeValue * gaugeValue < 0 { // if change of +/-
withAnimation(Animation.easeOut(duration: 1.0)) {
gaugeValue = 0
}
// delay for reaching 0 point
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
withAnimation(Animation.easeIn(duration: 1.0)) {
gaugeValue = newGaugeValue
}
}
} else { // no change of +/-
withAnimation(Animation.easeIn(duration: 1.0)) {
gaugeValue = newGaugeValue
}
}
} label: {
Text("New Data")
}
Text("\(gaugeValue)")
}
}
}