From 3c01fd29b50ebbe13947706318fe85a2094a37d7 Mon Sep 17 00:00:00 2001 From: opficdev Date: Tue, 3 Mar 2026 16:18:39 +0900 Subject: [PATCH 1/6] =?UTF-8?q?ui:=20=EB=8F=8B=EB=B3=B4=EA=B8=B0=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=EC=9D=84=20=ED=86=B5=ED=95=B4=20searchable?= =?UTF-8?q?=EC=9D=B4=20=EA=B5=AC=ED=98=84=EB=90=9C=20=EB=B7=B0=EB=A5=BC=20?= =?UTF-8?q?=EB=B3=B4=EC=97=AC=EC=A3=BC=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/Presentation/Enum/TodoScope.swift | 23 -- .../ViewModel/TodoListViewModel.swift | 25 ++- DevLog/Resource/Localizable.xcstrings | 5 + DevLog/UI/Home/TodoListView.swift | 206 ++++++++++++------ 4 files changed, 166 insertions(+), 93 deletions(-) delete mode 100644 DevLog/Presentation/Enum/TodoScope.swift diff --git a/DevLog/Presentation/Enum/TodoScope.swift b/DevLog/Presentation/Enum/TodoScope.swift deleted file mode 100644 index 1cb37856..00000000 --- a/DevLog/Presentation/Enum/TodoScope.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// TodoScope.swift -// DevLog -// -// Created by opfic on 6/12/25. -// - -import Foundation - -enum TodoScope: String, CaseIterable { - case title, content - - var localizedName: String { - let key: String.LocalizationValue - switch self { - case .title: - key = "TodoScope.title" - case .content: - key = "TodoScope.content" - } - return String(localized: key) - } -} diff --git a/DevLog/Presentation/ViewModel/TodoListViewModel.swift b/DevLog/Presentation/ViewModel/TodoListViewModel.swift index 0ca43bf6..ca424231 100644 --- a/DevLog/Presentation/ViewModel/TodoListViewModel.swift +++ b/DevLog/Presentation/ViewModel/TodoListViewModel.swift @@ -16,7 +16,8 @@ final class TodoListViewModel: Store { var showAlert: Bool = false var alertTitle: String = "" var alertMessage: String = "" - var scope: TodoScope = .title + var isSearching: Bool = false + var showAllSearchResults: Bool = false var query: TodoQuery var isLoading: Bool = false var showToast: Bool = false @@ -37,6 +38,8 @@ final class TodoListViewModel: Store { case togglePinnedOnly case setCompletionFilter(TodoQuery.CompletionFilter) case resetFilters + case setIsSearching(Bool) + case setShowAllSearchResults(Bool) case tapToggleCompleted(TodoListItem) case tapTogglePinned(TodoListItem) case undoDelete @@ -45,7 +48,6 @@ final class TodoListViewModel: Store { case confirmDelete case onAppear case loadNextPage - case setScope(TodoScope) case setSearchText(String) case setToast(isPresented: Bool) case upsertTodo(Todo) @@ -91,6 +93,8 @@ final class TodoListViewModel: Store { ) } + let searchResultsLimit = 5 + var appliedFilterCount: Int { var count = 0 if state.query.sortTarget != .createdAt { count += 1 } @@ -106,11 +110,11 @@ final class TodoListViewModel: Store { switch action { case .refresh, .setAlert, .setShowEditor, .swipeTodo, .setSortTarget, .setSortOrder, - .togglePinnedOnly, .setCompletionFilter, .resetFilters, .tapToggleCompleted, - .tapTogglePinned, .undoDelete: + .togglePinnedOnly, .setCompletionFilter, .resetFilters, .setIsSearching, + .setShowAllSearchResults, .tapToggleCompleted, .tapTogglePinned, .undoDelete: effects = reduceByUser(action, state: &state) - case .confirmDelete, .onAppear, .loadNextPage, .setScope, .setSearchText, .setToast, .upsertTodo: + case .confirmDelete, .onAppear, .loadNextPage, .setSearchText, .setToast, .upsertTodo: effects = reduceByView(action, state: &state) case .didToggleCompleted, .didTogglePinned, .setLoading, .appendTodos, .resetPagination, .setHasMore: @@ -246,6 +250,14 @@ private extension TodoListViewModel { state.query = TodoQuery(kind: state.kind) state.nextCursor = nil return [.fetch] + case .setIsSearching(let value): + state.isSearching = value + if !value { + state.searchText = "" + state.showAllSearchResults = false + } + case .setShowAllSearchResults(let value): + state.showAllSearchResults = value case .tapToggleCompleted(let todo): return [.toggleCompleted(todo)] case .tapTogglePinned(let todo): @@ -275,10 +287,9 @@ private extension TodoListViewModel { case .loadNextPage: guard state.hasMore, !state.isLoading, state.pendingTask == nil else { return [] } return [.loadNextPage] - case .setScope(let scope): - state.scope = scope case .setSearchText(let text): state.searchText = text + state.showAllSearchResults = false case .setToast(let isPresented): setToast(&state, isPresented: isPresented) case .upsertTodo(let todo): diff --git a/DevLog/Resource/Localizable.xcstrings b/DevLog/Resource/Localizable.xcstrings index fdae106f..5c3d2507 100644 --- a/DevLog/Resource/Localizable.xcstrings +++ b/DevLog/Resource/Localizable.xcstrings @@ -235,6 +235,7 @@ }, "TodoScope.content" : { + "extractionState" : "stale", "localizations" : { "ko" : { "stringUnit" : { @@ -245,6 +246,7 @@ } }, "TodoScope.title" : { + "extractionState" : "stale", "localizations" : { "ko" : { "stringUnit" : { @@ -271,6 +273,9 @@ }, "검색어를 입력해 저장한 앱 컨텐츠를 찾아보세요." : { + }, + "검색어를 입력해주세요." : { + }, "계정 삭제" : { diff --git a/DevLog/UI/Home/TodoListView.swift b/DevLog/UI/Home/TodoListView.swift index 4dff961e..8936d546 100644 --- a/DevLog/UI/Home/TodoListView.swift +++ b/DevLog/UI/Home/TodoListView.swift @@ -14,6 +14,79 @@ struct TodoListView: View { @Environment(\.colorScheme) private var colorScheme var body: some View { + Group { + if viewModel.state.isSearching { + todoSearchContent + } else { + todoListContent + } + } + .navigationDestination(for: Path.self) { path in + switch path { + case .detail(let todoID): + TodoDetailView(viewModel: TodoDetailViewModel( + fetchUseCase: container.resolve(FetchTodoByIDUseCase.self), + upsertUseCase: container.resolve(UpsertTodoUseCase.self), + todoID: todoID + )) + } + } + .alert( + viewModel.state.alertTitle, + isPresented: Binding( + get: { viewModel.state.showAlert }, + set: { viewModel.send(.setAlert($0)) } + )) { + Button("확인", role: .cancel) { } + } message: { + Text(viewModel.state.alertMessage) + } + .toast( + isPresented: Binding( + get: { viewModel.state.showToast }, + set: { viewModel.send(.setToast(isPresented: $0)) } + ), + duration: 5, + action: { viewModel.send(.undoDelete) }, + onDismiss: { viewModel.send(.confirmDelete) } + ) { + Label(viewModel.state.toastMessage, systemImage: "arrow.uturn.left") + } + .navigationTitle(viewModel.state.kind.localizedName) + .navigationBarTitleDisplayMode(.large) + .fullScreenCover(isPresented: Binding( + get: { viewModel.state.showEditor }, + set: { viewModel.send(.setShowEditor($0)) } + )) { + TodoEditorView( + viewModel: TodoEditorViewModel(kind: viewModel.state.kind), + onSubmit: { viewModel.send(.upsertTodo($0)) } + ) + } + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button { + viewModel.send(.setShowEditor(true)) + } label: { + Image(systemName: "plus") + } + } + if #available(iOS 26.0, *) { + ToolbarSpacer(.fixed, placement: .topBarTrailing) + } + ToolbarItem(placement: .topBarTrailing) { + Button { + viewModel.send(.setIsSearching(true)) + } label: { + Image(systemName: "magnifyingglass") + } + } + } + .toolbarBackground(.visible, for: .navigationBar) + .task { viewModel.send(.onAppear) } + } + + private var todoListContent: some View { ZStack { List { Section { @@ -72,80 +145,87 @@ struct TodoListView: View { .refreshable { viewModel.send(.refresh) } - .navigationDestination(for: Path.self) { path in - switch path { - case .detail(let todoID): - TodoDetailView(viewModel: TodoDetailViewModel( - fetchUseCase: container.resolve(FetchTodoByIDUseCase.self), - upsertUseCase: container.resolve(UpsertTodoUseCase.self), - todoID: todoID - )) - } - } if viewModel.state.isLoading { LoadingView() } } - .alert( - viewModel.state.alertTitle, - isPresented: Binding( - get: { viewModel.state.showAlert }, - set: { viewModel.send(.setAlert($0)) } - )) { - Button("확인", role: .cancel) { } - } message: { - Text(viewModel.state.alertMessage) - } - .toast( - isPresented: Binding( - get: { viewModel.state.showToast }, - set: { viewModel.send(.setToast(isPresented: $0)) } - ), - duration: 5, - action: { viewModel.send(.undoDelete) }, - onDismiss: { viewModel.send(.confirmDelete) } - ) { - Label(viewModel.state.toastMessage, systemImage: "arrow.uturn.left") - } - .navigationTitle(viewModel.state.kind.localizedName) - .navigationBarTitleDisplayMode(.large) - .fullScreenCover(isPresented: Binding( - get: { viewModel.state.showEditor }, - set: { viewModel.send(.setShowEditor($0)) } - )) { - TodoEditorView( - viewModel: TodoEditorViewModel(kind: viewModel.state.kind), - onSubmit: { viewModel.send(.upsertTodo($0)) } - ) + } + + @ViewBuilder + private var todoSearchContent: some View { + let searchTextBinding = Binding( + get: { viewModel.state.searchText }, + set: { viewModel.send(.setSearchText($0)) } + ) + let isSearchingBinding = Binding( + get: { viewModel.state.isSearching }, + set: { viewModel.send(.setIsSearching($0)) } + ) + + let filteredTodos = viewModel.state.todos.filter { todo in + guard !viewModel.state.searchText.isEmpty else { return true } + return todo.title.localizedCaseInsensitiveContains(viewModel.state.searchText) } - .toolbar { - ToolbarItemGroup(placement: .topBarTrailing) { - Button { - viewModel.send(.setShowEditor(true)) - } label: { - Image(systemName: "plus") + let limit = viewModel.searchResultsLimit + let displayedTodos = viewModel.state.showAllSearchResults + ? filteredTodos + : Array(filteredTodos.prefix(limit)) + + let content = ScrollView { + LazyVStack(spacing: 0) { + if viewModel.state.searchText.isEmpty { + Text("검색어를 입력해주세요.") + .foregroundStyle(Color.gray) + .frame(maxWidth: .infinity) + .padding(.top, 40) + } else if filteredTodos.isEmpty { + Text("검색 결과가 없습니다.") + .foregroundStyle(Color.gray) + .frame(maxWidth: .infinity) + .padding(.top, 40) + } else { + ForEach(displayedTodos) { todo in + Button { + router.push(Path.detail(todo.id)) + } label: { + VStack(spacing: 0) { + TodoItemRow(todo) + Divider() + } + } + } + .padding(.horizontal, 16) + + if !viewModel.state.showAllSearchResults, limit < filteredTodos.count { + Button("더보기") { + viewModel.send(.setShowAllSearchResults(true)) + } + .font(.subheadline) + .foregroundStyle(Color.gray) + .frame(maxWidth: .infinity, alignment: .center) + .padding(.top, 4) + } } } } - .toolbarBackground(.visible, for: .navigationBar) - .searchable( - text: Binding( - get: { viewModel.state.searchText }, - set: { viewModel.send(.setSearchText($0)) } - ), - placement: .navigationBarDrawer(displayMode: .always), - prompt: "\(viewModel.state.kind.localizedName) 검색" - ) - .searchScopes(Binding( - get: { viewModel.state.scope }, - set: { viewModel.send(.setScope($0)) } - )) { - ForEach(TodoScope.allCases, id: \.self) { scope in - Text(scope.localizedName).tag(scope) + + Group { + if #available(iOS 17.0, *) { + content.searchable( + text: searchTextBinding, + isPresented: isSearchingBinding, + placement: .navigationBarDrawer(displayMode: .always), + prompt: "\(viewModel.state.kind.localizedName) 검색" + ) + } else { + content.searchable( + text: searchTextBinding, + placement: .navigationBarDrawer(displayMode: .always), + prompt: "\(viewModel.state.kind.localizedName) 검색" + ) } } - .task { viewModel.send(.onAppear) } } private var headerView: some View { From 18f2d475f7fb3131b68374721e2ae8aac93e571e Mon Sep 17 00:00:00 2001 From: opficdev Date: Tue, 3 Mar 2026 16:47:40 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20=EA=B2=80=EC=83=89=EC=96=B4=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=EC=97=90=20=EB=8C=80=ED=95=B4=200.4=EC=B4=88?= =?UTF-8?q?=20=EB=94=94=EB=B0=94=EC=9A=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewModel/TodoListViewModel.swift | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/DevLog/Presentation/ViewModel/TodoListViewModel.swift b/DevLog/Presentation/ViewModel/TodoListViewModel.swift index ca424231..6883c9d5 100644 --- a/DevLog/Presentation/ViewModel/TodoListViewModel.swift +++ b/DevLog/Presentation/ViewModel/TodoListViewModel.swift @@ -71,6 +71,8 @@ final class TodoListViewModel: Store { } @Published private(set) var state: State + private let searchDebounceDelay: Double = 0.4 + private var searchDebounceTask: Task? private let fetchTodosUseCase: FetchTodosUseCase private let fetchTodoByIDUseCase: FetchTodoByIDUseCase private let upsertTodoUseCase: UpsertTodoUseCase @@ -253,6 +255,7 @@ private extension TodoListViewModel { case .setIsSearching(let value): state.isSearching = value if !value { + cancelDebounce() state.searchText = "" state.showAllSearchResults = false } @@ -290,6 +293,14 @@ private extension TodoListViewModel { case .setSearchText(let text): state.searchText = text state.showAllSearchResults = false + let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines) + if trimmed.isEmpty { + cancelDebounce() + state.searchResults = [] + } else { + state.isLoading = true + scheduleDebouncedQuery(text) + } case .setToast(let isPresented): setToast(&state, isPresented: isPresented) case .upsertTodo(let todo): @@ -352,6 +363,23 @@ private extension TodoListViewModel { state.toastMessage = "실행 취소" state.showToast = isPresented } + + func scheduleDebouncedQuery(_ query: String) { + searchDebounceTask?.cancel() + searchDebounceTask = Task { [weak self] in + guard let self else { return } + try? await Task.sleep(for: .seconds(searchDebounceDelay)) + if Task.isCancelled { return } + await MainActor.run { + self.send(.applySearchQuery(query)) + } + } + } + + func cancelDebounce() { + searchDebounceTask?.cancel() + searchDebounceTask = nil + } } extension TodoQuery.SortTarget { From 1d1fc9f7a0f555ef72274c7cff19621165dabbad Mon Sep 17 00:00:00 2001 From: opficdev Date: Tue, 3 Mar 2026 16:50:21 +0900 Subject: [PATCH 3/6] =?UTF-8?q?refactor:=20=EB=A1=9C=EC=BB=AC=20=ED=95=84?= =?UTF-8?q?=ED=84=B0=EB=A7=81=20=EB=8C=80=EC=8B=A0=20=EC=84=9C=EB=B2=84?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EA=B2=80=EC=83=89=ED=95=B4=EC=84=9C=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20=EA=B0=80=EC=A0=B8?= =?UTF-8?q?=EC=98=A4=EB=8F=84=EB=A1=9D=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewModel/TodoListViewModel.swift | 31 +++++++++++++++++-- DevLog/UI/Home/TodoListView.swift | 16 +++++----- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/DevLog/Presentation/ViewModel/TodoListViewModel.swift b/DevLog/Presentation/ViewModel/TodoListViewModel.swift index 6883c9d5..f6404b46 100644 --- a/DevLog/Presentation/ViewModel/TodoListViewModel.swift +++ b/DevLog/Presentation/ViewModel/TodoListViewModel.swift @@ -11,6 +11,7 @@ final class TodoListViewModel: Store { struct State { var todos: [TodoListItem] = [] var searchText: String = "" + var searchResults: [TodoListItem] = [] let kind: TodoKind var showEditor: Bool = false var showAlert: Bool = false @@ -53,6 +54,8 @@ final class TodoListViewModel: Store { case upsertTodo(Todo) // Run + case setSearchQuery(String) + case fetchSearchResults([TodoListItem]) case didToggleCompleted(TodoListItem) case didTogglePinned(TodoListItem) case setLoading(Bool) @@ -64,6 +67,7 @@ final class TodoListViewModel: Store { enum SideEffect { case fetch case loadNextPage + case search(String) case upsert(Todo) case delete(String) case toggleCompleted(TodoListItem) @@ -119,7 +123,8 @@ final class TodoListViewModel: Store { case .confirmDelete, .onAppear, .loadNextPage, .setSearchText, .setToast, .upsertTodo: effects = reduceByView(action, state: &state) - case .didToggleCompleted, .didTogglePinned, .setLoading, .appendTodos, .resetPagination, .setHasMore: + case .setSearchQuery, .fetchSearchResults, + .didToggleCompleted, .didTogglePinned, .setLoading, .appendTodos, .resetPagination, .setHasMore: effects = reduceByRun(action, state: &state) } @@ -156,6 +161,18 @@ final class TodoListViewModel: Store { send(.setAlert(true)) } } + case .search(let keyword): + Task { + do { + defer { send(.setLoading(false)) } + send(.setLoading(true)) + let query = TodoQuery(kind: state.kind, keyword: keyword) + let page = try await fetchTodosUseCase.execute(query, cursor: nil) + send(.fetchSearchResults(page.items.map { TodoListItem(from: $0) })) + } catch { + send(.setAlert(true)) + } + } case .upsert(let item): Task { do { @@ -257,6 +274,7 @@ private extension TodoListViewModel { if !value { cancelDebounce() state.searchText = "" + state.searchResults = [] state.showAllSearchResults = false } case .setShowAllSearchResults(let value): @@ -313,6 +331,15 @@ private extension TodoListViewModel { func reduceByRun(_ action: Action, state: inout State) -> [SideEffect] { switch action { + case .setSearchQuery(let query): + let trimmed = query.trimmingCharacters(in: .whitespacesAndNewlines) + if trimmed.isEmpty { + state.searchResults = [] + } else { + return [.search(trimmed)] + } + case .fetchSearchResults(let items): + state.searchResults = items case .didToggleCompleted(let todo): if let index = state.todos.firstIndex(where: { $0.id == todo.id }) { state.todos[index] = todo @@ -371,7 +398,7 @@ private extension TodoListViewModel { try? await Task.sleep(for: .seconds(searchDebounceDelay)) if Task.isCancelled { return } await MainActor.run { - self.send(.applySearchQuery(query)) + self.send(.setSearchQuery(query)) } } } diff --git a/DevLog/UI/Home/TodoListView.swift b/DevLog/UI/Home/TodoListView.swift index 8936d546..76ae79f0 100644 --- a/DevLog/UI/Home/TodoListView.swift +++ b/DevLog/UI/Home/TodoListView.swift @@ -163,14 +163,11 @@ struct TodoListView: View { set: { viewModel.send(.setIsSearching($0)) } ) - let filteredTodos = viewModel.state.todos.filter { todo in - guard !viewModel.state.searchText.isEmpty else { return true } - return todo.title.localizedCaseInsensitiveContains(viewModel.state.searchText) - } + let searchResults = viewModel.state.searchResults let limit = viewModel.searchResultsLimit let displayedTodos = viewModel.state.showAllSearchResults - ? filteredTodos - : Array(filteredTodos.prefix(limit)) + ? searchResults + : Array(searchResults.prefix(limit)) let content = ScrollView { LazyVStack(spacing: 0) { @@ -179,7 +176,10 @@ struct TodoListView: View { .foregroundStyle(Color.gray) .frame(maxWidth: .infinity) .padding(.top, 40) - } else if filteredTodos.isEmpty { + } else if viewModel.state.isLoading { + LoadingView() + .padding(.top, 40) + } else if searchResults.isEmpty { Text("검색 결과가 없습니다.") .foregroundStyle(Color.gray) .frame(maxWidth: .infinity) @@ -197,7 +197,7 @@ struct TodoListView: View { } .padding(.horizontal, 16) - if !viewModel.state.showAllSearchResults, limit < filteredTodos.count { + if !viewModel.state.showAllSearchResults, limit < searchResults.count { Button("더보기") { viewModel.send(.setShowAllSearchResults(true)) } From 78c95d44e1b7c85c5edfa87dc02b40273da8719a Mon Sep 17 00:00:00 2001 From: opficdev Date: Tue, 3 Mar 2026 17:21:28 +0900 Subject: [PATCH 4/6] =?UTF-8?q?fix:=20iOS=2017=20=EC=9D=B4=ED=95=98?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=8F=8B=EB=B3=B4=EA=B8=B0=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=EC=9D=84=20=ED=83=AD=ED=95=B4=EB=8F=84=20=ED=8F=AC?= =?UTF-8?q?=EC=BB=A4=EC=8B=B1=EC=9D=B4=20=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8A=94=20=ED=98=84=EC=83=81=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/UI/Home/TodoListView.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/DevLog/UI/Home/TodoListView.swift b/DevLog/UI/Home/TodoListView.swift index 76ae79f0..f93f7338 100644 --- a/DevLog/UI/Home/TodoListView.swift +++ b/DevLog/UI/Home/TodoListView.swift @@ -226,6 +226,11 @@ struct TodoListView: View { ) } } + .onAppear { + DispatchQueue.main.async { + viewModel.send(.setIsSearching(true)) + } + } } private var headerView: some View { From 9ea127d67b38d558d4da723bd9c2e823122c34ea Mon Sep 17 00:00:00 2001 From: opficdev Date: Tue, 3 Mar 2026 17:30:53 +0900 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20=EB=94=94=EB=B0=94=EC=9A=B4=EC=8A=A4?= =?UTF-8?q?=20=EC=A4=91=20=EA=B2=80=EC=83=89=EC=96=B4=EB=A5=BC=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=ED=95=98=EB=A9=B4=20LoadingView=EA=B0=80=20=EA=B2=8C?= =?UTF-8?q?=EC=86=8D=20=EB=9C=A8=EB=8A=94=20=ED=98=84=EC=83=81=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/Presentation/ViewModel/TodoListViewModel.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/DevLog/Presentation/ViewModel/TodoListViewModel.swift b/DevLog/Presentation/ViewModel/TodoListViewModel.swift index f6404b46..92402814 100644 --- a/DevLog/Presentation/ViewModel/TodoListViewModel.swift +++ b/DevLog/Presentation/ViewModel/TodoListViewModel.swift @@ -315,6 +315,7 @@ private extension TodoListViewModel { if trimmed.isEmpty { cancelDebounce() state.searchResults = [] + state.isLoading = false } else { state.isLoading = true scheduleDebouncedQuery(text) From f008dfd0aa6dbc899e8c57a1324019900a18db4f Mon Sep 17 00:00:00 2001 From: opficdev Date: Tue, 3 Mar 2026 17:34:22 +0900 Subject: [PATCH 6/6] =?UTF-8?q?chore:=20=EC=A0=9C=EA=B1=B0=EB=90=9C=20?= =?UTF-8?q?=EB=AC=B8=EC=9E=90=EC=97=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/Resource/Localizable.xcstrings | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/DevLog/Resource/Localizable.xcstrings b/DevLog/Resource/Localizable.xcstrings index 5c3d2507..58437a2e 100644 --- a/DevLog/Resource/Localizable.xcstrings +++ b/DevLog/Resource/Localizable.xcstrings @@ -233,28 +233,6 @@ }, "Todos" : { - }, - "TodoScope.content" : { - "extractionState" : "stale", - "localizations" : { - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "내용" - } - } - } - }, - "TodoScope.title" : { - "extractionState" : "stale", - "localizations" : { - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "제목" - } - } - } }, "Web Page" : {