Files
sodalive-ios/docs/20260306_내비게이션스택단일전환.md

21 KiB

20260306 내비게이션 스택 단일 전환 계획

작업 목표

  • iOS 16 기준으로 앱 전역 내비게이션을 단일 NavigationStack으로 통합한다.
  • ContentView 최상단 appStep 오버레이 방식(switch appState.appStep)을 점진적으로 제거한다.
  • 뒤로가기 시 화면 재생성으로 인한 재조회(데이터 재로딩) 빈도를 줄인다.

UI/UX 동결 원칙

  • 화면 레이아웃, 색상, 폰트, 문구, 컴포넌트 배치는 변경하지 않는다.
  • 정보 구조(탭 구성, 메뉴 진입 순서, 버튼 위치)는 유지한다.
  • 모달(sheet/fullScreenCover)과 푸시 전환의 사용 의도는 유지하고 라우팅 구현만 교체한다.
  • 뒤로가기 제스처/버튼 동작은 기존 사용자 기대와 동일하게 유지한다.
  • 변경 범위는 내비게이션 상태 관리(AppStep/AppState)와 라우팅 연결부로 한정한다.

사전 조사 체크리스트

  • 루트 렌더링 구조 확인 (SodaLive/Sources/ContentView.swiftswitch appState.appStep 기반 분기)
  • 라우트 정의 확인 (SodaLive/Sources/App/AppStep.swift 78개 case)
  • 전역 라우팅 상태 확인 (SodaLive/Sources/App/AppState.swiftsetAppStep, back, 내부 back stack)
  • AppState.shared.setAppStep 호출 분포 확인 (총 162회, 73개 파일)
  • AppState.shared.back 호출 분포 확인 (총 54회, 37개 파일)
  • NavigationView 사용 현황 확인 (18개 파일)
  • NavigationStack 사용 현황 확인 (2개 파일)
  • navigationDestination(for:) 사용 현황 확인 (2개 화면, Int -> CharacterDetailView)
  • 모달 기반 전환 현황 확인 (sheet 13회/7개 파일, fullScreenCover 4회/4개 파일)
  • 딥링크/푸시 진입 흐름 확인 (AppDelegate -> AppState.push* -> SplashView/HomeView)

분산 내비게이션 패턴 메모

  • 채팅 영역 2개 화면에서만 자체 NavigationStack을 보유하고 있어 앱 전역 스택과 분리되어 동작
  • NavigationView를 가진 부모 화면 + 하위 컴포넌트 NavigationLink 조합(중첩 구조)이 콘텐츠/시리즈/검색 화면에 광범위하게 분포
  • 댓글/공유/이미지뷰어/본인인증은 sheet/fullScreenCover로 분리되어 있어 스택 푸시와 별도 정책 필요

라우팅 핫스팟(우선 전환 대상)

  • setAppStep 상위 호출 파일 확인: MyPageView.swift(14), LiveView.swift(10), SettingsView.swift(8), LiveReservation/SectionLiveReservationView.swift(6), SplashView.swift(6)
  • back 상위 호출 파일 확인: LiveDetailView.swift(5), ProfileUpdateViewModel.swift(3), CreatorCommunityModifyView.swift(3), CanPgPaymentView.swift(3), CanPgPaymentViewModel.swift(3)
  • 루트 화면 재진입 영향 구간 확인: SplashView/HomeView에서 setAppStep(.main) 후 상세 스텝 연속 진입 패턴 존재

외부 레퍼런스 반영 메모

  • Apple 공식 NavigationStack/NavigationPath 문서 기준으로 value-based 라우팅(path + navigationDestination) 채택 필요 확인
  • NavigationLink(isActive:)/selection 기반 API deprecate 이슈 확인, 값 기반 링크(NavigationLink(value:))로 정리 필요
  • 상태 복원 요구 시 NavigationPath.CodableRepresentation 기반 직렬화/복원 전략 적용 가능성 확인
  • 탭/플로우별 path 분리 패턴(오픈소스 코디네이터 예시) 확인
  • SwiftUI 목적지 재사용으로 인한 onAppear 동작 함정 사례 확인(필요 시 .task(id:)/id 기반 제어)

