Skip to content

Commit 2f00400

Browse files
authored
[#145] 시트에 툴바를 통해 cancel, confirm 버튼을 구성한다 (#153)
* ui: iOS 26 기준 파란색 제거 * ui: SheetToolbar 구현 및 적용 * ui: 인디케이터 보이게 하기 * ui: adaptiveButtonStyle에 모양 지정과 ios 18이하에서는 클리어가 아닐 때는 material 적용 X * ui: .accentColor -> .blue * ui: 개선된 adaptiveButtonStyle 적용
1 parent 2a5a1e2 commit 2f00400

8 files changed

Lines changed: 111 additions & 63 deletions

File tree

DevLog/UI/Common/Componeent/CheckBox.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ struct CheckBox: View {
2121
if isChecked {
2222
Image(systemName: "checkmark.circle.fill")
2323
.symbolRenderingMode(.palette)
24-
.foregroundStyle(Color.white, Color.accentColor)
24+
.foregroundStyle(Color.white, Color.blue)
2525
} else {
2626
Image(systemName: "circle")
2727
.foregroundStyle(Color.gray)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//
2+
// SheetToolbar.swift
3+
// DevLog
4+
//
5+
// Created by 최윤진 on 2/27/26.
6+
//
7+
8+
import SwiftUI
9+
10+
struct SheetToolbar: ToolbarContent {
11+
let onCancel: () -> Void
12+
let onConfirm: () -> Void
13+
let isConfirmEnabled: Bool
14+
15+
init(
16+
onCancel: @escaping () -> Void,
17+
onConfirm: @escaping () -> Void,
18+
isConfirmEnabled: Bool = true
19+
) {
20+
self.onCancel = onCancel
21+
self.onConfirm = onConfirm
22+
self.isConfirmEnabled = isConfirmEnabled
23+
}
24+
25+
var body: some ToolbarContent {
26+
if #available(iOS 26.0, *) {
27+
ToolbarItem(placement: .topBarLeading) {
28+
Button(role: .cancel) {
29+
onCancel()
30+
}
31+
}
32+
33+
ToolbarItem(placement: .topBarTrailing) {
34+
Button(role: .confirm) {
35+
onConfirm()
36+
}
37+
.disabled(!isConfirmEnabled)
38+
}
39+
} else {
40+
ToolbarItem(placement: .topBarLeading) {
41+
Button {
42+
onCancel()
43+
} label: {
44+
Text("취소")
45+
}
46+
}
47+
48+
ToolbarItem(placement: .topBarTrailing) {
49+
Button {
50+
onConfirm()
51+
} label: {
52+
Text("확인")
53+
.bold()
54+
}
55+
.disabled(!isConfirmEnabled)
56+
}
57+
}
58+
}
59+
}

DevLog/UI/Common/Componeent/WebItemRow.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ struct WebItemRow: View {
3030
.multilineTextAlignment(.leading)
3131
.lineLimit(2)
3232
Text(item.displayURL)
33-
.foregroundStyle(Color.accentColor)
33+
.foregroundStyle(Color.blue)
3434
.underline()
3535
}
3636
Spacer()

DevLog/UI/Extension/View+.swift

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,31 @@ import SwiftUI
99

1010
extension View {
1111
@ViewBuilder
12-
func adaptiveButtonStyle(_ color: Color? = nil) -> some View {
13-
if #available(iOS 26.0, *), color == nil {
14-
self.buttonStyle(.glass)
12+
func adaptiveButtonStyle(
13+
shape: some Shape = .capsule,
14+
color: Color = .clear)
15+
-> some View {
16+
if #available(iOS 26.0, *) {
17+
self.foregroundStyle(Color(.label))
18+
.padding(8)
19+
.glassEffect(.regular.tint(color), in: shape)
1520
} else {
1621
self.foregroundStyle(Color(.label))
17-
.font(.footnote)
18-
.padding(.vertical, 8)
19-
.padding(.horizontal, 16)
22+
.padding(8)
2023
.background {
21-
Capsule()
22-
.fill(.ultraThinMaterial)
23-
.background {
24-
Capsule()
25-
.fill(color ?? Color.clear)
26-
}
27-
.overlay {
28-
Capsule()
29-
.stroke(Color.white.opacity(0.2), lineWidth: 1)
24+
Group {
25+
if color == .clear {
26+
shape
27+
.fill(.ultraThinMaterial)
28+
} else {
29+
shape
30+
.fill(color)
3031
}
32+
}
33+
.overlay {
34+
shape
35+
.stroke(Color.white.opacity(0.2), lineWidth: 1)
36+
}
3137
}
3238
}
3339
}

DevLog/UI/Home/TodoDetailView.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ struct TodoDetailView: View {
7474
viewModel.send(.setShowEditor(true))
7575
} label: {
7676
Text("수정")
77-
.foregroundColor(.accentColor)
7877
}
7978
}
8079
}

DevLog/UI/Home/TodoEditorView.swift

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,16 @@ struct TodoEditorView: View {
4646
.navigationTitle(viewModel.navigationTitle)
4747
.navigationBarTitleDisplayMode(.inline)
4848
.toolbarBackground(.background, for: .navigationBar)
49-
.toolbar { toolBar }
49+
.toolbar {
50+
SheetToolbar(
51+
onCancel: { dismiss() },
52+
onConfirm: {
53+
onSubmit?(viewModel.upsertTodo())
54+
dismiss()
55+
},
56+
isConfirmEnabled: viewModel.state.isValidToSave
57+
)
58+
}
5059
}
5160
}
5261

@@ -306,10 +315,13 @@ private struct TagEditor<Content: View>: View {
306315
tag = ""
307316
} label: {
308317
Image(systemName: "plus")
309-
.font(.title.bold())
310-
.padding(.vertical, 5)
318+
.font(.largeTitle)
319+
.foregroundStyle(Color.white)
311320
}
312-
.adaptiveButtonStyle((!tag.isEmpty && !tags.contains(tag)) ? .blue : .clear)
321+
.adaptiveButtonStyle(
322+
shape: .circle,
323+
color: (!tag.isEmpty && !tags.contains(tag)) ? Color.blue : .gray.opacity(0.4)
324+
)
313325
.disabled(tag.isEmpty || tags.contains(tag))
314326
}
315327
}
@@ -344,7 +356,7 @@ private struct DueDatePicker<Content: View>: View {
344356
)
345357
.labelsHidden()
346358
.datePickerStyle(.graphical)
347-
.presentationDragIndicator(.hidden)
359+
.presentationDragIndicator(.visible)
348360
.presentationDetents([.height(height)])
349361
.background {
350362
GeometryReader { geometry in

DevLog/UI/PushNotification/PushNotificationListView.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ struct PushNotificationListView: View {
123123
} label: {
124124
Text("정렬: \(viewModel.state.query.sortOrder.title)")
125125
}
126-
.adaptiveButtonStyle(viewModel.state.query.sortOrder == .oldest ? .blue : .clear)
126+
.adaptiveButtonStyle(color: viewModel.state.query.sortOrder == .oldest ? .blue : .clear)
127127

128128
Menu {
129129
ForEach(PushNotificationQuery.TimeFilter.availableOptions, id: \.id) { option in
@@ -144,14 +144,15 @@ struct PushNotificationListView: View {
144144
} label: {
145145
Text("기간")
146146
}
147-
.adaptiveButtonStyle(viewModel.state.query.timeFilter == .none ? .clear : .blue)
147+
.adaptiveButtonStyle(color: viewModel.state.query.timeFilter == .none ? .clear : .blue)
148148

149149
Button {
150150
viewModel.send(.toggleUnreadOnly)
151151
} label: {
152152
Text("읽지 않음")
153+
.foregroundStyle(viewModel.state.query.unreadOnly ? .white : Color(.label))
153154
}
154-
.adaptiveButtonStyle(viewModel.state.query.unreadOnly ? .blue : .clear)
155+
.adaptiveButtonStyle(color: viewModel.state.query.unreadOnly ? .blue : .clear)
155156
}
156157
}
157158
.scrollIndicators(.never)

DevLog/UI/Setting/PushNotificationSettingsView.swift

Lines changed: 8 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ struct PushNotificationSettingsView: View {
3232
if viewModel.state.pushNotificationHour == hour &&
3333
viewModel.state.pushNotificationMinute == 0 {
3434
Image(systemName: "checkmark")
35-
.foregroundStyle(Color.accentColor)
35+
.foregroundStyle(Color.blue)
3636
}
3737
}
3838
.contentShape(Rectangle())
@@ -48,7 +48,7 @@ struct PushNotificationSettingsView: View {
4848
.foregroundStyle(.secondary)
4949
if viewModel.state.pushNotificationMinute != 0 {
5050
Image(systemName: "checkmark")
51-
.foregroundStyle(Color.accentColor)
51+
.foregroundStyle(Color.blue)
5252
}
5353
}
5454
.contentShape(Rectangle())
@@ -88,7 +88,12 @@ struct PushNotificationSettingsView: View {
8888
.presentationDetents([.height(viewModel.state.sheetHeight)])
8989
.onAppear { UIDatePicker.appearance().minuteInterval = 5 }
9090
.onDisappear { UIDatePicker.appearance().minuteInterval = 1 /* 기본값으로 복원 */ }
91-
.toolbar { toolbar }
91+
.toolbar {
92+
SheetToolbar(
93+
onCancel: { viewModel.send(.rollbackUpdate) },
94+
onConfirm: { viewModel.send(.confirmUpdate) }
95+
)
96+
}
9297
.background(
9398
GeometryReader { geometry in
9499
Color.clear.onAppear {
@@ -100,40 +105,6 @@ struct PushNotificationSettingsView: View {
100105
}
101106
}
102107

103-
@ToolbarContentBuilder
104-
private var toolbar: some ToolbarContent {
105-
if #available(iOS 26.0, *) {
106-
ToolbarItem(placement: .topBarLeading) {
107-
Button(role: .cancel) {
108-
viewModel.send(.rollbackUpdate)
109-
}
110-
}
111-
112-
ToolbarItem(placement: .topBarTrailing) {
113-
Button(role: .confirm) {
114-
viewModel.send(.confirmUpdate)
115-
}
116-
}
117-
} else {
118-
ToolbarItem(placement: .topBarLeading) {
119-
Button {
120-
viewModel.send(.rollbackUpdate)
121-
} label: {
122-
Text("취소")
123-
}
124-
}
125-
126-
ToolbarItem(placement: .topBarTrailing) {
127-
Button {
128-
viewModel.send(.confirmUpdate)
129-
} label: {
130-
Text("확인")
131-
.bold()
132-
}
133-
}
134-
}
135-
}
136-
137108
private func formattedTimeString(_ date: Date) -> String {
138109
let minuteValue = Calendar.current.component(.minute, from: date)
139110
let formatStyle: Date.FormatStyle = .dateTime.hour(.twoDigits(amPM: .wide))

0 commit comments

Comments
 (0)