From f0829267b4490e860d5963902652ce0c6852c396 Mon Sep 17 00:00:00 2001 From: rrheon Date: Tue, 11 Aug 2026 11:33:52 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix(home):=20=EA=B4=91=EA=B3=A0=EA=B0=80=20?= =?UTF-8?q?"=EC=A7=80=EA=B8=88=20=EB=9C=A8=EB=8A=94=20=EB=B0=B0=ED=8B=80"?= =?UTF-8?q?=20=EC=9C=84=EB=A1=9C=20=EB=96=A0=EC=98=A4=EB=A5=B4=EB=8D=98=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 네이티브 광고 뷰가 배틀 섹션 조건 밖에 무조건 그려지고 있었다. 그래서 ① onAppear 도착 전 첫 프레임(스켈레톤 조건이 isLoading 만 봐서 미발동)과 ② 홈 API 실패·hotBattles 빈 응답 때 섹션만 접히면서 광고가 화면 최상단으로 올라왔다. - 광고를 "지금 뜨는 배틀" 아래로 넣고, 그 섹션이 비면 "Best 배틀" 아래로 폴백한다. if/else 라 AdFit 의 "한 화면에 같은 단위 1개" 제약은 그대로 지킨다. - 스켈레톤 조건에 !hasLoadedHome 을 더해 첫 프레임을 덮는다. - hasLoadedHome 을 성공했을 때만 세운다. 실패까지 완료로 굳히면 재진입해도 영영 재요청하지 않아 빈 홈에 갇힌다. Stage 빌드·시뮬로 히어로 → 지금뜨는배틀 → 광고 → Best배틀 순서 육안 확인. --- .../Sources/Main/Reducer/HomeFeature.swift | 4 ++- .../Home/Sources/Main/View/HomeView.swift | 26 +++++++++++++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Projects/Presentation/Home/Sources/Main/Reducer/HomeFeature.swift b/Projects/Presentation/Home/Sources/Main/Reducer/HomeFeature.swift index b054a6c..be29b05 100644 --- a/Projects/Presentation/Home/Sources/Main/Reducer/HomeFeature.swift +++ b/Projects/Presentation/Home/Sources/Main/Reducer/HomeFeature.swift @@ -249,9 +249,11 @@ extension HomeFeature { switch action { case let .homeResponse(result): state.isLoading = false - state.hasLoadedHome = true switch result { case let .success(bundle): + // 실패까지 로드 완료로 굳히면 재진입해도 영영 재요청하지 않아 + // 빈 홈(광고만 노출)에 갇힌다 — 성공했을 때만 완료로 본다. + state.hasLoadedHome = true let home = bundle.replacingEmptySectionsWithMocks state.newNotice = home.newNotice state.heroes = home.heroes diff --git a/Projects/Presentation/Home/Sources/Main/View/HomeView.swift b/Projects/Presentation/Home/Sources/Main/View/HomeView.swift index ddd7ffb..82b07d1 100644 --- a/Projects/Presentation/Home/Sources/Main/View/HomeView.swift +++ b/Projects/Presentation/Home/Sources/Main/View/HomeView.swift @@ -42,15 +42,20 @@ public struct HomeView: View { ) } + // 광고는 반드시 배틀 섹션 아래에만 노출한다 — 섹션 밖에 홀로 두면 + // 로드 전·빈 응답 때 섹션만 접혀 광고가 최상단으로 떠오른다. + // 기본 자리는 "지금 뜨는 배틀" 아래, 그 섹션이 비면 "Best 배틀" 아래로 + // 폴백한다(AdFit 은 한 화면에 같은 광고 단위 1개만 허용 — if/else 라 + // 동시에 두 개가 그려질 일은 없다). if !store.hotBattles.isEmpty { hotBattlesSection() + adSection() } - // 광고가 없으면 AdFitNativeAdView 가 스스로 접혀 높이 0 이 된다 — - // 섹션 간 spacing 32 가 두 번 겹치지 않도록 여백은 따로 주지 않는다. - AdFitNativeAdView(unit: .wide) - .frame(maxWidth: .infinity) if !store.bestBattles.isEmpty { bestBattlesSection() + if store.hotBattles.isEmpty { + adSection() + } } if !store.quizzes.isEmpty || !store.votes.isEmpty { todayPickeSection() @@ -84,7 +89,9 @@ public struct HomeView: View { extension HomeView { private var shouldShowSkeleton: Bool { - store.isLoading && + // hasLoadedHome 이 아직 false 인 첫 프레임(onAppear 도착 전)도 스켈레톤으로 + // 덮는다 — isLoading 만 보면 빈 콘텐츠 위 광고 자리만 먼저 번쩍인다. + (store.isLoading || !store.hasLoadedHome) && store.heroes.isEmpty && store.hotBattles.isEmpty && store.bestBattles.isEmpty && @@ -93,6 +100,15 @@ extension HomeView { store.newBattles.isEmpty } + /// 배틀 섹션 아래에 끼우는 네이티브 광고 한 줄. + /// 광고가 없으면 AdFitNativeAdView 가 스스로 접혀 높이 0 이 된다 — + /// 섹션 간 spacing 32 가 두 번 겹치지 않도록 여백은 따로 주지 않는다. + @ViewBuilder + private func adSection() -> some View { + AdFitNativeAdView(unit: .wide) + .frame(maxWidth: .infinity) + } + @ViewBuilder private func hotBattlesSection() -> some View { VStack(alignment: .leading, spacing: 12) { From ec7b93bcebebcce250fb5459300739f97a88b62a Mon Sep 17 00:00:00 2001 From: rrheon Date: Thu, 13 Aug 2026 13:45:01 +0900 Subject: [PATCH 2/2] =?UTF-8?q?style(home):=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=B0=98=EC=98=81=20=E2=80=94=20=EC=A7=80=EC=A0=81=EB=B0=9B?= =?UTF-8?q?=EC=9D=80=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 리뷰에서 필요 없다고 한 주석 두 곳을 지웠다. - HomeView: 광고 배치 분기 위 블록 주석 - HomeFeature: hasLoadedHome 위 설명 주석 adSection 에는 코드만 봐서는 알 수 없는 "AdFit 은 한 화면에 같은 광고 단위 1개만 허용" 제약만 한 줄 남겼다. --- .../Home/Sources/Main/Reducer/HomeFeature.swift | 2 -- .../Presentation/Home/Sources/Main/View/HomeView.swift | 9 +-------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/Projects/Presentation/Home/Sources/Main/Reducer/HomeFeature.swift b/Projects/Presentation/Home/Sources/Main/Reducer/HomeFeature.swift index be29b05..4ea8cb5 100644 --- a/Projects/Presentation/Home/Sources/Main/Reducer/HomeFeature.swift +++ b/Projects/Presentation/Home/Sources/Main/Reducer/HomeFeature.swift @@ -251,8 +251,6 @@ extension HomeFeature { state.isLoading = false switch result { case let .success(bundle): - // 실패까지 로드 완료로 굳히면 재진입해도 영영 재요청하지 않아 - // 빈 홈(광고만 노출)에 갇힌다 — 성공했을 때만 완료로 본다. state.hasLoadedHome = true let home = bundle.replacingEmptySectionsWithMocks state.newNotice = home.newNotice diff --git a/Projects/Presentation/Home/Sources/Main/View/HomeView.swift b/Projects/Presentation/Home/Sources/Main/View/HomeView.swift index 82b07d1..e3a63b7 100644 --- a/Projects/Presentation/Home/Sources/Main/View/HomeView.swift +++ b/Projects/Presentation/Home/Sources/Main/View/HomeView.swift @@ -42,11 +42,6 @@ public struct HomeView: View { ) } - // 광고는 반드시 배틀 섹션 아래에만 노출한다 — 섹션 밖에 홀로 두면 - // 로드 전·빈 응답 때 섹션만 접혀 광고가 최상단으로 떠오른다. - // 기본 자리는 "지금 뜨는 배틀" 아래, 그 섹션이 비면 "Best 배틀" 아래로 - // 폴백한다(AdFit 은 한 화면에 같은 광고 단위 1개만 허용 — if/else 라 - // 동시에 두 개가 그려질 일은 없다). if !store.hotBattles.isEmpty { hotBattlesSection() adSection() @@ -100,9 +95,7 @@ extension HomeView { store.newBattles.isEmpty } - /// 배틀 섹션 아래에 끼우는 네이티브 광고 한 줄. - /// 광고가 없으면 AdFitNativeAdView 가 스스로 접혀 높이 0 이 된다 — - /// 섹션 간 spacing 32 가 두 번 겹치지 않도록 여백은 따로 주지 않는다. + /// 배틀 섹션 아래 네이티브 광고 — AdFit 은 한 화면에 같은 단위 1개만 허용해 호출부에서 하나만 그린다. @ViewBuilder private func adSection() -> some View { AdFitNativeAdView(unit: .wide)