SwiftUI - NavigationTransition
안녕하세요. 그린입니다 🍏
이번 포스팅에서는 iOS 18에서 새로 나온 SwiftUI의 NavigationTransition에 대해 알아보겠습니다 🙋🏻
NavigationTransition
우선 전환 효과에 대한 얘기인것 같죠?
우리가 네비게이션 구조로 스택을 쌓아 화면을 전환하거나 시트 형태로 전환할때 보통 일반적으로 생각하고 있는 전환 효과가 있죠?
아주 익숙한 전환 효과입니다.
그런데, iOS 18부터는 이런 기본 전환 효과 외에도 줌 전환 효과를 사용할 수 있게 되었어요.
그래서 NavigationTransition이라는것도 나오면서 그에 맞는 뷰 모디파이어도 생겨났습니다.
그럼 먼저 NavigationTransition부터 알아봐야겠죠?
protocol NavigationTransition
NavigationTransition은 뷰가 이동할 때 사용할 전환을 정의하는 프로토콜입니다.
iOS 18 이상에서 사용 가능하죠.
기본적으로 내장된 전환 효과는 두가지가 존재해요.
static var automatic: AutomaticNavigationTransition
static func zoom(sourceID: some Hashable, in: Namespace.ID) -> ZoomNavigationTransition
하나는 현재에 맞춰 적절한 나타나기 전환을 자동으로 선택해주는 automatic입니다.
그리고 두번째로 zoom이 주어진 소스 뷰에서 나타나는 뷰를 확대/축소하는 탐색 전환 효과입니다.
automatic은 AutomaticNavigationTransition 타입이죠.
zoom은 ZoomNavigationTransition 타입으로 둘다 구조체이고 해당 프로토콜을 따른다고 볼 수 있습니다.
여기서 오늘 특히 알아볼 zoom에서는 sourceID와 in 파라미터를 받는데 이건 코드로 이어서 알아보시죠.
그리고 뷰에서 해당 전환 효과를 실제로 시켜줄 뷰 모디파이어가 있습니다.
nonisolated
func navigationTransition(_ style: some NavigationTransition) -> some View
파라미터를 보면 NavigationTransition 프로토콜을 채택하는 타입이라면 어떤것이든 괜찮죠.
즉, 우리는 zoom 효과를 해당 모디파이어 적용을 통해 쉽게 구현해볼 수 있는것이죠.
화면 전환 스타일을 설정해주는 뷰 모디파이어입니다.
import SwiftUI
struct ContentView: View {
@Namespace private var namespace
var body: some View {
NavigationStack {
NavigationLink {
DetailView()
.navigationTransition(.zoom(sourceID: "detail", in: namespace))
} label: {
Text("Go Detail")
.matchedTransitionSource(id: "detail", in: namespace)
}
}
}
}
struct DetailView: View {
var body: some View {
Text("Detail")
}
}
기본적인 사용은 위 코드와 같아요.
네비게이션이 되어 전환될 뷰에 navigationTransition 뷰 모디파이어를 적용합니다.
이때, automatic이나 zoom을 사용하죠.
automatic은 아이폰에서는 대체적으로 우리가 흔히 아는 기본 전환 슬라이드 효과처럼 나타납니다.
zoom으로 여기서 sourceID와 Namespace.ID 타입의 in 파라미터를 사용하죠.
sourceID는 전환 효과를 적용할 소스 ID를 의미합니다.
밑에서 해당 전환할 뷰의 텍스트 링크에 matchedTransitionSource의 id와 통일시켜준다면 이 텍스트가 이제 소스로 확대되어서 전환될 수 있어요.
namespace의 역활을 전환 애니메이션에서 소스와 대상 뷰를 연결하는 고유한 식별자 역할을 해줍니다.
즉, namespace 내에서 일치하는 ID를 가진 요소들 사이에 자연스러운 애니메이션 전환을 가능케하죠.
또한 여기선 private으로 선언했기에 ContentView 내부로 범위가 한정되어 좀 더 확실한 처리가 가능하죠.
여기서 레이블에 적용한 matchedTransitionSource 모디파이어를 볼까요?
nonisolated
func matchedTransitionSource(
id: some Hashable,
in namespace: Namespace.ID
) -> some View
해당 뷰를 확대/축소 전환과 같은 탐색 전환의 소스로 식별하게 해주는 뷰 모디파이어입니다.
id는 뷰에 표시되는 데이터의 식별자이고 namespace가 여기서 위와 마찬가지의 역할을 해줍니다.
namespace를 사용함으로 Go Detail 텍스트에서 DetailView로 부드럽게 확대되는 애니메이션 전환 효과를 줄 수 있는것이죠.
그럼 한번 위 코드를 실행해볼까요?
보면 뷰가 슬라이드 형태가 아닌 줌으로 나타납니다.
그전에 Go Detail 전환 소스 뷰도 확대되어 자연스럽게 나타나는걸 볼 수 있죠.
만약 이 sourceID를 달리 해주면 어떻게 나타날까요?
import SwiftUI
struct ContentView: View {
@Namespace private var namespace
var body: some View {
NavigationStack {
NavigationLink {
DetailView()
.navigationTransition(.zoom(sourceID: "green", in: namespace)) // 🙋🏻
} label: {
Text("Go Detail")
.matchedTransitionSource(id: "detail", in: namespace)
}
}
}
}
struct DetailView: View {
var body: some View {
Text("Detail")
}
}
matched되는 id와 sourceID를 다르게 주었습니다.
DetailView로 줌 효과를 가지고 전환되지만, Go Detail 소스 뷰는 확대되지 않죠.
만약, 지금처럼 단순한 텍스트가 아니라 어떤 이미지가 들어가거나 복잡한 뷰의 조합들이 리스트로 나와있고 해당 리스트에서 항목을 선택함에 따라 그에 맞는 뷰가 나와야한다면, 소스 뷰도 확대되어 전환 되는 효과가 더 자연스러울 수 있을것 같아요 😃
논리적인 차이는 없기에 적용하면서 더 자연스러운 효과를 주는것이 좋을것 같습니다.
마무리
되게 간단히 전환 효과를 적용해볼 수 있을것 같지 않나요?
기존에 이러한 효과를 구현하기위해 다양한 라이브러리들도 있고 긴 애니메이션 효과 코드를 적용해왔었는데, 단순히 한줄로 끝낼 수 있도록 업데이트가 되어 참 편리할것 같습니다 👍