From 38fb818f4b3dffed5c45ffc9c522663bf79f52bf Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Thu, 26 Feb 2026 01:20:37 +0900 Subject: [PATCH] =?UTF-8?q?fix(detail):=20=EC=BD=98=ED=85=90=EC=B8=A0/?= =?UTF-8?q?=EC=8B=9C=EB=A6=AC=EC=A6=88=20=EC=83=81=EC=84=B8=20=EB=A1=9C?= =?UTF-8?q?=EB=94=A9=20=EC=8B=A4=ED=8C=A8=20=EC=8B=9C=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=20=EB=B3=B5=EA=B7=80=EB=A5=BC=20=EC=A0=81=EC=9A=A9=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Content/Detail/ContentDetailView.swift | 35 ++++++++++--- .../Series/Detail/SeriesDetailView.swift | 50 +++++++++++++++++-- docs/20260226_콘텐츠상세로딩실패시뒤로이동.md | 22 ++++++++ ...260226_콘텐츠시리즈상세로딩실패자동복귀.md | 23 +++++++++ 4 files changed, 119 insertions(+), 11 deletions(-) create mode 100644 docs/20260226_콘텐츠상세로딩실패시뒤로이동.md create mode 100644 docs/20260226_콘텐츠시리즈상세로딩실패자동복귀.md diff --git a/SodaLive/Sources/Content/Detail/ContentDetailView.swift b/SodaLive/Sources/Content/Detail/ContentDetailView.swift index 9486428..9e9871e 100644 --- a/SodaLive/Sources/Content/Detail/ContentDetailView.swift +++ b/SodaLive/Sources/Content/Detail/ContentDetailView.swift @@ -21,6 +21,8 @@ struct ContentDetailView: View { @State private var isShowCommentListView = false @State private var isShowFollowNotifyDialog: Bool = false @State private var creatorId: Int = 0 + @State private var didTriggerAutoBackOnLoadFailure: Bool = false + @State private var isViewVisible: Bool = false var body: some View { GeometryReader { proxy in @@ -28,11 +30,7 @@ struct ContentDetailView: View { VStack(spacing: 0) { HStack(spacing: 0) { Button { - if presentationMode.wrappedValue.isPresented { - presentationMode.wrappedValue.dismiss() - } else { - AppState.shared.back() - } + goBack() } label: { Image("ic_back") .resizable() @@ -217,9 +215,26 @@ struct ContentDetailView: View { .navigationTitle("") .navigationBarBackButtonHidden() .onAppear { + isViewVisible = true + didTriggerAutoBackOnLoadFailure = false viewModel.contentId = contentId AppState.shared.pushAudioContentId = 0 } + .onDisappear { + isViewVisible = false + } + .onChange(of: viewModel.isShowPopup) { isShowing in + guard isShowing else { return } + guard viewModel.audioContent == nil else { return } + guard !didTriggerAutoBackOnLoadFailure else { return } + + didTriggerAutoBackOnLoadFailure = true + + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + guard isViewVisible else { return } + goBack() + } + } if let audioContent = viewModel.audioContent, isShowOrderView { VStack(spacing: 0) { @@ -423,7 +438,7 @@ struct ContentDetailView: View { .padding(.vertical, 13.3) .frame(width: screenSize().width - 66.7, alignment: .center) .appFont(size: 12, weight: .medium) - .background(Color(hex: "9970ff")) + .background(Color(hex: "3bb9f1")) .foregroundColor(Color.white) .multilineTextAlignment(.center) .cornerRadius(20) @@ -434,4 +449,12 @@ struct ContentDetailView: View { } } } + + private func goBack() { + if presentationMode.wrappedValue.isPresented { + presentationMode.wrappedValue.dismiss() + } else { + AppState.shared.back() + } + } } diff --git a/SodaLive/Sources/Content/Series/Detail/SeriesDetailView.swift b/SodaLive/Sources/Content/Series/Detail/SeriesDetailView.swift index 86f8926..d8b8fac 100644 --- a/SodaLive/Sources/Content/Series/Detail/SeriesDetailView.swift +++ b/SodaLive/Sources/Content/Series/Detail/SeriesDetailView.swift @@ -17,6 +17,8 @@ struct SeriesDetailView: View { @State private var isShowFollowNotifyDialog: Bool = false @State private var creatorId: Int = 0 + @State private var didTriggerAutoBackOnLoadFailure: Bool = false + @State private var isViewVisible: Bool = false var body: some View { BaseView(isLoading: $viewModel.isLoading) { @@ -38,11 +40,7 @@ struct SeriesDetailView: View { .resizable() .frame(width: 20, height: 20) .onTapGesture { - if presentationMode.wrappedValue.isPresented { - presentationMode.wrappedValue.dismiss() - } else { - AppState.shared.back() - } + goBack() } Spacer() @@ -243,9 +241,51 @@ struct SeriesDetailView: View { .navigationBarBackButtonHidden() } .onAppear { + isViewVisible = true + didTriggerAutoBackOnLoadFailure = false viewModel.seriesId = seriesId viewModel.getSeriesDetail() } + .onDisappear { + isViewVisible = false + } + .onChange(of: viewModel.isShowPopup) { isShowing in + guard isShowing else { return } + guard viewModel.seriesDetail == nil else { return } + guard !didTriggerAutoBackOnLoadFailure else { return } + + didTriggerAutoBackOnLoadFailure = true + + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + guard isViewVisible else { return } + goBack() + } + } + .popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) { + GeometryReader { geo in + HStack { + Spacer() + Text(viewModel.errorMessage) + .padding(.vertical, 13.3) + .frame(width: screenSize().width - 66.7, alignment: .center) + .appFont(size: 12, weight: .medium) + .background(Color(hex: "3bb9f1")) + .foregroundColor(Color.white) + .multilineTextAlignment(.center) + .cornerRadius(20) + .padding(.top, 66.7) + Spacer() + } + } + } + } + + private func goBack() { + if presentationMode.wrappedValue.isPresented { + presentationMode.wrappedValue.dismiss() + } else { + AppState.shared.back() + } } } diff --git a/docs/20260226_콘텐츠상세로딩실패시뒤로이동.md b/docs/20260226_콘텐츠상세로딩실패시뒤로이동.md new file mode 100644 index 0000000..4f7c944 --- /dev/null +++ b/docs/20260226_콘텐츠상세로딩실패시뒤로이동.md @@ -0,0 +1,22 @@ +## 작업 개요 + +- 콘텐츠 상세 페이지에서 상세 데이터 로딩 실패 시 토스트만 노출되고 이전 페이지로 자동 복귀하지 않는 문제를 수정한다. + +## 체크리스트 + +- [x] 콘텐츠 상세 로딩 실패 처리 지점 확인 +- [x] 로딩 실패 시 토스트 노출 후 이전 페이지 자동 이동 로직 추가 +- [x] 관련 상태 변수 초기화/중복 이동 방지 처리 추가 +- [x] LSP 진단 및 빌드 검증 + +## 검증 기록 + +- 무엇/왜/어떻게: `ContentDetailView`에서 로딩 실패 토스트 노출 시 `audioContent == nil` 조건을 만족하면 2초 후 자동 뒤로가기를 수행하도록 `onChange(of: viewModel.isShowPopup)`와 `goBack()` 재사용 로직을 추가했다. 중복 이동 방지를 위해 `didTriggerAutoBackOnLoadFailure`, 화면 생명주기 안전성을 위해 `isViewVisible` 상태를 함께 적용했다. +- 실행 명령: `pod install` + 결과: 성공 (`Pod installation complete`) +- 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build` + 결과: 성공 (`** BUILD SUCCEEDED **`) +- 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test` + 결과: 실패 (`Scheme SodaLive is not currently configured for the test action.`) +- 실행 도구: `lsp_diagnostics` (`ContentDetailView.swift`) + 결과: SourceKit 환경에서 `No such module 'Kingfisher'` 진단이 지속되었으나, 실제 `xcodebuild` 빌드는 성공해 코드 변경으로 인한 컴파일 오류는 없음을 확인했다. diff --git a/docs/20260226_콘텐츠시리즈상세로딩실패자동복귀.md b/docs/20260226_콘텐츠시리즈상세로딩실패자동복귀.md new file mode 100644 index 0000000..e7984dd --- /dev/null +++ b/docs/20260226_콘텐츠시리즈상세로딩실패자동복귀.md @@ -0,0 +1,23 @@ +## 작업 개요 + +- 콘텐츠 상세 토스트 색상을 지정값으로 변경하고, 시리즈 상세에서도 데이터 로딩 실패 시 토스트 노출 후 이전 페이지로 자동 복귀하도록 동작을 맞춘다. + +## 체크리스트 + +- [x] 콘텐츠 상세/시리즈 상세 기존 오류 처리 흐름 확인 +- [x] 콘텐츠 상세 토스트 배경색 `3bb9f1` 적용 +- [x] 시리즈 상세 로딩 실패 토스트 표시 및 자동 뒤로가기 추가 +- [x] 중복 뒤로가기 방지 및 화면 생명주기 안전 처리 적용 +- [x] 빌드/테스트 검증 및 기록 + +## 검증 기록 + +- 무엇/왜/어떻게: 콘텐츠 상세 토스트 배경색을 요청값 `3bb9f1`로 변경했다. 시리즈 상세는 기존에 로딩 실패 시 `errorMessage`/`isShowPopup`만 설정되어 화면이 유지되던 상태였기 때문에, `onChange(of: viewModel.isShowPopup)`에서 `seriesDetail == nil`인 로딩 실패 케이스를 감지해 토스트 2초 노출 뒤 `goBack()`으로 자동 복귀하도록 추가했다. 또한 `didTriggerAutoBackOnLoadFailure`와 `isViewVisible`로 중복/비가시 상태 뒤로가기 호출을 방지했다. +- 실행 도구: `lsp_diagnostics` (`ContentDetailView.swift`) + 결과: 이상 없음 +- 실행 도구: `lsp_diagnostics` (`SeriesDetailView.swift`) + 결과: SourceKit 환경에서 `No such module 'Kingfisher'` 진단 발생 +- 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build` + 결과: 성공 (`** BUILD SUCCEEDED **`) +- 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test` + 결과: 실패 (`Scheme SodaLive is not currently configured for the test action.`)