[SwiftUI] 침하하 앱을 만들어보자 Step 5

팔랑이·2026년 3월 7일

iOS/Swift

목록 보기
88/89

SwiftUI 침하하 앱 개발기


오늘의 결과물

Step 5. Board Drawer

Parent–Child Reducer 합성을 구현해 봤다.
Step 4에서 봤던 것처럼, Scope가 없으면 자식 Reducer가 실행되지 않는다.

Parent–Child Reducer 합성 — Scope

BoardDrawerReducerHomeReducer 안에 자식으로 붙이는 패턴이다.

var body: some ReducerOf<Self> {
    Scope(state: \.drawer, action: \.drawer) {
        BoardDrawerReducer()   // 이 줄이 없으면 드로어 액션이 아무 반응도 없음
    }
    Reduce { state, action in ... }
}

Scope는 부모 State의 특정 프로퍼티와 특정 Action 케이스를 자식 Reducer에 연결한다.
Scope 없이 case .drawer: return .none만 있으면 액션이 씹힌다.

자식 → 부모 소통 패턴

자식이 보낸 액션을 부모가 가로채서 처리한다.

// 자식 BoardDrawerReducer에서
case .boardSelected:
    state.isOpen = false   // 자기 State만 처리
    return .none

// 부모 HomeReducer에서
case let .drawer(.boardSelected(board)):
    state.selectedBoard = board   // 부모 State 업데이트
    return .none

자식은 자기 State만 책임지고, 부모가 필요한 것만 추가로 처리한다.

ZStack(alignment: .leading) — 드로어 레이어링

드로어가 화면 위에 겹쳐 보이려면 ZStack이 필요하다. alignment: .leading을 지정해야 드로어가 왼쪽을 기준으로 배치된다.

ZStack(alignment: .leading) {
    NavigationStack { ... }   // 메인 콘텐츠
    Color.black.opacity(...)  // 딤 오버레이
    BoardDrawerView(...)      // 드로어 패널
}

ZStack 없이 그냥 나열하면 세로로 쌓이기 때문에 오버레이가 안 된다.

.offset + .animation — 슬라이드 애니메이션

BoardDrawerView(...)
    .offset(x: store.drawer.isOpen ? 0 : -drawerWidth)
    .animation(.easeInOut(duration: 0.3), value: store.drawer.isOpen)

isOpen이 바뀔 때만 애니메이션이 트리거된다. value: 없이 .animation만 쓰면 모든 State 변화에 애니메이션이 붙어서 의도치 않은 동작이 생긴다.

contentShape(Rectangle()) — 탭 영역 확장

SwiftUI Button은 기본적으로 content 영역(텍스트, 이미지)만 탭을 인식한다. HStack 안에 Spacer()가 있어도 빈 영역은 탭이 안 된다.

HStack { Text("..."); Spacer() }
    .contentShape(Rectangle())   // 이걸 붙여야 Spacer 영역도 탭 인식

case vs case let

case .open:                        // 연관값 없음 — 그냥 case
case let .toggleSection(section):  // 연관값 있음 — case let으로 꺼내서 사용

기존에 쓰던 이것과 동일하다고 한다.

case .open:                        // 연관값 없음 — 그냥 case
case .toggleSection(let section):  // 연관값 있음 — case let으로 꺼내서 사용

근데 SwiftUI에서는 위처럼 쓰는게 국룰이라고 한다 (claude피셜)

Repository 분리 — PostRepository vs CommentRepository

처음엔 PostRepositoryfetchComments가 같이 있었다.
관심사 분리 원칙에 따라 댓글 관련 책임은 CommentRepository로 분리했다.

  • PostRepository — 게시글 CRUD
  • CommentRepository — 댓글 CRUD

TCA의 @Dependency도 각각 별도로 등록한다.
Reducer가 댓글만 필요하면 commentRepository만 주입받으면 되고, 테스트할 때도 댓글 mock만 교체하면 된다.

profile
정체되지 않는 성장

0개의 댓글