티스토리 뷰
[SwiftUI] LinkNavigator (2) - Creating TabBar, Custom NavigationBar
저번 포스팅에 이어서 LinkNavigator를 적용해 개발하며 삽질한 예시를 직접 보여드리려고 하는데요, 탭바와 네비게이션바에 대한 이슈가 조금 있어서 이를 해결해보았습니다.
TabBar 구현하기
탭바를 SwiftUI에서 만들어서 root뷰로 올릴지, 오픈소스이므로 패키지를 다운받아서 builder를 통해 탭바를 생성하는 방식으로 커스텀할지 고민이 되었습니다. 결국 둘 다 시도해본 결과, SwiftUI에서 만드는 편이 간편하기도하고 커스텀하기 용이하다는 생각이 들어 이 방식으로 결정하였습니다.
1. TabBar가 들어갈 RootView를 구성해줍니다.
이제 home이나 특정 뷰로 launch하는게 아니라 탭바 뷰를 launch뷰로 넣을 거기 때문에 root뷰를 구성해주고, 여기에 필요한 탭바를 와 뷰들을 넣습니다. 이때, navigator주입이 필요할 수 있으므로 root뷰에서 navigator 프로퍼티를 추가해줍니다.
struct RootView: View {
let navigator: LinkNavigatorType
var body: some View {
TabView {
HomeView(navigator: navigator)
.tabItem{
Image(systemName: "house")
Text("홈")
}
DiagnosisView()
.tabItem{
Image(systemName: "stethoscope")
Text("진단")
}
PreventView(preventViewModel: PreventViewModel(navigator: navigator))
.tabItem{
Image(systemName: "brain.head.profile")
Text("예방")
}
Text("설정 부분")
.tabItem{
Image(systemName: "gear")
Text("설정")
}
}
}
}
보시는 것처럼 navigator가 필요한 뷰들은 RootView가 가지고 있는 navigator를 넣어줍니다. ViewModel이 navigator를 받는 경우엔 ViewModel을 생성하면서 navigator를 넣어주었습니다.
2. RootView에 대한 RootRouteBuilder를 만들어줍니다.
RootView를 생성하면서 navigator도 함께 전달해줍니다.
struct RootRouteBuilder: RouteBuilder {
var matchPath: String { "root" } // ✅
var build: (LinkNavigatorType, [String: String], DependencyType) -> MatchingViewController? {
{ navigator, items, dependency in
return WrappingController(matchPath: matchPath) {
RootView(navigator: navigator) // ✅
}
}
}
}
3. RootRouteBuilder를 RouterGroup에 추가합니다.
struct AppRouterGroup {
var routers: [RouteBuilder] {
[
RootRouteBulider() // ✅
]
}
}
4. @main에서 launch path를 "root"로 변경합니다.
@main
struct AtchIApp: App {
let hkAuthorizationProvider = HKAuthorizationProvider()
@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
var navigator: LinkNavigator {
return appDelegate.navigator
}
var body: some Scene {
WindowGroup {
navigator
.launch(paths: ["root"], // ✅
items: [:])
}
}
}
그리고 빌드를 해보면...!!
네.. 탭바는 잘 작동하지만 탭바 및 부분과 로고 윗부분에 애매한 여백이 생기네요... 또한 앱 로고 부분은 따로 NavigationBar를 쓰지 않고 View로 만들어준건데 NavigationBar에 밀려서 살짝 아래로 내려왔습니다..ㅎㅎ..
Safe area 및 NavigationBar 커스텀
Safe area 무시하기
사실 탭바가 아래 부분에서 끊어지는 이유는 safe area 때문입니다. 따라서 safe area를 무시해주는 다음과 같은 속성을 써야하는데요
.ignoresSafeArea(edges: .all)
이를 RootView에 적용하니 안되더라구요 그래서 @main부분에 적용해주었습니다.
@main
struct AtchIApp: App {
let hkAuthorizationProvider = HKAuthorizationProvider()
@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
var navigator: LinkNavigator {
return appDelegate.navigator
}
var body: some Scene {
WindowGroup {
navigator
.launch(paths: ["root"],
items: [:])
.ignoresSafeArea(edges: .vertical) // ✅
}
}
}
이제 탭바가 밑에까지 채워져서 자연스러워졌네요!
다른 방법으로는 TabBar의 배경색을 시스템 배경색과 동일하게 설정해주면 티가 나지 않을 것 같습니다!
NavigationBar 커스텀
원래 뷰보다 윗부분에 여백이 생기는 이유는 저 NavigationBarContentView라는 뷰 때문입니다. 따라서 로고 부분을 하위뷰에 넣지 않고 네비게이션바 부분에 넣어주도록 하겠습니다.
struct HomeView: View {
let navigator: LinkNavigatorType
init(navigator: LinkNavigatorType) {
self.navigator = navigator
}
var body: some View {
VStack {
// 로고 + 앱이름
AppTitleBar() // ✅
.padding()
// ...
이렇게 되어 있던 것을
struct RootView: View {
let navigator: LinkNavigatorType
var body: some View {
TabView {
HomeView(navigator: navigator)
.tabItem{
Image(systemName: "house")
Text("홈")
}
DiagnosisView()
.tabItem{
Image(systemName: "stethoscope")
Text("진단")
}
PreventView(preventViewModel: PreventViewModel(navigator: navigator))
.tabItem{
Image(systemName: "brain.head.profile")
Text("예방")
}
Text("설정 부분")
.tabItem{
Image(systemName: "gear")
Text("설정")
}
}
.toolbar(content: { // ✅
ToolbarItemGroup(placement: .navigationBarLeading) {
AppTitleBar()
}
})
}
}
이렇게 toolbar 메서드를 이용하여 RootView에서 NavigationBar에 넣어주었습니다.
스크롤도 자연스럽고 Back버튼 나오는 애니메이션도 자연스러워졌습니다. (퀴즈 풀기 컴포넌트에 navigator.next를 이용해 화면전환하는 코드를 연결해주었어요!)
마치며
LinkNavigator가 UIHostingController로 SwiftUI뷰를 쌓으면서 살짝 UI적인 에러가 있었는데요, 사실 위 방법 말고도 이를 해결할 수 있는 여러 꼼수가 있을 것 같습니다. 예시에서는 네이티브 탭바와 네비게이션바를 이용했지만 아예 그냥 View로 커스텀해서 사용할 수도 있을 것 같고요!
사실 저 네비게이션바가 필요한 뷰가 있고 필요하지 않은 뷰가 있는데, 선택적으로 show/hide가 잘 되지 않더라구요.
.toolbar(.hidden, for: .navigationBar)
이런 메서드를 사용하면 숨길 수는 있지만, 이 코드가 뷰 생성시 1번만 실행되기 때문에 visible 뷰에 한번 갔다오면 hidden이 풀립니다..🥲
선택적으로 네비게이션바를 이용하는이 부분은 좀 더 고민해봐야겠네요!
읽어주셔서 감사합니다.
'iOS > SwiftUI' 카테고리의 다른 글
[iOS][SwiftUI] 위로 무한스크롤 구현하기 (2) | 2024.06.11 |
---|---|
[SwiftUI] LinkNavigator (1) - 기본 사용법, 초기 설정 (4) | 2023.05.02 |
[SwiftUI] NavigationStack으로 여러 종류의 뷰 Push하기 (0) | 2023.04.29 |
[iOS] 너무 커진 ViewModel 분리하기 (MVVM) (0) | 2023.04.28 |
[SwiftUI][Combine] 프로퍼티 래퍼 어노테이션 만들기(Property Wrapper Anotation) (0) | 2023.04.07 |
- Total
- Today
- Yesterday
- notion
- 아키텍쳐 패턴
- Swift Concurrency
- swift
- SwiftUI
- design pattern
- coordinator pattern
- 프로그래머스
- 코디네이터 패턴
- MVC
- SWM
- TestCode
- Flutter
- 리액티브 프로그래밍
- combine
- GetX
- 노션
- programmers
- 비동기/동기
- Flux
- MVI
- reactive programming
- healthkit
- Bloking/Non-bloking
- DocC
- RX
- ios
- 소프트웨어마에스트로
- MVVM
- Architecture Pattern
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |