탭뷰 커스텀으로 만들어봅시다
기본적인 탭뷰를 먼저 구성해줬습니다
이런 탭바를 만드는 과정은 3가지로 구분해줄 수 있겠음
아래에 있는 탭바를 만들고,
Tab들을 들고 있는 탭뷰가 있겠고, 탭아이템이 있겠죠
근데 tabItem을 보면 이전에 배운 PreferenceKey를 사용해야겠다는 감이 오죠?
패런트의 뷰를 차일드에서 바꿔주고 있으니까!!
이것들을 이용해서 구현해줄 예정
탭바부터 만들어보자
import SwiftUI
struct CustomTabBarView: View {
let tabs: [TabBarItem]
var body: some View {
HStack {
ForEach(tabs, id: \.self) { tab in
tabView(tab: tab)
}
}
}
}
extension CustomTabBarView {
private func tabView(tab: TabBarItem) -> some View {
VStack {
Image(systemName: tab.iconName)
.font(.subheadline)
Text(tab.title)
.font(.system(size: 10, weight: .semibold, design: .rounded))
}
.foregroundColor(tab.color)
.padding(.vertical, 8)
.frame(maxWidth: .infinity)
.background(tab.color.opacity(0.2))
.cornerRadius(10)
}
}
struct TabBarItem: Hashable {
let iconName: String
let title: String
let color: Color
}
struct CustomTabBarView_Previews: PreviewProvider {
static let tabs: [TabBarItem] = [
TabBarItem(iconName: "house", title: "Home", color: .red),
TabBarItem(iconName: "heart", title: "Favorites", color: .blue),
TabBarItem(iconName: "person", title: "Profile", color: .green),
]
static var previews: some View {
CustomTabBarView(tabs: tabs)
}
}
ForEach로 돌리기 위해서 TabBarItem을 Hashable하게 해주고
이 모델을 가지고 탭바 뷰를 그려주게 해줌 (메소드로 뷰 반환하게 해줬고)
탭들을 그려줄 때 바텀으로 쭉 빠져야하니까 background로 칼라넣고 ignoresSafeArea해줌
그리고 탭이 선택될 때 바뀌게 해줘야하잖음
selection이라는 프로퍼티를 만들어주고
tabView메소드도 수정해줌
switchToTab 메소드도 만들어주고!
메소드로 만들어준걸 onTapGesture에 넣어줬다!
ForEach로 만들어주니까 내려온 tab을 넣을 수 있어서
요런 데이터 플로우를 만들 수 있구나!
근데 이게 외부에서 쓰일거니까 selection을 바인딩으로 바꿔줘야함
하단탭바 만드는 것까진 완료!
이제 커스텀하게 만들어준 탭바를 들고 있을 컨테이너 뷰를 만들어야함! 뷰빌더도 써줘야겠죠
기존의 탭뷰가 어떻게 정의되어 있나 살펴보면
Generic한 타입을 두개 받고 있고 이 제네릭한 타입들에 프로토콜들을 준수하게 해줬음
그리고 init을 보면 Content타입이 뷰빌더로 선언된 걸 알 수 있죠!
이거 복사해서 가져와봅시다
요 두표현은 같은거입니다!!
where를 쓴게 아래 표현이랑 같아용
우리가 만들어준 TabBarItem을 쓸거니까 요거처럼 Generic하게 SelectionValue를 해주지 않아도 될거 같음!
뒤에 Content가 되는 것만 바꿔주면 될 듯
다시 탭뷰 다큐멘트가서 init이 어떻게 만들어져 있는지 확인하고
복사!
init에서 바인딩된 TabBarItem을 받을 수 있게 해주고
요렇게 틀이 완성이 되었습니다!
struct CustomTabBarContainerView<Content: View>: View {
@Binding var selection: TabBarItem
let content: Content
@State private var tabs: [TabBarItem] = []
init(selection: Binding<TabBarItem>, @ViewBuilder content: () -> Content) {
self._selection = selection
self.content = content()
}
var body: some View {
VStack(spacing: 0) {
ZStack {
content
}
CustomTabBarView(tabs: tabs, selection: $selection)
}
}
}
struct CustomTabBarContainerView_Previews: PreviewProvider {
static let tabs: [TabBarItem] = [
TabBarItem(iconName: "house", title: "Home", color: .red),
TabBarItem(iconName: "heart", title: "Favorites", color: .blue),
TabBarItem(iconName: "person", title: "Profile", color: .green),
]
static var previews: some View {
CustomTabBarContainerView(selection: .constant(tabs.first!)) {
Color.red
}
}
}
그러고 tabs에 TabBarItem들 넣어주면 잘 보이는 걸 볼 수 있음!
확인을 완료한 후엔 아래처럼 tabs를 비워주는데
애플의 탭뷰랑 최대한 비슷한 구조를 만들기 위해서 그럼
애플의 탭뷰를 자세히 보면
child뷰의 .tabItem이 ParentView인 TabView를 건드리고 있는걸 볼 수 있음
애플 탭뷰 만든걸로 돌아와서 변수로 기존 탭뷰 빼줬다
글구나서 CustomTabBarContainerView를 불러와줬는데
어떰!!
기존 탭뷰랑 비슷하죠?!
.tabBarItem이 달리는 것만 구현해주면 될듯!
새로운 preferencekey 파일을 만들어주자
import Foundation
import SwiftUI
struct TabBarItemsPreferenceKey: PreferenceKey {
static var defaultValue: [TabBarItem] = []
static func reduce(value: inout [TabBarItem], nextValue: () -> [TabBarItem]) {
value += nextValue()
}
}
struct TabBarItemViewModifier: ViewModifier {
let tab: TabBarItem
func body(content: Content) -> some View {
content
.preference(key: TabBarItemsPreferenceKey.self, value: [tab])
}
}
extension View {
func tabBarItem(tab: TabBarItem) -> some View {
self.modifier(TabBarItemViewModifier(tab: tab))
}
}
그리고 나서 .tabBarItem으로 탭들을 추가해줬음!
근데 탭바가 추가안된다!!
.onPreferenceChange를 설정 안했었음 ㅋㅋ
여기까지 했을 때
스크린 바뀌는 거 빼곤 탭바 아이템 선택되는 게 작동한다!
selection연결해주면 끝!
리팩토링 해봅시다
TabBarItem을 지금 struct로 선언했는데
지금처럼 모델보다는 enum으로 만들어주는 게 좋을 거 같다는 생각이 들지 않음?
import Foundation
import SwiftUI
//struct TabBarItem: Hashable {
// let iconName: String
// let title: String
// let color: Color
//}
enum TabBarItem: Hashable {
case home, favorites, profile
var iconName: String {
switch self {
case .home: return "house"
case .favorites: return "heart"
case .profile: return "person"
}
}
var title: String {
switch self {
case .home: return "Home"
case .favorites: return "Favorites"
case .profile: return "Profile"
}
}
var color: Color {
switch self {
case .home: return Color.red
case .favorites: return Color.blue
case .profile: return Color.green
}
}
}
기존의 struct 를 enum으로 바꿔줬음
이제 tabSelection에 TabBarItem을 하나하나 안 해주고 .home으로 enum 케이스만 넘겨주면 됨!!
보이나요!! 요렇게 깔끔해진게!!
빌드에러 뜨는 곳들 다 수정해주면 끝!!
tab.iconName이런 식으로 만들어주고 있었어서 요부분도 따로 수정 안해줘도 됨
조금 더 예쁘게 커스텀해봅시다~!~!