참고 링크

구현 체크리스트

  • AppRoute(가칭) 설계: NavigationStack에 적합한 Hashable 라우트 모델 정의
  • AppStep -> AppRoute 매핑표 작성: 78개 스텝을 루트 전환/푸시 전환으로 분리
  • 클로저/비-Hashable 연관값 대응 설계: 콜백 전달이 필요한 스텝(refresh, onSuccess 등)의 안전한 브리지 전략 수립
  • 전역 내비게이션 코디네이터(가칭) 설계: path, push, pop, reset API 정의
  • ContentView 루트 구조 개편: 단일 NavigationStack(path:) + navigationDestination 등록 구조로 전환
  • 스플래시/로그인/메인 루트 상태 전이 재정의: 기존 .splash, .main, .login 동작 동등성 보장
  • 딥링크/푸시 라우팅 재배선: pushRoomId/pushChannelId/pushAudioContentId/pushSeriesId/pushMessageId를 path 전환으로 일원화
  • 기존 AppState.shared.setAppStep 호출부 점진 전환(모듈 우선순위 적용)
  • 기존 AppState.shared.back 호출부를 stack pop 동작으로 전환 (DetailNavigationBar 포함)
  • 중첩 내비게이션 정리: NavigationView 18개 제거 및 NavigationStack 2개(채팅 화면) 중첩 해소
  • NavigationLink 로컬 푸시와 전역 라우트의 역할 분리 규칙 확정
  • 데이터 재로딩 방지 점검: 복귀 시 재요청이 발생하는 목록/상세 화면의 onAppear/ViewModel 생명주기 정리
  • 단계별 마이그레이션 플래그 또는 호환 레이어(setAppStep 브리지) 적용 여부 결정
  • 모듈별 전환 순서 확정 (권장: Root -> Home/Content -> MyPage/Settings -> Live -> Message/Chat)
  • 완료 검증 시나리오 문서화 (뒤로가기, 탭 전환, 푸시 진입, 로그인 전환, 결제/충전 플로우)

스크롤 유지/재로딩 후속 보정

  • ContentCurationView 복귀 시 무조건 재조회 방지(isInitialized + curationId 변경 감지)
  • ContentMainAlarmAllView 최초 진입 1회 조회 가드 적용
  • ContentMainAsmrAllView/ContentMainReplayAllView 테마 재설정 기반 중복 조회 방지
  • ContentMainIntroduceCreatorAllView 최초 진입 1회 조회 가드 적용
  • SeriesListAllView 초기화 가드 및 필터 변경 시에만 목록 리셋/재조회
  • SeriesMainHomeView/SeriesMainByGenreView 복귀 재조회 방지
  • Series 화면의 남은 로컬 NavigationLinkAppState.setAppStep 기반으로 전환해 루트 path 일관성 확보
  • 잔여 NavigationLink 2건(댓글 답글 로컬 플로우)은 의도적으로 유지
  • UserProfileView 복귀 시 무조건 getCreatorProfile 재호출 방지(최초 진입/미로딩 상태에만 조회)
  • 전역 step 화면에 기본 Navigation 뒤로가기 버튼 비노출 적용(ContentViewAppStepLayerView 호출부 공통 처리)
  • 언어 변경 soft restart 시 스플래시 상단 오프셋 보정(SplashView에서 toolbar(.hidden, for: .navigationBar) 적용)
  • 댓글 리스트 시트에서 답글 보기/쓰기 동작 복구(ContentDetailView/CreatorCommunityAllView 시트 컨텐츠를 NavigationStack으로 감싸고 nav bar 숨김 적용)

