diff --git a/DevLog/Presentation/Extension/View+.swift b/DevLog/Presentation/Extension/View+.swift deleted file mode 100644 index c3c9782..0000000 --- a/DevLog/Presentation/Extension/View+.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// View+.swift -// DevLog -// -// Created by 최윤진 on 11/22/25. -// - -import SwiftUI - -extension View { - var sceneWidth: CGFloat { - guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene - else { return UIScreen.main.bounds.width } - - return windowScene.screen.bounds.width - } - - var sceneHeight: CGFloat { - guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene - else { return UIScreen.main.bounds.height } - - return windowScene.screen.bounds.height - } -} diff --git a/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift b/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift index 5e3722d..c59eb77 100644 --- a/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift +++ b/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift @@ -6,13 +6,14 @@ // import Foundation +import OrderedCollections final class TodoEditorViewModel: Store { struct State { var title: String = "" var content: String = "" var dueDate: Date? - var tags: [String] = [] + var tags: OrderedSet = [] var tagText: String = "" var focusOnEditor: Bool = false var hasDueDate: Bool { return dueDate != nil } @@ -28,10 +29,10 @@ final class TodoEditorViewModel: Store { } enum Action { - case addTag + case addTag(String) case removeTag(String) case setContent(String) - case setDueDate(Date) + case setDueDate(Date?) case setTabViewTag(Tag) case setTagText(String) case setTitle(String) @@ -50,30 +51,36 @@ final class TodoEditorViewModel: Store { private let createdAt: Date? private let kind: TodoKind - init(title: String, todo: Todo? = nil) { - self.navigationTitle = title - self.id = todo?.id ?? UUID().uuidString - self.isPinned = todo?.isPinned ?? false - self.isCompleted = todo?.isCompleted ?? false - self.isChecked = todo?.isChecked ?? false - self.createdAt = todo?.createdAt ?? nil - self.kind = todo?.kind ?? .etc - if let todo { - state.title = todo.title - state.content = todo.content - state.dueDate = todo.dueDate - state.tags = todo.tags - } + // 새로운 Todo 생성용 생성자 + init(kind: TodoKind) { + self.navigationTitle = "새 \(kind.localizedName) 추가" + self.id = UUID().uuidString + self.isPinned = false + self.isCompleted = false + self.isChecked = false + self.createdAt = nil + self.kind = kind + } + + // 기존 Todo 편집용 생성자 + init(todo: Todo) { + self.navigationTitle = "편집" + self.id = todo.id + self.isPinned = todo.isPinned + self.isCompleted = todo.isCompleted + self.isChecked = todo.isChecked + self.createdAt = todo.createdAt + self.kind = todo.kind + state.title = todo.title + state.content = todo.content + state.dueDate = todo.dueDate + state.tags = OrderedSet(todo.tags) } func reduce(with action: Action) -> [SideEffect] { switch action { - case .addTag: - let tagText = state.tagText - if !state.tags.contains(tagText) && !tagText.isEmpty { - state.tags.append(tagText) - state.tagText = "" - } + case .addTag(let tag): + if !tag.isEmpty { state.tags.append(tag) } case .removeTag(let tagText): state.tags.removeAll { $0 == tagText } case .setContent(let stringValue), @@ -81,8 +88,10 @@ final class TodoEditorViewModel: Store { .setTitle(let stringValue): handleStringAction(action, stringValue: stringValue) case .setDueDate(let dueDate): - if let tomorrowDate = calendar.date(byAdding: .day, value: 1, to: Date()) { + if let tomorrowDate = calendar.date(byAdding: .day, value: 1, to: Date()), let dueDate { state.dueDate = max(dueDate, tomorrowDate) + } else { + state.dueDate = nil } case .setTabViewTag(let tag): state.tabViewTag = tag @@ -125,7 +134,7 @@ extension TodoEditorViewModel { createdAt: self.createdAt ?? date, updatedAt: date, dueDate: state.dueDate, - tags: state.tags, + tags: state.tags.map { $0 }, kind: self.kind ) } diff --git a/DevLog/Resource/Localizable.xcstrings b/DevLog/Resource/Localizable.xcstrings index cc5e3f8..ef94b08 100644 --- a/DevLog/Resource/Localizable.xcstrings +++ b/DevLog/Resource/Localizable.xcstrings @@ -250,9 +250,6 @@ }, "계정 연동" : { - }, - "내용을 입력하세요" : { - }, "네트워크 문제" : { @@ -289,6 +286,9 @@ }, "생성" : { + }, + "설명(선택 사항)" : { + }, "설정" : { @@ -352,6 +352,9 @@ }, "태그" : { + }, + "태그 입력" : { + }, "테마" : { diff --git a/DevLog/UI/Common/Componeent/Tag+.swift b/DevLog/UI/Common/Componeent/Tag+.swift new file mode 100644 index 0000000..c0806af --- /dev/null +++ b/DevLog/UI/Common/Componeent/Tag+.swift @@ -0,0 +1,142 @@ +// +// Tag+.swift +// DevLog +// +// Created by 최윤진 on 2/6/26. +// + +import SwiftUI + +struct Tag: View { + @Environment(\.colorScheme) private var colorScheme + @State private var height: CGFloat = 0 + private let name: String + private let isEditing: Bool + private var action: (() -> Void)? + + init(_ name: String, isEditing: Bool, action: (() -> Void)? = nil) { + self.name = name + self.isEditing = isEditing + self.action = action + } + + var body: some View { + HStack(spacing: 4) { + Text(name) + .foregroundStyle(.blue) + .bold() + .lineLimit(1) + .fixedSize() + .padding(.vertical, 4) + .padding(.leading, 8) + .padding(.trailing, isEditing ? 0 : 8) + .background { + GeometryReader { geo in + Color.clear + .onAppear { + height = geo.size.height + } + } + } + + if isEditing { + Button { + action?() + } label: { + Image(systemName: "xmark.circle.fill") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: height, height: height) + .symbolRenderingMode(.palette) + .foregroundStyle( + .blue, + .black.opacity(colorScheme == .light ? 0 : 0.4) + ) + + } + } + } + .background { + Capsule() + .fill(.blue.opacity(0.2)) + } + } +} + +struct TagLayout: Layout { + var verticalSpacing: CGFloat = 8 + var horizontalSpacing: CGFloat = 8 + + func sizeThatFits( + proposal: ProposedViewSize, + subviews: Subviews, + cache: inout () + ) -> CGSize { + let maxWidth = proposal.width ?? .infinity + let rows = computeRows(maxWidth: maxWidth, subviews: subviews) + let height = + rows.reduce(0) { $0 + $1.maxHeight } + + CGFloat(max(0, rows.count - 1)) * verticalSpacing + return CGSize(width: proposal.width ?? 0, height: height) + } + + func placeSubviews( + in bounds: CGRect, + proposal: ProposedViewSize, + subviews: Subviews, + cache: inout () + ) { + let rows = computeRows(maxWidth: bounds.width, subviews: subviews) + var minY = bounds.minY + + for row in rows { + var minX = bounds.minX + + for index in row.indices { + let size = subviews[index].sizeThatFits(.unspecified) + subviews[index].place( + at: CGPoint(x: minX, y: minY), + proposal: ProposedViewSize(size) + ) + minX += size.width + horizontalSpacing + } + + minY += row.maxHeight + verticalSpacing + } + } + + private func computeRows( + maxWidth: CGFloat, + subviews: Subviews + ) -> [Row] { + let availableWidth = maxWidth > 0 ? maxWidth : .infinity + var rows: [Row] = [] + var currentRow = Row() + var currentWidth: CGFloat = 0 + + for (index, subview) in subviews.enumerated() { + let size = subview.sizeThatFits(.unspecified) + + if currentWidth + size.width > availableWidth && !currentRow.indices.isEmpty { + rows.append(currentRow) + currentRow = Row() + currentWidth = 0 + } + + currentRow.indices.append(index) + currentRow.maxHeight = max(currentRow.maxHeight, size.height) + currentWidth += size.width + horizontalSpacing + } + + if !currentRow.indices.isEmpty { + rows.append(currentRow) + } + + return rows + } + + private struct Row { + var indices: [Int] = [] + var maxHeight: CGFloat = 0 + } +} diff --git a/DevLog/UI/Extension/EnvironmentValues+.swift b/DevLog/UI/Extension/EnvironmentValues+.swift new file mode 100644 index 0000000..a7fe565 --- /dev/null +++ b/DevLog/UI/Extension/EnvironmentValues+.swift @@ -0,0 +1,31 @@ +// +// EnvironmentValues+.swift +// DevLog +// +// Created by 최윤진 on 2/6/26. +// + +import SwiftUI + +extension EnvironmentValues { + + var safeAreaInsets: EdgeInsets { + self[SafeAreaInsetsKey.self] + } + + private struct SafeAreaInsetsKey: EnvironmentKey { + static var defaultValue: EdgeInsets { + guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first(where: { $0.isKeyWindow }) else { + return EdgeInsets() + } + return window.safeAreaInsets.insets + } + } +} + +extension UIEdgeInsets { + var insets: EdgeInsets { + EdgeInsets(top: top, leading: left, bottom: bottom, trailing: right) + } +} diff --git a/DevLog/UI/Extension/View+.swift b/DevLog/UI/Extension/View+.swift new file mode 100644 index 0000000..de3f12c --- /dev/null +++ b/DevLog/UI/Extension/View+.swift @@ -0,0 +1,56 @@ +// +// View+.swift +// DevLog +// +// Created by 최윤진 on 11/22/25. +// + +import SwiftUI + +extension View { + var sceneWidth: CGFloat { + guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene + else { return UIScreen.main.bounds.width } + + return windowScene.screen.bounds.width + } + + var sceneHeight: CGFloat { + guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene + else { return UIScreen.main.bounds.height } + + return windowScene.screen.bounds.height + } + + var safeAreaInsets: UIEdgeInsets { + guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first + else { return UIEdgeInsets.zero } + + return window.safeAreaInsets + } + + @ViewBuilder + func adaptiveButtonStyle(_ color: Color? = nil) -> some View { + if #available(iOS 26.0, *), color == nil { + self.buttonStyle(.glass) + } else { + self.foregroundStyle(Color(.label)) + .font(.footnote) + .padding(.vertical, 8) + .padding(.horizontal, 16) + .background { + Capsule() + .fill(.ultraThinMaterial) + .background { + Capsule() + .fill(color ?? Color.clear) + } + .overlay { + Capsule() + .stroke(Color.white.opacity(0.2), lineWidth: 1) + } + } + } + } +} diff --git a/DevLog/UI/Home/HomeView.swift b/DevLog/UI/Home/HomeView.swift index 8f35c29..855031f 100644 --- a/DevLog/UI/Home/HomeView.swift +++ b/DevLog/UI/Home/HomeView.swift @@ -121,7 +121,7 @@ struct HomeView: View { .navigationDestination(for: Path.self) { path in switch path { case .kind(let todoKind): - TodoView(viewModel:TodoViewModel( + TodoView(viewModel: TodoViewModel( fetchTodosByKindUseCase: container.resolve(FetchTodosByKindUseCase.self), upsertTodoUseCase: container.resolve(UpsertTodoUseCase.self), kind: todoKind diff --git a/DevLog/UI/Home/TodoDetailView.swift b/DevLog/UI/Home/TodoDetailView.swift index 7d726b3..b02cc4c 100644 --- a/DevLog/UI/Home/TodoDetailView.swift +++ b/DevLog/UI/Home/TodoDetailView.swift @@ -62,7 +62,7 @@ struct TodoDetailView: View { } .fullScreenCover(isPresented: $showEditor) { TodoEditorView( - viewModel: TodoEditorViewModel(title: "수정", todo: todo), + viewModel: TodoEditorViewModel(todo: todo), onSubmit: { onSubmit?($0) } ) } diff --git a/DevLog/UI/Home/TodoEditorView.swift b/DevLog/UI/Home/TodoEditorView.swift index 8c45a0b..c0a6f48 100644 --- a/DevLog/UI/Home/TodoEditorView.swift +++ b/DevLog/UI/Home/TodoEditorView.swift @@ -5,167 +5,350 @@ // Created by opfic on 5/31/25. // -import SwiftUI import MarkdownUI +import OrderedCollections +import SwiftUI struct TodoEditorView: View { @StateObject var viewModel: TodoEditorViewModel @Environment(\.dismiss) private var dismiss - @FocusState var focusOnTagField: Bool + @FocusState private var field: Field? + @State private var showDueDatePicker: Bool = false var onSubmit: ((Todo) -> Void)? var body: some View { NavigationStack { - ScrollView { - VStack(spacing: 10) { - TextField("", text: Binding( - get: { viewModel.state.title }, - set: { viewModel.send(.setTitle($0)) } - ), - prompt: Text("제목").foregroundColor(Color.gray) - ) - .font(.title3) - .padding(.horizontal) - Divider() - if let dueDate = viewModel.state.dueDate { - HStack { - DatePicker("마감일", - selection: Binding( - get: { dueDate }, - set: { viewModel.send(.setDueDate($0)) } - ), - displayedComponents: .date) - .datePickerStyle(.compact) - .foregroundStyle(viewModel.state.hasDueDate ? Color.primary : Color.secondary) - Divider() - Button(action: { - viewModel.send(.toggleDueDate) - }) { - CheckBox(isChecked: viewModel.state.hasDueDate) + ZStack(alignment: .bottom) { + ScrollView { + LazyVStack(spacing: 10) { + titleField + LazyVStack( + alignment: .leading, + spacing: 0, + pinnedViews: [.sectionHeaders] + ) { + Section { + tabView + } header: { + tabViewSelector } } - .padding(.horizontal) } - Divider() - HStack { - Text("태그") - .foregroundStyle(viewModel.state.tags.isEmpty ? Color.secondary : Color.primary) - Divider() - ScrollView(.horizontal, showsIndicators: false) { - HStack { - ForEach(viewModel.state.tags, id: \.self) { tag in - HStack { - Text(tag) - Button(action: { - viewModel.send(.removeTag(tag)) - }) { - Image(systemName: "xmark") - .font(.caption) - .foregroundStyle(Color.gray) - } - } - .padding(.horizontal, 8) - .background( - Capsule() - .fill(Color(UIColor.systemFill)) - ) - } + } + .onTapGesture { + field = .description + } + accessoryBar + .padding(.horizontal) + .padding(.bottom, 16 + safeAreaInsets.bottom / 4) + } + .navigationTitle(viewModel.navigationTitle) + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.background, for: .navigationBar) + .toolbar { toolBar } + } + } - TextField("", - text: Binding( - get: { viewModel.state.tagText }, - set: { viewModel.send(.setTagText($0)) } - )) - .focused($focusOnTagField) - .onSubmit { - viewModel.send(.addTag) - } - .onChange(of: focusOnTagField) { focused in - if !focused { - viewModel.send(.addTag) - } - } - } - } - Divider() - Button(action: { - focusOnTagField.toggle() - }) { - Image(systemName: "\(focusOnTagField ? "xmark" : "plus").circle.fill") - .foregroundStyle(Color.gray) - .font(.title2) - } + private var titleField: some View { + TextField( + "", + text: Binding( + get: { viewModel.state.title }, + set: { viewModel.send(.setTitle($0)) } + ), + prompt: Text("제목").foregroundColor(Color.gray) + ) + .focused($field, equals: .title) + .padding(.horizontal) + } + + private var tabViewSelector: some View { + VStack(spacing: 0) { + HStack(spacing: 0) { + Button(action: { + viewModel.send(.setTabViewTag(.editor)) + field = .description + }) { + Text("편집") + .frame(maxWidth: .infinity) + .foregroundStyle( + viewModel.state.tabViewTag == .editor ? Color.primary : Color.secondary + ) + } + Divider() + Button(action: { + viewModel.send(.setTabViewTag(.preview)) + field = nil + }) { + Text("미리보기") + .frame(maxWidth: .infinity) + .foregroundStyle( + viewModel.state.tabViewTag == .preview ? Color.primary : Color.gray + ) + } + } + .padding(.vertical, 10) + .background(Color(UIColor.systemBackground)) + } + } + + private var tabView: some View { + Group { + if viewModel.state.tabViewTag == .editor { + TextField( + "", + text: Binding( + get: { viewModel.state.content }, + set: { viewModel.send(.setContent($0)) } + ), + prompt: Text("설명(선택 사항)").font(.callout), + axis: .vertical + ) + .focused($field, equals: .description) + } else { + Markdown(viewModel.state.content) + .markdownTheme(.basic) + } + } + .padding(.horizontal) + .padding(.top, 10) + } + + private var accessoryBar: some View { + HStack { + TagEditor( + tags: viewModel.state.tags, + addAction: { viewModel.send(.addTag($0)) }, + deleteAction: { viewModel.send(.removeTag($0)) } + ) { + Label { + Text("태그") + } icon: { + Image(systemName: "tag") + .foregroundStyle(.gray) + } + } + .adaptiveButtonStyle() + DueDatePicker(selection: Binding( + get: { viewModel.state.dueDate ?? Date() }, + set: { viewModel.send(.setDueDate($0)) } + )) { + HStack { + Label { + Text("마감일") + } icon: { + Image(systemName: "calendar") + .foregroundStyle(.gray) } - .padding(.horizontal) + Image(systemName: "checkmark.square") + .symbolRenderingMode(.palette) + .foregroundStyle( + viewModel.state.hasDueDate ? .blue : .clear, + .gray + ) + .onTapGesture { + viewModel.send(.setDueDate(viewModel.state.hasDueDate ? nil : Date())) + } } - LazyVStack(alignment: .leading, spacing: 0, pinnedViews: [.sectionHeaders]) { - Section { - Group { - if viewModel.state.tabViewTag == .editor { - TextField( - "", - text: Binding( - get: { viewModel.state.content }, - set: { viewModel.send(.setContent($0)) } - ), - prompt: Text("내용을 입력하세요"), - axis: .vertical - ) - } else { - Markdown(viewModel.state.content) - .markdownTheme(.basic) + } + .adaptiveButtonStyle() + } + } + + @ToolbarContentBuilder + private var toolBar: some ToolbarContent { + ToolbarItem(placement: .topBarLeading) { + Button { + dismiss() + } label: { + Image(systemName: "xmark") + .bold() + } + } + ToolbarItem(placement: .topBarTrailing) { + Button(action: { + onSubmit?(viewModel.upsertTodo()) + dismiss() + }) { + Text("추가") + } + .disabled(!viewModel.state.isValidToSave) + } + } + + private enum Field: Hashable { + case title, description, tag + } +} + +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 { + TagLayout { + ForEach(tags, id: \.self) { tagText in + Tag(tagText, isEditing: true) { + deleteAction(tagText) } } - .padding(.horizontal) - } header: { - VStack(spacing: 0) { - Divider() - HStack(spacing: 0) { - Button(action: { - viewModel.send(.setTabViewTag(.editor)) - }) { - Text("편집") - .frame(maxWidth: .infinity) - .foregroundStyle( - viewModel.state.tabViewTag == .editor ? Color.primary : Color.secondary - ) + } + .background { + GeometryReader { geometry in + Color.clear + .onAppear { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + tagsHeight = geometry.size.height + sheetHeight += tagsHeight + (tagsHeight == 0 ? 0 : spacing) + } } - Divider() - Button(action: { - viewModel.send(.setTabViewTag(.preview)) - }) { - Text("미리보기") - .frame(maxWidth: .infinity) - .foregroundStyle( - viewModel.state.tabViewTag == .preview ? Color.primary : Color.gray - ) + .onChange(of: tags) { newTags in + DispatchQueue.main.async { + tagsHeight = geometry.size.height + sheetHeight = fieldHeight + tagsHeight + (newTags.isEmpty ? 0 : spacing) + } } - } - .padding(.vertical, 10) - .background(Color(UIColor.systemBackground)) - Divider() } } } + .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 + } + } + } + } - .navigationTitle(viewModel.navigationTitle) - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .topBarLeading) { - Button(action: { - dismiss() - }) { - Image(systemName: "xmark")} - .bold() + .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) } - ToolbarItem(placement: .topBarTrailing) { - Button(action: { - onSubmit?(viewModel.upsertTodo()) - dismiss() - }) { - Text("추가") + } + .background { + Capsule() + .fill(.ultraThinMaterial) + .overlay { + Capsule() + .stroke(Color.white.opacity(0.2), lineWidth: 1) + } + } + + Button { + addAction(tag) + tag = "" + } label: { + Image(systemName: "plus") + .font(.title.bold()) + .padding(.vertical, 5) + } + .adaptiveButtonStyle((!tag.isEmpty && !tags.contains(tag)) ? .blue : .clear) + .disabled(tag.isEmpty || tags.contains(tag)) + } + } +} + +private struct DueDatePicker: View { + @Environment(\.safeAreaInsets) private var safeAreaInsets + @State private var isPresented: Bool = false + @State private var height: CGFloat = .pi + @Binding var dueDate: Date + @ViewBuilder private var content: () -> Content + + init( + selection dueDate: Binding, + @ViewBuilder content: @escaping () -> Content + ) { + self._dueDate = dueDate + self.content = content + } + + var body: some View { + Button { + isPresented.toggle() + } label: { + content() + } + .sheet(isPresented: $isPresented) { + DatePicker( + "", + selection: $dueDate, + displayedComponents: .date + ) + .labelsHidden() + .datePickerStyle(.graphical) + .presentationDragIndicator(.hidden) + .presentationDetents([.height(height)]) + .background { + GeometryReader { geometry in + Color.clear.onAppear { + height = geometry.size.height + safeAreaInsets.bottom + safeAreaInsets.top } - .disabled(!viewModel.state.isValidToSave) } } } diff --git a/DevLog/UI/Home/TodoView.swift b/DevLog/UI/Home/TodoView.swift index 825d2e6..cacddaa 100644 --- a/DevLog/UI/Home/TodoView.swift +++ b/DevLog/UI/Home/TodoView.swift @@ -96,11 +96,10 @@ struct TodoView: View { .navigationBarTitleDisplayMode(.large) .fullScreenCover(isPresented: Binding( get: { viewModel.state.showEditor }, - set: { _, _ in viewModel.send(.openEditor) }) + set: { _, _ in viewModel.send(.closeEditor) }) ) { - let title = "새 \(viewModel.state.kind.localizedName)" TodoEditorView( - viewModel: TodoEditorViewModel(title: title), + viewModel: TodoEditorViewModel(kind: viewModel.state.kind), onSubmit: { viewModel.send(.upsertTodo($0)) } ) }