SwiftUI) @Namespace 란?

UIKit에서 SwifUI를 주 프레임워크로 사용하다보면 UI 제작에 편리함이 많지만 반대로 어려움도 많았습니다. 그 중 하나가 바로 애니메니션이었습니다. SwiftUI에서 애니메이션은 쉽다면 쉽다고 할 수 있지만 제가 느끼는 어려움은 전환에 대한 어려움이었습니다. 그래서 해당 키워드를 알게되었고 이 속성에 대해서 공부해보고자 합니다. SwiftUI에서 애니메이션을 효과적으로 제어하기 위해서는 @Namespace 속성 래퍼를 이해하는 것이 매우 중요합니다. 특히, matchedGeometryEffect와 함께 사용하면 뷰 간의 전환 애니메이션을 부드럽고 자연스럽게 만들어 주는 강력한 기능을 제공합니다. 해당 속성 값의 개념, 사용법 그리고 예시를 통해 공유하고자 합니다.

1. @Namespace란?

Namespace는 SwiftUI에서 뷰의 일관된 애니메이션 전환을 위해 사용되는 속성 패러입니다. 주로 machedGeometryEffect와 함께 사용되며, 서로 다른 뷰 간에 일관된 애니메이션을 생성해 줍니다. 예를 들어, 두 개의 서로 다른 뷰가 하나의 물리적 객체처럼 전환될 수 있도록 도와줍니다. SwiftUI에서 뷰 전환 애니메이션을 자연스럽게 만드는데 핵심적인 역할을 하며, 뷰 간의 위치와 크기를 공유하는 일종의 공간을 의미합니다.

2. 사용법

SwiftUI에서 애니메이션의 시작과 끝 상태를 일치시키기 위해 사용됩니다. 이를 통해 애니메이션이 매끄럽게 이어지도록 설정할 수 있습니다.

struct NamespaceEx: View {
    @Namespace private var animationNamespace

    @State private var isExpanded = false

    var body: some View {
        VStack {
            if isExpanded {
                RoundedRectangle(cornerRadius: 25)
                    .fill(Color.blue)
                    .matchedGeometryEffect(id: "rectangle", in: animationNamespace)
                    .frame(width: 300, height: 200)
            } else {
                RoundedRectangle(cornerRadius: 25)
                    .fill(Color.blue)
                    .matchedGeometryEffect(id: "rectangle", in: animationNamespace)
                    .frame(width: 100, height: 100)
            }
        }
        .onTapGesture {
            withAnimation {
                isExpanded.toggle()
            }
        }
    }
}

위 코드는 원래 isExpanded라는 조건문을 통해 나뉜 사실상 다른 뷰입니다. Namespace를 사용하여 animationNamespace를 생성했습니다. 그 후 macedGeometryEffect와 결합하며 사각형의 크기가 전환될 떄 자연스러운 애니메이션 효과를 얻을 수 있습니다.

3. matchedGeometryEffect

해당 메서드는 SwiftUI에서 서로 다른 위치에 있는 뷰를 자연스럽게 연결해 이동시키는 애니메이션을 구현할 때 사용하는 기능입니다. 특정 뷰 간에 동일한 기하학적 특성을 공유하게 하여 애니메이션 동안 위치, 크기, 모양이 부드럽게 전환되도록 도와줍니다. 이 메서드는 다음과 같은 매개변수를 가집니다.
id: 뷰를 구분하기 위한 식별자입니다. 이 값은 보통 Hashable 프로토콜을 따르는 데이터의 id를 사용합니다. 같은 id를 가지는 두 뷰 간에 애니메이션이 발생하도록 설정합니다.
namespace: @Namespace로 생성된 네임스페이스입니다. 동일한 네임스페이스에서 id가 일치하는 뷰들끼리 매칭되며, 두 뷰가 같은 네임스페이스에 포함되어 있어야 애니메이션이 연결됩니다.
properties: 애니메이션 동안 동기화할 기하학적 속성을 지정합니다. 기본값은 .frame이며, 뷰의 프레임 정보를 공유하도록 설정합니다. .position, .size 등 다른 속성을 지정해 더 정밀한 제어가 가능합니다.
anchor: 뷰의 기준점을 지정하는 값입니다. 기본값은 .center로, 뷰의 중심점을 기준으로 애니메이션이 전환됩니다.
isSource: true인 뷰를 기준으로 애니메이션이 시작됩니다. 여러 뷰가 매칭된 상태에서 기준점이 되는 뷰를 지정해 줍니다.

반환값

전역 뷰 데이터베이스에 기하학적 특성을 공유하는 뷰를 정의한 새 뷰를 반환합니다.

작동 방식

이 메서드는 동일한 id와 네임스페이스를 공유하는 뷰들이 전환될 때, 한 개의 뷰가 이동하는 것처럼 보이게 합니다. isSource = true 로 지정된 뷰가 원본 기하학적 데이터를 제공하며, 이 뷰의 properties 값이 일치하는 다른 뷰와 애니메이션 동안 매칭됩니다.

struct TabBarView: View {
    @State private var currentTab: Int = 0
    @Namespace var namespace
    var options: [String] = ["Hi", "HiHi2", "HiHiHi3", "HiHiHiHi4", "HiHiHiHiHi5"]

    var body: some View {
        ScrollViewReader { proxy in
            ScrollView(.horizontal, showsIndicators: false) {
                HStack(spacing: 20) {
                    ForEach(Array(zip(self.options.indices, self.options)), id: \.0) { index, name in
                        TabBarItem(currentTab: self.$currentTab, namespace: namespace, tabBarItemName: name, tab: index)
                    }
                }
                .padding(.horizontal)
            }
            .background(Color.white)
            .frame(height: 80)
            .edgesIgnoringSafeArea(.all)
        }
    }
}

TabBarItem에 있는 하단 바는 ForEach에 각각 들어있기 때문에 전환 애니메이션을 자연스럽게 하기 위해서 namespace 속성을 사용하였습니다.

Leave a Comment