Navigation 컨테이너 정리 대상 파일

  • SodaLive/Sources/MyPage/OrderList/OrderListAllView.swift (NavigationView 제거)
  • SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentListView.swift (NavigationView 제거)
  • SodaLive/Sources/Search/SearchView.swift (NavigationView 제거)
  • SodaLive/Sources/Content/ContentListView.swift (NavigationView 제거)
  • SodaLive/Sources/Content/Box/ContentBoxView.swift (NavigationView 제거)
  • SodaLive/Sources/Content/All/ContentAllView.swift (NavigationView 제거)
  • SodaLive/Sources/Content/Main/V2/Alarm/All/ContentMainAlarmAllView.swift (NavigationView 제거)
  • SodaLive/Sources/Content/All/ContentRankingAllView.swift (NavigationView 제거)
  • SodaLive/Sources/Content/Curation/ContentCurationView.swift (NavigationView 제거)
  • SodaLive/Sources/Content/Main/V2/Replay/All/ContentMainReplayAllView.swift (NavigationView 제거)
  • SodaLive/Sources/Content/All/ByTheme/ContentAllByThemeView.swift (NavigationView 제거)
  • SodaLive/Sources/Content/All/ContentNewAllView.swift (NavigationView 제거)
  • SodaLive/Sources/Content/Main/V2/ContentMainViewV2.swift (NavigationView 제거)
  • SodaLive/Sources/Content/Main/V2/Free/All/ContentMainIntroduceCreatorAllView.swift (NavigationView 제거)
  • SodaLive/Sources/Content/Series/Main/SeriesMainView.swift (NavigationView 제거)
  • SodaLive/Sources/Content/Main/V2/Asmr/All/ContentMainAsmrAllView.swift (NavigationView 제거)
  • SodaLive/Sources/Content/Series/SeriesListAllView.swift (NavigationView 제거)
  • SodaLive/Sources/Content/Detail/Comment/AudioContentCommentListView.swift (NavigationView 제거)
  • SodaLive/Sources/Chat/Character/New/Views/NewCharacterListView.swift (중첩 NavigationStack 제거)
  • SodaLive/Sources/Chat/Original/Detail/OriginalWorkDetailView.swift (중첩 NavigationStack 제거)

화면군 우선순위(초안)

  • P0: Root/App (ContentView, AppState, AppStep, SplashView, HomeView 푸시 진입 처리)
  • P1: Content/Series/Search (NavigationView 다수 분포 구간)
  • P1: MyPage/Settings (계정/설정/캔 결제 흐름)
  • P2: Live/Audition (콜백 기반 스텝 다수 구간)
  • P2: Message/Chat (일부 NavigationStack 선구현 화면 정합화)

리스크 및 대응 계획

  • 리스크: AppStep 연관값에 클로저가 많아 NavigationStackHashable 경로 모델과 충돌 가능
  • 대응: 라우트에는 식별자/파라미터만 담고, 콜백은 코디네이터의 임시 액션 저장소(토큰 기반)로 분리
  • 리스크: 푸시/딥링크 타이밍(현재 DispatchQueue.main.asyncAfter) 의존 로직 회귀 가능
  • 대응: 루트 준비 완료 시점 이벤트를 기준으로 경로 적용 순서를 표준화
  • 리스크: 기존 NavigationView 내부 NavigationLink 동작과 전역 스택 충돌 가능
  • 대응: "로컬 화면 내부 계층"과 "앱 전역 화면 전환" 규칙을 문서화하고 중복 push 금지 가드 추가

검증 계획(구현 단계에서 수행)

  • lsp_diagnostics로 수정 파일 오류 0 확인
  • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build
  • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
  • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test
  • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test

