Анимация SwiftUI не всегда работает

Я работаю над кодом, чтобы научиться 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) {
        
    }
}

Пожалуйста, сделайте ваш пример воспроизводимым. См. Как создать минимальный воспроизводимый пример

pawello2222 22.12.2020 23:48

Я обновил исходный пост кодом, который может воспроизвести проблему. Запустите его и нажмите кнопку Roll Dice несколько раз. Вы увидите, что анимация запускается только тогда, когда изображение не изменилось по сравнению с предыдущим роликом. Моя цель — запустить анимацию для каждого кубика при каждом броске.

KMerenda 23.12.2020 05:08
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
199
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В вашем сценарии идентификаторы данных в ForEach должны оставаться прежними, иначе соответствующие представления заменяются и анимация не работает.

Исправить это просто — используйте diceNumber в качестве идентификатора:

ForEach(diceThrow.rolls, id: \.diceNumber) { printedRoll in
    Text("\(printedRoll.diceValue)")

Протестировано с Xcode 12.1/iOS 14.1

Другие вопросы по теме