Skip to content

Commit ed8cd34

Browse files
authored
[#439] MainView의 destination ViewModel 생성 및 생명주기를 개선한다 (#446)
* chore: pr 템플릿에 작업 의도 추가 * refactor: 뷰모델 생성 역할을 뷰에서 코디네이터로 전환 * refactor: MainViewCoordinator 하위 화면 ViewModel 보관 방식 단순화 MainViewCoordinator가 생성하는 하위 화면 ViewModel을 단일 인스턴스 기준으로 보관하도록 변경 - TodoListViewModel은 현재 category와 일치할 때 기존 인스턴스 재사용 - TodoDetailViewModel은 현재 todoId와 showEditButton 조건이 일치할 때 기존 인스턴스 재사용 - category별 dictionary와 todoId별 dictionary 제거 - ViewModel이 가진 생성 기준 값을 재사용 조건으로 활용하여 코디네이터의 별도 key 상태 제거 - compact/regular 전환으로 같은 하위 화면이 재생성될 때 기존 ViewModel 인스턴스를 유지하는 구조 * refactor: category를 상태(State)에서 제거
1 parent 273ea71 commit ed8cd34

6 files changed

Lines changed: 62 additions & 40 deletions

File tree

.github/pull_request_template.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
<!-- 이슈가 완전히 해결되었다면 아래에 예약어를 남겨주세요. -->
44
- closed #이슈번호
55

6+
## 🎯 의도
7+
68
## 📝 작업 내용
79

810
### 📌 요약
911

1012
### 🔍 상세
1113

12-
## 📸 영상 / 이미지 (Optional)
14+
## 📸 영상 / 이미지 (Optional)

DevLog/Presentation/ViewModel/TodoDetailViewModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ final class TodoDetailViewModel: Store {
4040
}
4141

4242
private(set) var state: State = .init()
43+
let todoId: String
4344
let showEditButton: Bool
4445
private let fetchTodoUseCase: FetchTodoByIdUseCase
4546
private let fetchReferenceItemsUseCase: FetchReferenceItemsUseCase
4647
private let upsertUseCase: UpsertTodoUseCase
47-
private let todoId: String
4848
private let loadingState = LoadingState()
4949

5050
init(

DevLog/Presentation/ViewModel/TodoListViewModel.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ final class TodoListViewModel: Store {
1313
var todos: [TodoListItem] = []
1414
var searchText: String = ""
1515
var searchResults: [TodoListItem] = []
16-
let category: TodoCategory
1716
var showEditor: Bool = false
1817
var showAlert: Bool = false
1918
var alertTitle: String = ""
@@ -81,6 +80,7 @@ final class TodoListViewModel: Store {
8180
case request
8281
}
8382

83+
let category: TodoCategory
8484
private(set) var state: State
8585
private let fetchTodosUseCase: FetchTodosUseCase
8686
private let fetchTodoByIdUseCase: FetchTodoByIdUseCase
@@ -106,8 +106,8 @@ final class TodoListViewModel: Store {
106106
self.upsertTodoUseCase = upsertTodoUseCase
107107
self.deleteTodoUseCase = deleteTodoUseCase
108108
self.undoDeleteTodoUseCase = undoDeleteTodoUseCase
109+
self.category = category
109110
self.state = State(
110-
category: category,
111111
query: TodoQuery(category: category)
112112
)
113113
}
@@ -191,7 +191,7 @@ final class TodoListViewModel: Store {
191191
self.endLoading(.immediate)
192192
}
193193
}
194-
let query = TodoQuery(category: state.category, keyword: keyword)
194+
let query = TodoQuery(category: category, keyword: keyword)
195195
let page = try await fetchTodosUseCase.execute(query, cursor: nil)
196196
if Task.isCancelled { return }
197197
send(.fetchSearchResults(page.items.compactMap { TodoListItem(from: $0) }))
@@ -307,7 +307,7 @@ private extension TodoListViewModel {
307307
self.nextCursor = nil
308308
return [.fetch]
309309
case .resetFilters:
310-
state.query = TodoQuery(category: state.category)
310+
state.query = TodoQuery(category: category)
311311
self.nextCursor = nil
312312
return [.fetch]
313313
case .setIsSearching(let value):

DevLog/UI/Home/TodoListView.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ struct TodoListView: View {
4545
prompt: Text(
4646
String.localizedStringWithFormat(
4747
String(localized: "todo_list_search_prompt_format"),
48-
TodoCategoryItem(from: viewModel.state.category).localizedName
48+
TodoCategoryItem(from: viewModel.category).localizedName
4949
)
5050
)
5151
)
@@ -71,14 +71,14 @@ struct TodoListView: View {
7171
) {
7272
Label(viewModel.state.toastMessage, systemImage: "arrow.uturn.left")
7373
}
74-
.navigationTitle(TodoCategoryItem(from: viewModel.state.category).localizedName)
74+
.navigationTitle(TodoCategoryItem(from: viewModel.category).localizedName)
7575
.fullScreenCover(isPresented: Binding(
7676
get: { viewModel.state.showEditor },
7777
set: { viewModel.send(.setShowEditor($0)) }
7878
)) {
7979
TodoEditorView(
8080
viewModel: TodoEditorViewModel(
81-
category: viewModel.state.category,
81+
category: viewModel.category,
8282
fetchPreferencesUseCase: container.resolve(FetchTodoCategoryPreferencesUseCase.self),
8383
fetchReferenceItemsUseCase: container.resolve(FetchReferenceItemsUseCase.self)
8484
),
@@ -222,7 +222,7 @@ struct TodoListView: View {
222222
prompt: Text(
223223
String.localizedStringWithFormat(
224224
String(localized: "todo_list_search_prompt_format"),
225-
TodoCategoryItem(from: viewModel.state.category).localizedName
225+
TodoCategoryItem(from: viewModel.category).localizedName
226226
)
227227
)
228228
)
@@ -240,7 +240,7 @@ struct TodoListView: View {
240240
Text(
241241
String.localizedStringWithFormat(
242242
String(localized: "todo_list_search_instruction_format"),
243-
TodoCategoryItem(from: viewModel.state.category).localizedName
243+
TodoCategoryItem(from: viewModel.category).localizedName
244244
)
245245
)
246246
.foregroundStyle(Color.gray)

DevLog/UI/Main/MainView.swift

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,11 @@ struct MainView: View {
1313
@State private var homeViewCoordinator: HomeViewCoordinator
1414
@State private var todayViewCoordinator: TodayViewCoordinator
1515
@Binding var selectedTab: MainTab
16-
private let container: DIContainer
1716

1817
init(
1918
container: DIContainer,
2019
selectedTab: Binding<MainTab>
2120
) {
22-
self.container = container
2321
self._coordinator = State(initialValue: MainViewCoordinator(container: container))
2422
self._homeViewCoordinator = State(initialValue: HomeViewCoordinator(container: container))
2523
self._todayViewCoordinator = State(initialValue: TodayViewCoordinator(container: container))
@@ -117,7 +115,7 @@ struct MainView: View {
117115
Group {
118116
if let todoId = coordinator.todoIdToPresent?.id {
119117
TodoDetailView(
120-
viewModel: makeTodoDetailViewModel(
118+
viewModel: coordinator.todoDetailViewModel(
121119
todoId: todoId,
122120
showEditButton: false
123121
)
@@ -235,11 +233,11 @@ struct MainView: View {
235233
switch homeRoute {
236234
case .category(let item):
237235
TodoListView(
238-
viewModel: makeTodoListViewModel(category: item.todoCategory)
236+
viewModel: coordinator.todoListViewModel(category: item.todoCategory)
239237
)
240238
.id(item.id)
241239
case .todo(let item):
242-
TodoDetailView(viewModel: makeTodoDetailViewModel(todoId: item.id))
240+
TodoDetailView(viewModel: coordinator.todoDetailViewModel(todoId: item.id))
243241
.id(item.id)
244242
case .webPage(let item):
245243
WebView(url: item.url)
@@ -302,7 +300,7 @@ struct MainView: View {
302300
private func todayDestinationView(_ todayRoute: TodayRoute) -> some View {
303301
switch todayRoute {
304302
case .todo(let item):
305-
TodoDetailView(viewModel: makeTodoDetailViewModel(todoId: item.id))
303+
TodoDetailView(viewModel: coordinator.todoDetailViewModel(todoId: item.id))
306304
.id(item.id)
307305
}
308306
}
@@ -378,29 +376,6 @@ private extension MainView {
378376
)
379377
}
380378

381-
func makeTodoListViewModel(category: TodoCategory) -> TodoListViewModel {
382-
TodoListViewModel(
383-
fetchTodosUseCase: container.resolve(FetchTodosUseCase.self),
384-
fetchTodoByIdUseCase: container.resolve(FetchTodoByIdUseCase.self),
385-
upsertTodoUseCase: container.resolve(UpsertTodoUseCase.self),
386-
deleteTodoUseCase: container.resolve(DeleteTodoUseCase.self),
387-
undoDeleteTodoUseCase: container.resolve(UndoDeleteTodoUseCase.self),
388-
category: category
389-
)
390-
}
391-
392-
func makeTodoDetailViewModel(
393-
todoId: String,
394-
showEditButton: Bool = true
395-
) -> TodoDetailViewModel {
396-
TodoDetailViewModel(
397-
fetchTodoUseCase: container.resolve(FetchTodoByIdUseCase.self),
398-
fetchReferenceItemsUseCase: container.resolve(FetchReferenceItemsUseCase.self),
399-
upsertUseCase: container.resolve(UpsertTodoUseCase.self),
400-
todoId: todoId,
401-
showEditButton: showEditButton
402-
)
403-
}
404379
}
405380

406381
private enum MainTabSplitStyle {

DevLog/UI/Main/MainViewCoordinator.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,14 @@ final class MainViewCoordinator {
1414
let pushNotificationListViewModel: PushNotificationListViewModel
1515
let profileViewModel: ProfileViewModel
1616
var todoIdToPresent: TodoIdItem?
17+
private let diContainer: DIContainer
18+
@ObservationIgnored
19+
private var todoListViewModel: TodoListViewModel?
20+
@ObservationIgnored
21+
private var todoDetailViewModel: TodoDetailViewModel?
1722

1823
init(container: DIContainer) {
24+
self.diContainer = container
1925
self.mainViewModel = MainViewModel(
2026
unreadPushCountUseCase: container.resolve(ObserveUnreadPushCountUseCase.self)
2127
)
@@ -36,4 +42,43 @@ final class MainViewCoordinator {
3642
updateHeatmapActivityTypesUseCase: container.resolve(UpdateHeatmapActivityTypesUseCase.self)
3743
)
3844
}
45+
46+
func todoListViewModel(category: TodoCategory) -> TodoListViewModel {
47+
if let todoListViewModel,
48+
todoListViewModel.category == category {
49+
return todoListViewModel
50+
}
51+
52+
let todoListViewModel = TodoListViewModel(
53+
fetchTodosUseCase: diContainer.resolve(FetchTodosUseCase.self),
54+
fetchTodoByIdUseCase: diContainer.resolve(FetchTodoByIdUseCase.self),
55+
upsertTodoUseCase: diContainer.resolve(UpsertTodoUseCase.self),
56+
deleteTodoUseCase: diContainer.resolve(DeleteTodoUseCase.self),
57+
undoDeleteTodoUseCase: diContainer.resolve(UndoDeleteTodoUseCase.self),
58+
category: category
59+
)
60+
self.todoListViewModel = todoListViewModel
61+
return todoListViewModel
62+
}
63+
64+
func todoDetailViewModel(
65+
todoId: String,
66+
showEditButton: Bool = true
67+
) -> TodoDetailViewModel {
68+
if let todoDetailViewModel,
69+
todoDetailViewModel.todoId == todoId,
70+
todoDetailViewModel.showEditButton == showEditButton {
71+
return todoDetailViewModel
72+
}
73+
74+
let todoDetailViewModel = TodoDetailViewModel(
75+
fetchTodoUseCase: diContainer.resolve(FetchTodoByIdUseCase.self),
76+
fetchReferenceItemsUseCase: diContainer.resolve(FetchReferenceItemsUseCase.self),
77+
upsertUseCase: diContainer.resolve(UpsertTodoUseCase.self),
78+
todoId: todoId,
79+
showEditButton: showEditButton
80+
)
81+
self.todoDetailViewModel = todoDetailViewModel
82+
return todoDetailViewModel
83+
}
3984
}

0 commit comments

Comments
 (0)