SwiftUI 2.0で matchedGeometryEffect を使ってみる
SwiftUI 2.0で追加されたAPI
SwiftUI 2.0で追加された新機能のうち、Heroアニメーションを簡単に作れるAPIがあったのでちょっと触ってみました。
とりあえず完成形
こんな感じのSegmentControlっぽいUIを作ってみます。
Githubはこちらです。 github.com
開発環境
ボタンを作る
選択に使うボタンのViewを作ります。 そのまえに、適当にenumを定義しておきました。SF Symbolsから、適当に4つほど選出しています。
enum ButtonType: String, CaseIterable { case share = "square.and.arrow.up" case trash = "trash" case folder = "folder" case person = "person" }
ボタンのビューはこんな感じで作ります。
AccentColorについては、適宜Assetsで好きな色を定義してください。
struct CustomButton: View { // 選択状態を表すプロパティ. @Binding var selected: ButtonType // 自分自身のボタンタイプ. let type: ButtonType var body: some View { ZStack { // 選択中だったら背景に円を描画する. if selected == type { Circle() // AccentColorはAssetsで定義すること. .fill(Color.accentColor) } Button(action: { // ボタンをタップしたら選択状態を切り替える. selected = type }, label: { // enumから画像を表示する. Image(systemName: type.rawValue) .resizable() .renderingMode(.original) .aspectRatio(contentMode: .fit) .frame(width: 44, height: 44) }) } .frame(width: 80, height: 80) } }
画面上にボタンを並べる
では、画面上にボタンを並べてみます
struct ContentView: View { @State private var selected = ButtonType.share // 選択状態の初期値. var body: some View { HStack { // enumをforeachで回して、CustomButtonを横に並べる. ForEach(ButtonType.allCases, id: \.self) { type in CustomButton(selected: $selected, type: type) } } } }
では動かしてみます。
選択状態を切り替えて、見た目も変わるようになりましたね。では、ここからアニメーションを追加していきましょう。
アニメーションさせる
まずはボタンの選択時の状態変化がアニメーションを伴うように、ボタンタップ時の挙動を一部修正します。
// 一部抜粋. Button(action: { // アニメーションを伴わせる withAnimation { selected = type } }, label: { // 省略 })
.matchedGeometryEffectを設定する
アニメーションさせたいViewに対して.matchedGeometryEffect
を指定します。
これは、識別子とNamespaceを与えて、同期したいアニメーションをグルーピングする感じです。
まずはnamespaceを宣言します。
struct CustomButton: View { // 省略 var namespace: Namespace.ID // namespaceを追加する. // 省略 }
次に、アニメーションさせたい背景ビューに対して.matchedGeometryEffect
を指定します。識別子は、アニメーションを同期したいグループ間で一致していれば何でもいいです。
// 選択中だったら背景に円を描画する. if selected == type { Circle() .fill(Color.accentColor) .matchedGeometryEffect(id: "CustomButton", in: namespace) }
次に、呼び出しているView側に修正を加えます
struct ContentView: View { @State private var selected = ButtonType.share // 追加する. @Namespace var namespace var body: some View { HStack { ForEach(ButtonType.allCases, id: \.self) { type in // 引数にnamespaceを与えるように修正する. CustomButton( selected: $selected, type: type, namespace: namespace ) } } } }
以上で完成です! とても簡単にできちゃいますね!
まとめ
Heroアニメーションはテンション上がるので、他にも色々と試して記事にしたいと思います!
こちらの書籍はとても参考になったのでおすすめです!