Skip to content

Commit a536573

Browse files
authored
[#489] TodoList, TodoDetail, TodoEditor 플로우에 사용되는 뷰모델의 생성주기를 개선한다 (#502)
* chore: objectVersion 업데이트 * refactor: MainViewCoordinator에서 Todo 관련 뷰모델 책임 분리 * refactor: MainViewCoordinator 뷰모델 이름 수정 * refactor: Scene 대신 SwiftUI 친화적인 Window로 수정
1 parent b04aba5 commit a536573

4 files changed

Lines changed: 80 additions & 60 deletions

File tree

Application/DevLogPresentation/DevLogPresentation.xcodeproj/project.pbxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
archiveVersion = 1;
44
classes = {
55
};
6-
objectVersion = 70;
6+
objectVersion = 71;
77
objects = {
88

99
/* Begin PBXBuildFile section */
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//
2+
// TodoWindowCoordinator.swift
3+
// DevLogPresentation
4+
//
5+
// Created by opfic on 5/31/26.
6+
//
7+
8+
import Foundation
9+
import DevLogCore
10+
import DevLogDomain
11+
12+
@MainActor
13+
@Observable
14+
final class TodoWindowCoordinator {
15+
private let diContainer: DIContainer
16+
@ObservationIgnored
17+
private var listViewModel: TodoListViewModel?
18+
@ObservationIgnored
19+
private var detailViewModel: TodoDetailViewModel?
20+
21+
init(container: DIContainer) {
22+
self.diContainer = container
23+
}
24+
25+
func makeListViewModel(category: TodoCategory) -> TodoListViewModel {
26+
if let listViewModel,
27+
listViewModel.category == category {
28+
return listViewModel
29+
}
30+
31+
let listViewModel = TodoListViewModel(
32+
fetchTodosUseCase: diContainer.resolve(FetchTodosUseCase.self),
33+
fetchTodoByIdUseCase: diContainer.resolve(FetchTodoByIdUseCase.self),
34+
upsertTodoUseCase: diContainer.resolve(UpsertTodoUseCase.self),
35+
deleteTodoUseCase: diContainer.resolve(DeleteTodoUseCase.self),
36+
undoDeleteTodoUseCase: diContainer.resolve(UndoDeleteTodoUseCase.self),
37+
trackAnalyticsEventUseCase: diContainer.resolve(TrackAnalyticsEventUseCase.self),
38+
category: category
39+
)
40+
self.listViewModel = listViewModel
41+
return listViewModel
42+
}
43+
44+
func makeDetailViewModel(
45+
todoId: String,
46+
showEditButton: Bool = true
47+
) -> TodoDetailViewModel {
48+
if let detailViewModel,
49+
detailViewModel.todoId == todoId,
50+
detailViewModel.showEditButton == showEditButton {
51+
return detailViewModel
52+
}
53+
54+
let detailViewModel = TodoDetailViewModel(
55+
fetchTodoUseCase: diContainer.resolve(FetchTodoByIdUseCase.self),
56+
fetchReferenceItemsUseCase: diContainer.resolve(FetchReferenceItemsUseCase.self),
57+
upsertUseCase: diContainer.resolve(UpsertTodoUseCase.self),
58+
todoId: todoId,
59+
showEditButton: showEditButton
60+
)
61+
self.detailViewModel = detailViewModel
62+
return detailViewModel
63+
}
64+
}

Application/DevLogPresentation/Sources/Main/MainView.swift

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import DevLogDomain
1212
struct MainView: View {
1313
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
1414
@State private var coordinator: MainViewCoordinator
15+
@State private var todoWindowCoordinator: TodoWindowCoordinator
1516
@State private var homeViewCoordinator: HomeViewCoordinator
1617
@State private var todayViewCoordinator: TodayViewCoordinator
1718
@State private var pushNotificationListViewCoordinator: PushNotificationListViewCoordinator
@@ -23,6 +24,7 @@ struct MainView: View {
2324
selectedTab: Binding<MainTab?>
2425
) {
2526
self._coordinator = State(initialValue: MainViewCoordinator(container: container))
27+
self._todoWindowCoordinator = State(initialValue: TodoWindowCoordinator(container: container))
2628
self._homeViewCoordinator = State(initialValue: HomeViewCoordinator(container: container))
2729
self._todayViewCoordinator = State(initialValue: TodayViewCoordinator(container: container))
2830
self._pushNotificationListViewCoordinator = State(
@@ -43,11 +45,11 @@ struct MainView: View {
4345
}
4446
}
4547
.onAppear {
46-
coordinator.mainViewModel.send(.onAppear)
48+
coordinator.viewModel.send(.onAppear)
4749
}
4850
.onChange(of: selectedTab, initial: true) { _, newValue in
4951
guard let newValue else { return }
50-
coordinator.mainViewModel.send(.selectedTabChanged(newValue))
52+
coordinator.viewModel.send(.selectedTabChanged(newValue))
5153
if newValue == .home {
5254
homeViewCoordinator.fetchData()
5355
} else if newValue == .today {
@@ -59,12 +61,12 @@ struct MainView: View {
5961
}
6062
}
6163
.alert(
62-
coordinator.mainViewModel.state.alertTitle,
64+
coordinator.viewModel.state.alertTitle,
6365
isPresented: mainAlertPresented
6466
) {
6567
Button(String(localized: "common_close"), role: .cancel) { }
6668
} message: {
67-
Text(coordinator.mainViewModel.state.alertMessage)
69+
Text(coordinator.viewModel.state.alertMessage)
6870
}
6971
}
7072

@@ -86,7 +88,7 @@ struct MainView: View {
8688
.tabItem {
8789
tabLabel(.notification)
8890
}
89-
.badge(coordinator.mainViewModel.state.unreadPushCount)
91+
.badge(coordinator.viewModel.state.unreadPushCount)
9092
.tag(MainTab.notification as MainTab?)
9193

9294
profileView
@@ -189,7 +191,7 @@ struct MainView: View {
189191
private func sidebarRow(_ tab: MainTab) -> some View {
190192
if tab == .notification {
191193
tabLabel(tab)
192-
.badge(coordinator.mainViewModel.state.unreadPushCount)
194+
.badge(coordinator.viewModel.state.unreadPushCount)
193195
.tag(tab)
194196
} else {
195197
tabLabel(tab)
@@ -254,11 +256,11 @@ struct MainView: View {
254256
switch homeRoute {
255257
case .category(let item):
256258
TodoListView(
257-
viewModel: coordinator.todoListViewModel(category: item.todoCategory)
259+
viewModel: todoWindowCoordinator.makeListViewModel(category: item.todoCategory)
258260
)
259261
.id(item.id)
260262
case .todo(let item):
261-
TodoDetailView(viewModel: coordinator.todoDetailViewModel(todoId: item.id))
263+
TodoDetailView(viewModel: todoWindowCoordinator.makeDetailViewModel(todoId: item.id))
262264
.id(item.id)
263265
case .webPage(let item):
264266
WebView(url: item.url)
@@ -321,7 +323,7 @@ struct MainView: View {
321323
private func todayDestinationView(_ todayRoute: TodayRoute) -> some View {
322324
switch todayRoute {
323325
case .todo(let item):
324-
TodoDetailView(viewModel: coordinator.todoDetailViewModel(todoId: item.id))
326+
TodoDetailView(viewModel: todoWindowCoordinator.makeDetailViewModel(todoId: item.id))
325327
.id(item.id)
326328
}
327329
}
@@ -345,8 +347,8 @@ private extension MainView {
345347

346348
var mainAlertPresented: Binding<Bool> {
347349
Binding(
348-
get: { coordinator.mainViewModel.state.showAlert },
349-
set: { coordinator.mainViewModel.send(.setAlert($0)) }
350+
get: { coordinator.viewModel.state.showAlert },
351+
set: { coordinator.viewModel.send(.setAlert($0)) }
350352
)
351353
}
352354

Application/DevLogPresentation/Sources/Main/MainViewCoordinator.swift

Lines changed: 2 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -12,58 +12,12 @@ import DevLogDomain
1212
@MainActor
1313
@Observable
1414
final class MainViewCoordinator {
15-
let mainViewModel: MainViewModel
16-
private let diContainer: DIContainer
17-
@ObservationIgnored
18-
private var todoListViewModel: TodoListViewModel?
19-
@ObservationIgnored
20-
private var todoDetailViewModel: TodoDetailViewModel?
15+
let viewModel: MainViewModel
2116

2217
init(container: DIContainer) {
23-
self.diContainer = container
24-
self.mainViewModel = MainViewModel(
18+
self.viewModel = MainViewModel(
2519
trackAnalyticsEventUseCase: container.resolve(TrackAnalyticsEventUseCase.self),
2620
unreadPushCountUseCase: container.resolve(ObserveUnreadPushCountUseCase.self)
2721
)
2822
}
29-
30-
func todoListViewModel(category: TodoCategory) -> TodoListViewModel {
31-
if let todoListViewModel,
32-
todoListViewModel.category == category {
33-
return todoListViewModel
34-
}
35-
36-
let todoListViewModel = TodoListViewModel(
37-
fetchTodosUseCase: diContainer.resolve(FetchTodosUseCase.self),
38-
fetchTodoByIdUseCase: diContainer.resolve(FetchTodoByIdUseCase.self),
39-
upsertTodoUseCase: diContainer.resolve(UpsertTodoUseCase.self),
40-
deleteTodoUseCase: diContainer.resolve(DeleteTodoUseCase.self),
41-
undoDeleteTodoUseCase: diContainer.resolve(UndoDeleteTodoUseCase.self),
42-
trackAnalyticsEventUseCase: diContainer.resolve(TrackAnalyticsEventUseCase.self),
43-
category: category
44-
)
45-
self.todoListViewModel = todoListViewModel
46-
return todoListViewModel
47-
}
48-
49-
func todoDetailViewModel(
50-
todoId: String,
51-
showEditButton: Bool = true
52-
) -> TodoDetailViewModel {
53-
if let todoDetailViewModel,
54-
todoDetailViewModel.todoId == todoId,
55-
todoDetailViewModel.showEditButton == showEditButton {
56-
return todoDetailViewModel
57-
}
58-
59-
let todoDetailViewModel = TodoDetailViewModel(
60-
fetchTodoUseCase: diContainer.resolve(FetchTodoByIdUseCase.self),
61-
fetchReferenceItemsUseCase: diContainer.resolve(FetchReferenceItemsUseCase.self),
62-
upsertUseCase: diContainer.resolve(UpsertTodoUseCase.self),
63-
todoId: todoId,
64-
showEditButton: showEditButton
65-
)
66-
self.todoDetailViewModel = todoDetailViewModel
67-
return todoDetailViewModel
68-
}
6923
}

0 commit comments

Comments
 (0)