Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -431,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)
}
}
Expand Down
51 changes: 21 additions & 30 deletions Projects/Feature/Home/Sources/Home/HomeEmptyContentSection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,42 +10,34 @@ import FeatureHomeInterface
import SharedDesignSystem

/// `hadFirstGoal`을 읽는 빈 상태 영역입니다.
/// `emptyScrollHeight`는 로컬 `@State`로 관리해 다른 콘텐츠 section 재렌더링에 의해
/// 초기화되지 않게 합니다.
/// `goalEmptyView`의 center anchor를 기기 화면 기준 y축 중앙에 배치합니다.
struct HomeEmptyContentSection: View {
let store: StoreOf<HomeReducer>

@State private var emptyScrollHeight: CGFloat = 0

var body: some View {
VStack(spacing: 0) {
HomeHeaderRow(store: store)
.padding(.horizontal, 20)
.padding(.top, 16)
GeometryReader { geo in
let frame = geo.frame(in: .global)
let deviceHeight = UIScreen.main.bounds.height
let deviceCenterYInSection = max(0, deviceHeight / 2 - frame.minY)

ScrollView {
goalEmptyView
// 실제 가시 영역 기준으로 중앙 정렬되도록 탭바 높이만큼 차감
.frame(maxWidth: .infinity, minHeight: max(0, emptyScrollHeight - 58))
.padding(.bottom, 58)
}
.scrollIndicators(.hidden)
.refreshable {
store.send(.view(.refreshPulled))
}
.overlay(alignment: .bottomTrailing) {
emptyArrow
}
.frame(maxHeight: .infinity)
.background {
GeometryReader { geo in
Color.clear
.onAppear { emptyScrollHeight = geo.size.height }
.onChange(of: geo.size.height) { _, newValue in
emptyScrollHeight = newValue
}
ScrollView {
goalEmptyView
.frame(width: geo.size.width)
.position(x: geo.size.width / 2, y: deviceCenterYInSection)
}
Comment on lines +24 to +28

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ScrollView 내부에서 높이가 모호한 상태로 .position(y:)를 쓰고 있어서 조금 불안정해질수도 있을 거 같은데
minHeight을 주고, 정렬하는건 어떨까??

ScrollView {
    goalEmptyView
        .frame(maxWidth: .infinity)
        .frame(minHeight: max(0, geo.size.height - TXTabBarLayout.height))

이렇게!!

@jihun32 jihun32 Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요구사항이 기기 기준 y축 center로 오는건데 예시 코드대로 하면 기기 기준이 아닌 ScrollView 기준 y축 center로 오더라구
(왼쪽 피그마, 오른쪽 예시 코드 적용)
image

그래서 기기 높이 구해서 position으로 셋팅한건데 확인해보고 더 좋은 방법 있으면 수정하고 아니면 머지할게 확인해줘~@clxxrlove

let frame = geo.frame(in: .global)
let deviceHeight = UIScreen.main.bounds.height
let deviceCenterYInSection = max(0, deviceHeight / 2 - frame.minY) 

ScrollView {
                    goalEmptyView
                        .frame(width: geo.size.width)
                        .position(x: geo.size.width / 2, y: deviceCenterYInSection)
                }

.scrollIndicators(.hidden)
.refreshable {
store.send(.view(.refreshPulled))
}
.overlay(alignment: .bottomTrailing) {
if store.hadFirstGoal == false {
emptyArrow
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.frame(maxHeight: .infinity)
}
}

Expand Down Expand Up @@ -79,12 +71,11 @@ struct HomeEmptyContentSection: View {
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}

var emptyArrow: some View {
Image.Illustration.arrow
.padding(.bottom, 71 + 58)
.padding(.bottom, 71 + TXTabBarLayout.height)
.padding(.trailing, 86)
.ignoresSafeArea()
}
Expand Down
19 changes: 14 additions & 5 deletions Projects/Feature/MakeGoal/Sources/MakeGoalView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,9 @@ private extension MakeGoalView {
Spacer()

if store.showPeriodCount {
dropDownButton { store.send(.view(.periodSelected)) }
dropDownButton(text: store.periodCountText) {
store.send(.view(.periodSelected))
}
}
}
}
Expand All @@ -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)
Expand All @@ -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)
}
Expand All @@ -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
Expand Down
8 changes: 2 additions & 6 deletions Projects/Feature/Stats/Sources/Detail/StatsDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,7 @@ private extension StatsDetailView {
summaryTitle(for: summary.title)
summartyContent(content: summary.content, isCompletedCount: summary.isCompletedCount)
.layoutPriority(1)

Spacer()
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
Expand Down Expand Up @@ -188,7 +187,6 @@ private extension StatsDetailView {
Text(content[0])
.typography(.b4_12b)
.foregroundStyle(Color.Gray.gray500)
.lineLimit(1)

if isCompletedCount {
Text("|")
Expand All @@ -199,11 +197,9 @@ private extension StatsDetailView {
Text(content[1])
.typography(.b4_12b)
.foregroundStyle(Color.Gray.gray500)
.lineLimit(1)
}
}
.lineLimit(1)
.fixedSize(horizontal: true, vertical: false)
.frame(maxWidth: .infinity, alignment: .leading)
}

@ViewBuilder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,27 @@ 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 {
ZStack(alignment: .top) {
Capsule()
.fill(style.backgroundColor(state: state))
.frame(maxWidth: size.frameWidth)
.frame(height: size.backgroundHeight(state: state))
.padding(.top, size.bottomYOffset(state: state))
.frame(height: size.backgroundHeight)
.padding(.top, size.backgroundYOffset)

Text(style.text)
.typography(size.typography)
Expand All @@ -34,8 +42,8 @@ struct TXRoundButton: View {
lineWidth: size.borderWidth
)
.background(style.foregroundColor(state: state), in: .capsule)
.padding(.top, size.foregroundYOffset(state: state))
}
.padding(.top, size.topYOffset(state: state))
}
.buttonStyle(.plain)
} else {
Expand Down Expand Up @@ -112,29 +120,19 @@ private extension TXButtonShape.TXRoundSize {
}
}

func backgroundHeight(state: TXButtonShape.TXRoundState) -> CGFloat {
var backgroundHeight: CGFloat {
switch self {
case .l, .m: 70

case .s:
switch state {
case .standard: 31
case .disabled: 28
}
case .s: 28
}
}

func bottomYOffset(state: TXButtonShape.TXRoundState) -> CGFloat {
switch self {
case .s, .l, .m:
switch state {
case .standard: 4
case .disabled: 1
}
}
var backgroundYOffset: CGFloat {
4
}

func topYOffset(state: TXButtonShape.TXRoundState) -> CGFloat {
func foregroundYOffset(state: TXButtonShape.TXRoundState) -> CGFloat {
switch self {
case .s, .l, .m:
switch state {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import SwiftUI
/// ```
public struct TXButton: View {
let shape: TXButtonShape
let allowsActionWhenDisabled: Bool
let onTap: () -> Void

/// 버튼을 생성합니다.
Expand All @@ -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
}

Expand All @@ -65,6 +68,7 @@ public struct TXButton: View {
case .round:
TXRoundButton(
shape: shape,
allowsActionWhenDisabled: allowsActionWhenDisabled,
onTap: onTap
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ private extension GoalCardView {
size: .s,
state: isButtonDisabled ? .disabled : .standard
),
allowsActionWhenDisabled: true,
onTap: { buttonAction?() }
)
.padding(.bottom, 14)
Expand All @@ -179,7 +180,7 @@ private extension GoalCardView {
.padding(.bottom, 10)
}
}
.frame(maxHeight: .infinity)
.frame(maxHeight: .infinity, alignment: .center)
}

func emojiImage(emoji: Image) -> some View {
Expand Down
Loading