Sponsor Link
環境&対象
- macOS Monterey 12.3 RC
- Xcode 13.3 RC
- iOS 15.4RC
PropertyWrapper
今回は、PropertyWrapper を使っていきます。PropertyWrapper 自体については、以下の記事で説明しています。
[Swift] PropertyWrapper 手を動かして理解する(その1 wrappedValue)
[Swift] PropertyWrapper 手を動かして理解する(その2 wrappedValue の初期化)
[Swift] PropertyWrapper 手を動かして理解する(その3 $ もしくは projectedValue の理解)
ちなみに、今回は、projectedValue は、大きな役割は果たしません。
Animation
SwiftUI では、非常に簡単にアニメーションさせることができるようになっています。
プロパティを withAnimation 内で変更するとアニメーションになります。
//
// ContentView.swift
//
// Created by : Tomoaki Yagishita on 2022/03/10
// © 2022 SmallDeskSoftware
//
import SwiftUI
struct ContentView: View {
@State private var offset = false
var body: some View {
VStack {
Button(action: {
withAnimation {
offset.toggle()
}
}, label: {
Text("Toggle offset")
})
Text("Hello, world!")
.offset(y: offset ? 100 : 0)
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
上記のコードで、以下のようなアニメーションになります。
変数操作を withAnimation{ … } の中で行っていることでアニメーションが行われます。
withAnimation で行うことのできるアニメーションは、複数用意されています。
Apple のドキュメントは、こちら。
withAnimation の引数に Animation を指定することで、指定したアニメーションにすることができます。
以下の例は、引数を Animation.easeInOut(duration: 2)を指定しています。
//
// ContentView.swift
//
// Created by : Tomoaki Yagishita on 2022/03/10
// © 2022 SmallDeskSoftware
//
import SwiftUI
struct ContentView: View {
@State private var offset = false
var body: some View {
VStack {
Button(action: {
withAnimation(.easeInOut(duration: 2) ){
offset.toggle()
}
}, label: {
Text("Toggle offset")
})
Text("Hello, world!")
.offset(y: offset ? 100 : 0)
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Animationさせたいプロパティ
withAnimation は、非常に便利なのですが、もっと便利にしたくなりました。
アニメーションさせる時には、大抵 アニメーション対象とするプロパティが決まってきます。そのプロパティの変更を withAnimation で変更すれば良いのですが、プロパティが複数箇所で変更されると、withAnimation 指定する箇所が増えてきます。プロパティを変更する箇所が増えるに従って、withAnimation を付け忘れてしまったりしそうです。さらに、指定アニメーションを変更したくなった時には、アニメーションの変更忘れまで発生してしまいそうです。
複数箇所の withAnimation の変更管理をするのが疲れそうなので、逆転の発想で、「このプロパティは、常に withAnimatin で変更する & 常に 使用する animation は .easeInOut」のように変数定義の箇所で指定できれば、付け忘れや 変更忘れを気にしなくて良くなりそうです。
記事の最初で振り返った property wrapper は、このように 変数操作時に特定の手順を行うことを設定できる便利な仕組みです。
propertyWrapper AnimateState
@propertyWrapper
public struct AnimateState: DynamicProperty {
let storage: State
let animation: Animation?
public init(wrappedValue: T, animation: Animation? = nil) {
self.storage = State(initialValue: wrappedValue)
self.animation = animation
}
public var wrappedValue: T {
get {
self.storage.wrappedValue
}
nonmutating set {
withAnimation(animation) {
self.storage.wrappedValue = newValue
}
}
}
public var projectedValue: Binding {
storage.projectedValue
}
}
当初 @State と併用する @Animate も考えたのですが、使い道がないことに気づき、両方を合わせた @AnimateState という property wrapper にしました。
AnimateState 使用例
上で定義した AnimateState を使用すると、animation まで指定していたサンプルコードは、以下のようになります。
//
// ContentView.swift
//
// Created by : Tomoaki Yagishita on 2022/03/10
// © 2022 SmallDeskSoftware
//
import SwiftUI
struct ContentView: View {
@AnimateState(wrappedValue: false, animation: Animation.easeInOut(duration: 2))
private var offset: Bool
var body: some View {
VStack {
Button(action: {
offset.toggle()
}, label: {
Text("Toggle offset")
})
Text("Hello, world!")
.offset(y: offset ? 100 : 0)
}
.padding()
}
}
@propertyWrapper
public struct AnimateState: DynamicProperty {
let storage: State
let animation: Animation?
public init(wrappedValue: T, animation: Animation? = nil) {
self.storage = State(initialValue: wrappedValue)
self.animation = animation
}
public var wrappedValue: T {
get {
self.storage.wrappedValue
}
nonmutating set {
withAnimation(animation) {
self.storage.wrappedValue = newValue
}
}
}
public var projectedValue: Binding {
storage.projectedValue
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
上記の例では、プロパティの変更箇所は1か所ですが、複数に増えても withAnimation を付け忘れたりすることがなくなりますし、使用する Animation を都度 指定する必要ありません。
まとめ
SwiftUI で アニメーションをより手軽にするための Property Wrapper を作ってみました。
- withAnimation を使って、プロパティを変更するとアニメーションする
- Animation の種類は複数用意されている
- PropertyWrapper と withAnimation を組み合わせるとより手軽
説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。
Sponsor Link