diff --git a/DevLog.xcodeproj/project.pbxproj b/DevLog.xcodeproj/project.pbxproj index e5361a8b..5e232c00 100644 --- a/DevLog.xcodeproj/project.pbxproj +++ b/DevLog.xcodeproj/project.pbxproj @@ -392,7 +392,7 @@ INFOPLIST_KEY_UIStatusBarStyle = ""; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; - IPHONEOS_DEPLOYMENT_TARGET = 16; + IPHONEOS_DEPLOYMENT_TARGET = 17; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -438,7 +438,7 @@ INFOPLIST_KEY_UIStatusBarStyle = ""; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; - IPHONEOS_DEPLOYMENT_TARGET = 16; + IPHONEOS_DEPLOYMENT_TARGET = 17; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/DevLog/App/RootView.swift b/DevLog/App/RootView.swift index fa9e1bdc..7d19c7af 100644 --- a/DevLog/App/RootView.swift +++ b/DevLog/App/RootView.swift @@ -9,7 +9,7 @@ import SwiftUI struct RootView: View { @Environment(\.diContainer) var container: DIContainer - @StateObject var viewModel: RootViewModel + @State var viewModel: RootViewModel var body: some View { ZStack { @@ -50,7 +50,7 @@ struct RootView: View { } message: { Text(viewModel.state.alertMessage) } - .onChange(of: viewModel.state.isFirstLaunch) { newValue in + .onChange(of: viewModel.state.isFirstLaunch) { _, newValue in if newValue { viewModel.send(.setFirstLaunch(false)) viewModel.send(.signOutAuto) diff --git a/DevLog/Presentation/Protocol/Store.swift b/DevLog/Presentation/Protocol/Store.swift index 6cb09da9..314fbd58 100644 --- a/DevLog/Presentation/Protocol/Store.swift +++ b/DevLog/Presentation/Protocol/Store.swift @@ -8,7 +8,7 @@ import Foundation @MainActor -protocol Store: ObservableObject { +protocol Store: AnyObject { associatedtype State associatedtype Action associatedtype SideEffect diff --git a/DevLog/Presentation/ViewModel/AccountViewModel.swift b/DevLog/Presentation/ViewModel/AccountViewModel.swift index 096d3f06..9f20208d 100644 --- a/DevLog/Presentation/ViewModel/AccountViewModel.swift +++ b/DevLog/Presentation/ViewModel/AccountViewModel.swift @@ -7,6 +7,7 @@ import Foundation +@Observable final class AccountViewModel: Store { struct State { var currentProvider: AuthProvider? @@ -47,7 +48,7 @@ final class AccountViewModel: Store { case unlinkSuccess } - @Published private(set) var state: State = .init() + private(set) var state: State = .init() private let fetchProvidersUseCase: FetchAuthProvidersUseCase private let linkProviderUseCase: LinkAuthProviderUseCase private let unlinkProviderUseCase: UnlinkAuthProviderUseCase diff --git a/DevLog/Presentation/ViewModel/HomeViewModel.swift b/DevLog/Presentation/ViewModel/HomeViewModel.swift index a9bff5b6..f23544e8 100644 --- a/DevLog/Presentation/ViewModel/HomeViewModel.swift +++ b/DevLog/Presentation/ViewModel/HomeViewModel.swift @@ -7,6 +7,7 @@ import Foundation +@Observable final class HomeViewModel: Store { struct State: Equatable { var todoKindPreferences = TodoKind.allCases.map { TodoKindPreference(kind: $0, isVisible: true) } @@ -81,7 +82,7 @@ final class HomeViewModel: Store { case urlInputAlert } - @Published private(set) var state = State() + private(set) var state = State() private let upsertTodoUseCase: UpsertTodoUseCase private let addWebPageUseCase: AddWebPageUseCase private let deleteWebPageUseCase: DeleteWebPageUseCase diff --git a/DevLog/Presentation/ViewModel/LoginViewModel.swift b/DevLog/Presentation/ViewModel/LoginViewModel.swift index 622d3e07..2dce8f01 100644 --- a/DevLog/Presentation/ViewModel/LoginViewModel.swift +++ b/DevLog/Presentation/ViewModel/LoginViewModel.swift @@ -10,6 +10,7 @@ import Foundation import FirebaseAuth import GoogleSignIn +@Observable final class LoginViewModel: Store { struct State { var signIn: Bool? @@ -37,7 +38,7 @@ final class LoginViewModel: Store { private let signOutUseCase: SignOutUseCase private let sessionUseCase: AuthSessionUseCase - @Published private(set) var state = State() + private(set) var state = State() private var cancellables = Set() init( diff --git a/DevLog/Presentation/ViewModel/ProfileViewModel.swift b/DevLog/Presentation/ViewModel/ProfileViewModel.swift index bb3ac8e7..8b551805 100644 --- a/DevLog/Presentation/ViewModel/ProfileViewModel.swift +++ b/DevLog/Presentation/ViewModel/ProfileViewModel.swift @@ -7,6 +7,7 @@ import Foundation +@Observable final class ProfileViewModel: Store { struct State { var name: String = "" @@ -51,7 +52,7 @@ final class ProfileViewModel: Store { case updateHeatmapActivityTypes(Set) } - @Published private(set) var state = State() + private(set) var state = State() private let fetchUserDataUseCase: FetchUserDataUseCase private let fetchTodosUseCase: FetchTodosUseCase private let upsertStatusMessageUseCase: UpsertStatusMessageUseCase diff --git a/DevLog/Presentation/ViewModel/PushNotificationListViewModel.swift b/DevLog/Presentation/ViewModel/PushNotificationListViewModel.swift index 91497b48..24a12680 100644 --- a/DevLog/Presentation/ViewModel/PushNotificationListViewModel.swift +++ b/DevLog/Presentation/ViewModel/PushNotificationListViewModel.swift @@ -7,6 +7,7 @@ import Foundation +@Observable final class PushNotificationListViewModel: Store { struct State { var notifications: [PushNotificationItem] = [] @@ -50,7 +51,7 @@ final class PushNotificationListViewModel: Store { case toggleRead(String) } - @Published private(set) var state: State + private(set) var state: State private let fetchUseCase: FetchPushNotificationsUseCase private let deleteUseCase: DeletePushNotificationUseCase private let toggleReadUseCase: TogglePushNotificationReadUseCase diff --git a/DevLog/Presentation/ViewModel/PushNotificationSettingsViewModel.swift b/DevLog/Presentation/ViewModel/PushNotificationSettingsViewModel.swift index e7829128..8f394629 100644 --- a/DevLog/Presentation/ViewModel/PushNotificationSettingsViewModel.swift +++ b/DevLog/Presentation/ViewModel/PushNotificationSettingsViewModel.swift @@ -7,6 +7,7 @@ import Foundation +@Observable final class PushNotificationSettingsViewModel: Store { struct State { var pushNotificationEnable: Bool = false @@ -45,7 +46,7 @@ final class PushNotificationSettingsViewModel: Store { case updatePushNotificationSettings } - @Published private(set) var state: State = .init() + private(set) var state: State = .init() private let calendar = Calendar.current private let fetchPushSettingsUseCase: FetchPushSettingsUseCase private let updatePushSettingsUseCase: UpdatePushSettingsUseCase diff --git a/DevLog/Presentation/ViewModel/RootViewModel.swift b/DevLog/Presentation/ViewModel/RootViewModel.swift index 1cd8c4cb..1d3c2eae 100644 --- a/DevLog/Presentation/ViewModel/RootViewModel.swift +++ b/DevLog/Presentation/ViewModel/RootViewModel.swift @@ -8,6 +8,7 @@ import Foundation import Combine +@Observable final class RootViewModel: Store { struct State { var showAlert: Bool = false @@ -32,7 +33,7 @@ final class RootViewModel: Store { case signOut } - @Published private(set) var state: State + private(set) var state: State private let connectivityProvider = NWPathConnectivityProvider() private var cancellables = Set() private let sessionUseCase: AuthSessionUseCase diff --git a/DevLog/Presentation/ViewModel/SearchViewModel.swift b/DevLog/Presentation/ViewModel/SearchViewModel.swift index c3b72687..f8910042 100644 --- a/DevLog/Presentation/ViewModel/SearchViewModel.swift +++ b/DevLog/Presentation/ViewModel/SearchViewModel.swift @@ -8,6 +8,7 @@ import Foundation import OrderedCollections +@Observable final class SearchViewModel: Store { struct State { var isLoading: Bool = false @@ -42,7 +43,7 @@ final class SearchViewModel: Store { case fetch(String) } - @Published private(set) var state: State = .init() + private(set) var state: State = .init() private let fetchWebPagesUseCase: FetchWebPagesUseCase private let fetchTodosUseCase: FetchTodosUseCase private let fetchRecentSearchQueriesUseCase: FetchRecentSearchQueriesUseCase diff --git a/DevLog/Presentation/ViewModel/SettingViewModel.swift b/DevLog/Presentation/ViewModel/SettingViewModel.swift index 80a96040..e99c1dd1 100644 --- a/DevLog/Presentation/ViewModel/SettingViewModel.swift +++ b/DevLog/Presentation/ViewModel/SettingViewModel.swift @@ -8,6 +8,7 @@ import Foundation import Combine +@Observable final class SettingViewModel: Store { struct State { var theme: SystemTheme = .automatic @@ -41,7 +42,7 @@ final class SettingViewModel: Store { case signOut, deleteAuth, error, removeCache } - @Published private(set) var state = State() + private(set) var state = State() private let deleteAuthuseCase: DeleteAuthUseCase private let signOutUseCase: SignOutUseCase private let sessionUseCase: AuthSessionUseCase diff --git a/DevLog/Presentation/ViewModel/TodoDetailViewModel.swift b/DevLog/Presentation/ViewModel/TodoDetailViewModel.swift index 33cd7d91..d1d8e15c 100644 --- a/DevLog/Presentation/ViewModel/TodoDetailViewModel.swift +++ b/DevLog/Presentation/ViewModel/TodoDetailViewModel.swift @@ -7,6 +7,7 @@ import Foundation +@Observable final class TodoDetailViewModel: Store { struct State { var todo: Todo? @@ -33,7 +34,7 @@ final class TodoDetailViewModel: Store { case upsertTodo(Todo) } - @Published private(set) var state: State = .init() + private(set) var state: State = .init() private let fetchUseCase: FetchTodoByIDUseCase private let upsertUseCase: UpsertTodoUseCase private let todoID: String diff --git a/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift b/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift index f1dd17ce..b2d6a8cc 100644 --- a/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift +++ b/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift @@ -8,6 +8,7 @@ import Foundation import OrderedCollections +@Observable final class TodoEditorViewModel: Store { struct State { var title: String = "" @@ -41,7 +42,7 @@ final class TodoEditorViewModel: Store { enum SideEffect { } - @Published private(set) var state = State() + private(set) var state = State() private let calendar = Calendar.current let navigationTitle: String private let id: String diff --git a/DevLog/Presentation/ViewModel/TodoListViewModel.swift b/DevLog/Presentation/ViewModel/TodoListViewModel.swift index 92402814..d27e22f5 100644 --- a/DevLog/Presentation/ViewModel/TodoListViewModel.swift +++ b/DevLog/Presentation/ViewModel/TodoListViewModel.swift @@ -7,6 +7,7 @@ import Foundation +@Observable final class TodoListViewModel: Store { struct State { var todos: [TodoListItem] = [] @@ -74,7 +75,7 @@ final class TodoListViewModel: Store { case togglePinned(TodoListItem) } - @Published private(set) var state: State + private(set) var state: State private let searchDebounceDelay: Double = 0.4 private var searchDebounceTask: Task? private let fetchTodosUseCase: FetchTodosUseCase diff --git a/DevLog/Presentation/ViewModel/TodoManageViewModel.swift b/DevLog/Presentation/ViewModel/TodoManageViewModel.swift index bf3549c8..86b1abf5 100644 --- a/DevLog/Presentation/ViewModel/TodoManageViewModel.swift +++ b/DevLog/Presentation/ViewModel/TodoManageViewModel.swift @@ -7,6 +7,7 @@ import Foundation +@Observable final class TodoManageViewModel: Store { struct State { var todoKindPreferences: [TodoKindPreference] @@ -19,7 +20,7 @@ final class TodoManageViewModel: Store { enum SideEffect { } - @Published private(set) var state: State + private(set) var state: State init(_ todoKindPreferences: [TodoKindPreference]) { self.state = State(todoKindPreferences: todoKindPreferences) diff --git a/DevLog/UI/Common/Component/Toast.swift b/DevLog/UI/Common/Component/Toast.swift index 28f15b6b..18b38cd1 100644 --- a/DevLog/UI/Common/Component/Toast.swift +++ b/DevLog/UI/Common/Component/Toast.swift @@ -51,7 +51,7 @@ private struct ToastOverlayView: View { ) .offset(y: yOffset) .opacity(opacityValue) - .onChange(of: isPresented) { newValue in + .onChange(of: isPresented) { _, newValue in if newValue { resetForNewPresentation() presentAnimated() diff --git a/DevLog/UI/Common/NavigationRouter.swift b/DevLog/UI/Common/NavigationRouter.swift index dcafa3ac..1713ed69 100644 --- a/DevLog/UI/Common/NavigationRouter.swift +++ b/DevLog/UI/Common/NavigationRouter.swift @@ -7,8 +7,9 @@ import SwiftUI -final class NavigationRouter: ObservableObject { - @Published var path = NavigationPath() +@Observable +final class NavigationRouter { + var path = NavigationPath() func push(_ element: any Hashable) { Task { @MainActor in diff --git a/DevLog/UI/Common/Searchable.swift b/DevLog/UI/Common/Searchable.swift deleted file mode 100644 index 94be3bdf..00000000 --- a/DevLog/UI/Common/Searchable.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// Searchable.swift -// DevLog -// -// Created by opfic on 5/22/25. -// - -import SwiftUI - -struct Searchable: View { - @Environment(\.isSearching) private var isSearching - @Binding var bindedSearching: Bool - - init(isSearching: Binding) { - self._bindedSearching = isSearching - } - - var body: some View { - EmptyView() - .onChange(of: isSearching) { newValue in - bindedSearching = newValue - } - } -} diff --git a/DevLog/UI/Home/HomeView.swift b/DevLog/UI/Home/HomeView.swift index fd91eee3..7490a961 100644 --- a/DevLog/UI/Home/HomeView.swift +++ b/DevLog/UI/Home/HomeView.swift @@ -10,8 +10,8 @@ import SwiftUI struct HomeView: View { @Environment(\.diContainer) var container: any DIContainer @Environment(\.sceneWidth) var sceneWidth: CGFloat - @StateObject private var router = NavigationRouter() - @StateObject var viewModel: HomeViewModel + @State private var router = NavigationRouter() + @State var viewModel: HomeViewModel var body: some View { NavigationStack(path: $router.path) { @@ -32,7 +32,7 @@ struct HomeView: View { deleteTodoUseCase: container.resolve(DeleteTodoUseCase.self), kind: todoKind )) - .environmentObject(router) + .environment(router) case .detail(let todoID): TodoDetailView(viewModel: TodoDetailViewModel( fetchUseCase: container.resolve(FetchTodoByIDUseCase.self), diff --git a/DevLog/UI/Home/TodoDetailView.swift b/DevLog/UI/Home/TodoDetailView.swift index 4fb81c06..a1696db1 100644 --- a/DevLog/UI/Home/TodoDetailView.swift +++ b/DevLog/UI/Home/TodoDetailView.swift @@ -8,7 +8,7 @@ import SwiftUI struct TodoDetailView: View { - @StateObject var viewModel: TodoDetailViewModel + @State var viewModel: TodoDetailViewModel var body: some View { ZStack { diff --git a/DevLog/UI/Home/TodoEditorView.swift b/DevLog/UI/Home/TodoEditorView.swift index 23f674ce..bb4a42dc 100644 --- a/DevLog/UI/Home/TodoEditorView.swift +++ b/DevLog/UI/Home/TodoEditorView.swift @@ -10,7 +10,7 @@ import OrderedCollections import SwiftUI struct TodoEditorView: View { - @StateObject var viewModel: TodoEditorViewModel + @State var viewModel: TodoEditorViewModel @Environment(\.safeAreaInsets) private var safeAreaInsets @Environment(\.dismiss) private var dismiss @FocusState private var field: Field? @@ -245,7 +245,7 @@ private struct TagEditor: View { sheetHeight += tagsHeight + (tagsHeight == 0 ? 0 : spacing) } } - .onChange(of: tags) { newTags in + .onChange(of: tags) { _, newTags in DispatchQueue.main.async { tagsHeight = geometry.size.height sheetHeight = fieldHeight + tagsHeight + (newTags.isEmpty ? 0 : spacing) diff --git a/DevLog/UI/Home/TodoListView.swift b/DevLog/UI/Home/TodoListView.swift index f93f7338..4bb71357 100644 --- a/DevLog/UI/Home/TodoListView.swift +++ b/DevLog/UI/Home/TodoListView.swift @@ -8,8 +8,8 @@ import SwiftUI struct TodoListView: View { - @StateObject var viewModel: TodoListViewModel - @EnvironmentObject var router: NavigationRouter + @State var viewModel: TodoListViewModel + @Environment(NavigationRouter.self) var router @Environment(\.diContainer) var container: DIContainer @Environment(\.colorScheme) private var colorScheme diff --git a/DevLog/UI/Home/TodoManageView.swift b/DevLog/UI/Home/TodoManageView.swift index e1046e43..328d4b4c 100644 --- a/DevLog/UI/Home/TodoManageView.swift +++ b/DevLog/UI/Home/TodoManageView.swift @@ -8,7 +8,7 @@ import SwiftUI struct TodoManageView: View { - @StateObject var viewModel: TodoManageViewModel + @State var viewModel: TodoManageViewModel var onDismiss: (([TodoKindPreference]) -> Void)? var body: some View { diff --git a/DevLog/UI/Login/LoginView.swift b/DevLog/UI/Login/LoginView.swift index d4154d26..7943db7a 100644 --- a/DevLog/UI/Login/LoginView.swift +++ b/DevLog/UI/Login/LoginView.swift @@ -10,7 +10,7 @@ import SwiftUI struct LoginView: View { @Environment(\.colorScheme) var colorScheme @Environment(\.sceneWidth) var sceneWidth - @StateObject var viewModel: LoginViewModel + @State var viewModel: LoginViewModel var body: some View { ZStack { diff --git a/DevLog/UI/Profile/ProfileView.swift b/DevLog/UI/Profile/ProfileView.swift index 0a436687..57e17240 100644 --- a/DevLog/UI/Profile/ProfileView.swift +++ b/DevLog/UI/Profile/ProfileView.swift @@ -8,8 +8,8 @@ import SwiftUI struct ProfileView: View { - @StateObject var viewModel: ProfileViewModel - @StateObject private var router = NavigationRouter() + @State var viewModel: ProfileViewModel + @State private var router = NavigationRouter() @Environment(\.diContainer) private var container @FocusState private var focusedOnStatusMessageTextField: Bool @@ -97,7 +97,7 @@ struct ProfileView: View { observeSystemThemeUseCase: container.resolve(ObserveSystemThemeUseCase.self), updateSystemThemeUseCase: container.resolve(UpdateSystemThemeUseCase.self) )) - .environmentObject(router) + .environment(router) case .activity(let activity): ProfileActivityTodoDetailView(activity: activity) } @@ -105,7 +105,7 @@ struct ProfileView: View { .onAppear { viewModel.send(.onAppear) } - .onChange(of: focusedOnStatusMessageTextField) { newValue in + .onChange(of: focusedOnStatusMessageTextField) { _, newValue in withAnimation { viewModel.send(.updateStatusTextFieldFocus(newValue)) } diff --git a/DevLog/UI/PushNotification/PushNotificationListView.swift b/DevLog/UI/PushNotification/PushNotificationListView.swift index 4411a6a3..e6b43f14 100644 --- a/DevLog/UI/PushNotification/PushNotificationListView.swift +++ b/DevLog/UI/PushNotification/PushNotificationListView.swift @@ -8,8 +8,8 @@ import SwiftUI struct PushNotificationListView: View { - @StateObject private var router = NavigationRouter() - @StateObject var viewModel: PushNotificationListViewModel + @State private var router = NavigationRouter() + @State var viewModel: PushNotificationListViewModel @Environment(\.sceneWidth) private var sceneWidth @Environment(\.colorScheme) private var colorScheme @Environment(\.diContainer) private var container: DIContainer diff --git a/DevLog/UI/Search/SearchView.swift b/DevLog/UI/Search/SearchView.swift index 86245b19..52acc452 100644 --- a/DevLog/UI/Search/SearchView.swift +++ b/DevLog/UI/Search/SearchView.swift @@ -11,8 +11,8 @@ struct SearchView: View { @Environment(\.dismiss) private var dismiss @Environment(\.sceneWidth) private var sceneWidth @Environment(\.diContainer) private var container: DIContainer - @StateObject private var router = NavigationRouter() - @StateObject var viewModel: SearchViewModel + @State private var router = NavigationRouter() + @State var viewModel: SearchViewModel var body: some View { NavigationStack(path: $router.path) { @@ -41,7 +41,7 @@ struct SearchView: View { viewModel.send(.setSearching(true)) } } - .onChange(of: viewModel.state.isSearching) { isSearching in + .onChange(of: viewModel.state.isSearching) { _, isSearching in if !isSearching { dismiss() } @@ -54,8 +54,6 @@ struct SearchView: View { } message: { Text(viewModel.state.alertMessage) } - // TODO: iOS 16에서 introspect 모듈을 사용하여 .searchable의 isPresented를 관리한다 - // .introspect(.searchField, on: iOS(.v16)) { searchBar in } } } diff --git a/DevLog/UI/Setting/AccountView.swift b/DevLog/UI/Setting/AccountView.swift index 8e0b7679..2f07f8ef 100644 --- a/DevLog/UI/Setting/AccountView.swift +++ b/DevLog/UI/Setting/AccountView.swift @@ -9,7 +9,7 @@ import SwiftUI import FirebaseAuth struct AccountView: View { - @StateObject var viewModel: AccountViewModel + @State var viewModel: AccountViewModel var body: some View { List { diff --git a/DevLog/UI/Setting/PushNotificationSettingsView.swift b/DevLog/UI/Setting/PushNotificationSettingsView.swift index 32898b5c..57465104 100644 --- a/DevLog/UI/Setting/PushNotificationSettingsView.swift +++ b/DevLog/UI/Setting/PushNotificationSettingsView.swift @@ -8,7 +8,7 @@ import SwiftUI struct PushNotificationSettingsView: View { - @StateObject var viewModel: PushNotificationSettingsViewModel + @State var viewModel: PushNotificationSettingsViewModel var body: some View { List { diff --git a/DevLog/UI/Setting/SettingView.swift b/DevLog/UI/Setting/SettingView.swift index b2d561c7..ca688978 100644 --- a/DevLog/UI/Setting/SettingView.swift +++ b/DevLog/UI/Setting/SettingView.swift @@ -9,8 +9,8 @@ import SwiftUI struct SettingView: View { @Environment(\.diContainer) var container: DIContainer - @StateObject var viewModel: SettingViewModel - @EnvironmentObject var router: NavigationRouter + @State var viewModel: SettingViewModel + @Environment(NavigationRouter.self) var router var body: some View { Form {