From e7f36d96c833c51aaaeb552235af4fdd54818b9b Mon Sep 17 00:00:00 2001 From: Jini Date: Mon, 9 Feb 2026 15:45:50 +0900 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20=EB=AD=89=EC=B9=98=20=ED=8E=B8?= =?UTF-8?q?=EC=A7=91=20=EA=B4=80=EB=A0=A8=20=EB=AC=B4=ED=95=9C=EB=A1=9C?= =?UTF-8?q?=EB=94=A9=20=ED=98=84=EC=83=81=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Views/Detail/BundleDetailView.swift | 37 +++++++++--- .../Bundle/Views/Edit/BundleEditView.swift | 4 ++ .../Views/Edit/BundleNameEditView.swift | 60 +++++++++++++++---- 3 files changed, 84 insertions(+), 17 deletions(-) diff --git a/Keychy/Keychy/Presentation/Bundle/Views/Detail/BundleDetailView.swift b/Keychy/Keychy/Presentation/Bundle/Views/Detail/BundleDetailView.swift index a9d2b55d..85ab507d 100644 --- a/Keychy/Keychy/Presentation/Bundle/Views/Detail/BundleDetailView.swift +++ b/Keychy/Keychy/Presentation/Bundle/Views/Detail/BundleDetailView.swift @@ -90,12 +90,6 @@ struct BundleDetailView: View { withAnimation(.easeOut(duration: 0.3)) { isSceneReady = true } - // 마지막으로 로드한 구성 id를 뷰모델에 저장 (뷰모델이 다음 진입 시 동일 구성 판정) - bundleVM.updateLastConfigIds( - background: bundleVM.selectedBackground, - carabiner: bundleVM.selectedCarabiner, - keyringDataList: keyringDataList - ) } } } @@ -222,6 +216,17 @@ extension BundleDetailView { let newKeyringDataList = await bundleVM.createKeyringDataList(bundle: bundle, carabiner: carabiner) keyringDataList = newKeyringDataList + // 항상 lastConfigIds 업데이트 + let bgId = bundleVM.makeBackgroundId(bundleVM.selectedBackground) + let cbId = bundleVM.makeCarabinerId(bundleVM.selectedCarabiner) + let krId = bundleVM.makeKeyringsId(newKeyringDataList) + + bundleVM.lastBackgroundIdForDetail = bgId + bundleVM.lastCarabinerIdForDetail = cbId + bundleVM.lastKeyringsIdForDetail = krId + + sceneReloadTrigger = UUID() + // 키링 데이터까지 불러오고 난 후에도 키링의 개수가 0개라면 바로 씬을 준비 완료 상태로 체크 if newKeyringDataList.isEmpty { isSceneReady = true @@ -289,12 +294,17 @@ extension BundleDetailView { @MainActor private func handleBundleChange() async { guard let bundle = bundleVM.selectedBundle else { return } + // 동일 구성인지 확인(+변경 감지) - if bundleVM.shouldSkipReloadForReturnedConfig() { + let shouldSkip = bundleVM.shouldSkipReloadForReturnedConfig() + + if shouldSkip { restoreSceneIfNeeded(bundle) isSceneReady = true return } + + // 리로드 진행 - loadBundleData 호출 isSceneReady = false readyDelayTask?.cancel() readyDelayTask = nil @@ -312,6 +322,19 @@ extension BundleDetailView { if bundleVM.selectedCarabiner == nil { bundleVM.selectedCarabiner = bundleVM.resolveCarabiner(from: bundle.selectedCarabiner) } + + // keyringDataList도 복원 (NameEditView에서 돌아올 때 필요) + if keyringDataList.isEmpty, let carabiner = bundleVM.selectedCarabiner { + Task { + let newKeyringDataList = await bundleVM.createKeyringDataList(bundle: bundle, carabiner: carabiner) + await MainActor.run { + keyringDataList = newKeyringDataList + isSceneReady = true + } + } + return + } + readyDelayTask?.cancel() readyDelayTask = nil isSceneReady = true diff --git a/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleEditView.swift b/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleEditView.swift index 6e0075c5..4836d09d 100644 --- a/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleEditView.swift +++ b/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleEditView.swift @@ -246,6 +246,10 @@ extension BundleEditView { private var customNavigationBar: some View { CustomNavigationBar { BackToolbarButton { + bundleVM.lastBackgroundIdForDetail = "" + bundleVM.lastCarabinerIdForDetail = "" + bundleVM.lastKeyringsIdForDetail = "" + isNavigatingAway = true router.pop() } diff --git a/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleNameEditView.swift b/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleNameEditView.swift index 06ab3bbf..6070d68d 100644 --- a/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleNameEditView.swift +++ b/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleNameEditView.swift @@ -54,6 +54,16 @@ struct BundleNameEditView: View { morePadding = 40 } TabBarManager.hide() + + Task { + await withCheckedContinuation { (continuation: CheckedContinuation) in + bundleVM.fetchAllBackgrounds { _ in + bundleVM.fetchAllCarabiners { _ in + continuation.resume() + } + } + } + } } // 키보드 높이 변경 시 SwiftUI 애니메이션 비활성화 .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { notification in @@ -150,7 +160,36 @@ extension BundleNameEditView { private var customNavigationBar: some View { CustomNavigationBar { BackToolbarButton { - router.pop() + + if let bundle = bundleVM.selectedBundle { + // resolveBackground/Carabiner가 nil을 반환할 수 있으므로 확인 + guard let bg = bundleVM.resolveBackground(from: bundle.selectedBackground), + let cb = bundleVM.resolveCarabiner(from: bundle.selectedCarabiner) else { + router.pop() + return + } + + let bgId = bundleVM.makeBackgroundId(bg) + let cbId = bundleVM.makeCarabinerId(cb) + + bundleVM.returnBackgroundId = bgId + bundleVM.returnCarabinerId = cbId + + Task { + var krList: [MultiKeyringScene.KeyringData] = [] + krList = await bundleVM.createKeyringDataList(bundle: bundle, carabiner: cb) + let krId = bundleVM.makeKeyringsId(krList) + + await MainActor.run { + bundleVM.returnKeyringsId = krId + // returnIds 설정 완료, pop + router.pop() + } + } + } else { + // bundle이 nil, 바로 pop + router.pop() + } } } center: { EmptyView() @@ -182,8 +221,8 @@ extension BundleNameEditView { let bgId = bundleVM.makeBackgroundId(bg) let cbId = bundleVM.makeCarabinerId(cb) - // keyringsId는 현재 번들의 구성 기반으로 재구성 Task { + // returnIds 먼저 설정 var krList: [MultiKeyringScene.KeyringData] = [] if let carabiner = cb { krList = await bundleVM.createKeyringDataList(bundle: bundle, carabiner: carabiner) @@ -195,14 +234,15 @@ extension BundleNameEditView { bundleVM.returnCarabinerId = cbId bundleVM.returnKeyringsId = krId } - } - - bundleVM.updateBundleName(bundle: bundle, newName: bundleName.trimmingCharacters(in: .whitespacesAndNewlines)) { success in - DispatchQueue.main.async { - self.isUpdating = false - if success { - bundleVM.selectedBundle?.name = self.bundleName.trimmingCharacters(in: .whitespacesAndNewlines) - router.pop() + + // returnIds 설정 완료 후 이름 업데이트 + bundleVM.updateBundleName(bundle: bundle, newName: bundleName.trimmingCharacters(in: .whitespacesAndNewlines)) { success in + DispatchQueue.main.async { + self.isUpdating = false + if success { + bundleVM.selectedBundle?.name = self.bundleName.trimmingCharacters(in: .whitespacesAndNewlines) + router.pop() + } } } } From fe5be659534476c1492f938e3b7de4396c11a615 Mon Sep 17 00:00:00 2001 From: Jini Date: Mon, 9 Feb 2026 15:46:31 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=EB=A6=AC=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=ED=8A=B8=EB=A6=AC=EA=B1=B0=20=EC=9A=A9=20id=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Bundle/Views/Detail/BundleDetailView.swift | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Keychy/Keychy/Presentation/Bundle/Views/Detail/BundleDetailView.swift b/Keychy/Keychy/Presentation/Bundle/Views/Detail/BundleDetailView.swift index 85ab507d..c450f4d1 100644 --- a/Keychy/Keychy/Presentation/Bundle/Views/Detail/BundleDetailView.swift +++ b/Keychy/Keychy/Presentation/Bundle/Views/Detail/BundleDetailView.swift @@ -43,6 +43,7 @@ struct BundleDetailView: View { @State var isNavigatingDeeper: Bool = true @State private var dismissTask: Task? @State private var readyDelayTask: Task? + @State private var sceneReloadTrigger = UUID() /// MultiKeyringScene에 전달할 키링 데이터 리스트 @State var keyringDataList: [MultiKeyringScene.KeyringData] = [] @@ -97,14 +98,7 @@ struct BundleDetailView: View { ) .animation(.easeInOut(duration: 0.3), value: isSceneReady) /// 씬 재생성 조건을 위한 ID 설정 -> 배경, 카라비너, 키링 구성이 변경되면 씬을 완전히 재생성 - .id("scene_\(background.id ?? "")_\(carabiner.id ?? "")_\(keyringDataList.map { "\($0.index)_\($0.bodyImageURL.hashValue)" }.joined(separator: "_"))") - .onAppear { - bundleVM.returnBackgroundId = bundle.selectedBackground - bundleVM.returnCarabinerId = bundle.selectedCarabiner - bundleVM.returnKeyringsId = bundle.keyrings - .sorted() - .joined(separator: "|") - } + .id("scene_\(background.id ?? "")_\(carabiner.id ?? "")_\(keyringDataList.map { "\($0.index)_\($0.bodyImageURL.hashValue)" }.joined(separator: "_"))_\(sceneReloadTrigger.uuidString)") VStack { Spacer() From 9105c2ce8a359cbfb06cc15c1b98dd853e0dfba4 Mon Sep 17 00:00:00 2001 From: Jini Date: Mon, 9 Feb 2026 16:09:35 +0900 Subject: [PATCH 3/4] =?UTF-8?q?style:=20=EB=AD=89=EC=B9=98=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EB=B3=80=EA=B2=BD=20=EC=99=84=EB=A3=8C=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Views/Edit/BundleNameEditView.swift | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleNameEditView.swift b/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleNameEditView.swift index 6070d68d..8a1367bb 100644 --- a/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleNameEditView.swift +++ b/Keychy/Keychy/Presentation/Bundle/Views/Edit/BundleNameEditView.swift @@ -194,11 +194,27 @@ extension BundleNameEditView { } center: { EmptyView() } trailing: { - NextToolbarButton { - handleCheckButtonTap() - } - .disabled(isUpdating || bundleName.isEmpty || bundleName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || hasProfanity) + completeButton + } + } + + private var completeButton: some View { + let isCompleteEnabled = !isUpdating && + !bundleName.isEmpty && + !bundleName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && + !hasProfanity + + return Button { + guard isCompleteEnabled else { return } + handleCheckButtonTap() + } label: { + Text("완료") + .typography(.suit17B) + .foregroundStyle(isCompleteEnabled ? .main500 : .gray200) } + .frame(width: 62, height: 44) + .glassEffect(.regular.interactive(), in: .capsule) + .disabled(!isCompleteEnabled) } private func handleCheckButtonTap() { From 42cdb26a5d33e3b970494957a496cdfe06873caf Mon Sep 17 00:00:00 2001 From: Jini Date: Mon, 9 Feb 2026 16:35:42 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20.id=20=EB=AC=B8=EC=9E=90=EC=97=B4=20?= =?UTF-8?q?=EA=B0=80=EB=8F=85=EC=84=B1=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Bundle/Views/Detail/BundleDetailView.swift | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Keychy/Keychy/Presentation/Bundle/Views/Detail/BundleDetailView.swift b/Keychy/Keychy/Presentation/Bundle/Views/Detail/BundleDetailView.swift index c450f4d1..231ac50b 100644 --- a/Keychy/Keychy/Presentation/Bundle/Views/Detail/BundleDetailView.swift +++ b/Keychy/Keychy/Presentation/Bundle/Views/Detail/BundleDetailView.swift @@ -98,7 +98,7 @@ struct BundleDetailView: View { ) .animation(.easeInOut(duration: 0.3), value: isSceneReady) /// 씬 재생성 조건을 위한 ID 설정 -> 배경, 카라비너, 키링 구성이 변경되면 씬을 완전히 재생성 - .id("scene_\(background.id ?? "")_\(carabiner.id ?? "")_\(keyringDataList.map { "\($0.index)_\($0.bodyImageURL.hashValue)" }.joined(separator: "_"))_\(sceneReloadTrigger.uuidString)") + .id(sceneId(background: background, carabiner: carabiner)) VStack { Spacer() @@ -346,6 +346,19 @@ extension BundleDetailView { } .joined(separator: ";") } + + private func sceneId( + background: Background, + carabiner: Carabiner + ) -> String { + let bgId = background.id ?? "" + let cbId = carabiner.id ?? "" + let krIds = keyringDataList + .map { "\($0.index)_\($0.bodyImageURL.hashValue)" } + .joined(separator: "_") + + return "scene_\(bgId)_\(cbId)_\(krIds)_\(sceneReloadTrigger.uuidString)" + } }