【Swift UI】matchedGeometryEffectで複数Viewにアニメーションを実装する

【Swift UI】matchedGeometryEffectで複数Viewにアニメーションを実装する

この記事からわかること

  • Swift UImatchedGeometryEffectモディファイア使い方
  • アニメーション実装方法
  • Namespaceとは?

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

matchedGeometryEffect

公式リファレンス:matchedGeometryEffect


nonisolated
func matchedGeometryEffect<ID>(
    id: ID,
    in namespace: Namespace.ID,
    properties: MatchedGeometryProperties = .frame,
    anchor: UnitPoint = .center,
    isSource: Bool = true
) -> some View where ID : Hashable

Swift UIのmatchedGeometryEffect2つ以上のView間の変化をシームレスに変化させることができるモディファイアです。Swift UIではwithAnimationを使用することで1つのViewに対しての変化を簡単にシームレスな変化(アニメーション)にすることができました。

【Swift UI】withAnimationの使い方!アニメーションを実装する

matchedGeometryEffectでは2つ以上のViewに対して同じ名前空間(Namespace)を付与することで同一のオブジェクトと認識させその変化をシームレスにすることが可能になっています。例えば以下のような青とオレンジの四角形Viewが2つありこれの表示/非表示を切り替える動作をシームレスにすることができます。

【Swift UI】matchedGeometryEffectで複数Viewにアニメーションを実装する
struct HorizontalRectangle: View {
    @Namespace private var animationNamespace
    @State private var isExpanded = false

    var body: some View {
        HStack {
                    
            if isExpanded {
                // 青色四角形View
                RoundedRectangle(cornerRadius: 25)
                    .fill(Color.blue)
                    .frame(width: 100, height: 100)
                    .matchedGeometryEffect(id: "rectangle", in: animationNamespace)
                    .onTapGesture {
                        withAnimation(.easeOut(duration: 1)) {
                            isExpanded.toggle()
                        }
                    }
            }
            
            Spacer()
            
            if !isExpanded {
                // オレンジ四角形View
                RoundedRectangle(cornerRadius: 25)
                    .fill(Color.orange)
                    .frame(width: 100, height: 100)
                    .matchedGeometryEffect(id: "rectangle", in: animationNamespace)
                    .onTapGesture {
                        withAnimation(.easeOut(duration: 1)) {
                            isExpanded.toggle()
                        }
                    }
            }
            
        }.padding(.horizontal)
    }
}

実装のポイント

matchedGeometryEffectを使用するポイントは以下の通りです。

matchedGeometryEffectの引数idには同じオブジェクトと見なすため識別子を引数inにはNamespaceを指定して名前空間を作成します。同じ名前空間にある同じ識別子を持ったViewが同一のオブジェクトとして認識され、その変化がシームレスに動作するようになる感じです。

MatchedGeometryProperties

引数propertiesにはMatchedGeometryProperties型でアニメーションを許容する箇所を指定することができます。

公式リファレンス:MatchedGeometryProperties

カスタムでセグメントピッカーを実装する

matchedGeometryEffectを使用することでPicker構造体を使用せずにカスタムなセグメントピッカーを作成することも可能です。

【Swift UI】matchedGeometryEffectで複数Viewにアニメーションを実装する
enum TabItem: String, CaseIterable {
    case one
    case two
    case three
}

struct CustomTabPickerView: View {
    
    @Namespace private var tabAnimation
    @Binding var selectedItem: TabItem
    
    var body: some View {
        HStack {
            ForEach(TabItem.allCases, id: \.self) { tab in
                Button {
                    withAnimation {
                        selectedItem = tab
                    }
                } label: {
                    ZStack {
                        
                        if selectedItem == tab {
                            RoundedRectangle(cornerRadius: 30)
                                .frame(width: 75, height: 40)
                                .foregroundColor(.orange)
                                .matchedGeometryEffect(id: "block", in: tabAnimation)
                        } else {
                            RoundedRectangle(cornerRadius: 30)
                                .frame(width: 75, height: 40)
                                .foregroundColor(.clear)
                        }
                        
                        Text(tab.rawValue)
                            .font(.system(size: 14))
                            .foregroundColor(selectedItem == tab ? .black : .orange)
                    }
                }.frame(width: 75, height: 40)
            }
        }.background(.white)
            .clipShape(RoundedRectangle(cornerRadius: 30))
            .shadow(color: .black.opacity(0.2), radius: 5, x: 3, y: 3)
    }
}

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index