From 1f1b1395e7c55fff31912a10e294a012c663f237 Mon Sep 17 00:00:00 2001 From: jihun Date: Mon, 1 Jun 2026 15:57:10 +0900 Subject: [PATCH 01/16] =?UTF-8?q?fix:=20=EC=88=98=EC=A0=95=EC=A4=91?= =?UTF-8?q?=EC=9D=BC=20=EB=95=8C=20=EC=B9=B4=EB=93=9C=20=EC=9D=B8=ED=84=B0?= =?UTF-8?q?=EB=9E=99=EC=85=98=20=EB=A7=89=EC=9D=8C=20-=20#330?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift index 8575065a..87779177 100644 --- a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift +++ b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift @@ -149,6 +149,7 @@ private extension GoalDetailView { .gesture( DragGesture() .onChanged { value in + guard !store.isEditing else { return } let translation = value.translation let width = resistedDragWidth( for: translation.width, @@ -169,6 +170,7 @@ private extension GoalDetailView { isCrossingDuringDrag = shouldCrossCards(for: width) } .onEnded { _ in + guard !store.isEditing else { return } withAnimation(.spring(response: 0.2, dampingFraction: 0.94)) { resetDragState() store.send(.view(.cardSwiped)) From ae5f3b56ccacaeaf8b5b1284d09a9db6fa10384e Mon Sep 17 00:00:00 2001 From: jihun Date: Mon, 1 Jun 2026 16:30:51 +0900 Subject: [PATCH 02/16] =?UTF-8?q?fix:=20=EB=82=98,=20=EC=83=81=EB=8C=80?= =?UTF-8?q?=EB=B0=A9=20=EC=BD=94=EB=A9=98=ED=8A=B8=20=EB=91=98=20=EB=8B=A4?= =?UTF-8?q?=20=EC=9E=88=EC=9D=84=20=EB=95=8C=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EC=95=88=EB=90=98=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Detail/GoalDetailView.swift | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift index 87779177..85662fba 100644 --- a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift +++ b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift @@ -274,7 +274,8 @@ private extension GoalDetailView { imageData: imageData, imageURL: imageURL, comment: comment, - showsMyEmoji: showsMyEmoji + showsMyEmoji: showsMyEmoji, + isFront: isFront ) .opacity(isFront ? 1 : 0) } @@ -286,10 +287,12 @@ private extension GoalDetailView { imageData: Data?, imageURL: String?, comment: String, - showsMyEmoji: Bool + showsMyEmoji: Bool, + isFront: Bool ) -> some View { if isCompleted { completedImageCard( + isFront: isFront, imageData: imageData, imageURL: imageURL, comment: comment, @@ -325,6 +328,7 @@ private extension GoalDetailView { @ViewBuilder func completedImageCard( + isFront: Bool, imageData: Data?, imageURL: String?, comment: String, @@ -332,14 +336,22 @@ private extension GoalDetailView { ) -> some View { if let imageData, let editedImage = UIImage(data: imageData) { - completedImageCardContainer(comment: comment, showsMyEmoji: showsMyEmoji) { + completedImageCardContainer( + comment: comment, + showsMyEmoji: showsMyEmoji, + isFront: isFront + ) { Image(uiImage: editedImage) .resizable() .scaledToFill() } } else if let imageURL, let url = URL(string: imageURL) { - completedImageCardContainer(comment: comment, showsMyEmoji: showsMyEmoji) { + completedImageCardContainer( + comment: comment, + showsMyEmoji: showsMyEmoji, + isFront: isFront + ) { KFImage(url) .resizable() .scaledToFill() @@ -406,6 +418,7 @@ private extension GoalDetailView { func completedImageCardContainer( comment: String, showsMyEmoji: Bool, + isFront: Bool, @ViewBuilder content: @escaping () -> Content ) -> some View { let shape = RoundedRectangle(cornerRadius: 20) @@ -421,7 +434,7 @@ private extension GoalDetailView { .overlay(dimmedView) .clipShape(shape) .overlay(alignment: .bottom) { - if !comment.isEmpty { + if isFront, !comment.isEmpty { commentCircle(comment: comment) .padding(.bottom, 26) } From 8a5d088897f550ef1c43cd528afc1ae5a7302e43 Mon Sep 17 00:00:00 2001 From: jihun Date: Mon, 1 Jun 2026 19:49:07 +0900 Subject: [PATCH 03/16] =?UTF-8?q?fix:=20TXCommentCircle=20safeAreaPadding?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0=20-=20#330?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/ProofPhoto/ProofPhotoView.swift | 3 +-- .../Components/TextField/TXCommentCircle.swift | 12 +----------- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/Projects/Feature/ProofPhoto/Sources/ProofPhoto/ProofPhotoView.swift b/Projects/Feature/ProofPhoto/Sources/ProofPhoto/ProofPhotoView.swift index 19d70e8b..25066860 100644 --- a/Projects/Feature/ProofPhoto/Sources/ProofPhoto/ProofPhotoView.swift +++ b/Projects/Feature/ProofPhoto/Sources/ProofPhoto/ProofPhotoView.swift @@ -347,7 +347,7 @@ private extension ProofPhotoView { } .padding(.bottom, 28) .frame(width: rectFrame.width, height: rectFrame.height, alignment: .bottom) - .offset(x: posX, y: posY) + .offset(x: posX, y: posY - keyboardInset) .animation(.easeOut(duration: 0.25), value: keyboardInset) } } @@ -362,7 +362,6 @@ private extension ProofPhotoView { TXCommentCircle( commentText: $store.commentText, isEditable: true, - keyboardInset: keyboardInset, isFocused: $store.isCommentFocused, onFocused: { isFocused in store.send(.view(.focusChanged(isFocused))) diff --git a/Projects/Shared/DesignSystem/Sources/Components/TextField/TXCommentCircle.swift b/Projects/Shared/DesignSystem/Sources/Components/TextField/TXCommentCircle.swift index c0f633e3..ab83ae44 100644 --- a/Projects/Shared/DesignSystem/Sources/Components/TextField/TXCommentCircle.swift +++ b/Projects/Shared/DesignSystem/Sources/Components/TextField/TXCommentCircle.swift @@ -12,7 +12,6 @@ public struct TXCommentCircle: View { @Binding private var commentText: String @FocusState private var isFocused: Bool private let isEditable: Bool - private let keyboardInset: CGFloat private let externalFocus: Binding? public var onFocused: ((Bool) -> Void)? @@ -28,13 +27,11 @@ public struct TXCommentCircle: View { public init( commentText: Binding, isEditable: Bool, - keyboardInset: CGFloat, isFocused: Binding? = nil, onFocused: ((Bool) -> Void)? = nil ) { self._commentText = commentText self.isEditable = isEditable - self.keyboardInset = keyboardInset self.externalFocus = isFocused self.onFocused = onFocused } @@ -55,12 +52,6 @@ public struct TXCommentCircle: View { commentText = String(commentText.prefix(Constants.maxCount)) } } - .safeAreaInset(edge: .bottom) { - if isFocused { - Color.clear - .frame(height: keyboardInset) - } - } .onChange(of: isFocused) { onFocused?(isFocused) externalFocus?.wrappedValue = isFocused @@ -189,7 +180,6 @@ private struct PositionedCircleShape: Shape { @Previewable @State var text: String = "" TXCommentCircle( commentText: $text, - isEditable: true, - keyboardInset: .zero + isEditable: true ) } From f7fe5c3a34eceff96e4614d6bcc01325b4409534 Mon Sep 17 00:00:00 2001 From: jihun Date: Mon, 1 Jun 2026 19:49:40 +0900 Subject: [PATCH 04/16] =?UTF-8?q?fix:=20=ED=82=A4=EB=B3=B4=EB=93=9C=20?= =?UTF-8?q?=EC=98=AC=EB=9D=BC=EC=98=AC=20=EB=95=8C=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?View=20=EB=B0=80=EB=A6=AC=EB=8A=94=20=EC=9D=B4=EC=8A=88=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20#330?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Detail/GoalDetailView.swift | 133 ++++++++++-------- 1 file changed, 76 insertions(+), 57 deletions(-) diff --git a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift index 85662fba..3c1d42fd 100644 --- a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift +++ b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift @@ -62,31 +62,23 @@ public struct GoalDetailView: View { } public var body: some View { - VStack(spacing: 0) { - navigationBar - .zIndex(1) - - if store.item != nil { - cardView - .padding(.horizontal, 27) - .padding(.top, isSEDevice ? 47 : 103) - - if store.isCompleted { - completedBottomContent - } else if store.currentCompletedGoal?.status != .completed { - bottomButton - .padding(.top, 105) - .overlay(alignment: .bottomLeading) { - pokeImage - .offset(x: 79, y: -45) - } + GeometryReader { _ in + ZStack { + mainContent + + if store.isEditing && store.isCommentFocused { + dimmedView + .ignoresSafeArea() + } + + if shouldShowCommentOverlay { + floatingCommentOverlay } } - - Spacer() + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) } .ignoresSafeArea(.keyboard) - .background(dimmedView) + .background(Color.Common.white) .toolbar(.hidden, for: .navigationBar) .observeKeyboardFrame($keyboardFrame) .onAppear { @@ -121,6 +113,32 @@ public struct GoalDetailView: View { // MARK: - SubViews private extension GoalDetailView { + var mainContent: some View { + VStack(spacing: 0) { + navigationBar + .zIndex(1) + + if store.item != nil { + cardView + .padding(.horizontal, 27) + .padding(.top, isSEDevice ? 47 : 103) + + if store.isCompleted { + completedBottomContent + } else if store.currentCompletedGoal?.status != .completed { + bottomButton + .padding(.top, 105) + .overlay(alignment: .bottomLeading) { + pokeImage + .offset(x: 79, y: -45) + } + } + } + + Spacer() + } + } + var navigationBar: some View { TXNavigationBar( style: .subContent( @@ -135,7 +153,6 @@ private extension GoalDetailView { store.send(.view(.navigationBarTapped(action))) } ) - .overlay(dimmedView) } var cardView: some View { @@ -186,7 +203,6 @@ private extension GoalDetailView { isCompleted: store.myCardIsCompleted, imageData: store.pendingEditedImageData, imageURL: store.myCard?.imageUrl, - comment: store.myCard?.comment ?? "", showsMyEmoji: effectiveIsFrontMyCard && store.selectedReactionEmoji != nil ) .offset(x: cardOffset * (effectiveIsFrontMyCard ? 1 : -1)) @@ -199,7 +215,6 @@ private extension GoalDetailView { isCompleted: store.partnerCardIsCompleted, imageData: nil, imageURL: store.partnerCard?.imageUrl, - comment: store.partnerCard?.comment ?? "", showsMyEmoji: false ) .offset(x: cardOffset * (effectiveIsFrontMyCard ? -1 : 1)) @@ -252,7 +267,6 @@ private extension GoalDetailView { lineWidth: 1.6 ) .frame(width: 336, height: 336) - .overlay(dimmedView) .clipShape(shape) } @@ -262,7 +276,6 @@ private extension GoalDetailView { isCompleted: Bool, imageData: Data?, imageURL: String?, - comment: String, showsMyEmoji: Bool ) -> some View { ZStack { @@ -273,9 +286,7 @@ private extension GoalDetailView { isCompleted: isCompleted, imageData: imageData, imageURL: imageURL, - comment: comment, - showsMyEmoji: showsMyEmoji, - isFront: isFront + showsMyEmoji: showsMyEmoji ) .opacity(isFront ? 1 : 0) } @@ -286,16 +297,12 @@ private extension GoalDetailView { isCompleted: Bool, imageData: Data?, imageURL: String?, - comment: String, - showsMyEmoji: Bool, - isFront: Bool + showsMyEmoji: Bool ) -> some View { if isCompleted { completedImageCard( - isFront: isFront, imageData: imageData, imageURL: imageURL, - comment: comment, showsMyEmoji: showsMyEmoji ) } else { @@ -323,23 +330,18 @@ private extension GoalDetailView { shape: shape, lineWidth: 1.6 ) - .overlay(dimmedView) } @ViewBuilder func completedImageCard( - isFront: Bool, imageData: Data?, imageURL: String?, - comment: String, showsMyEmoji: Bool ) -> some View { if let imageData, let editedImage = UIImage(data: imageData) { completedImageCardContainer( - comment: comment, - showsMyEmoji: showsMyEmoji, - isFront: isFront + showsMyEmoji: showsMyEmoji ) { Image(uiImage: editedImage) .resizable() @@ -348,9 +350,7 @@ private extension GoalDetailView { } else if let imageURL, let url = URL(string: imageURL) { completedImageCardContainer( - comment: comment, - showsMyEmoji: showsMyEmoji, - isFront: isFront + showsMyEmoji: showsMyEmoji ) { KFImage(url) .resizable() @@ -387,22 +387,46 @@ private extension GoalDetailView { ) .perfControl(slug: "goal-detail", element: "primary-cta") } - + @ViewBuilder func commentCircle(comment: String) -> some View { - let keyboardInset = max(0, rectFrame.maxY - keyboardFrame.minY) TXCommentCircle( commentText: store.isEditing ? $store.commentText : .constant(comment), isEditable: store.isEditing, - keyboardInset: keyboardInset, isFocused: $store.isCommentFocused, onFocused: { isFocused in store.send(.view(.focusChanged(isFocused))) } ) - .animation(.easeOut(duration: 0.25), value: keyboardInset) } - + + var shouldShowCommentOverlay: Bool { + guard store.isCompleted, rectFrame != .zero else { return false } + return store.isEditing || !currentFrontComment.isEmpty + } + + var currentFrontComment: String { + if effectiveIsFrontMyCard { + return store.myCard?.comment ?? "" + } else { + return store.partnerCard?.comment ?? "" + } + } + + var floatingCommentOverlay: some View { + GeometryReader { rootGeo in + let rootFrame = rootGeo.frame(in: .global) + let posX = rectFrame.minX - rootFrame.minX + let posY = rectFrame.minY - rootFrame.minY + + commentCircle(comment: currentFrontComment) + .padding(.bottom, 26) + .frame(width: rectFrame.width, height: rectFrame.height, alignment: .bottom) + .offset(x: posX, y: posY - keyboardInset) + .animation(.easeOut(duration: 0.25), value: keyboardInset) + } + } + var dimmedView: some View { Color.Dimmed.dimmed70 .opacity(store.isEditing && store.isCommentFocused ? 1 : 0) @@ -416,9 +440,7 @@ private extension GoalDetailView { } func completedImageCardContainer( - comment: String, showsMyEmoji: Bool, - isFront: Bool, @ViewBuilder content: @escaping () -> Content ) -> some View { let shape = RoundedRectangle(cornerRadius: 20) @@ -431,14 +453,7 @@ private extension GoalDetailView { .frame(maxWidth: .infinity, maxHeight: .infinity) .clipped() } - .overlay(dimmedView) .clipShape(shape) - .overlay(alignment: .bottom) { - if isFront, !comment.isEmpty { - commentCircle(comment: comment) - .padding(.bottom, 26) - } - } .insideBorder( Color.Gray.gray500, shape: shape, @@ -515,6 +530,10 @@ private extension GoalDetailView { isCrossingDuringDrag ? !store.isFrontMyCard : store.isFrontMyCard } + var keyboardInset: CGFloat { + max(0, rectFrame.maxY - keyboardFrame.minY) + } + func repeatedCardOffset(for width: CGFloat) -> CGFloat { let maxOffset = Constants.maxCardOffset let direction: CGFloat = width >= 0 ? 1 : -1 From bbc04f2dac9b578f3beece2783a759d9056c3db0 Mon Sep 17 00:00:00 2001 From: jihun Date: Mon, 1 Jun 2026 20:33:31 +0900 Subject: [PATCH 05/16] =?UTF-8?q?fix:=20=EC=B0=8C=EB=A5=B4=EA=B8=B0=20disa?= =?UTF-8?q?ble=EC=83=81=ED=83=9C=EC=9D=BC=20=EB=95=8C=20=EB=88=8C=EB=9F=AC?= =?UTF-8?q?=EB=8F=84=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20=EB=82=98=EC=98=A4?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20-=20#331?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Components/Button/Round/TXRoundButton.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Projects/Shared/DesignSystem/Sources/Components/Button/Round/TXRoundButton.swift b/Projects/Shared/DesignSystem/Sources/Components/Button/Round/TXRoundButton.swift index cbcebbb3..ea895461 100644 --- a/Projects/Shared/DesignSystem/Sources/Components/Button/Round/TXRoundButton.swift +++ b/Projects/Shared/DesignSystem/Sources/Components/Button/Round/TXRoundButton.swift @@ -14,9 +14,7 @@ struct TXRoundButton: View { public var body: some View { if case let .round(style, size, state) = shape { Button { - if state != .disabled { - onTap() - } + onTap() } label: { ZStack { Capsule() From e08105f702c72b9a91149273002bd097bba77a05 Mon Sep 17 00:00:00 2001 From: jihun Date: Mon, 1 Jun 2026 20:51:30 +0900 Subject: [PATCH 06/16] =?UTF-8?q?fix:=20=ED=86=B5=EA=B3=84=20=ED=83=AD=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=ED=84=B0=EC=B9=98=20=EB=B2=94=EC=9C=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20#332?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DesignSystem/Sources/Components/Tab/TopBar/TXTopTabBar.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Projects/Shared/DesignSystem/Sources/Components/Tab/TopBar/TXTopTabBar.swift b/Projects/Shared/DesignSystem/Sources/Components/Tab/TopBar/TXTopTabBar.swift index 7eddf617..077a57dd 100644 --- a/Projects/Shared/DesignSystem/Sources/Components/Tab/TopBar/TXTopTabBar.swift +++ b/Projects/Shared/DesignSystem/Sources/Components/Tab/TopBar/TXTopTabBar.swift @@ -43,7 +43,6 @@ struct TXTopTabBar: View { } label: { tabItem(item: item, isSelected: selectedItem == item) } - .buttonStyle(.plain) .frame(maxWidth: .infinity) } } From b856600fec36447866c5709170b4d4146a87e97a Mon Sep 17 00:00:00 2001 From: jihun Date: Mon, 1 Jun 2026 21:03:12 +0900 Subject: [PATCH 07/16] =?UTF-8?q?fix:=20=EB=AA=A9=ED=91=9C=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=9D=B4=EB=AA=A8=EC=A7=80=20=EB=B2=84=ED=8A=BC,?= =?UTF-8?q?=20=EB=93=9C=EB=A1=AD=EB=8B=A4=EC=9A=B4=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=ED=84=B0=EC=B9=98=20=EB=B2=94=EC=9C=84=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?-=20#333?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/MakeGoal/Sources/MakeGoalView.swift | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Projects/Feature/MakeGoal/Sources/MakeGoalView.swift b/Projects/Feature/MakeGoal/Sources/MakeGoalView.swift index fd20682d..147d9888 100644 --- a/Projects/Feature/MakeGoal/Sources/MakeGoalView.swift +++ b/Projects/Feature/MakeGoal/Sources/MakeGoalView.swift @@ -122,7 +122,7 @@ private extension MakeGoalView { backgroundColor: Color.Common.white ) ), - onTap: { } + onTap: { store.send(.view(.emojiButtonTapped)) } ) .insideBorder( Color.Gray.gray500, @@ -177,7 +177,6 @@ private extension MakeGoalView { Spacer() if store.showPeriodCount { - valueText(store.periodCountText) dropDownButton { store.send(.view(.periodSelected)) } } } @@ -191,7 +190,6 @@ private extension MakeGoalView { Spacer() - valueText(store.startDateText) dropDownButton { store.send(.view(.startDateTapped)) } } .frame(height: 32) @@ -216,7 +214,6 @@ private extension MakeGoalView { Spacer() - valueText(store.endDateText) dropDownButton { store.send(.view(.endDateTapped)) } } .padding(.vertical, 21.5) @@ -240,11 +237,15 @@ private extension MakeGoalView { } func dropDownButton(_ action: @escaping () -> Void) -> some View { - Button { - action() - } label: { + HStack(spacing: 0) { + Text(store.startDateText) + .typography(.b2_14r) + .foregroundStyle(Color.Gray.gray500) Image.Icon.Symbol.arrow2Down } + .onTapGesture { + action() + } } func sectionTitleText(_ text: String) -> some View { From 32f9af788323bd7c764e391dccc147563d23e586 Mon Sep 17 00:00:00 2001 From: jihun Date: Mon, 1 Jun 2026 21:14:25 +0900 Subject: [PATCH 08/16] =?UTF-8?q?refactor:=20TXTabBarHeight=20=EC=9E=AC?= =?UTF-8?q?=EC=83=A4=EC=9A=A9=EC=9D=84=20=EC=9C=84=ED=95=B4=20Shared?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC=20-=20#334?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/MainTab/Sources/View/MainTabView.swift | 10 ++-------- .../Sources/Components/Bar/TabBar/TXTabBar.swift | 3 +-- .../Components/Bar/TabBar/TXTabBarLayout.swift | 13 +++++++++++++ 3 files changed, 16 insertions(+), 10 deletions(-) create mode 100644 Projects/Shared/DesignSystem/Sources/Components/Bar/TabBar/TXTabBarLayout.swift diff --git a/Projects/Feature/MainTab/Sources/View/MainTabView.swift b/Projects/Feature/MainTab/Sources/View/MainTabView.swift index 7a0dffcc..cd0c8616 100644 --- a/Projects/Feature/MainTab/Sources/View/MainTabView.swift +++ b/Projects/Feature/MainTab/Sources/View/MainTabView.swift @@ -64,7 +64,7 @@ public struct MainTabView: View { } .txToast( item: $store.home.home.presentation.toast, - customPadding: Constants.tabBarHeight + customPadding: TXTabBarLayout.height ) .txLoading(isPresented: isTabLoading) } @@ -94,13 +94,7 @@ private extension MainTabView { ) .shadow(color: .black.opacity(0.16), radius: 20, x: 2, y: 1) .padding(.trailing, 16) - .padding(.bottom, 12 + Constants.tabBarHeight) - } -} - -private extension MainTabView { - enum Constants { - static let tabBarHeight: CGFloat = 58 + .padding(.bottom, 12 + TXTabBarLayout.height) } } diff --git a/Projects/Shared/DesignSystem/Sources/Components/Bar/TabBar/TXTabBar.swift b/Projects/Shared/DesignSystem/Sources/Components/Bar/TabBar/TXTabBar.swift index cf35de20..2fb422d9 100644 --- a/Projects/Shared/DesignSystem/Sources/Components/Bar/TabBar/TXTabBar.swift +++ b/Projects/Shared/DesignSystem/Sources/Components/Bar/TabBar/TXTabBar.swift @@ -23,7 +23,7 @@ struct TXTabBar: View { tabItemView(item: item) } } - .frame(height: Constants.tabBarHeight) + .frame(height: TXTabBarLayout.height) .background(Constants.backgroundColor) .insideRectEdgeBorder( width: Constants.borderWidth, @@ -60,7 +60,6 @@ private extension TXTabBar { // MARK: - Constants private extension TXTabBar { enum Constants { - static let tabBarHeight: CGFloat = 58 static let iconSize: CGFloat = 24 static let iconLabelSpacing: CGFloat = 4 static let topPadding: CGFloat = 12 diff --git a/Projects/Shared/DesignSystem/Sources/Components/Bar/TabBar/TXTabBarLayout.swift b/Projects/Shared/DesignSystem/Sources/Components/Bar/TabBar/TXTabBarLayout.swift new file mode 100644 index 00000000..d2f29d64 --- /dev/null +++ b/Projects/Shared/DesignSystem/Sources/Components/Bar/TabBar/TXTabBarLayout.swift @@ -0,0 +1,13 @@ +// +// TXTabBarLayout.swift +// SharedDesignSystem +// +// Created by 정지훈 on 6/1/26. +// + +import Foundation + +/// 하단 탭바와 주변 레이아웃에서 공유하는 치수입니다. +public enum TXTabBarLayout { + public static let height: CGFloat = 58 +} From e8fb503fafffa150d9c9fcbe7c38f4ccf8f06b80 Mon Sep 17 00:00:00 2001 From: jihun Date: Mon, 1 Jun 2026 21:15:34 +0900 Subject: [PATCH 09/16] =?UTF-8?q?feat:=20=ED=86=B5=EA=B3=84=20bottom=20pad?= =?UTF-8?q?ding=20=EC=B6=94=EA=B0=80=20-=20#334?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/Feature/Stats/Sources/Stats/StatsView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Projects/Feature/Stats/Sources/Stats/StatsView.swift b/Projects/Feature/Stats/Sources/Stats/StatsView.swift index b325190f..535adced 100644 --- a/Projects/Feature/Stats/Sources/Stats/StatsView.swift +++ b/Projects/Feature/Stats/Sources/Stats/StatsView.swift @@ -101,7 +101,8 @@ private extension StatsView { } } .padding(.top, store.isOngoing ? 12 : 20) - .padding([.horizontal, .bottom], 20) + .padding(.horizontal, 20) + .padding(.bottom, 85 + TXTabBarLayout.height) .perfFeed("stats") } .background(Color.Gray.gray50) From 3614a4b276a3ad8a96ea21fa93df362bef98038b Mon Sep 17 00:00:00 2001 From: jihun Date: Mon, 1 Jun 2026 21:22:22 +0900 Subject: [PATCH 10/16] =?UTF-8?q?refactor:=20=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A1=A4=20=ED=95=98=EB=A9=B4=20monthNavigation=20=EC=95=88?= =?UTF-8?q?=EB=B3=B4=EC=9D=B4=EA=B2=8C=20=EC=88=98=EC=A0=95=20-=20#335?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/Feature/Stats/Sources/Stats/StatsView.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Projects/Feature/Stats/Sources/Stats/StatsView.swift b/Projects/Feature/Stats/Sources/Stats/StatsView.swift index 535adced..b5fa40fb 100644 --- a/Projects/Feature/Stats/Sources/Stats/StatsView.swift +++ b/Projects/Feature/Stats/Sources/Stats/StatsView.swift @@ -19,11 +19,6 @@ struct StatsView: View { VStack(spacing: 0) { navigationBar topTabBar - if store.isOngoing { - monthNavigation - .padding(.top, 16) - .background(Color.Gray.gray50) - } if let items = store.items, !items.isEmpty { cardList @@ -88,6 +83,12 @@ private extension StatsView { private var scrollCardList: some View { ScrollView { + if store.isOngoing { + monthNavigation + .padding(.top, 16) + .background(Color.Gray.gray50) + } + LazyVStack(spacing: 16) { ForEach(store.items ?? [], id: \.self.goalId) { item in StatsCardView( From 539a011e24dc6a4df654c2af6f171ec34161f667 Mon Sep 17 00:00:00 2001 From: jihun Date: Tue, 2 Jun 2026 20:07:44 +0900 Subject: [PATCH 11/16] =?UTF-8?q?fix:=20=EC=9D=B8=EC=A6=9D=EC=83=B7=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EB=94=94=EC=9E=90=EC=9D=B8=20handoff=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20-=20#336?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Detail/GoalDetailView.swift | 20 ++-- .../Sources/Detail/ReactionBarView.swift | 93 ++++++++++++++----- 2 files changed, 83 insertions(+), 30 deletions(-) diff --git a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift index 3c1d42fd..e2461faa 100644 --- a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift +++ b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift @@ -121,7 +121,7 @@ private extension GoalDetailView { if store.item != nil { cardView .padding(.horizontal, 27) - .padding(.top, isSEDevice ? 47 : 103) + .padding(.top, Constants.cardTopPadding) if store.isCompleted { completedBottomContent @@ -235,7 +235,7 @@ private extension GoalDetailView { if store.isShowReactionBar { reactionBar - .padding(.top, isSEDevice ? 23 : 73) + .padding(.top, Constants.emojiTopPadding) .padding(.horizontal, 20) } } @@ -266,7 +266,7 @@ private extension GoalDetailView { shape: shape, lineWidth: 1.6 ) - .frame(width: 336, height: 336) + .frame(width: Constants.cardSize, height: Constants.cardSize) .clipShape(shape) } @@ -446,7 +446,7 @@ private extension GoalDetailView { let shape = RoundedRectangle(cornerRadius: 20) return Color.clear - .frame(width: 336, height: 336) + .frame(width: Constants.cardSize, height: Constants.cardSize) .readSize { rectFrame = $0 } .overlay { content() @@ -565,19 +565,21 @@ private extension GoalDetailView { cardOffset = .zero isCrossingDuringDrag = false } - - // 다른곳에서도 쓸 때 Util로 빼기 - private var isSEDevice: Bool { - UIScreen.main.bounds.height <= 667 - } } // MARK: - Constants private extension GoalDetailView { enum Constants { + static var isSEDevice: Bool { + UIScreen.main.bounds.height <= 667 + } + static let maxCardOffset: CGFloat = 100 static let dragVelocityThreshold: CGFloat = 1200 static let minimumDragResistance: CGFloat = 0.35 + static var cardTopPadding: CGFloat { isSEDevice ? 34 : 89 } + static var cardSize: CGFloat { isSEDevice ? 321 : 336 } + static var emojiTopPadding: CGFloat { isSEDevice ? 19 : 69 } } } diff --git a/Projects/Feature/GoalDetail/Sources/Detail/ReactionBarView.swift b/Projects/Feature/GoalDetail/Sources/Detail/ReactionBarView.swift index c03921a2..a54af3db 100644 --- a/Projects/Feature/GoalDetail/Sources/Detail/ReactionBarView.swift +++ b/Projects/Feature/GoalDetail/Sources/Detail/ReactionBarView.swift @@ -27,8 +27,8 @@ struct ReactionBarView: View { var body: some View { GeometryReader { proxy in ZStack(alignment: .top) { - shadowView(proxy: proxy) - .offset(y: 10) + shadowView + .offset(y: 9) reactionBar(proxy: proxy) } @@ -46,46 +46,97 @@ struct ReactionBarView: View { // MARK: - SubViews private extension ReactionBarView { - func shadowView(proxy: GeometryProxy) -> some View { + var shadowView: some View { Color.Gray.gray200 - .frame(width: proxy.size.width, height: 67) + .frame(maxWidth: 368) + .frame(height: 68) .clipShape(.capsule) } func reactionBar(proxy: GeometryProxy) -> some View { HStack(spacing: 0) { ForEach(ReactionEmoji.allCases, id: \.self) { emoji in - Button { - onSelect(emoji) - flyingReactionEmitter.emit( - emoji: emoji, - config: .reactionBar(width: proxy.size.width) - ) - } label: { - emoji.image - .padding(.horizontal, 8) + Group { + if case .happy = emoji { + firstButton(proxy: proxy, emoji: emoji) + } else if case .fuck = emoji { + lastButton(proxy: proxy, emoji: emoji) + } else { + rectButton(proxy: proxy, emoji: emoji) + } } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(selectedEmoji == emoji ? Color.Gray.gray300 : Color.clear) .perfControl(slug: "goal-detail", element: "reaction-\(emoji.rawValue)") - - if emoji != ReactionEmoji.allCases.last { - Rectangle() - .frame(width: 1) - } } } .background(Color.Gray.gray100) - .frame(width: proxy.size.width, height: 68) + .frame(maxWidth: 368) + .frame(height: 68) .clipShape(.capsule) .overlay( Capsule() .stroke(Color.Gray.gray500, lineWidth: 1) ) } + + func firstButton(proxy: GeometryProxy, emoji: ReactionEmoji) -> some View { + Button { + onSelect(emoji) + flyingReactionEmitter.emit( + emoji: emoji, + config: .reactionBar(width: proxy.size.width) + ) + } label: { + emoji.image + .padding(.leading, 10) + .padding(.trailing, 6) + .padding(.bottom, 2) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + .frame(minWidth: 70, maxWidth: 84, maxHeight: .infinity) + .background(selectedEmoji == emoji ? Color.Gray.gray300 : Color.clear) + .clipShape(UnevenRoundedRectangle(topLeadingRadius: 999, bottomLeadingRadius: 999)) + } + + func lastButton(proxy: GeometryProxy, emoji: ReactionEmoji) -> some View { + Button { + onSelect(emoji) + flyingReactionEmitter.emit( + emoji: emoji, + config: .reactionBar(width: proxy.size.width) + ) + } label: { + emoji.image + .padding(.leading, 3) + .padding(.trailing, 10) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + .frame(minWidth: 70, maxWidth: 84, maxHeight: .infinity) + .background(selectedEmoji == emoji ? Color.Gray.gray300 : Color.clear) + .clipShape(UnevenRoundedRectangle(bottomTrailingRadius: 999, topTrailingRadius: 999)) + } + + func rectButton(proxy: GeometryProxy, emoji: ReactionEmoji) -> some View { + Button { + onSelect(emoji) + flyingReactionEmitter.emit( + emoji: emoji, + config: .reactionBar(width: proxy.size.width) + ) + } label: { + emoji.image + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + .frame(minWidth: 70, maxWidth: 80, maxHeight: .infinity) + .background(selectedEmoji == emoji ? Color.Gray.gray300 : Color.clear) + .overlay( + Rectangle() + .stroke(Color.Gray.gray500, lineWidth: 1) + ) + } } private extension ReactionBarView { + static func reactionBarConfig(width: CGFloat) -> FlyingReactionConfig { let minX: CGFloat = 8 let maxXInset: CGFloat = 32 From 443c625f72c00c34dca101a1dc48bf5f859a0157 Mon Sep 17 00:00:00 2001 From: jihun Date: Tue, 2 Jun 2026 20:30:22 +0900 Subject: [PATCH 12/16] =?UTF-8?q?fix:=20=EC=83=81=EB=8C=80=EB=A7=8C=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=ED=96=88=EC=9D=84=20=EB=95=8C=20comment=20pa?= =?UTF-8?q?dding=20=EC=88=98=EC=A0=95=20-=20#336?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Detail/GoalDetailView.swift | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift index e2461faa..e329fdb3 100644 --- a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift +++ b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift @@ -157,6 +157,8 @@ private extension GoalDetailView { var cardView: some View { ZStack { + cardFrameReader + myCard .zIndex(effectiveIsFrontMyCard ? 1 : 0) @@ -195,6 +197,12 @@ private extension GoalDetailView { } ) } + + var cardFrameReader: some View { + Color.clear + .frame(width: Constants.cardSize, height: Constants.cardSize) + .readSize { rectFrame = $0 } + } @ViewBuilder var myCard: some View { @@ -401,7 +409,7 @@ private extension GoalDetailView { } var shouldShowCommentOverlay: Bool { - guard store.isCompleted, rectFrame != .zero else { return false } + guard effectiveFrontCardIsCompleted, rectFrame != .zero else { return false } return store.isEditing || !currentFrontComment.isEmpty } @@ -422,6 +430,7 @@ private extension GoalDetailView { commentCircle(comment: currentFrontComment) .padding(.bottom, 26) .frame(width: rectFrame.width, height: rectFrame.height, alignment: .bottom) + .rotationEffect(frontCardRotation) .offset(x: posX, y: posY - keyboardInset) .animation(.easeOut(duration: 0.25), value: keyboardInset) } @@ -447,7 +456,6 @@ private extension GoalDetailView { return Color.clear .frame(width: Constants.cardSize, height: Constants.cardSize) - .readSize { rectFrame = $0 } .overlay { content() .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -534,6 +542,14 @@ private extension GoalDetailView { max(0, rectFrame.maxY - keyboardFrame.minY) } + var frontCardRotation: Angle { + effectiveIsFrontMyCard ? .degrees(0) : .degrees(-8) + } + + var effectiveFrontCardIsCompleted: Bool { + effectiveIsFrontMyCard ? store.myCardIsCompleted : store.partnerCardIsCompleted + } + func repeatedCardOffset(for width: CGFloat) -> CGFloat { let maxOffset = Constants.maxCardOffset let direction: CGFloat = width >= 0 ? 1 : -1 From 405eabae92efac738e47e00916e476cb9ab5f254 Mon Sep 17 00:00:00 2001 From: jihun Date: Tue, 2 Jun 2026 20:36:32 +0900 Subject: [PATCH 13/16] =?UTF-8?q?fix:=20=EC=9D=B8=EC=A6=9D=EC=83=B7=20?= =?UTF-8?q?=EC=83=81=ED=95=98=20=EC=8A=A4=EC=99=80=EC=9D=B4=ED=94=84=20?= =?UTF-8?q?=EC=95=88=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20-?= =?UTF-8?q?=20#337?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Detail/GoalDetailView.swift | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift index e329fdb3..f748f95f 100644 --- a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift +++ b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift @@ -188,8 +188,22 @@ private extension GoalDetailView { cardOffset = repeatedCardOffset(for: width) isCrossingDuringDrag = shouldCrossCards(for: width) } - .onEnded { _ in + .onEnded { value in guard !store.isEditing else { return } + + let translation = value.translation + let width = resistedDragWidth( + for: translation.width, + velocity: value.velocity.width + ) + + guard abs(width) >= abs(translation.height) else { + withAnimation(.spring(response: 0.2, dampingFraction: 0.94)) { + resetDragState() + } + return + } + withAnimation(.spring(response: 0.2, dampingFraction: 0.94)) { resetDragState() store.send(.view(.cardSwiped)) From 16eb266d9f49ddbf6f3d5f371ab36d96c388dda6 Mon Sep 17 00:00:00 2001 From: jihun Date: Thu, 4 Jun 2026 14:06:56 +0900 Subject: [PATCH 14/16] =?UTF-8?q?fix:=20disable=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EC=95=A1=EC=85=98=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81=20?= =?UTF-8?q?-=20#337?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Button/Round/TXRoundButton.swift | 10 +++++++++- .../Sources/Components/Button/TXButton.swift | 4 ++++ .../Sources/Components/Card/Goal/GoalCardView.swift | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Projects/Shared/DesignSystem/Sources/Components/Button/Round/TXRoundButton.swift b/Projects/Shared/DesignSystem/Sources/Components/Button/Round/TXRoundButton.swift index ea895461..0266469e 100644 --- a/Projects/Shared/DesignSystem/Sources/Components/Button/Round/TXRoundButton.swift +++ b/Projects/Shared/DesignSystem/Sources/Components/Button/Round/TXRoundButton.swift @@ -9,12 +9,20 @@ import SwiftUI struct TXRoundButton: View { let shape: TXButtonShape + let allowsActionWhenDisabled: Bool let onTap: () -> Void public var body: some View { if case let .round(style, size, state) = shape { Button { - onTap() + switch state { + case .disabled: + if allowsActionWhenDisabled { + onTap() + } + case .standard: + onTap() + } } label: { ZStack { Capsule() diff --git a/Projects/Shared/DesignSystem/Sources/Components/Button/TXButton.swift b/Projects/Shared/DesignSystem/Sources/Components/Button/TXButton.swift index 6af90249..cf8d27e7 100644 --- a/Projects/Shared/DesignSystem/Sources/Components/Button/TXButton.swift +++ b/Projects/Shared/DesignSystem/Sources/Components/Button/TXButton.swift @@ -24,6 +24,7 @@ import SwiftUI /// ``` public struct TXButton: View { let shape: TXButtonShape + let allowsActionWhenDisabled: Bool let onTap: () -> Void /// 버튼을 생성합니다. @@ -41,9 +42,11 @@ public struct TXButton: View { /// ``` public init( shape: TXButtonShape, + allowsActionWhenDisabled: Bool = false, onTap: @escaping () -> Void ) { self.shape = shape + self.allowsActionWhenDisabled = allowsActionWhenDisabled self.onTap = onTap } @@ -65,6 +68,7 @@ public struct TXButton: View { case .round: TXRoundButton( shape: shape, + allowsActionWhenDisabled: allowsActionWhenDisabled, onTap: onTap ) diff --git a/Projects/Shared/DesignSystem/Sources/Components/Card/Goal/GoalCardView.swift b/Projects/Shared/DesignSystem/Sources/Components/Card/Goal/GoalCardView.swift index 524cb235..3bddb7d2 100644 --- a/Projects/Shared/DesignSystem/Sources/Components/Card/Goal/GoalCardView.swift +++ b/Projects/Shared/DesignSystem/Sources/Components/Card/Goal/GoalCardView.swift @@ -169,6 +169,7 @@ private extension GoalCardView { size: .s, state: isButtonDisabled ? .disabled : .standard ), + allowsActionWhenDisabled: true, onTap: { buttonAction?() } ) .padding(.bottom, 14) From bf9aecfe2ba4129f9f67372df1950230ff44286e Mon Sep 17 00:00:00 2001 From: jihun Date: Thu, 4 Jun 2026 14:09:28 +0900 Subject: [PATCH 15/16] =?UTF-8?q?fix:=20dropdownButton=20=ED=85=8D?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=A3=BC=EC=9E=85=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=B0=98=EC=98=81=20-=20#337?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MakeGoal/Sources/MakeGoalView.swift | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Projects/Feature/MakeGoal/Sources/MakeGoalView.swift b/Projects/Feature/MakeGoal/Sources/MakeGoalView.swift index 147d9888..7f94a323 100644 --- a/Projects/Feature/MakeGoal/Sources/MakeGoalView.swift +++ b/Projects/Feature/MakeGoal/Sources/MakeGoalView.swift @@ -177,7 +177,9 @@ private extension MakeGoalView { Spacer() if store.showPeriodCount { - dropDownButton { store.send(.view(.periodSelected)) } + dropDownButton(text: store.periodCountText) { + store.send(.view(.periodSelected)) + } } } } @@ -190,7 +192,9 @@ private extension MakeGoalView { Spacer() - dropDownButton { store.send(.view(.startDateTapped)) } + dropDownButton(text: store.startDateText) { + store.send(.view(.startDateTapped)) + } } .frame(height: 32) .padding(.vertical, 16) @@ -214,7 +218,9 @@ private extension MakeGoalView { Spacer() - dropDownButton { store.send(.view(.endDateTapped)) } + dropDownButton(text: store.endDateText) { + store.send(.view(.endDateTapped)) + } } .padding(.vertical, 21.5) } @@ -236,9 +242,12 @@ private extension MakeGoalView { .padding(.vertical, -1) } - func dropDownButton(_ action: @escaping () -> Void) -> some View { + func dropDownButton( + text: String, + action: @escaping () -> Void + ) -> some View { HStack(spacing: 0) { - Text(store.startDateText) + Text(text) .typography(.b2_14r) .foregroundStyle(Color.Gray.gray500) Image.Icon.Symbol.arrow2Down From f12e9b5ea5f53dcf45b0a896ceba44eff8949be1 Mon Sep 17 00:00:00 2001 From: jihun Date: Thu, 4 Jun 2026 14:13:39 +0900 Subject: [PATCH 16/16] =?UTF-8?q?fix:=20=EC=9D=B8=EC=A6=9D=EC=83=B7=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=EB=9E=99=EC=85=98=20=EC=8B=9C=20=EB=93=9C?= =?UTF-8?q?=EB=9E=98=EA=B7=B8=20=EC=9C=84=EC=B9=98=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20=EC=BD=94=EB=A9=98=ED=8A=B8=20offset=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20-?= =?UTF-8?q?=20#337?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift index f748f95f..75bd83c3 100644 --- a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift +++ b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift @@ -445,7 +445,7 @@ private extension GoalDetailView { .padding(.bottom, 26) .frame(width: rectFrame.width, height: rectFrame.height, alignment: .bottom) .rotationEffect(frontCardRotation) - .offset(x: posX, y: posY - keyboardInset) + .offset(x: posX + cardOffset, y: posY - keyboardInset) .animation(.easeOut(duration: 0.25), value: keyboardInset) } }