seong_hye, the developer

SwiftUI) matchedGeometryEffect에 대해 알아보기 본문

IOS/SwiftUI

SwiftUI) matchedGeometryEffect에 대해 알아보기

seong_hye 2025. 7. 8.

 

📘 matchedGeometryEffect

두 개 이상의 뷰를 같은 것으로 인식시켜서 레이아웃이 바뀔 떄 자연스럽게 이어지는 애니메이션을 만들어주는 도구

히어로 애니메이션 (Hero Animation) 이라고 불림


🔹개념

SwiftUI는 상태 변경 시 View를 새로 다시 그림

-> 일반적으로는 기본 뷰가 사라지고, 새로운 뷰가 나타나는 전환이 일어남

matchedGeometryEffect(id:in:)을 사용하면, 두 뷰를 같은 ID로 묶어서 SwiftUI가 "같은 요소"라고 인식

SwiftUI는 위치, 크기, 모양, 클리핑, 코너 반경 등을 자동으로 보간해줌


🔹문법

.matchedGeometryEffect(id: "sharedID", id: namespace)

id: 같은 그룹 안에서 고유하게 뷰를 매칭시킬 식별자

namespace: @Namespace로 선언하는 일종의 "애니메이션 공간"


🔹 기본 예제

 

🔍 탭 바 선택에 따라 선이 움직여지는 애니메이션

@Binding var currentTab: Int
@Namespace var namespace
var tabBarOptions: [String] = ["전체", "드라마", "영화", "애니메이션", "TV 프로그램", "스포츠"]

var body: some View {
    ScrollView(.horizontal, showsIndicators: false) {
        HStack(spacing: 20) {
            ForEach(tabBarOptions.indices, id: \.self) { index in
                Button {
                    currentTab = index
                } label: {
                    VStack {
                        Spacer()
                        Text(tabBarOptions[index])
                        if currentTab == index {
                            Color.black
                                .frame(height: 2)
                                .matchedGeometryEffect(id: "titleLine",  in: namespace)
                        } else {
                            Color.clear.frame(height: 2)
                        }
                    }
                    .animation(.spring(), value: currentTab)
                }
                .buttonStyle(.plain)
            }
        }
        .padding(.horizontal)
    }
    .background(Color.white)
    .frame(height: 40)
}

 

🔹 결과 화면


🔍 선택에 따라 원과 사각형이 바뀌는 애니메이션

VStack {
    if show {
        Spacer()

        RoundedRectangle(cornerRadius: 50.0)
            .matchedGeometryEffect(id: "circle", in: namespace)
            .frame(minWidth: 0, maxWidth: .infinity, maxHeight: 300)
            .padding()
            .foregroundColor(Color(.systemGreen))
            .onTapGesture {
                withAnimation(.smooth) { show.toggle() }
            }

    } else {
        RoundedRectangle(cornerRadius: 50.0)
            .matchedGeometryEffect(id: "circle", in: namespace)
            .frame(width: 100, height: 100)
            .foregroundColor(Color(.systemOrange))
            .onTapGesture {
                withAnimation(.smooth) { show.toggle() }
            }

        Spacer()
    }
}

 

🔹 결과 화면


🔹특징

양방향 매칭 (if / else 구조에서 서로 다른 뷰라도 같은 ID와 namespace면 이어짐)

위치, 크기, 코너 반경, 클리핑, 그림자 등이 자동 보간

같은 ID를 여러 개 두면 SwiftUI가 혼란스러워함 -> 하나씩만 매칭

transition과 함께 쓰면 더 자연스러움


🔹한계 / 주의사항

스크롤 뷰 안에서는 애니메이션이 어색할 수 있음 (좌표계 이슈)

동시에 두 개의 뷰가 존재하면 SwiftUI가 어떤 걸 연결할지 헷갈림

이펙트는 단일 namespace 내에서만 유효

복잡한 레이아웃(그리드, 리스트)에선 .zIndex를 적절히 설정해야 깜빡임 방지


 

Comments