From 74227160d72896888916391b00cf65e6f80e5dd3 Mon Sep 17 00:00:00 2001 From: opficdev Date: Tue, 17 Mar 2026 17:23:31 +0900 Subject: [PATCH 1/7] =?UTF-8?q?ui:=20=EC=A4=91=EC=9A=94=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C,=20=ED=83=9C=EA=B7=B8,=20=EB=A7=88=EA=B0=90=EC=9D=BC?= =?UTF-8?q?=EC=9D=84=20=EB=B3=84=EB=8F=84=EC=9D=98=20=EC=8B=9C=ED=8A=B8?= =?UTF-8?q?=EB=A1=9C=20=EA=B5=AC=EC=84=B1=ED=95=B4=EC=84=9C=20=EC=BB=A8?= =?UTF-8?q?=ED=85=90=EC=B8=A0=20=EC=9E=91=EC=84=B1=20=EC=8B=9C=20=EA=B0=80?= =?UTF-8?q?=EB=A6=AC=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=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 --- .../ViewModel/TodoEditorViewModel.swift | 25 +++- DevLog/Resource/Localizable.xcstrings | 21 ++- DevLog/UI/Home/TodoEditorView.swift | 140 +++++++++++------- 3 files changed, 125 insertions(+), 61 deletions(-) diff --git a/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift b/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift index 60149c9..8b32fe8 100644 --- a/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift +++ b/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift @@ -16,6 +16,7 @@ final class TodoEditorViewModel: Store { let content: String let dueDate: Date? let tags: [String] + let kind: TodoKind init(todo: Todo) { self.isPinned = todo.isPinned @@ -23,6 +24,7 @@ final class TodoEditorViewModel: Store { self.content = todo.content self.dueDate = todo.dueDate self.tags = todo.tags + self.kind = todo.kind } init(state: State) { @@ -31,6 +33,7 @@ final class TodoEditorViewModel: Store { self.content = state.content self.dueDate = state.dueDate self.tags = Array(state.tags) + self.kind = state.kind } } @@ -43,6 +46,7 @@ final class TodoEditorViewModel: Store { var tagText: String = "" var focusOnEditor: Bool = false var tabViewTag: Tag = .editor + var kind: TodoKind = .etc var isValidToSave: Bool { !title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } @@ -57,6 +61,7 @@ final class TodoEditorViewModel: Store { case removeTag(String) case setContent(String) case setDueDate(Date?) + case setKind(TodoKind) case setTabViewTag(Tag) case setTagText(String) case setTitle(String) @@ -67,15 +72,21 @@ final class TodoEditorViewModel: Store { private(set) var state = State() private let calendar = Calendar.current - let navigationTitle: String private let id: String private let isCompleted: Bool private let isChecked: Bool private let createdAt: Date? private let completedAt: Date? - private let kind: TodoKind private let originalDraft: Draft? + var navigationTitle: String { + if originalDraft == nil { + return "새 \(state.kind.localizedName) 추가" + } + + return "편집" + } + var hasChanges: Bool { guard let originalDraft else { return true } return originalDraft != Draft(state: state) @@ -87,31 +98,29 @@ final class TodoEditorViewModel: Store { // 새로운 Todo 생성용 생성자 init(kind: TodoKind) { - self.navigationTitle = "새 \(kind.localizedName) 추가" self.id = UUID().uuidString self.isCompleted = false self.isChecked = false self.createdAt = nil self.completedAt = nil - self.kind = kind self.originalDraft = nil + state.kind = kind } // 기존 Todo 편집용 생성자 init(todo: Todo) { - self.navigationTitle = "편집" self.id = todo.id self.isCompleted = todo.isCompleted self.isChecked = todo.isChecked self.createdAt = todo.createdAt self.completedAt = todo.completedAt - self.kind = todo.kind self.originalDraft = Draft(todo: todo) state.isPinned = todo.isPinned state.title = todo.title state.content = todo.content state.dueDate = todo.dueDate state.tags = OrderedSet(todo.tags) + state.kind = todo.kind } func reduce(with action: Action) -> [SideEffect] { @@ -134,6 +143,8 @@ final class TodoEditorViewModel: Store { } else { state.dueDate = nil } + case .setKind(let todoKind): + state.kind = todoKind case .setTabViewTag(let tag): state.tabViewTag = tag case .togglePinned: @@ -177,7 +188,7 @@ extension TodoEditorViewModel { completedAt: self.completedAt, dueDate: state.dueDate, tags: state.tags.map { $0 }, - kind: self.kind + kind: state.kind ) } } diff --git a/DevLog/Resource/Localizable.xcstrings b/DevLog/Resource/Localizable.xcstrings index 8c68d1c..a99ab88 100644 --- a/DevLog/Resource/Localizable.xcstrings +++ b/DevLog/Resource/Localizable.xcstrings @@ -332,6 +332,9 @@ }, "설정에서의 푸시 알람 설정과 별개입니다." : { + }, + "세부 정보" : { + }, "소셜 계정" : { @@ -347,6 +350,9 @@ }, "알림" : { + }, + "없음" : { + }, "연결" : { @@ -359,6 +365,9 @@ }, "오늘" : { + }, + "옵션" : { + }, "완료" : { @@ -414,9 +423,6 @@ }, "주차" : { - }, - "중요" : { - }, "중요 표시" : { @@ -441,6 +447,9 @@ }, "취소" : { + }, + "카테고리" : { + }, "컨텐츠" : { @@ -450,9 +459,15 @@ }, "태그" : { + }, + "태그 없음" : { + }, "태그 입력" : { + }, + "태그 편집" : { + }, "테마" : { diff --git a/DevLog/UI/Home/TodoEditorView.swift b/DevLog/UI/Home/TodoEditorView.swift index f7126e3..6e1acc6 100644 --- a/DevLog/UI/Home/TodoEditorView.swift +++ b/DevLog/UI/Home/TodoEditorView.swift @@ -11,44 +11,48 @@ import SwiftUI struct TodoEditorView: View { @State var viewModel: TodoEditorViewModel - @Environment(\.safeAreaInsets) private var safeAreaInsets @Environment(\.dismiss) private var dismiss @FocusState private var field: Field? - @State private var showDueDatePicker: Bool = false + @State private var showInfo: Bool = false private let calendar = Calendar.current var onSubmit: ((Todo) -> Void)? var body: some View { NavigationStack { - ZStack(alignment: .bottom) { - ScrollView { - LazyVStack(spacing: 10) { - titleField - LazyVStack( - alignment: .leading, - spacing: 0, - pinnedViews: [.sectionHeaders] - ) { - Section { - tabView - } header: { - tabViewSelector - } + ScrollView { + LazyVStack(spacing: 10) { + titleField + LazyVStack( + alignment: .leading, + spacing: 0, + pinnedViews: [.sectionHeaders] + ) { + Section { + tabView + } header: { + tabViewSelector } } } - .onTapGesture { - field = .description - } - accessoryBar - .padding(.horizontal) - .padding(.bottom, 16 + safeAreaInsets.bottom / 4) + } + .onTapGesture { + field = .description } .navigationTitle(viewModel.navigationTitle) .navigationBarTitleDisplayMode(.inline) .toolbarBackground(.background, for: .navigationBar) + .sheet(isPresented: $showInfo) { + editorInfoSheet + } .toolbar { ToolbarLeadingButton { dismiss() } + ToolbarItem(placement: .topBarTrailing) { + Button { + showInfo = true + } label: { + Image(systemName: "info.circle") + } + } ToolbarTrailingButton { submit() } @@ -150,33 +154,65 @@ struct TodoEditorView: View { .padding(.vertical, 8) } - private var accessoryBar: some View { - HStack { - Button { - viewModel.send(.togglePinned) - } label: { - Label { - Text("중요") - } icon: { - Image(systemName: viewModel.state.isPinned ? "star.fill" : "star") - .foregroundStyle(viewModel.state.isPinned ? .yellow : .gray) + private var editorInfoSheet: some View { + NavigationStack { + List { + Section("카테고리") { + Picker( + "카테고리", + selection: Binding( + get: { viewModel.state.kind }, + set: { viewModel.send(.setKind($0)) } + ) + ) { + ForEach(TodoKind.allCases) { todoKind in + Label(todoKind.localizedName, systemImage: todoKind.symbolName) + .tag(todoKind) + } + } + } + + Section("옵션") { + Toggle( + "중요 표시", + isOn: Binding( + get: { viewModel.state.isPinned }, + set: { isPinned in + if viewModel.state.isPinned != isPinned { + viewModel.send(.togglePinned) + } + } + ) + ) + + dueDateControl + } + + Section("태그") { + TagEditor( + tags: viewModel.state.tags, + addAction: { viewModel.send(.addTag($0)) }, + deleteAction: { viewModel.send(.removeTag($0)) } + ) { + Label("태그 편집", systemImage: "tag") + } + + if viewModel.state.tags.isEmpty { + Text("태그 없음") + .foregroundStyle(.secondary) + } else { + TagList(viewModel.state.tags) + .padding(.vertical, 4) + } } - .adaptiveButtonStyle() } - TagEditor( - tags: viewModel.state.tags, - addAction: { viewModel.send(.addTag($0)) }, - deleteAction: { viewModel.send(.removeTag($0)) } - ) { - Label { - Text("태그") - } icon: { - Image(systemName: "tag") - .foregroundStyle(.gray) + .navigationTitle("세부 정보") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarLeadingButton { + showInfo = false } - .adaptiveButtonStyle() } - dueDateControl } } @@ -185,21 +221,23 @@ struct TodoEditorView: View { get: { viewModel.state.dueDate ?? Date() }, set: { viewModel.send(.setDueDate($0)) } )) { - Label { + HStack { + Label("마감일", systemImage: "calendar") + .foregroundStyle(.primary) + + Spacer() + if let dueDate = viewModel.state.dueDate { Tag(dueDateText(for: dueDate), isEditing: true) { viewModel.send(.setDueDate(nil)) } .padding(.vertical, -4) } else { - Text("마감일") + Text("없음") + .foregroundStyle(.secondary) } - } icon: { - Image(systemName: "calendar") - .foregroundStyle(.gray) } } - .adaptiveButtonStyle() } private func submit() { From c81f60b6a80e60df8c175b340b3833974ab4752d Mon Sep 17 00:00:00 2001 From: opficdev Date: Tue, 17 Mar 2026 17:37:01 +0900 Subject: [PATCH 2/7] =?UTF-8?q?ui:=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC?= =?UTF-8?q?=EB=A5=BC=20=EC=98=B5=EC=85=98=20=EC=AA=BD=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=91=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/UI/Home/TodoEditorView.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/DevLog/UI/Home/TodoEditorView.swift b/DevLog/UI/Home/TodoEditorView.swift index 6e1acc6..d6068ca 100644 --- a/DevLog/UI/Home/TodoEditorView.swift +++ b/DevLog/UI/Home/TodoEditorView.swift @@ -157,7 +157,7 @@ struct TodoEditorView: View { private var editorInfoSheet: some View { NavigationStack { List { - Section("카테고리") { + Section("옵션") { Picker( "카테고리", selection: Binding( @@ -166,13 +166,11 @@ struct TodoEditorView: View { ) ) { ForEach(TodoKind.allCases) { todoKind in - Label(todoKind.localizedName, systemImage: todoKind.symbolName) + Text(todoKind.localizedName) .tag(todoKind) } } - } - Section("옵션") { Toggle( "중요 표시", isOn: Binding( @@ -205,6 +203,7 @@ struct TodoEditorView: View { .padding(.vertical, 4) } } + .alignmentGuide(.listRowSeparatorLeading) { $0[.leading] } } .navigationTitle("세부 정보") .navigationBarTitleDisplayMode(.inline) From 69904dbf5498781b3d3ddfa891708ad5cb84076f Mon Sep 17 00:00:00 2001 From: opficdev Date: Tue, 17 Mar 2026 18:32:45 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat:=20=EB=B7=B0=EB=AA=A8=EB=8D=B8?= =?UTF-8?q?=EB=A1=9C=20=EC=8B=9C=ED=8A=B8=20=EB=A1=9C=EC=A7=81=20=EC=98=AE?= =?UTF-8?q?=EA=B9=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/Presentation/ViewModel/TodoEditorViewModel.swift | 4 ++++ DevLog/UI/Home/TodoEditorView.swift | 9 ++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift b/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift index 8b32fe8..78a1dcf 100644 --- a/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift +++ b/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift @@ -42,6 +42,7 @@ final class TodoEditorViewModel: Store { var title: String = "" var content: String = "" var dueDate: Date? + var showInfo: Bool = false var tags: OrderedSet = [] var tagText: String = "" var focusOnEditor: Bool = false @@ -62,6 +63,7 @@ final class TodoEditorViewModel: Store { case setContent(String) case setDueDate(Date?) case setKind(TodoKind) + case setShowInfo(Bool) case setTabViewTag(Tag) case setTagText(String) case setTitle(String) @@ -145,6 +147,8 @@ final class TodoEditorViewModel: Store { } case .setKind(let todoKind): state.kind = todoKind + case .setShowInfo(let isPresented): + state.showInfo = isPresented case .setTabViewTag(let tag): state.tabViewTag = tag case .togglePinned: diff --git a/DevLog/UI/Home/TodoEditorView.swift b/DevLog/UI/Home/TodoEditorView.swift index d6068ca..e9a488c 100644 --- a/DevLog/UI/Home/TodoEditorView.swift +++ b/DevLog/UI/Home/TodoEditorView.swift @@ -13,7 +13,6 @@ struct TodoEditorView: View { @State var viewModel: TodoEditorViewModel @Environment(\.dismiss) private var dismiss @FocusState private var field: Field? - @State private var showInfo: Bool = false private let calendar = Calendar.current var onSubmit: ((Todo) -> Void)? @@ -41,14 +40,15 @@ struct TodoEditorView: View { .navigationTitle(viewModel.navigationTitle) .navigationBarTitleDisplayMode(.inline) .toolbarBackground(.background, for: .navigationBar) - .sheet(isPresented: $showInfo) { - editorInfoSheet + .sheet(isPresented: Binding( + get: { viewModel.state.showInfo }, + set: { viewModel.send(.setShowInfo($0)) } } .toolbar { ToolbarLeadingButton { dismiss() } ToolbarItem(placement: .topBarTrailing) { Button { - showInfo = true + viewModel.send(.setShowInfo(true)) } label: { Image(systemName: "info.circle") } @@ -209,7 +209,6 @@ struct TodoEditorView: View { .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarLeadingButton { - showInfo = false } } } From e1db90d49956a7fb51c54fcd62de63fede231eb6 Mon Sep 17 00:00:00 2001 From: opficdev Date: Tue, 17 Mar 2026 18:33:04 +0900 Subject: [PATCH 4/7] style: description -> content --- DevLog/UI/Home/TodoEditorView.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DevLog/UI/Home/TodoEditorView.swift b/DevLog/UI/Home/TodoEditorView.swift index e9a488c..052d6a0 100644 --- a/DevLog/UI/Home/TodoEditorView.swift +++ b/DevLog/UI/Home/TodoEditorView.swift @@ -35,7 +35,7 @@ struct TodoEditorView: View { } } .onTapGesture { - field = .description + field = .content } .navigationTitle(viewModel.navigationTitle) .navigationBarTitleDisplayMode(.inline) @@ -81,7 +81,7 @@ struct TodoEditorView: View { HStack(spacing: 0) { Button(action: { viewModel.send(.setTabViewTag(.editor)) - field = .description + field = .content }) { Text("편집") .frame(maxWidth: .infinity) @@ -121,7 +121,7 @@ struct TodoEditorView: View { axis: .vertical ) .font(.callout) - .focused($field, equals: .description) + .focused($field, equals: .content) } } else { if viewModel.state.content.isEmpty { From 33480c69ce5c3a1d62b0c4a1b2f5ec909c2cb674 Mon Sep 17 00:00:00 2001 From: opficdev Date: Tue, 17 Mar 2026 19:28:54 +0900 Subject: [PATCH 5/7] =?UTF-8?q?ui:=20=EC=8B=9C=ED=8A=B8=EB=A1=9C=20?= =?UTF-8?q?=ED=83=9C=EA=B7=B8=20=ED=99=95=EC=9D=B8=20=EB=B0=8F=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/Resource/Localizable.xcstrings | 6 - DevLog/UI/Common/Component/Tag+.swift | 1 + DevLog/UI/Home/TodoEditorView.swift | 213 ++++++++------------------ 3 files changed, 68 insertions(+), 152 deletions(-) diff --git a/DevLog/Resource/Localizable.xcstrings b/DevLog/Resource/Localizable.xcstrings index a99ab88..f94a144 100644 --- a/DevLog/Resource/Localizable.xcstrings +++ b/DevLog/Resource/Localizable.xcstrings @@ -462,12 +462,6 @@ }, "태그 없음" : { - }, - "태그 입력" : { - - }, - "태그 편집" : { - }, "테마" : { diff --git a/DevLog/UI/Common/Component/Tag+.swift b/DevLog/UI/Common/Component/Tag+.swift index 0e71cfc..3c47488 100644 --- a/DevLog/UI/Common/Component/Tag+.swift +++ b/DevLog/UI/Common/Component/Tag+.swift @@ -54,6 +54,7 @@ struct Tag: View { ) } + .buttonStyle(.plain) } } .background { diff --git a/DevLog/UI/Home/TodoEditorView.swift b/DevLog/UI/Home/TodoEditorView.swift index 052d6a0..687d5d0 100644 --- a/DevLog/UI/Home/TodoEditorView.swift +++ b/DevLog/UI/Home/TodoEditorView.swift @@ -43,6 +43,10 @@ struct TodoEditorView: View { .sheet(isPresented: Binding( get: { viewModel.state.showInfo }, set: { viewModel.send(.setShowInfo($0)) } + )) { + TodoEditorInfoSheetView(viewModel: viewModel) { + viewModel.send(.setShowInfo(false)) + } } .toolbar { ToolbarLeadingButton { dismiss() } @@ -154,7 +158,24 @@ struct TodoEditorView: View { .padding(.vertical, 8) } - private var editorInfoSheet: some View { + private func submit() { + let todo = viewModel.makeTodo() + onSubmit?(todo) + dismiss() + } + + private enum Field: Hashable { + case title, content + } +} + +private struct TodoEditorInfoSheetView: View { + @Bindable var viewModel: TodoEditorViewModel + let onClose: () -> Void + @FocusState private var isTagFieldFocused: Bool + private let calendar = Calendar.current + + var body: some View { NavigationStack { List { Section("옵션") { @@ -187,28 +208,51 @@ struct TodoEditorView: View { } Section("태그") { - TagEditor( - tags: viewModel.state.tags, - addAction: { viewModel.send(.addTag($0)) }, - deleteAction: { viewModel.send(.removeTag($0)) } - ) { - Label("태그 편집", systemImage: "tag") + HStack(spacing: 12) { + TextField( + "추가", + text: Binding( + get: { viewModel.state.tagText }, + set: { viewModel.send(.setTagText($0)) } + ) + ) + .frame(height: UIFont.preferredFont(forTextStyle: .title2).lineHeight) + .textInputAutocapitalization(.never) + .focused($isTagFieldFocused) + .onSubmit { + submitTag() + } + + if isTagFieldFocused { + Button { + submitTag() + } label: { + Image(systemName: "plus.circle.fill") + .font(.title2) + .foregroundStyle(canSubmitTag ? .blue : .secondary) + } + .disabled(!canSubmitTag) + } } if viewModel.state.tags.isEmpty { Text("태그 없음") .foregroundStyle(.secondary) - } else { - TagList(viewModel.state.tags) .padding(.vertical, 4) + } else { + TagList( + viewModel.state.tags, + isEditing: isTagFieldFocused, + action: { viewModel.send(.removeTag($0)) } + ) } } - .alignmentGuide(.listRowSeparatorLeading) { $0[.leading] } } .navigationTitle("세부 정보") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarLeadingButton { + onClose() } } } @@ -220,7 +264,7 @@ struct TodoEditorView: View { set: { viewModel.send(.setDueDate($0)) } )) { HStack { - Label("마감일", systemImage: "calendar") + Text("마감일") .foregroundStyle(.primary) Spacer() @@ -238,14 +282,20 @@ struct TodoEditorView: View { } } - private func submit() { - let todo = viewModel.makeTodo() - onSubmit?(todo) - dismiss() + private func submitTag() { + guard canSubmitTag else { return } + + let tagText = normalizedTagText + viewModel.send(.addTag(tagText)) + viewModel.send(.setTagText("")) } - private enum Field: Hashable { - case title, description, tag + private var normalizedTagText: String { + viewModel.state.tagText.trimmingCharacters(in: .whitespacesAndNewlines) + } + + private var canSubmitTag: Bool { + !normalizedTagText.isEmpty && !viewModel.state.tags.contains(normalizedTagText) } private func dueDateText(for dueDate: Date) -> String { @@ -264,135 +314,6 @@ struct TodoEditorView: View { } } -private struct TagEditor: View { - @Environment(\.safeAreaInsets) private var safeAreaInsets - @State private var isPresented: Bool = false - @State private var sheetHeight: CGFloat = .pi - @State private var tagsHeight: CGFloat = 0 - @State private var fieldHeight: CGFloat = 0 - @State private var tag = "" - @ViewBuilder private var content: () -> Content - private let tags: OrderedSet - private let addAction: (String) -> Void - private let deleteAction: (String) -> Void - private let spacing: CGFloat = 8 - - init( - tags: OrderedSet, - addAction: @escaping (String) -> Void = { _ in }, - deleteAction: @escaping (String) -> Void = { _ in }, - @ViewBuilder content: @escaping () -> Content - ) { - self.tags = tags - self.addAction = addAction - self.deleteAction = deleteAction - self.content = content - } - - var body: some View { - Button { - isPresented = true - } label: { - content() - } - .sheet( - isPresented: $isPresented, - onDismiss: { tag = "" } - ) { - VStack(spacing: tags.isEmpty ? 0 : spacing) { - ScrollView { - TagList(tags, isEditing: true, action: deleteAction) - .background { - GeometryReader { geometry in - Color.clear - .onAppear { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - tagsHeight = geometry.size.height - sheetHeight += tagsHeight + (tagsHeight == 0 ? 0 : spacing) - } - } - .onChange(of: tags) { _, newTags in - DispatchQueue.main.async { - tagsHeight = geometry.size.height - sheetHeight = fieldHeight + tagsHeight + (newTags.isEmpty ? 0 : spacing) - } - } - } - } - } - .scrollIndicators(.hidden) - .frame(maxHeight: tagsHeight) - .padding(.top, tags.isEmpty ? 0 : 8) - - tagField - .background { - GeometryReader { geometry in - Color.clear - .onAppear { - fieldHeight = geometry.size.height + 16 - sheetHeight = fieldHeight - } - } - } - - } - .padding(.horizontal) - .presentationDragIndicator(.hidden) - .presentationDetents([.height(sheetHeight)]) - } - } - - private var tagField: some View { - HStack { - HStack { - TextField("태그 입력", text: $tag) - .keyboardType(.webSearch) - .padding(tag.isEmpty ? .all : [.leading, .vertical]) - .onSubmit { - isPresented = false - } - - if !tag.isEmpty { - Button { - tag = "" - } label: { - Image(systemName: "xmark.circle.fill") - .font(.title) - .symbolRenderingMode(.palette) - .foregroundStyle( - Color(.label), - Color(.systemBackground) - ) - } - .padding(.trailing) - } - } - .background { - Capsule() - .fill(.ultraThinMaterial) - .overlay { - Capsule() - .stroke(Color.white.opacity(0.2), lineWidth: 1) - } - } - - Button { - addAction(tag) - tag = "" - } label: { - Image(systemName: "plus") - .font(.largeTitle) - .foregroundStyle(Color.white) - .adaptiveButtonStyle( - shape: .circle, - color: (!tag.isEmpty && !tags.contains(tag)) ? Color.blue : .gray.opacity(0.4) - ) - } - .disabled(tag.isEmpty || tags.contains(tag)) - } - } -} - private struct DueDatePicker: View { @Environment(\.safeAreaInsets) private var safeAreaInsets @State private var isPresented: Bool = false From 16361ba4ecf77fcd9f0b2fc93e810ec027101699 Mon Sep 17 00:00:00 2001 From: opficdev Date: Tue, 17 Mar 2026 19:40:58 +0900 Subject: [PATCH 6/7] =?UTF-8?q?ui:=20=ED=86=A0=EA=B8=80=20=EC=83=89?= =?UTF-8?q?=EC=83=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/UI/Home/TodoEditorView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/DevLog/UI/Home/TodoEditorView.swift b/DevLog/UI/Home/TodoEditorView.swift index 687d5d0..db09a35 100644 --- a/DevLog/UI/Home/TodoEditorView.swift +++ b/DevLog/UI/Home/TodoEditorView.swift @@ -203,6 +203,7 @@ private struct TodoEditorInfoSheetView: View { } ) ) + .tint(.blue) dueDateControl } From fbd972d75fb18c6e0c8a6d93c1e9791e6be5ae98 Mon Sep 17 00:00:00 2001 From: opficdev Date: Tue, 17 Mar 2026 20:51:15 +0900 Subject: [PATCH 7/7] =?UTF-8?q?refactor:=20=EB=B3=80=EA=B2=BD=20=EA=B0=92?= =?UTF-8?q?=EC=9D=84=20=EC=A0=81=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/Presentation/ViewModel/TodoEditorViewModel.swift | 6 +++--- DevLog/UI/Home/TodoEditorView.swift | 6 +----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift b/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift index 78a1dcf..397a508 100644 --- a/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift +++ b/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift @@ -63,11 +63,11 @@ final class TodoEditorViewModel: Store { case setContent(String) case setDueDate(Date?) case setKind(TodoKind) + case setPinned(Bool) case setShowInfo(Bool) case setTabViewTag(Tag) case setTagText(String) case setTitle(String) - case togglePinned } enum SideEffect { } @@ -147,12 +147,12 @@ final class TodoEditorViewModel: Store { } case .setKind(let todoKind): state.kind = todoKind + case .setPinned(let isPinned): + state.isPinned = isPinned case .setShowInfo(let isPresented): state.showInfo = isPresented case .setTabViewTag(let tag): state.tabViewTag = tag - case .togglePinned: - state.isPinned.toggle() } if self.state != state { self.state = state } diff --git a/DevLog/UI/Home/TodoEditorView.swift b/DevLog/UI/Home/TodoEditorView.swift index db09a35..f8534a1 100644 --- a/DevLog/UI/Home/TodoEditorView.swift +++ b/DevLog/UI/Home/TodoEditorView.swift @@ -196,11 +196,7 @@ private struct TodoEditorInfoSheetView: View { "중요 표시", isOn: Binding( get: { viewModel.state.isPinned }, - set: { isPinned in - if viewModel.state.isPinned != isPinned { - viewModel.send(.togglePinned) - } - } + set: { viewModel.send(.setPinned($0)) } ) ) .tint(.blue)