TCA

iOS 17 아래 버전

@Bindable 은 iOS 17 이후로 제공

@ObservableState 를 사용하기 위해서는 @Perception.Bindable 로 사용해야 한다.

추가로 WithPerceptionTracking 를 해야 정상 동작한다.

Action에도 @CasePathable 를 추가해야 한다.

struct RootView: View {
  
  @Perception.Bindable
  private var store: StoreOf<RootFeature>
  
  init(store: StoreOf<RootFeature>) {
    self.store = store
  }
  
  var body: some View {
    WithPerceptionTracking {
      TabView(selection: $store.selectedTab.sending(\.tabSelected)) {
        HomeView(store: store.scope(state: \.home, action: \.home))
          .tabItem { Text(RootTab.home.title) }
          .tag(RootTab.home)
        
        FeedView(store: store.scope(state: \.feed, action: \.feed))
          .tabItem { Text(RootTab.feed.title) }
          .tag(RootTab.feed)
        
        TodayView(store: store.scope(state: \.today, action: \.today))
          .tabItem { Text(RootTab.today.title) }
          .tag(RootTab.today)
        
        MyView(store: store.scope(state: \.my, action: \.my))
          .tabItem { Text(RootTab.my.title) }
          .tag(RootTab.my)
      }
    }
  }
  
}

@Reducer
struct RootFeature {
  
  @ObservableState
  struct State: Equatable {
    var selectedTab: RootTab = .home
    var home: HomeFeature.State = .init()
    var feed: FeedFeature.State = .init()
    var today: TodayFeature.State = .init()
    var my: MyFeature.State = .init()
  }
  
  @CasePathable
  enum Action {
    case home(HomeFeature.Action)
    case feed(FeedFeature.Action)
    case today(TodayFeature.Action)
    case my(MyFeature.Action)
    
    case tabSelected(RootTab)
  }
  
  var body: some ReducerOf<Self> {
    Scope(state: \.home, action: \.home) {
      HomeFeature()
    }
    
    Scope(state: \.feed, action: \.feed) {
      FeedFeature()
    }
    
    Scope(state: \.today, action: \.today) {
      TodayFeature()
    }
    
    Scope(state: \.my, action: \.my) {
      MyFeature()
    }
    
    Reduce { state, action in
      switch action {
      case .home, .feed, .today, .my:
        return .none
        
      case let .tabSelected(selectedTab):
        state.selectedTab = selectedTab
        return .none
      }
    }
  }
  
}

GeometryReader 내부도 WithPerceptionTracking로 감싸야 한다

외부 뿐만 아니라 GeometryReader를 사용하는 경우 내부에도 감싸야 함

WithPerceptionTracking {
  VStack {
    GeometryReader { proxy in
      WithPerceptionTracking {
        Path { path in
          path.move(to: CGPoint(x: proxy.size.width / 2, y: proxy.size.height / 2))
          path.addLine(to: CGPoint(x: proxy.size.width / 2, y: 0))
        }
        .stroke(.primary, lineWidth: 3)
        .rotationEffect(.degrees(Double(store.secondsElapsed) * 360 / 60))
      }
    }
  }
}

Last updated