SwiftUI 动画并不总是运行
Posted
技术标签:
【中文标题】SwiftUI 动画并不总是运行【英文标题】:SwiftUI Animation Doesn't Always Run 【发布时间】:2020-12-22 22:25:30 【问题描述】:我正在编写一些代码来自学 SwiftUI,而我目前的项目是一个掷骰子应用程序。
在我看来,我展示了一些使用 SFSymbols 的图像来表示掷骰后每个骰子的值。我在每张图像上使用了两个动画(.rotation3Deffect
和 .rotationEffect
)来提供骰子的视觉滚动。
我发现动画只会在单个骰子值(或者在代码的情况下,如果Image(systemName: "\(diceValue).square.fill")
)从一卷更改为下一卷时运行。换句话说,如果我掷三个骰子,它们落在 1-4-6,然后我再次掷骰子,它们落在 1-4-7,1 和 4 图像将执行动画,但图像从 6到 7 没有动画 - 它只是更新到新图像。知道为什么动画只在与前一次掷骰子没有变化的图像上运行吗?
import SwiftUI
struct Dice
var sides: Int
var values: [Int]
var calculatedValues = [Int]()
for v in 1...sides
calculatedValues.append(v)
return calculatedValues
func rollDice() -> Int
let index = Int.random(in: 0..<values.count)
return values[index]
struct Roll: Hashable, Identifiable
let id = UUID()
var diceNumber: Int = 0
var diceValue: Int = 0
struct DiceThrow: Hashable
var rolls = [Roll]()
var totalScore: Int
var score = 0
for roll in rolls
score += roll.diceValue
return score
class ThrowStore: ObservableObject, Hashable
@Published var diceThrows = [DiceThrow]()
static func ==(lhs: ThrowStore, rhs: ThrowStore) -> Bool
return lhs.diceThrows == rhs.diceThrows
func hash(into hasher: inout Hasher)
hasher.combine(diceThrows)
class Settings: ObservableObject
@Published var dicePerRoll: Int = 1
@Published var sidesPerDice: Int = 4
init(dicePerRoll: Int, sidesPerDice: Int)
self.dicePerRoll = dicePerRoll
self.sidesPerDice = sidesPerDice
struct ContentView: View
var throwStore = ThrowStore()
let settings = Settings(dicePerRoll: 3, sidesPerDice: 6)
@State private var roll = Roll()
@State private var diceThrow = DiceThrow()
@State private var isRotated = false
@State private var rotationAngle: Double = 0
var animation: Animation
Animation.easeOut(duration: 3)
var body: some View
VStack
if self.throwStore.diceThrows.count != 0
HStack
Text("For dice roll #\(self.throwStore.diceThrows.count)...")
.fontWeight(.bold)
.font(.largeTitle)
.padding(.leading)
Spacer()
List
HStack(alignment: .center)
Spacer()
ForEach(diceThrow.rolls, id: \.self) printedRoll in
Text("\(printedRoll.diceValue)")
.font(.title)
.foregroundColor(.white)
.frame(width: 50, height: 50)
.background(RoundedRectangle(cornerRadius: 4))
.rotation3DEffect(Angle.degrees(isRotated ? 3600 : 0), axis: (x: 1, y: 0, z: 0))
.rotationEffect(Angle.degrees(isRotated ? 3600 : 0))
.animation(animation)
Spacer()
if self.throwStore.diceThrows.count != 0
HStack
Text("Total for this roll:")
Spacer()
Text("\(self.diceThrow.totalScore)")
.padding(.leading)
.padding(.trailing)
.background(Capsule().stroke())
Spacer()
Button("Roll the dice")
self.diceThrow.rolls = [Roll]()
self.isRotated.toggle()
for diceNumber in 1...settings.dicePerRoll
let currentDice = Dice(sides: settings.sidesPerDice)
roll.diceNumber = diceNumber
roll.diceValue = currentDice.rollDice()
self.diceThrow.rolls.append(roll)
self.throwStore.diceThrows.append(self.diceThrow)
.frame(width: 200)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.clipShape(Capsule())
.padding(.bottom)
func showDiceRoll(_ sides: Int)
【问题讨论】:
请使您的示例可重现。见How to create a Minimal, Reproducible Example 我已经用可以重现问题的代码更新了原始帖子。运行它并多次点击掷骰子按钮。您将看到动画仅在图像未从前一卷更改时运行。我的目标是让动画在每次滚动时为每个骰子运行。 【参考方案1】:在您的场景中,ForEach
中的数据标识符应该保持不变,否则相应的视图将被替换并且动画不起作用。
解决方法很简单 - 使用 diceNumber
作为 ID:
ForEach(diceThrow.rolls, id: \.diceNumber) printedRoll in
Text("\(printedRoll.diceValue)")
使用 Xcode 12.1 / ios 14.1 测试
【讨论】:
以上是关于SwiftUI 动画并不总是运行的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI DatePicker 并不总是更新 State<Date> 变量
SwiftUI4.0有趣的动画升级:新iOS16视图内容过渡动画