SwiftUI 침하하 앱 개발기
오늘의 결과물

Parent–Child Reducer 합성을 구현해 봤다.
Step 4에서 봤던 것처럼, Scope가 없으면 자식 Reducer가 실행되지 않는다.
ScopeBoardDrawerReducer를 HomeReducer 안에 자식으로 붙이는 패턴이다.
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 letcase .open: // 연관값 없음 — 그냥 case
case let .toggleSection(section): // 연관값 있음 — case let으로 꺼내서 사용
기존에 쓰던 이것과 동일하다고 한다.
case .open: // 연관값 없음 — 그냥 case
case .toggleSection(let section): // 연관값 있음 — case let으로 꺼내서 사용
근데 SwiftUI에서는 위처럼 쓰는게 국룰이라고 한다 (claude피셜)
처음엔 PostRepository에 fetchComments가 같이 있었다.
관심사 분리 원칙에 따라 댓글 관련 책임은 CommentRepository로 분리했다.
PostRepository — 게시글 CRUDCommentRepository — 댓글 CRUDTCA의 @Dependency도 각각 별도로 등록한다.
Reducer가 댓글만 필요하면 commentRepository만 주입받으면 되고, 테스트할 때도 댓글 mock만 교체하면 된다.