SwiftUI:无法为图像设置动画 [英] SwiftUI: Unable to animate images

查看:31
本文介绍了SwiftUI:无法为图像设置动画的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在 SwiftUI 中制作与 Apple 的控制中心扬声器或亮度 HUD 类似的扬声器动画.我正在使用 Apple 提供的 SFSymbols 中的 4 张图像:

  1. "speaker.slash.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 枚举

导入 SwiftUI枚举 SpeakerSymbol:CaseIterable {案例 SpeakerEmpty、扬声器 1、扬声器 2、扬声器 3var 图像:图像 {切换自我{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 {已设置{如果是SoundEnabled {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}} 别的 {selectedSymbol = .speaker3selectedSymbol = .speaker2selectedSymbol = .speaker1selectedSymbol = .speakerEmpty}}}}

<块引用>

个人资料视图

struct ProfileView:查看{@ObservedObject var selection = SpeakerSelection()var主体:一些视图{导航视图{虚拟堆栈{垫片()Text("Speaker Toggle").font(.largeTitle)垫片()切换(isOn: withAnimation {$selection.isSoundEnabled}, 标签: {selection.selectedSymbol.image.frame(宽度:50).animation(Animation.default.delay(0.2)).transition(.asymmetric(insertion: .opacity, remove: .opacity))//扬声器符号()文本(声音")}).padding().animation(.default).font(.largeTitle)垫片()垫片()}}}func SpeakerSymbol() ->任意视图{开关选择.selectedSymbol {case .speakerEmpty: 返回 AnyView(SpeakerSymbol.speakerEmpty.image.animation(Animation.default.delay(0.2)).transition(.asymmetric(insertion: .opacity, remove: .opacity)))case .speaker1: return AnyView(SpeakerSymbol.speaker1.image.animation(Animation.default.delay(0.2)).transition(.asymmetric(insertion: .opacity, remove: .opacity)))case .speaker2: return AnyView(SpeakerSymbol.speaker2.image.animation(Animation.default.delay(0.2)).transition(.asymmetric(insertion: .opacity, remove: .opacity)))case .speaker3: return AnyView(SpeakerSymbol.speaker3.image.animation(Animation.default.delay(0.2)).transition(.asymmetric(insertion: .opacity, remove: .opacity)))}}}

我想我的代码逻辑没问题.图像正在按原样替换.但是没有动画效果工作.令人沮丧的是,我尝试了很多可能的方法,但都没有奏效.

解决方案

这是可能的方法.我出于演示目的简化了它并减少了发布代码,但想法应该清晰且易于转移到您的真实代码中

结果演示(确实比在gif上流畅得多):

修改后的模型

enum SpeakerSymbol: Int, CaseIterable {//继承自 Int 方便下面案例 SpeakerEmpty、扬声器 1、扬声器 2、扬声器 3var 图像:一些视图 {变量名:字符串切换自我{case .speakerEmpty: name = "speaker.slash.fill"case .speaker1: name = "speaker.1.fill"case .speaker2: name = "speaker.2.fill"case .speaker3: name = "speaker.3.fill"}返回图像(系统名称:名称).字体(.largeTitle)}}

SpeakerSymbol 的动画修饰符,需要让 SwiftUI 动画知道更改的 SpeakerSymbol 值能够动画

struct SpeakerModifier: AnimatableModifier {var 符号:SpeakerSymbol初始化(符号:SpeakerSymbol){self.symbol = 符号self.animating = Double(symbol.rawValue)//枚举到 Double}private var animating: Double//Double 支持 Animatablevar animatableData: Double {//Animatable 协议的必需部分获取{动画}设置 { 动画 = newValue }}func 正文(内容:内容)->一些视图{return SpeakerSymbol(rawValue: Int(animating))!.image//Double ->枚举}}

使用演示

struct TestSpeakerModifier:查看{@State 私有 var 扬声器:SpeakerSymbol = .speakerEmptyvar主体:一些视图{虚拟堆栈{Color.clear//<<只是持有人区.modifier(SpeakerModifier(符号:扬声器)).frame(宽:80,高:80)分隔线()按钮(切换"){withAnimation {//枚举状态之间的动画自言自语 =(self.speaker == .speakerEmpty ? .speaker3 : .speakerEmpty)}}}}}

I am trying to make similar Speaker Animation in SwiftUI as that of Apple's Control Center Speaker or Brightness HUD. I'm using 4 images from SFSymbols provided by Apple:

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

So, I have a Switch (Toggle in SwiftUI) to toggle my Sound option with an animation of four images. This is how I want images to be animated: When the toggle is on:

  1. speakerEmpty should animate to -> speaker1 then
  2. speaker1 should animate to -> speaker2 then
  3. speaker2 should animate to -> speaker3

And reverse for when the toggle is off.

Here is the code I tried:

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")
    }
}
}

Speaker Selection Observable Object

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

        }
    }
}
}

Profile View

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)))
    }
  }
}

I guess my code logic is fine. Images are replacing fine as it should. But there is no animation effect working. It's frustrating I've tried a bunch of possible ways but none is working.

解决方案

Here is possible approach. I simplified it for a demo purpose and less posting code, but idea should be clear and easy transferrable to your real code

Result demo (really it is much fluent than on gif):

Modified model

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)
    }
}

Animatable modifier for SpeakerSymbol, required to let SwiftUI animation know that changed SpeakerSymbol value is able to animate

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
    }
}

Demo of usage

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)
                }
            }
        }
    }
}

这篇关于SwiftUI:无法为图像设置动画的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