Skip to content

TodoListView 헤더 트러블슈팅 ‐ 네비게이션바 UI

최윤진 edited this page Mar 6, 2026 · 9 revisions

Note

headerView와 연계되는 내비게이션바의 UI를 개선하기 위한 시도를 작성하였습니다.
이 문서를 읽기 전 해당 문서를 먼저 읽고 오는 것을 추천합니다.
해결 PR
사이드 이펙트 해결 PR

개선 형태

2026-03-05.6.21.44.mov
2026-03-05.6.23.34.mov
개선 전 개선 후

문제 정의

  • 네비게이션 바의 배경색을 .systemBackground로 변경하고, 하단 경계선(shadow)을 제거하고자 함.
  • iOS 26+에서는 Liquid Glass 등 새로운 디자인이 적용되므로, iOS 17/18에서만 커스텀 적용이 필요.

시도 1: toolbarBackground modifier

  • .toolbarBackground(.visible, for: .navigationBar).toolbarBackground(Color(.systemBackground), for: .navigationBar) 적용.
  • 배경색은 변경되나, 하단 경계선(shadow)을 숨기는 SwiftUI modifier가 존재하지 않음.

시도 2: UINavigationBar.appearance() (프록시)

  • onAppear에서 UINavigationBarAppearance를 생성하여 UINavigationBar.appearance()에 설정.
  • shadowColor = .clear로 경계선 제거 성공.
  • 문제: large → inline 전환 시 SwiftUI가 내부적으로 appearance를 덮어써서 inline 모드에서 적용 안 됨.
  • appearance 프록시는 새로 생성되는 navigation bar에만 적용되는 한계.

시도 3: NavigationBarConfigurator (UIViewControllerRepresentable)

  • UIViewControllerRepresentable로 실제 UINavigationController의 navigation bar 인스턴스에 직접 접근.
  • updateUIViewController에서 appearance를 설정하여 large/inline 모두 적용.
  • dismantleUIViewController에서 configureWithDefaultBackground()로 시스템 기본값 복원.
  • #available(iOS 26, *)로 iOS 26 이상에서는 스킵.

configureWithDefaultBackground vs configureWithOpaqueBackground

  • configureWithOpaqueBackground(): 불투명 배경 (.bar와 유사하지 않음).
  • configureWithDefaultBackground(): 시스템 기본 반투명(blur) 배경 (SwiftUI의 .bar에 가까움).
  • UIKit의 UINavigationBarAppearance에서는 SwiftUI의 Material(.bar)을 직접 사용할 수 없음.

시도 4: UIScrollView KVO를 통한 스크롤 추적 (iOS 17 지원)

  • 문제: iOS 18의 onScrollGeometryChange를 사용하여 스크롤을 추적했으나, 이 API는 iOS 17에서 지원되지 않아 KVO 기반의 커스텀 솔루션이 필요함.
  • 시도 4-1 (superview 체인 탐색): ScrollViewOffsetTracker(UIViewRepresentable).background()로 추가하여 superview 체인을 탐색. 하지만 추가된 UIViewUIScrollView 외부에 위치하여 탐색에 실패함.
  • 시도 4-2 (superview + sibling subview 탐색): superview 체인을 올라가며 각 단계에서 sibling subview도 재귀적으로 탐색하도록 로직 변경.
  • 결과: iOS 17에서도 UIScrollView 인스턴스를 찾아내어 KVO로 contentOffset을 연속 추적하는 데 성공함.
  • 최종 구현: iOS 18 이상은 onScrollGeometryChange를 사용하고, iOS 17은 ScrollViewOffsetTracker를 통한 KVO 관찰 방식을 분기하여 사용.

부수적 문제 해결 (사이드 이펙트)

구현 과정에서 iOS 17을 대응하며 몇 가지 부수적인 문제가 발생하여 이를 추가로 해결함.

1. iOS 17 네비게이션 바 투명 현상 (PushNotificationListView)

  • 문제: 자체 NavigationStack을 가진 뷰에서 iOS 17 구동 시, 스크롤이 최상단에 위치할 때 네비게이션 바가 투명하게 표시됨 (scrollEdgeAppearance가 nil인 상태가 원인).
  • 해결: NavigationBarConfiguratoralwaysVisible: Bool 파라미터를 추가함. true일 경우 scrollEdgeAppearance에도 기본 배경이 설정되도록 수정함 (iOS 18 이상에서는 발생하지 않으므로 무시됨).

2. 스크롤 트래킹 미작동 이슈

  • 문제: onAppear 블록이 headerView 내부의 ScrollView에 위치하여 스크롤 트래커가 제대로 작동하지 않음.
  • 해결: 트래킹 뷰 위치를 List 레벨(.background 다음)로 이동하여 스크롤 이벤트를 정상적으로 포착하도록 수정함.

3. headerView 레이아웃 불안정 이슈

  • 문제: iOS 17에서 headerView 내부에 ScrollView(.horizontal)를 배치할 경우 레이아웃이 깨지거나 불안정해지는 현상 발생.
  • 해결: OS 버전에 따라 분기 처리하여, iOS 17에서는 ScrollView를 제거하고 일반 뷰 형태로 렌더링되도록 수정함.

최종 상태 요약 (2026-03-06 기준)

  • headerView: safeAreaInset(edge: .top)에 배치하고, VStack으로 Divider를 포함함.
  • 스크롤 추적: 분기된 onScrollOffsetChange 로직 적용 (iOS 18+ : onScrollGeometryChange, iOS 17 : KVO ScrollViewOffsetTracker).
  • headerView 구조 (OS 분기 처리):
    • iOS 18+: ScrollView(.horizontal) + .frame(height: 36) + .scrollDisabled(!isScrollTrackingEnabled) + .contentMargins(.leading, 16) 적용.
    • iOS 17: ScrollView 미사용, headerContent + .padding(.leading, 16) + .frame(maxWidth: .infinity, alignment: .leading) 적용.
  • headerView 배경: iOS 26 미만은 Color(.secondarySystemBackground), iOS 26 이상은 Color.clear 적용.
  • Section 상단 separator: .listSectionSeparator(.hidden, edges: .top) 적용 및 첫 번째 row에 Divider() overlay 추가 (iOS 26 전용).
  • 네비게이션 바 커스텀: NavigationBarConfigurator를 통해 iOS 17/18의 배경색 및 shadow를 조정하고, dismantleUIViewController에서 시스템 복원을 보장함 (자체 NavigationStack을 가진 뷰는 alwaysVisible: true 적용).
  • 상태 관리: isScrollTrackingEnabled 상태 변수를 통해 초기 레이아웃을 안정화하고, headerView 스크롤 활성화 상태를 연동 처리함.

Clone this wiki locally