SwiftUI: невозможно анимировать изображения - PullRequest
2 голосов
/ 01 февраля 2020

Я пытаюсь сделать анимацию динамика в SwiftUI такой же, как в Apple Control Center Speaker или Brightness HUD. Я использую 4 изображения из SFSymbols, предоставленных Apple:

  1. "speaker.sla sh .fill" // speakerEmpty
  2. "speaker.1.fill" // speaker1
  3. "speaker.2.fill" // speaker2
  4. "speaker.3.fill" // speaker3

Итак, у меня есть переключатель (Включить SwiftUI) для переключения моего звука с анимацией из четырех изображений. Вот как я хочу, чтобы изображения были анимированы: когда переключатель включен:

  1. speakerEmpty должен анимироваться до -> speaker1, тогда
  2. speaker1 должен анимироваться до - > speaker2 затем
  3. speaker2 должен анимироваться до -> speaker3

и наоборот при выключении переключателя.

Вот код Я пробовал:

SpeakerSymbol Enum

import SwiftUI

enum SpeakerSymbol:CaseIterable {
case speakerEmpty, speaker1, speaker2, speaker3

var image:Image {
    switch self {
        case .speakerEmpty: return Image(systemName: "speaker.slash.fill")
        case .speaker1: return Image(systemName: "speaker.1.fill")
        case .speaker2: return Image(systemName: "speaker.2.fill")
        case .speaker3: return Image(systemName: "speaker.3.fill")
    }
}
}

Выбор динамика Наблюдаемый объект

final class SpeakerSelection: ObservableObject {
  @Published var selectedSymbol:SpeakerSymbol = .speaker3
  @Published var isSoundEnabled = true {
    didSet {
        if isSoundEnabled {
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                self.selectedSymbol = .speakerEmpty
            }
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                self.selectedSymbol = .speaker1
            }
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                self.selectedSymbol = .speaker2
            }
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                self.selectedSymbol = .speaker3
            }
        } else {
            selectedSymbol = .speaker3
            selectedSymbol = .speaker2
            selectedSymbol = .speaker1
            selectedSymbol = .speakerEmpty

        }
    }
}
}

Просмотр профиля

struct ProfileView: View {
@ObservedObject var selection = SpeakerSelection()

var body: some View {

    NavigationView {
        VStack {
            Spacer()
            Text("Speaker Toggle").font(.largeTitle)
            Spacer()

            Toggle(isOn: withAnimation {
                $selection.isSoundEnabled
                }, label: {
                    selection.selectedSymbol.image
                        .frame(width: 50)
                        .animation(Animation.default.delay(0.2))
                        .transition(.asymmetric(insertion: .opacity, removal: .opacity))
                    //                            speakerSymbol()
                    Text("Sound")
            }).padding().animation(.default).font(.largeTitle)
            Spacer()
            Spacer()
        }
    }
}
func speakerSymbol() -> AnyView {
    switch selection.selectedSymbol {
        case .speakerEmpty: return AnyView(SpeakerSymbol.speakerEmpty.image
            .animation(Animation.default.delay(0.2))
            .transition(.asymmetric(insertion: .opacity, removal: .opacity)))
        case .speaker1: return AnyView(SpeakerSymbol.speaker1.image
            .animation(Animation.default.delay(0.2))
            .transition(.asymmetric(insertion: .opacity, removal: .opacity)))
        case .speaker2: return AnyView(SpeakerSymbol.speaker2.image
            .animation(Animation.default.delay(0.2))
            .transition(.asymmetric(insertion: .opacity, removal: .opacity)))
        case .speaker3: return AnyView(SpeakerSymbol.speaker3.image
            .animation(Animation.default.delay(0.2))
            .transition(.asymmetric(insertion: .opacity, removal: .opacity)))
    }
  }
}

Полагаю, мой код logi c в порядке. Изображения заменяют нормально как надо. Но анимационный эффект не работает. Это расстраивает, я пробовал кучу возможных способов, но ни один не работает.

1 Ответ

2 голосов
/ 02 февраля 2020

Здесь возможен подход. Я упростил это для демонстрационной цели и меньшего количества публикуемого кода, но идея должна быть понятной и легко переносимой в ваш реальный код

Результат демонстрации (на самом деле он намного лучше, чем на gif):

enter image description here

Модифицированная модель

enum SpeakerSymbol: Int, CaseIterable { // Inherited from Int for convenient below

    case speakerEmpty, speaker1, speaker2, speaker3

    var image: some View {
        var name: String
        switch self {
        case .speakerEmpty: name = "speaker.slash.fill"
        case .speaker1: name = "speaker.1.fill"
        case .speaker2: name = "speaker.2.fill"
        case .speaker3: name = "speaker.3.fill"
        }
        return Image(systemName: name).font(.largeTitle)
    }
}

Модификатор анимации для SpeakerSymbol, необходимый для того, чтобы анимация SwiftUI знала, что измененное значение SpeakerSymbol способно анимировать

struct SpeakerModifier: AnimatableModifier {
    var symbol: SpeakerSymbol

    init(symbol: SpeakerSymbol) {
        self.symbol = symbol
        self.animating = Double(symbol.rawValue) // enum to Double
    }

    private var animating: Double // Double supports Animatable
    var animatableData: Double { // required part of Animatable protocol
        get { animating }
        set { animating = newValue }
    }

    func body(content: Content) -> some View {
        return SpeakerSymbol(rawValue: Int(animating))!.image // Double -> enum
    }
}

Демонстрация использования

struct TestSpeakerModifier: View {
    @State private var speaker: SpeakerSymbol = .speakerEmpty

    var body: some View {
        VStack {
            Color.clear // << just holder area
                .modifier(SpeakerModifier(symbol: speaker))
                .frame(width: 80, height: 80)
            Divider()
            Button("Toggle") {
                withAnimation { // animates between enum states
                    self.speaker = 
                       (self.speaker == .speakerEmpty ? .speaker3 : .speakerEmpty)
                }
            }
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...