SwiftUIで画像切り替えのアニメーションを実現する
はじめに
Pixelfieという画層をピクセル風に変換するアプリをリリースしています。
最近、変換過程を時間経過とともに見せるという変更を行いました。
少し分かりづらいかもしれませんが、変換過程では下の例のように画像を差し替える際にクロスブラー的なアニメーションを入れています。
今回は同じことをやりたいと思っている方の参考になればと思いその方法を紹介します。
SwiftUIの画像切り替えのアニメーション実装方法
まず、アニメーション実装の方向性として
- Transitionを使う
- Animationを使う
の2つが考えられます。
Transitionとは
Transitionとは、あるViewを別のViewに切り替える際に適用されるアニメーションです。
AとBという2つのViewがあったとして、Aの代わりにBを表示したいという場合に、切り替わりをアニメーションで表現することができます。
Animationとは
Animationとは、そのView自体の色や大きさ位置などを変える際に適用されるアニメーションです。
例えばある操作でViewの大きさを2倍にしたいというときに、ムクムクっと大きくするなどのアニメーションを追加することができます。
で結局どうすれば良いの?
今回の用途であれば、後者のAnimationで表現する方が良いと思います。
なぜならばTransitionで表現するとなると、Image()が2つ必要になり、管理がややこしくなるためです。
具体的な例で示します。
Transitionの例
このように2つの画像を切り替えをアニメーション(Transition)で実現するとなった場合、次のように書くことができます。
import SwiftUI
struct ContentView: View {
@State private var flag = true
var body: some View {
VStack {
if flag {
Image(systemName: "sun.max.fill")
.resizable()
.frame(width: 100, height: 100, alignment: .center)
.transition(.opacity)
.padding()
} else {
Image(systemName: "moon.fill")
.resizable()
.frame(width: 100, height: 100, alignment: .center)
.transition(.opacity)
.padding()
}
Button("Change") {
withAnimation {
self.flag.toggle()
}
}
}
}
}
単純で分かりやすいですが、切り替える先のImage()を事前に定義しておく必要があるのが欠点です。
事前に切り替えしたい画像の数が分かっていれば良いですが、状況によって変わるとなった場合非常に扱いづらくなります。
用意するImage()の数は1つで同じようなことを表現することはできないでしょうか?
それにはAnimationと非同期処理を組み合わせれば実現ができます。
Animationの例
AnimationはそのViewのプロパティの変更をアニメーションとして表現するため、
次のポイントを抑えて切り替えを実現します。
- opacityを設定し、flagのON/OFFによって消える、表示されるを制御する
- flagをOFFにしたら消えっぱなしになってしまうため、ある程度時間が経過したらONにして再度表示させる
- 再表示前に切り替えたい画像に変更する
- アニメーションはメインスレッドで行われるため、ウェイトは別スレッドで行う
import SwiftUI
struct ContentView: View {
@State private var flag = true
@State private var imageName = "sun.max.fill"
var body: some View {
VStack {
Image(systemName: imageName)
.resizable()
.frame(width: 100, height: 100, alignment: .center)
.opacity(flag ? 1 : 0)
.padding()
Button("Change") {
withAnimation {
let newImageName = (imageName == "sun.max.fill") ? "moon.fill" : "sun.max.fill"
self.flag.toggle()
DispatchQueue.global().async {
Thread.sleep(forTimeInterval: 0.2)
DispatchQueue.main.sync {
self.imageName = newImageName
self.flag.toggle()
}
}
}
}
}
}
}
実際の挙動はこんな感じです。ウェイトの時間を調節すれば、消え具合を調整することができます。
おまけ
DispatchQueueを使って実装すると、ネストが深くなるのと、他の非同期処理との待ち合わせが難しくなります。
きれいに書くのであればPromiseKitを使って書くことをお勧めします。
import SwiftUI
import PromiseKit
struct ContentView: View {
@State private var flag = true
@State private var image = UIImage()
var body: some View {
VStack {
Image(uiImage: image)
.resizable()
.frame(width: 100, height: 100, alignment: .center)
.opacity(flag ? 1 : 0)
.padding()
Button("Change") {
withAnimation {
firstly { () -> Guarantee<UIImage> in
return Guarantee { seal in
DispatchQueue.global().async {
let newImage = convert(self.image) // 時間がかかる処理
seal(newImage)
}
}
} .then { image -> Guarantee<UIImage> in
self.flag.toggle()
return Guarantee { seal in
DispatchQueue.global().async {
Thread.sleep(forTimeInterval: 0.2)
seal(image)
}
}
} .done { image in
self.image = image
self.flag.toggle()
}
}
}
}
}
}
Author And Source
この問題について(SwiftUIで画像切り替えのアニメーションを実現する), 我々は、より多くの情報をここで見つけました https://qiita.com/noby111/items/cd80af70ed076144e348著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .