iyOmSd/Title: SwiftUI

[SwiftUI] Namespace + matchedGeometryEffect(feat. 상단탭바 UI)

냄수 2022. 7. 14. 18:59
반응형

SwiftUI로 위와같은 UI기능을 만드려고 하면서 알게 된 

Namespace에 대해 간단하게 정리해보려고해요

먼저 개념을 둘러보면

 

프로퍼티를 포함하는 객체(뷰)의 영구 id로 정의된 네임스페이스에 접근할 수 있는 동적 속성 타입

객체의 정보를 id로 다른 뷰와 공유할 수 있는거에요

이 속성과 같이 사용하는게 

View.matchedGeometryEffect(id: in:) 함수구요

 

동일한 키를 가진 다른 뷰가 새로운 뷰를 삽입하는 경우

시스템은 이전 위치에서 새로운 위치로 이동하는 하나의 뷰처럼 보이도록

window공간에 frame 사각형을 합칩니다.

일반적인 전환 메커니즘은 전환중 두 뷰가 각각 렌더링 되는 방식을 정의하며, 

뷰의 렌더링이아닌 연결될 geometry에만 정렬합니다.

 

 

사용은 간단해요

상단탭바같이 사용하기위해 구현하려다보니 스유로는 잘 생각이안나더라구요

어느 하이라키에 저 언더바를 넣어야할지 부터 어떻게 위치를 잡아야할지...

막막하더라구요

먼저 각 버튼을 HStack에 담아서 탭바버튼을 만들거에요

그리고 각각 버튼안에 언더바(검정선)을 넣어줄거에요


  
struct TabBarView: View {
@Binding var currentTab: Int
var tabBarOptions: [String] = ["hello", "world", "this", "is", "something"]
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 20) {
ForEach(tabBarOptions.indices, id: \.self) { index in
let title = tabBarOptions[index]
TabBarItem(currentTab: $currentTab,
title: title,
tab: index)
}
}
.padding(.horizontal)
}
.background(Color.white)
.frame(height: 40)
}
}
struct TabBarItem: View {
@Binding var currentTab: Int
var title: String
var tab: Int
var body: some View {
Button {
currentTab = tab
} label: {
VStack {
Spacer()
Text(title)
if currentTab == tab {
Color.black
.frame(height: 2)
} else {
Color.clear.frame(height: 2)
}
}
.animation(.spring(), value: currentTab)
}
.buttonStyle(.plain)
}
}

현 상태로는 버튼을 클릭하면 이렇게 버튼안에있는 버튼이 켜졋다 꺼졋다 하죠

이것을 이제

@Namespace를 사용하면

스르르륵 움직이게 할 수 있어요!!

 

위의 코드를 조금 손봐야해요

우선 모든 버튼의 Namespace를 같게해줘야해요


  
struct TabBarView: View {
@Binding var currentTab: Int
@Namespace var namespace << 추가된 부분
var tabBarOptions: [String] = ["hello", "world", "this", "is", "something"]
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 20) {
ForEach(tabBarOptions.indices, id: \.self) { index in
let title = tabBarOptions[index]
TabBarItem(currentTab: $currentTab,
namespace: namespace, << 추가된 부분
title: title,
tab: index)
}
}
.padding(.horizontal)
}
.background(Color.white)
.frame(height: 40)
}
}
struct TabBarItem: View {
@Binding var currentTab: Int
let namespace: Namespace.ID << 추가된 부분
var title: String
var tab: Int
var body: some View {
Button {
currentTab = tab
} label: {
VStack {
Spacer()
Text(title)
if currentTab == tab {
Color.black
.frame(height: 2)
.matchedGeometryEffect(id: "underline", << 추가된 부분
in: namespace.self)
} else {
Color.clear.frame(height: 2)
}
}
.animation(.spring(), value: currentTab)
}
.buttonStyle(.plain)
}
}

탭바에서 Namespace를 만들어서

각 버튼을 그 Namespace.ID를 받아생성해요

그런 뒤 

View.matchedGeometryEffect

를 이용해서 애니메이션을 처리해주면

서로 다른 뷰이지만 뷰에 대한 정보가 공유되서 같은뷰처럼 취급되고

애니메이션이 어디로 이동할지 결과를 알고있어요

따라서

눌려진 버튼에있는 언더바가 히든되고 누른 버튼에 있는 언더바가 보인다

->

눌려진 버튼의 언더바가 누른버튼 언더바로 이동한다

처럼

언더바가 깜박이는게아니라

누른 버튼으로 슬라이드되서 원하는 동작을 만들 수 있죠

 

 

 

 

 

 

 

반응형