728x90
구현하게 된 이유
진행 중인 프로젝트 디자인은 기본 TabView와 유사했음에도, 기본 TabView로는 구현할 수 없었습니다.
'SwiftUI에서 TabView item들의 속성은 조정할 수 있게 해주지' 라는 생각이 많이 들었던.. 동시에 앞으로의 프로젝트가 험난하겠구나(?)가 느껴진 순간이었습니다.
기능 및 디자인
1. 기능: item 클릭 시, 추가 동작 제어
2. 디자인: 기본 CustomView와 유사. (이미지 사이즈만 조정)
Model 코드
Enum으로 다음과 같은 모델을 만들었고, 필요한 Property를 추가했습니다.
//
// CustomTabView.swift
//
// Created by Eddy on 2/19/24.
//
import SwiftUI
public enum TabItem: CaseIterable {
case left // tabItem 갯수만큼 case 추가
case center
case right
var image: Image { // item 이미지
switch self {
case .left: .icons(.ic_home_outlined)
case .center: .init(systemName: "plus.app.fill")
case .right: .icons(.ic_my_outlined)
}
}
var text: String { // item 텍스트
switch self {
case .left: "홈"
case .center: ""
case .right: "마이페이지"
}
}
var selectedColor: Color { // item 선택시 foregroundStyle
return .colors(.gray800)
}
var unsSelectedColor: Color { // item 미선택시 foregroundStyle
return .colors(.gray300)
}
var imageSize: CGFloat { // 텍스트 유무에 따른 이미지 사이즈
if text.isEmpty {
return 42
} else {
return 24
}
}
}
CustomTabView 코드
//
// CustomTabView.swift
//
// Created by Eddy on 2/19/24.
//
import SwiftUI
public struct CustomTabView<Content: View>: View {
// MARK: - Properties
@Binding private var isSelectedItem: TabItem
private let content: (TabItem) -> Content
private let onTappedItem: ((TabItem) -> Void)?
// MARK: - Init
public init(isSelectedItem: Binding<TabItem>,
@ViewBuilder content: @escaping (TabItem) -> Content,
onTappedItem: ((TabItem) -> Void)? = nil) {
self._isSelectedItem = isSelectedItem
self.content = content
self.onTappedItem = onTappedItem
}
// MARK: - Views
public var body: some View {
ZStack {
content(isSelectedItem)
.frame(maxWidth: .infinity, maxHeight: .infinity)
VStack(spacing: 0) {
Divider()
.frame(maxHeight: .infinity, alignment: .bottom)
HStack {
buttons()
}
.frame(maxWidth: .infinity)
.ignoresSafeArea(edges: .horizontal)
.background(.white)
}
}
}
}
fileprivate extension CustomTabView {
@ViewBuilder
func buttons() -> some View {
ForEach(TabItem.allCases, id: \.self) { item in
Button {
if !item.text.isEmpty {
isSelectedItem = item
// 텍스트가 있으면, 해당 탭을 눌렀을 때 화면 전환
// 텍스트 유무와 상관없이 화면전환을 하려면 if문 제거.
}
action(item: item)
} label: {
buttonLabel(item: item)
}
.frame(maxWidth: .infinity)
.padding(.top, 12)
}
}
@ViewBuilder
func buttonLabel(item: TabItem) -> some View {
if item.text.isEmpty { // text가 없을때, 이미지 사이즈 확장
item.image
.resizable()
.renderingMode(.template)
.scaledToFit()
.foregroundStyle(.colors(.gray900))
.frame(width: item.imageSize, height: item.imageSize)
} else {
VStack(spacing: 2) { // text가 있을 때
item.image
.resizable()
.renderingMode(.template)
.scaledToFit()
.foregroundStyle(isSelectedItem == item ? item.selectedColor : item.unsSelectedColor)
.frame(width: item.imageSize, height: item.imageSize)
Text(item.text)
.pretendard(.medium12)
.foregroundStyle(isSelectedItem == item ? item.selectedColor : item.unsSelectedColor)
}
}
}
// MARK: - Methods
func action(item: TabItem) { // 탭을 눌렀을 때의 추가 동작
guard onTappedItem != nil else { return }
onTappedItem!(item)
}
}
사용 코드 예제
//
// CustomTabView.swift
//
// Created by Eddy on 2/19/24.
//
import SwiftUI
struct MainView: View {
// MARK: - Properties
@State private var isSelectedItem: TabItem = .left
// MARK: - Views
var body: some View {
CustomTabView(isSelectedItem: $isSelectedItem) { item in
switch item {
case .left:
EmptyView() // 왼쪽 뷰로 이동
case .center:
EmptyView() // 가운데 뷰로 이동
case .right:
EmptyView() // 오른쪽 뷰로 이동
}
} onTappedItem: { item in // (Optional) 버튼 동작
switch item {
case .left:
print("왼쪽 버튼 동작")
case .center:
print("가운데 버튼 동작")
case .right:
print("오른쪽 버튼 동작")
}
}
}
}
어쩌다보니 TabView를 직접 구현하게 되었는데, 의외로 굉장히 간단했습니다.
이정도라면 직접 CustomView를 구현해보기 좋은 레벨이라는 생각이 들었습니다.
SwiftUI에서는 이렇게 직접 구현해야하는 일이 은근히 많다는 점이, 까다롭기도 하고 재밌기도 하네요.
조금 더 디벨롭한다면, 특정 모델에 종속적인 View가 아닌 좀 더 범용성을 넣는다면 좋을 것 같았습니다. 프로젝트 개발 기간은 정해져있기에.. 일단은 이정도로 만족하게 되었네요. 앱에 TabView가 여러개 사용되는 앱은 많지 않으니까요.
반응형
'🍎 iOS > DevNote' 카테고리의 다른 글
[SwiftUI] ObservableObject, @StateObject, @ObservedObject 알아보기 (0) | 2024.03.25 |
---|---|
[Swift] NSCache 이해하기 (0) | 2024.03.17 |
[Github] 우리는 왜 Squash & Merge와 No Fast-forward Merge 방식을 채택했을까? (2) | 2024.02.04 |
[Swift] @main (0) | 2023.07.30 |
[UIKit] UITableView에 Cell 내부 UIButton이 동작하지 않는 이유( = ContentView) (0) | 2023.07.28 |
댓글