Я работаю над кодом, чтобы научиться 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) {
}
}
Я обновил исходный пост кодом, который может воспроизвести проблему. Запустите его и нажмите кнопку Roll Dice несколько раз. Вы увидите, что анимация запускается только тогда, когда изображение не изменилось по сравнению с предыдущим роликом. Моя цель — запустить анимацию для каждого кубика при каждом броске.
В вашем сценарии идентификаторы данных в ForEach
должны оставаться прежними, иначе соответствующие представления заменяются и анимация не работает.
Исправить это просто — используйте diceNumber
в качестве идентификатора:
ForEach(diceThrow.rolls, id: \.diceNumber) { printedRoll in
Text("\(printedRoll.diceValue)")
Протестировано с Xcode 12.1/iOS 14.1
Пожалуйста, сделайте ваш пример воспроизводимым. См. Как создать минимальный воспроизводимый пример