SwiftUI:无法为图像设置动画 [英] SwiftUI: Unable to animate images
问题描述
我正在尝试在 SwiftUI 中制作与 Apple 的控制中心扬声器或亮度 HUD 类似的扬声器动画.我正在使用 Apple 提供的 SFSymbols 中的 4 张图像:
- "speaker.slash.fill"
//SpeakerEmpty
- "speaker.1.fill"
//Speaker1
- "speaker.2.fill"
//Speaker2
- "speaker.3.fill"
//Speaker3
所以,我有一个开关(SwiftUI 中的切换)来切换带有四个图像动画的声音选项.这就是我希望图像动画的方式:打开开关时:
speakerEmpty
应该动画到 ->speaker1
然后speaker1
应该动画到 ->speaker2
然后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:
- "speaker.slash.fill"
// speakerEmpty
- "speaker.1.fill"
// speaker1
- "speaker.2.fill"
// speaker2
- "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:
speakerEmpty
should animate to ->speaker1
thenspeaker1
should animate to ->speaker2
thenspeaker2
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屋!