검증 기록

  • 무엇/왜/어떻게: 내비게이션 단일화 계획 수립을 위해 코드베이스 전수 탐색으로 현재 라우팅 구조와 분산 내비게이션 사용처를 먼저 계량했다.
  • 실행 명령: grep 패턴 검색 (NavigationView, NavigationStack, NavigationLink, navigationDestination, setAppStep, back)
  • 결과: NavigationView 18개 파일, NavigationStack 2개 파일, AppState.shared.setAppStep 162회, AppState.shared.back 54회 확인.
  • 실행 명령: ast_grep_search (NavigationView { $$$ }, NavigationStack { $$$ }, func setAppStep($$$) { $$$ })
  • 결과: 중첩 NavigationStack 화면 2개와 전역 라우팅 함수 정의 위치(AppState.swift)를 교차 검증.
  • 실행 명령: lsp_symbols (ContentView.swift, AppState.swift, AppStep.swift)
  • 결과: 루트 분기 지점(ContentView.body), 전역 라우팅 API(setAppStep, back), 라우트 열거형(AppStep) 심볼 확인.
  • 실행 명령: rg -n "NavigationView|NavigationStack|setAppStep\(|AppState\.shared\.back\(|navigationDestination|NavigationLink" "SodaLive/Sources"
  • 결과: 로컬 환경에 rg 미설치(command not found)로 확인되어 grep/ast-grep 기반으로 대체 탐색 수행.
  • 실행 명령: background librarian 조사 (bg_0803bb96)
  • 결과: Apple 공식 문서 기준(NavigationStack, NavigationPath, deprecated NavigationLink(isActive:))과 오픈소스 코디네이터 패턴(탭별 NavigationPath, 딥링크 path 매핑, 상태 복원) 근거 확보.
  • 실행 명령: background explore 조사 (bg_7711b310)
  • 결과: NavigationView 18개, NavigationStack 2개, navigationDestination 2개, 모달 전환(sheet/fullScreenCover) 분산 사용처를 파일 단위로 확정.
  • 무엇/왜/어떻게: AppStateAppRoute 기반 navigationPath를 도입하고 setAppStep/back를 path push/pop/reset 브리지로 전환했다. ContentView는 단일 루트 NavigationStack(path:) + navigationDestination(for: AppRoute) 구조로 교체하고, 기존 화면 매핑은 AppStepLayerView로 유지해 UI를 고정했다.
  • 실행 명령: ast_grep_replace (NavigationView { $$$ } -> Group { $$$ }, NavigationStack { $$$ } -> Group { $$$ })
  • 결과: 대상 20개 파일(18 NavigationView, 2 NavigationStack)의 로컬 컨테이너 제거 완료.
  • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build
  • 결과: 1차 AppStep Equatable 비교 오류 수정 후 재실행에서 ** BUILD SUCCEEDED **.
  • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
  • 결과: ** BUILD SUCCEEDED **.
  • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test
  • 결과: Scheme SodaLive is not currently configured for the test action.
  • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test
  • 결과: Scheme SodaLive-dev is not currently configured for the test action.
  • 무엇/왜/어떻게: 언어 변경 적용 후 AppState.softRestart() 경로에서 스플래시가 NavigationStack 컨텍스트 안에 렌더링되며 네비게이션 바 안전영역만큼 아래로 밀리는 현상을 확인했다. 스플래시 화면에서 네비게이션 바를 숨기도록 수정해 오프셋을 제거했다.
  • 실행 명령: background 분석 explore/librarian (bg_ff67655b, bg_750d2d2e, bg_cb65fc63)
  • 결과: 공통 원인으로 ContentView의 단일 NavigationStack 내 스플래시 렌더링 시 nav bar inset 개입이 확인되었고, Apple 문서 기준 toolbar(_:for:)로 navigation bar를 숨기는 방식이 유효함을 확인.
  • 실행 명령: lsp_diagnostics (SodaLive/Sources/Splash/SplashView.swift)
  • 결과: SourceKit 인덱싱 컨텍스트에서 FirebaseRemoteConfig 모듈 미해석 오탐(No such module 'FirebaseRemoteConfig') 발생, 빌드로 실제 유효성 확인.
  • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build && xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
  • 결과: 두 스킴 모두 ** BUILD SUCCEEDED **.
  • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test; xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test
  • 결과: 두 스킴 모두 is not currently configured for the test action으로 테스트 액션 미구성 상태.
  • 무엇/왜/어떻게: 모든 페이지에서 시스템 기본 뒤로가기 버튼을 감추기 위해, 단일 루트 라우팅 지점인 ContentViewAppStepLayerView 렌더링 2곳(루트 overlay / navigationDestination)에 공통으로 .navigationBarBackButtonHidden(true)를 적용했다.
  • 실행 명령: lsp_diagnostics (SodaLive/Sources/ContentView.swift)
  • 결과: SourceKit 인덱싱 컨텍스트 부재로 외부 타입 다수 미해석 오탐이 발생했고, 실제 유효성은 빌드로 확인.
  • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build
  • 결과: ** BUILD SUCCEEDED ** (중간 1회는 동시 빌드로 build.db lock 실패 후 단독 재실행 성공).
  • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
  • 결과: ** BUILD SUCCEEDED **.
  • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test; xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test
  • 결과: 두 스킴 모두 is not currently configured for the test action으로 테스트 액션 미구성 상태 재확인.
  • 실행 명령: lsp_diagnostics (수정 파일 일괄 점검)
  • 결과: ContentView.swift/AppState.swift 등에서 SourceKit 워크스페이스 인덱싱 한계로 외부 타입 미해석 오탐이 발생했다. 실제 컴파일은 두 스킴 빌드 성공으로 검증.
  • 무엇/왜/어떻게: 뒤로가기 시 스크롤 위치 초기화와 불필요 재로딩을 줄이기 위해, 목록/탭 화면의 .onAppear 무조건 조회를 초기 1회 가드로 정리하고 Series 계열 잔여 로컬 push를 AppState 경로 기반으로 통일했다.
  • 실행 명령: grep -n "NavigationLink\\s*\\(" "SodaLive/Sources/**/*.swift"(동등 패턴 검색)
  • 결과: NavigationLink 잔여는 댓글 답글 로컬 플로우 2건(CreatorCommunityCommentItemView.swift, AudioContentCommentItemView.swift)만 확인.
  • 무엇/왜/어떻게: NavigationStack 마이그레이션 후 댓글 리스트(AudioContentCommentListView, CreatorCommunityCommentListView)가 .sheet로만 표시되면서 내부 NavigationLink가 네비게이션 컨텍스트 없이 렌더링되어 답글 보기/쓰기 진입이 무반응이 되었다. 두 시트 호출부(ContentDetailView, CreatorCommunityAllView)를 NavigationStack으로 감싸 reply push가 동작하도록 복구했다.
  • 실행 명령: background 분석 explore (bg_5ad57f11, bg_a7c4e178)
  • 결과: 공통 원인으로 “시트 내부 NavigationStack 부재 + item view의 NavigationLink 의존”이 확인됨.
  • 실행 명령: lsp_diagnostics (SodaLive/Sources/Content/Detail/ContentDetailView.swift, SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllView.swift)
  • 결과: SourceKit 인덱싱 컨텍스트에서 외부 모듈/타입 미해석 오탐이 발생했고, 실제 유효성은 빌드로 확인.
  • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build && xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
  • 결과: 두 스킴 모두 ** BUILD SUCCEEDED **.
  • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test; xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test
  • 결과: 두 스킴 모두 is not currently configured for the test action으로 테스트 액션 미구성 상태.
  • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build
  • 결과: ** BUILD SUCCEEDED **.
  • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
  • 결과: ** BUILD SUCCEEDED **.
  • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test
  • 결과: Scheme SodaLive is not currently configured for the test action.
  • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test
  • 결과: Scheme SodaLive-dev is not currently configured for the test action.
  • 실행 명령: lsp_diagnostics (이번 수정 8개 파일)
  • 결과: SourceKit 인덱싱 컨텍스트 부재로 다수 오탐(Cannot find ... in scope)이 재현되었고, 문법/타입 안정성은 두 스킴 빌드 성공으로 최종 검증.
  • 무엇/왜/어떻게: UserProfileView에서 콘텐츠 상세로 이동 후 뒤로 복귀 시 .onAppear가 매번 getCreatorProfile를 호출해 creatorProfile = nil 재초기화가 발생했고, 이로 인해 스크롤이 최상단으로 리셋되던 문제를 조회 조건 가드로 수정했다.
  • 실행 명령: lsp_diagnostics (SodaLive/Sources/Explorer/Profile/UserProfileView.swift)
  • 결과: SourceKit 인덱싱 컨텍스트에서 Kingfisher 모듈 미해석 오탐(No such module 'Kingfisher')이 발생했으며, 실제 빌드로 유효성 확인.
  • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build
  • 결과: ** BUILD SUCCEEDED **.
  • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
  • 결과: ** BUILD SUCCEEDED **.
  • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test
  • 결과: Scheme SodaLive is not currently configured for the test action.
  • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test
  • 결과: Scheme SodaLive-dev is not currently configured for the test action.