상세 컨텐츠

본문 제목

[TCA] TCA UI Case Study - TabView

Swift

by Mr.Garlic 2024. 8. 12. 15:47

본문

The Composable Architecture를 이용해 TabView 사용하기

안녕하세요, 오랜만에 포스팅을 하는 마늘맨입니다.
오늘 포스팅할 내용은 엄청 큰건 아니지만 의외로 좀 헤매실 수 있는 내용이라 정리해보았습니다.

 

오늘 만들어 볼 것

애플의 HIG에 딱히 부합하는 뷰는 아니에요..

그렇지만 이런 뷰 자주 만드시죠..?

저도 요런 상단에 탭이 있는 뷰들을 자주 만들게 되는데요 ~

오늘은 TCA로 이 탭뷰 만드는 방법을 알아보도록 하겠습니다.

 

스펙

탭을 누르면 인디케이터가 움직여주면 되구요!

스와이프 해서 페이징이 가능하고

상단을 탭해도 이동이 되면 되겠죠 !

 

 

 

상단 탭

상단탭은 사실 대강 만들어도 됩니다. 오늘의 핵심 주제는 TCA + TabView의 케이스 스터디 이기 때문에...

import SwiftUI
import ComposableArchitecture


struct TopTabView: View {
    @Bindable
    var store: StoreOf<RandomProfileListReducer>
    
    var body: some View {
        VStack {
            HStack {
                ForEach(self.store.tabList.map { $0.rawValue }, id: \.self) { title in
                    VStack {
                        Text(title)
                            .frame(maxWidth: .infinity, minHeight: 50)
                            .foregroundStyle(.gray)
                    }
                    .onTapGesture {
                        Task {
                            withAnimation {
                                if let index = store.tabList.firstIndex(where: { $0.rawValue == title }) {
                                    store.send(.changeTabIndex(index))
                                } else {
                                    store.send(.changeTabIndex(0))
                                }
                            }
                        }
                    }
                }
            }
            GeometryReader(content: { geometry in
                HStack {
                    let capsuleWidth = geometry.size.width / CGFloat(store.tabList.count)
                    Capsule()
                        .frame(width: capsuleWidth, height: 3)
                        .position(x: geometry.size.width / CGFloat(store.tabList.count) * CGFloat(store.state.selectedIndex) + (capsuleWidth / 2) )
                }
            })
        }
        .animation(.easeIn)
    }
}

 

탭뷰를 사용하기

이제 아래에 탭뷰를 추가해줍니다.

이때 상위 리듀서를 공유하도록 작성해야 탭을 눌러서 페이징을 하든, 스와이프를 해서 페이징을 하든 싱크가 맞겠죠~

코드 보시면 아시겠지만! RandomProfileListReducer.State의 내부에 selectedIndex, Action으로 changeTabIndex를 준비해두셔야 되겠죠!

저는 그냥 저 표현 $store.selectedIndex.sending(\.changeTabIndex) 를 바로 못찾아서 조금 해맸습니다~

 

import SwiftUI
import ComposableArchitecture

enum GenderType: String, CaseIterable {
    case male = "남성"
    case female = "여성"
    case text = "test1"
    case test1 = "test2"
}

struct RandomProfileView: View {
    @Bindable var store: StoreOf<RandomProfileListReducer>
    
    var body: some View {
        VStack {
        // 위에서 만든 상단의 탭뷰
            TopTabView(store: self.store)
        // 페이징이 되는 진짜(?) 탭뷰
            TabView(selection: $store.selectedIndex.sending(\.changeTabIndex)) {
                ForEach(GenderType.allCases.indices) { index in
                    ProfileListView(store: self.store)
                        .tag(index)
                }
            }
            .tabViewStyle(.page(indexDisplayMode: .never))
        }
    }
}

 

참고

TCA 깃헙 UI Case Studies

관련글 더보기