Skip to content

Commit 9112f28

Browse files
authored
[#368] LoadingView 띄우는 형태를 LoadingState()로 관리하여 즉시 뜨는게 아닌 일정 시간 딜레이 후 그 동안에도 비동기 로딩 중이면 뜨도록 개선한다 (#370)
* fix: Task가 취소되지 않았을 경우 로딩을 끝내버릴 수 있는 이슈 해결 * fix: 검색 로딩 상태 반영이 누락되던 문제 수정 * style: SearchViewModel과 동일한 역할 하는 메서드와 이름 일원화 * feat: 검색 액션의 빈 문자열 방어 로직 추가
1 parent 3392576 commit 9112f28

File tree

2 files changed

+41
-15
lines changed

2 files changed

+41
-15
lines changed

DevLog/Presentation/ViewModel/SearchViewModel.swift

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ final class SearchViewModel: Store {
4040
}
4141

4242
enum SideEffect {
43+
case cancelSearch
44+
case debounceFetch(String)
4345
case fetch(String)
4446
}
4547

@@ -103,23 +105,31 @@ final class SearchViewModel: Store {
103105
state.isLoading = isLoading
104106
case .setSearching(let isSearching):
105107
state.isSearching = isSearching
108+
if !isSearching {
109+
effects = [.cancelSearch]
110+
}
106111
case .setSearchQuery(let query):
107112
guard state.searchQuery != query else { return [] }
108113
state.searchQuery = query
109114
state.showAllTodos = false
110115
state.showAllWebPages = false
111116
let trimmed = query.trimmingCharacters(in: .whitespacesAndNewlines)
112117
if trimmed.isEmpty {
113-
cancelSearch()
114118
state.webPages = []
115119
state.todos = []
120+
effects = [.cancelSearch]
116121
} else {
117-
cancelSearch()
118-
beginLoading(.immediate)
119-
scheduleDebouncedFetch(trimmed)
122+
effects = [.cancelSearch, .debounceFetch(trimmed)]
120123
}
121124
case .applySearchQuery(let query):
122-
effects = [.fetch(query)]
125+
let trimmed = query.trimmingCharacters(in: .whitespacesAndNewlines)
126+
if trimmed.isEmpty {
127+
state.webPages = []
128+
state.todos = []
129+
effects = [.cancelSearch]
130+
} else {
131+
effects = [.fetch(trimmed)]
132+
}
123133
case .setShowAllTodos(let shouldShowAll):
124134
state.showAllTodos = shouldShowAll
125135
case .setShowAllWebPages(let shouldShowAll):
@@ -132,6 +142,11 @@ final class SearchViewModel: Store {
132142

133143
func run(_ effect: SideEffect) {
134144
switch effect {
145+
case .cancelSearch:
146+
cancelSearch()
147+
case .debounceFetch(let query):
148+
beginLoading(.immediate)
149+
scheduleDebouncedFetch(query)
135150
case .fetch(let query):
136151
searchTasks[.request]?.cancel()
137152
let requestTask = Task { [weak self] in

DevLog/Presentation/ViewModel/TodoListViewModel.swift

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ final class TodoListViewModel: Store {
5252
case upsertTodo(Todo)
5353

5454
// Run
55-
case triggerSearch(String)
55+
case applySearchQuery(String)
5656
case fetchSearchResults([TodoListItem])
5757
case didToggleCompleted(TodoListItem)
5858
case didTogglePinned(TodoListItem)
@@ -64,6 +64,8 @@ final class TodoListViewModel: Store {
6464
}
6565

6666
enum SideEffect {
67+
case cancelSearch
68+
case debounceSearch(String)
6769
case fetch
6870
case loadNextPage
6971
case search(String)
@@ -134,7 +136,7 @@ final class TodoListViewModel: Store {
134136
case .onAppear, .loadNextPage, .setSearchText, .setToast, .upsertTodo:
135137
effects = reduceByView(action, state: &state)
136138

137-
case .triggerSearch, .fetchSearchResults, .didToggleCompleted, .didTogglePinned,
139+
case .applySearchQuery, .fetchSearchResults, .didToggleCompleted, .didTogglePinned,
138140
.restoreTodo, .setLoading, .appendTodos, .resetPagination, .setHasMore:
139141
effects = reduceByRun(action, state: &state)
140142
}
@@ -146,6 +148,11 @@ final class TodoListViewModel: Store {
146148
// swiftlint:disable function_body_length
147149
func run(_ effect: SideEffect) {
148150
switch effect {
151+
case .cancelSearch:
152+
cancelSearch()
153+
case .debounceSearch(let keyword):
154+
beginLoading(.immediate)
155+
scheduleDebouncedSearch(keyword)
149156
case .fetch:
150157
beginLoading(.delayed)
151158
Task {
@@ -312,10 +319,10 @@ private extension TodoListViewModel {
312319
case .setIsSearching(let value):
313320
state.isSearching = value
314321
if !value {
315-
cancelSearch()
316322
state.searchText = ""
317323
state.searchResults = []
318324
state.showAllSearchResults = false
325+
return [.cancelSearch]
319326
}
320327
case .setShowAllSearchResults(let value):
321328
state.showAllSearchResults = value
@@ -346,12 +353,10 @@ private extension TodoListViewModel {
346353
state.showAllSearchResults = false
347354
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
348355
if trimmed.isEmpty {
349-
cancelSearch()
350356
state.searchResults = []
357+
return [.cancelSearch]
351358
} else {
352-
cancelSearch()
353-
beginLoading(.immediate)
354-
scheduleDebouncedSearch(trimmed)
359+
return [.cancelSearch, .debounceSearch(trimmed)]
355360
}
356361
case .setToast(let isPresented):
357362
setToast(&state, isPresented: isPresented)
@@ -366,8 +371,14 @@ private extension TodoListViewModel {
366371

367372
func reduceByRun(_ action: Action, state: inout State) -> [SideEffect] {
368373
switch action {
369-
case .triggerSearch(let query):
370-
return [.search(query)]
374+
case .applySearchQuery(let query):
375+
let trimmed = query.trimmingCharacters(in: .whitespacesAndNewlines)
376+
if trimmed.isEmpty {
377+
state.searchResults = []
378+
return [.cancelSearch]
379+
} else {
380+
return [.search(trimmed)]
381+
}
371382
case .fetchSearchResults(let items):
372383
state.searchResults = items
373384
case .didToggleCompleted(let todo):
@@ -435,7 +446,7 @@ private extension TodoListViewModel {
435446
if Task.isCancelled { return }
436447
await MainActor.run {
437448
self.searchTasks[.debounce] = nil
438-
self.send(.triggerSearch(query))
449+
self.send(.applySearchQuery(query))
439450
}
440451
}
441452
searchTasks[.debounce] = debounceTask

0 commit comments

Comments
 (0)