From d9d85ebab589e11c2b9b54624422a7284b9f75eb Mon Sep 17 00:00:00 2001 From: ryuchanghwi <78063938+ryuchanghwi@users.noreply.github.com> Date: Fri, 2 Jan 2026 20:57:57 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20realm=20->=20swiftData=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WorkoutDone/AGENTS.md | 142 +++++++++++ .../WorkoutDone.xcodeproj/project.pbxproj | 63 ++--- .../xcshareddata/swiftpm/Package.resolved | 21 +- .../xcschemes/WorkoutDone.xcscheme | 4 +- .../WorkoutDone/Resources/AppDelegate.swift | 3 - .../WorkoutDone/Resources/SceneDelegate.swift | 4 - .../Manager/Realm/BodyInfoDataManager.swift | 35 ++- .../Classes/Manager/Realm/DataManager.swift | 12 +- .../Protocols/RealmProviderProtocol.swift | 8 +- .../Realm/Providers/MockRealmProvider.swift | 26 +- .../Providers/ProductionRealmProvider.swift | 10 +- .../Classes/Manager/Realm/RealmManager.swift | 142 ++++------- .../Realm/WorkoutDoneDataManager.swift | 10 +- .../Sources/Model/MyRoutine/MyRoutine.swift | 21 +- .../Model/MyRoutine/MyWeightTraining.swift | 14 +- .../TemporaryRoutine/TemporaryRoutine.swift | 28 ++- .../Sources/Model/WorkOutDone/BodyInfo.swift | 17 +- .../Model/WorkOutDone/FrameImage.swift | 14 +- .../Sources/Model/WorkOutDone/Routine.swift | 14 +- .../Model/WorkOutDone/WeightTraining.swift | 22 +- .../WorkOutDone/WeightTrainingInfo.swift | 23 +- .../Model/WorkOutDone/WorkOutDoneData.swift | 39 +-- .../HomeButton/FrameImageViewModel.swift | 32 ++- .../HomeButtonLessPressShutterViewModel.swift | 42 ++-- .../DuringEditRoutineViewModel.swift | 8 +- .../DuringWorkout/DuringSetViewModel.swift | 46 ++-- .../DuringWorkoutResultViewModel.swift | 16 +- .../DuringWorkoutViewModel.swift | 6 +- .../DuringWorkout/EndWorkoutViewModel.swift | 61 ++--- .../InputWorkoutDataViewModel.swift | 44 ++-- .../HomeScene/Home/CalendarViewModel.swift | 9 +- .../HomeScene/Home/HomeViewModel.swift | 224 ++++++++---------- .../ImageSelectionViewModel.swift | 10 +- .../PhotoFrameTypeViewModel.swift | 47 ++-- .../RegisterMyBodyInfoViewController.swift | 2 +- .../RegisterMyBodyInfoViewModel.swift | 12 +- .../DeleteRecordAlertViewModel.swift | 16 +- .../WorkoutResultViewModel.swift | 11 +- .../Graph/FatPercentageGraphViewModel.swift | 8 +- .../Graph/SkeletalMuslemassViewModel.swift | 8 +- .../Analyze/Graph/WeightGraphViewModel.swift | 8 +- .../MyRecord/Gallery/GalleryViewModel.swift | 30 ++- .../Routine/RoutineViewModel.swift | 69 ++---- .../WorkoutDone/Utils/RealmManager.swift | 32 --- .../Utils/SwiftDataIdentifiers.swift | 14 ++ .../WorkoutDone/Utils/SwiftDataStack.swift | 30 +++ .../BodyInfoDataManagerTest.swift | 14 +- .../WorkoutDoneTests/HomeViewModelTests.swift | 106 +++++++++ .../SwiftDataManagerTests.swift | 34 +++ .../WorkoutDoneTests/WorkoutDoneTests.swift | 1 - 50 files changed, 855 insertions(+), 757 deletions(-) create mode 100644 WorkoutDone/AGENTS.md delete mode 100644 WorkoutDone/WorkoutDone/Utils/RealmManager.swift create mode 100644 WorkoutDone/WorkoutDone/Utils/SwiftDataIdentifiers.swift create mode 100644 WorkoutDone/WorkoutDone/Utils/SwiftDataStack.swift create mode 100644 WorkoutDone/WorkoutDoneTests/HomeViewModelTests.swift create mode 100644 WorkoutDone/WorkoutDoneTests/SwiftDataManagerTests.swift diff --git a/WorkoutDone/AGENTS.md b/WorkoutDone/AGENTS.md new file mode 100644 index 00000000..fc9cbfc2 --- /dev/null +++ b/WorkoutDone/AGENTS.md @@ -0,0 +1,142 @@ +0. Overview +This document defines the mandatory development rules for all human and AI agents working on this project. +The goal is to maintain: +Test-protected code +Domain-Driven Design (DDD) +Clear architectural boundaries +Stability across Xcode project regeneration +All agents must treat this document as a contract, not a guideline. + +1. Mandatory Development Cycle +All changes must follow this exact workflow: +Define -> Test -> Implement -> Test -> Generate -> Verify +Any deviation invalidates the change. + +2. Step 1 -- Define (Problem Definition) +Before implementation, clearly define: +The domain problem being solved +The target layer: +Domain / Application / Presentation +The owner of state changes (Entity / Domain Service / UseCase) +Prohibited +"Implement first, refactor later" +UI-driven domain decisions +Business rules inside ViewModels + +3. Step 2 -- Test First (Required) +Core Principles +No feature without tests +Domain behavior must be expressed in tests first +Tests validate behavior, not implementation details +Test Naming +func test_whenCondition_thenExpectedOutcome() +Example: +func test_whenOrderIsCancelled_thenPaymentIsNotCaptured() +Test Rules +Tests must read in domain language +UI / Network / Storage dependencies must be mocked +Domain tests must not depend on Apple frameworks + +4. Step 3 -- Implement +Implementation Rules +Write the minimum code required to pass tests +One type, one responsibility +Prefer state and types over conditional branching +Forbidden +No Boolean parameters without semantic meaning +No "helper", "manager", "util" naming +No external mutation of internal state + +5. Step 4 -- Run Tests (Mandatory) +All tests must pass. +Cmd + U +or CI: +xcodebuild test +No progression with failing tests +Never comment out tests to proceed + +6. Step 5 -- Generate Xcode Project +Xcode project generation is allowed only after tests pass. +Example (Tuist): +tuist generate +After generation: +Clean build +Full test run + +7. Architecture Principles (DDD) +Layering +Presentation + - View / ViewModel + +Application + - UseCase / ApplicationService + +Domain + - Entity + - ValueObject + - DomainService + - Repository (Protocol) + +Infrastructure + - Repository Implementations +Dependency Direction +Presentation -> Application -> Domain + +8. Domain Layer Rules (Strict) +The Domain layer is the core asset of this project. +Rules +No UIKit / SwiftUI / Combine / Alamofire imports +No singletons +No persistence or networking knowledge +Fully understandable through tests alone +Entity Rules +Has a unique identity +State changes only via methods +Invalid states are unrepresentable +order.cancel(by: user) +Value Object Rules +Immutable +Validated at creation +Equality by value +Repository Rules +Domain defines protocols only +Implementations live in Infrastructure +Async APIs use async/await + +9. Application Layer Rules +Orchestrates domain objects +Defines transaction boundaries +Contains no business rules itself + +10. Test Quality Bar +A test is considered good if: +The domain rule is clear by reading the test +It survives refactoring of internals +Failure messages explain the cause + +11. Code Review Checklist (Mandatory) +Agents must verify: +Tests were written first +No domain logic in ViewModels +Domain language is used in names +No boolean flags without meaning +Dependency direction is respected + +12. AI Agent Rules +AI agents must: +Never write implementation before tests +Explain layer placement decisions +Preserve external behavior during refactors +Explicitly state assumptions when required + +13. Definition of Done +A feature is complete only if: +Tests exist and pass +Xcode generation succeeds +Domain boundaries are intact +Intent is clear from tests alone + +14. Guiding Principle +UI can be replaced. +Domain endures. +Tests prove it. diff --git a/WorkoutDone/WorkoutDone.xcodeproj/project.pbxproj b/WorkoutDone/WorkoutDone.xcodeproj/project.pbxproj index af9d4596..f45395cb 100644 --- a/WorkoutDone/WorkoutDone.xcodeproj/project.pbxproj +++ b/WorkoutDone/WorkoutDone.xcodeproj/project.pbxproj @@ -14,14 +14,15 @@ 92005FDD29B1A49600586063 /* WorkoutDoneTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92005FDC29B1A49600586063 /* WorkoutDoneTests.swift */; }; 92005FE729B1A49600586063 /* WorkoutDoneUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92005FE629B1A49600586063 /* WorkoutDoneUITests.swift */; }; 92005FE929B1A49600586063 /* WorkoutDoneUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92005FE829B1A49600586063 /* WorkoutDoneUITestsLaunchTests.swift */; }; + A5B905772F066E01002106A3 /* SwiftDataManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B905762F066E01002106A3 /* SwiftDataManagerTests.swift */; }; + A5B9057A2F066E02002106A3 /* SwiftDataIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B905782F066E02002106A3 /* SwiftDataIdentifiers.swift */; }; + A5B9057B2F066E02002106A3 /* SwiftDataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B905792F066E02002106A3 /* SwiftDataStack.swift */; }; 92005FF729B1A4C700586063 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 92005FF629B1A4C700586063 /* SnapKit */; }; 92005FFA29B1A4D800586063 /* Then in Frameworks */ = {isa = PBXBuildFile; productRef = 92005FF929B1A4D800586063 /* Then */; }; 92005FFD29B1A51F00586063 /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 92005FFC29B1A51F00586063 /* RxCocoa */; }; 92005FFF29B1A51F00586063 /* RxRelay in Frameworks */ = {isa = PBXBuildFile; productRef = 92005FFE29B1A51F00586063 /* RxRelay */; }; 9200600129B1A51F00586063 /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 9200600029B1A51F00586063 /* RxSwift */; }; 9200600429B1A53A00586063 /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = 9200600329B1A53A00586063 /* DeviceKit */; }; - 9200600729B1A55A00586063 /* Realm in Frameworks */ = {isa = PBXBuildFile; productRef = 9200600629B1A55A00586063 /* Realm */; }; - 9200600929B1A55A00586063 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 9200600829B1A55A00586063 /* RealmSwift */; }; 9200601D29B1A74300586063 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9200601C29B1A74300586063 /* OnboardingViewController.swift */; }; 9200602429B1A7CC00586063 /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9200602329B1A7CC00586063 /* BaseViewController.swift */; }; 9200602729B1A7F400586063 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9200602629B1A7F400586063 /* UIColor.swift */; }; @@ -136,7 +137,6 @@ A5552D362A12834D00835B40 /* LimitedPhotoGalleryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5552D352A12834D00835B40 /* LimitedPhotoGalleryView.swift */; }; A5632CD82A0E2C08007EC335 /* ImageSelectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5632CD72A0E2C08007EC335 /* ImageSelectionViewModel.swift */; }; A5632CDA2A0E30F5007EC335 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5632CD92A0E30F5007EC335 /* UIImage.swift */; }; - A56757952A9DC44000D35381 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = A56757942A9DC44000D35381 /* RealmSwift */; }; A56D904229E08604009FD0B2 /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A56D904129E08604009FD0B2 /* HomeViewModel.swift */; }; A56F61502A3ECA42000879F5 /* InputWorkoutDataViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A56F614F2A3ECA42000879F5 /* InputWorkoutDataViewModel.swift */; }; A56FCBB92A9DB29700A23797 /* RegisterMyBodyInfoViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A56FCBB82A9DB29700A23797 /* RegisterMyBodyInfoViewModelTests.swift */; }; @@ -180,6 +180,7 @@ A5A10CAB29D59EF300E45FC8 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A10CAA29D59EF300E45FC8 /* Date.swift */; }; A5A584212AC1975D001DEC8B /* BodyInfoDataManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A584202AC1975D001DEC8B /* BodyInfoDataManagerTest.swift */; }; A5AB4CCE2A73A27D003F8997 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5AB4CCD2A73A27D003F8997 /* Config.swift */; }; + A5B905742F066DD0009F00E7 /* HomeViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B905732F066DD0009F00E7 /* HomeViewModelTests.swift */; }; A5C23DD32AB851B000800190 /* DataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5C23DD22AB851B000800190 /* DataManager.swift */; }; A5C23DD72AB9691A00800190 /* WorkoutDoneDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5C23DD62AB9691A00800190 /* WorkoutDoneDataManager.swift */; }; A5C23DD92AB96A0E00800190 /* BodyInfoDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5C23DD82AB96A0E00800190 /* BodyInfoDataManager.swift */; }; @@ -379,6 +380,10 @@ A5A584202AC1975D001DEC8B /* BodyInfoDataManagerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BodyInfoDataManagerTest.swift; sourceTree = ""; }; A5AB4CCC2A739F27003F8997 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; A5AB4CCD2A73A27D003F8997 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; + A5B905732F066DD0009F00E7 /* HomeViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModelTests.swift; sourceTree = ""; }; + A5B905762F066E01002106A3 /* SwiftDataManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftDataManagerTests.swift; sourceTree = ""; }; + A5B905782F066E02002106A3 /* SwiftDataIdentifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftDataIdentifiers.swift; sourceTree = ""; }; + A5B905792F066E02002106A3 /* SwiftDataStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftDataStack.swift; sourceTree = ""; }; A5C23DD22AB851B000800190 /* DataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataManager.swift; sourceTree = ""; }; A5C23DD62AB9691A00800190 /* WorkoutDoneDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutDoneDataManager.swift; sourceTree = ""; }; A5C23DD82AB96A0E00800190 /* BodyInfoDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BodyInfoDataManager.swift; sourceTree = ""; }; @@ -398,8 +403,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9200600929B1A55A00586063 /* RealmSwift in Frameworks */, - 9200600729B1A55A00586063 /* Realm in Frameworks */, 92005FFD29B1A51F00586063 /* RxCocoa in Frameworks */, 92005FF729B1A4C700586063 /* SnapKit in Frameworks */, 9200600129B1A51F00586063 /* RxSwift in Frameworks */, @@ -415,7 +418,6 @@ files = ( A55083342AB5E5C5002106A3 /* RxSwift in Frameworks */, A55083322AB5E5C0002106A3 /* RxCocoa in Frameworks */, - A56757952A9DC44000D35381 /* RealmSwift in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -465,6 +467,8 @@ 92005FDB29B1A49600586063 /* WorkoutDoneTests */ = { isa = PBXGroup; children = ( + A5B905732F066DD0009F00E7 /* HomeViewModelTests.swift */, + A5B905762F066E01002106A3 /* SwiftDataManagerTests.swift */, A5A5841F2AC19729001DEC8B /* BodyInfoDataManagerTest */, A5A5841A2AC17696001DEC8B /* RegisterMyBodyInfoTests */, 92005FDC29B1A49600586063 /* WorkoutDoneTests.swift */, @@ -621,6 +625,8 @@ A5021EE72A1906EB007DE096 /* Constants.swift */, A551F5C32A2923E300F96860 /* UNNotificationCenter.swift */, A5AB4CCD2A73A27D003F8997 /* Config.swift */, + A5B905782F066E02002106A3 /* SwiftDataIdentifiers.swift */, + A5B905792F066E02002106A3 /* SwiftDataStack.swift */, ); path = Utils; sourceTree = ""; @@ -1078,8 +1084,6 @@ 92005FFE29B1A51F00586063 /* RxRelay */, 9200600029B1A51F00586063 /* RxSwift */, 9200600329B1A53A00586063 /* DeviceKit */, - 9200600629B1A55A00586063 /* Realm */, - 9200600829B1A55A00586063 /* RealmSwift */, ); productName = WorkoutDone; productReference = 92005FC229B1A49400586063 /* WorkoutDone.app */; @@ -1100,7 +1104,6 @@ ); name = WorkoutDoneTests; packageProductDependencies = ( - A56757942A9DC44000D35381 /* RealmSwift */, A55083312AB5E5C0002106A3 /* RxCocoa */, A55083332AB5E5C5002106A3 /* RxSwift */, ); @@ -1163,7 +1166,6 @@ 92005FF829B1A4D800586063 /* XCRemoteSwiftPackageReference "Then" */, 92005FFB29B1A51F00586063 /* XCRemoteSwiftPackageReference "RxSwift" */, 9200600229B1A53A00586063 /* XCRemoteSwiftPackageReference "DeviceKit" */, - 9200600529B1A55A00586063 /* XCRemoteSwiftPackageReference "realm-swift" */, ); productRefGroup = 92005FC329B1A49400586063 /* Products */; projectDirPath = ""; @@ -1382,6 +1384,8 @@ 9200601D29B1A74300586063 /* OnboardingViewController.swift in Sources */, A5091F2B2A31BCDD002FE6DA /* InputWorkoutDataViewController.swift in Sources */, A5021EE82A1906EB007DE096 /* Constants.swift in Sources */, + A5B9057A2F066E02002106A3 /* SwiftDataIdentifiers.swift in Sources */, + A5B9057B2F066E02002106A3 /* SwiftDataStack.swift in Sources */, 9264A5EC29C4B0610010856F /* HomeButtonCameraViewController.swift in Sources */, 92DCF54229D5B41700F90002 /* MonthHeaderView.swift in Sources */, 929158472A3D92E40022BEF9 /* WorkoutSequenceCell.swift in Sources */, @@ -1405,6 +1409,8 @@ 92005FDD29B1A49600586063 /* WorkoutDoneTests.swift in Sources */, A56FCBB92A9DB29700A23797 /* RegisterMyBodyInfoViewModelTests.swift in Sources */, A5A584212AC1975D001DEC8B /* BodyInfoDataManagerTest.swift in Sources */, + A5B905742F066DD0009F00E7 /* HomeViewModelTests.swift in Sources */, + A5B905772F066E01002106A3 /* SwiftDataManagerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1495,7 +1501,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -1550,7 +1556,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -1578,7 +1584,7 @@ INFOPLIST_KEY_UIMainStoryboardFile = ""; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UIUserInterfaceStyle = Light; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1613,7 +1619,7 @@ INFOPLIST_KEY_UIMainStoryboardFile = ""; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UIUserInterfaceStyle = Light; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1637,8 +1643,9 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = JB8T59WMFR; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = WorkoutDone.WorkoutDoneTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1656,8 +1663,9 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = JB8T59WMFR; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = WorkoutDone.WorkoutDoneTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1776,14 +1784,6 @@ minimumVersion = 5.0.0; }; }; - 9200600529B1A55A00586063 /* XCRemoteSwiftPackageReference "realm-swift" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/realm/realm-swift"; - requirement = { - branch = master; - kind = branch; - }; - }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -1817,16 +1817,6 @@ package = 9200600229B1A53A00586063 /* XCRemoteSwiftPackageReference "DeviceKit" */; productName = DeviceKit; }; - 9200600629B1A55A00586063 /* Realm */ = { - isa = XCSwiftPackageProductDependency; - package = 9200600529B1A55A00586063 /* XCRemoteSwiftPackageReference "realm-swift" */; - productName = Realm; - }; - 9200600829B1A55A00586063 /* RealmSwift */ = { - isa = XCSwiftPackageProductDependency; - package = 9200600529B1A55A00586063 /* XCRemoteSwiftPackageReference "realm-swift" */; - productName = RealmSwift; - }; A55083312AB5E5C0002106A3 /* RxCocoa */ = { isa = XCSwiftPackageProductDependency; package = 92005FFB29B1A51F00586063 /* XCRemoteSwiftPackageReference "RxSwift" */; @@ -1837,11 +1827,6 @@ package = 92005FFB29B1A51F00586063 /* XCRemoteSwiftPackageReference "RxSwift" */; productName = RxSwift; }; - A56757942A9DC44000D35381 /* RealmSwift */ = { - isa = XCSwiftPackageProductDependency; - package = 9200600529B1A55A00586063 /* XCRemoteSwiftPackageReference "realm-swift" */; - productName = RealmSwift; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 92005FBA29B1A49400586063 /* Project object */; diff --git a/WorkoutDone/WorkoutDone.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/WorkoutDone/WorkoutDone.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ab6c1d38..a41d3f6f 100644 --- a/WorkoutDone/WorkoutDone.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/WorkoutDone/WorkoutDone.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "5e776322b647aac2561a2e5bcc51d62db4d87d473979f8f442be633b6109da3f", "pins" : [ { "identity" : "devicekit", @@ -9,24 +10,6 @@ "version" : "5.0.0" } }, - { - "identity" : "realm-core", - "kind" : "remoteSourceControl", - "location" : "https://github.com/realm/realm-core.git", - "state" : { - "revision" : "37cc58865648f343f7d6e538d45980e7f2351211", - "version" : "13.5.0" - } - }, - { - "identity" : "realm-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/realm/realm-swift", - "state" : { - "branch" : "master", - "revision" : "9da26e1cc9575dfd63060152c3c616a135426f03" - } - }, { "identity" : "rxswift", "kind" : "remoteSourceControl", @@ -55,5 +38,5 @@ } } ], - "version" : 2 + "version" : 3 } diff --git a/WorkoutDone/WorkoutDone.xcodeproj/xcshareddata/xcschemes/WorkoutDone.xcscheme b/WorkoutDone/WorkoutDone.xcodeproj/xcshareddata/xcschemes/WorkoutDone.xcscheme index a556b342..65a10e54 100644 --- a/WorkoutDone/WorkoutDone.xcodeproj/xcshareddata/xcschemes/WorkoutDone.xcscheme +++ b/WorkoutDone/WorkoutDone.xcodeproj/xcshareddata/xcschemes/WorkoutDone.xcscheme @@ -75,7 +75,7 @@ @@ -85,7 +85,7 @@ isEnabled = "YES"> diff --git a/WorkoutDone/WorkoutDone/Resources/AppDelegate.swift b/WorkoutDone/WorkoutDone/Resources/AppDelegate.swift index 8efc76b2..5a4ff4d1 100644 --- a/WorkoutDone/WorkoutDone/Resources/AppDelegate.swift +++ b/WorkoutDone/WorkoutDone/Resources/AppDelegate.swift @@ -6,7 +6,6 @@ // import UIKit -import RealmSwift import NotificationCenter @main @@ -14,8 +13,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. - let realm = try! Realm() - print(Realm.Configuration.defaultConfiguration.fileURL) return true } diff --git a/WorkoutDone/WorkoutDone/Resources/SceneDelegate.swift b/WorkoutDone/WorkoutDone/Resources/SceneDelegate.swift index ce49e31d..165d3164 100644 --- a/WorkoutDone/WorkoutDone/Resources/SceneDelegate.swift +++ b/WorkoutDone/WorkoutDone/Resources/SceneDelegate.swift @@ -6,7 +6,6 @@ // import UIKit -import RealmSwift @@ -16,8 +15,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { setRootViewController(scene) - let realm = try! Realm() - print(Realm.Configuration.defaultConfiguration.fileURL) print("willConnectTo") } @@ -97,4 +94,3 @@ extension SceneDelegate { } } - diff --git a/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/BodyInfoDataManager.swift b/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/BodyInfoDataManager.swift index 9881acb7..d381329c 100644 --- a/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/BodyInfoDataManager.swift +++ b/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/BodyInfoDataManager.swift @@ -1,39 +1,36 @@ import Foundation class BodyInfoDataManager { - let realmManager: RealmManager - - init(realmManager: RealmManager) { - self.realmManager = realmManager + let dataManager: SwiftDataManager + + init(dataManager: SwiftDataManager) { + self.dataManager = dataManager } func readBodyInfoData(id: Int) -> BodyInfo? { - let bodyInfoData = realmManager.readData(id: id, type: WorkOutDoneData.self)?.bodyInfo + let bodyInfoData = dataManager.readData(id: id, type: WorkOutDoneData.self)?.bodyInfo return bodyInfoData } func createBodyInfoData(weight: Double?, skeletalMusleMass: Double?, fatPercentage: Double?, date: String, id: Int) { let workoutDoneData = WorkOutDoneData(id: id, date: date) - let bodyInfo = BodyInfo() - bodyInfo.weight = weight - bodyInfo.skeletalMuscleMass = skeletalMusleMass - bodyInfo.fatPercentage = fatPercentage + let bodyInfo = BodyInfo(weight: weight, skeletalMuscleMass: skeletalMusleMass, fatPercentage: fatPercentage) workoutDoneData.bodyInfo = bodyInfo - realmManager.createData(data: workoutDoneData) + dataManager.createData(data: workoutDoneData) } func deleteBodyInfoData(id: Int) { - if let workoutDoneData = realmManager.readData(id: id, type: WorkOutDoneData.self) { - realmManager.deleteData(data: workoutDoneData.bodyInfo!) + if let workoutDoneData = dataManager.readData(id: id, type: WorkOutDoneData.self), + let bodyInfo = workoutDoneData.bodyInfo { + dataManager.deleteData(data: bodyInfo) } } func updateBodyInfoData(workoutDoneData: WorkOutDoneData, weight: Double?, skeletalMuscleMass: Double?, fatPercentage: Double?) { - realmManager.updateData(data: workoutDoneData) { updatedWorkOutDoneData in - let bodyInfo = BodyInfo() - bodyInfo.weight = weight - bodyInfo.skeletalMuscleMass = skeletalMuscleMass - bodyInfo.fatPercentage = fatPercentage - updatedWorkOutDoneData.bodyInfo = bodyInfo - + dataManager.updateData(data: workoutDoneData) { updatedWorkOutDoneData in + updatedWorkOutDoneData.bodyInfo = BodyInfo( + weight: weight, + skeletalMuscleMass: skeletalMuscleMass, + fatPercentage: fatPercentage + ) } } } diff --git a/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/DataManager.swift b/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/DataManager.swift index 233d0ddb..2faeae33 100644 --- a/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/DataManager.swift +++ b/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/DataManager.swift @@ -1,8 +1,10 @@ -import RealmSwift +import SwiftData protocol DataManager { - func createData(data: T) - func readData(id: Int, type: T.Type) -> T? - func updateData(data: T, updateBlock: (T) -> Void) - func deleteData(data: T) + func createData(data: T) + func createData(data: [T]) + func readData(id: Int, type: T.Type) -> T? + func readData(id: String, type: T.Type) -> T? + func updateData(data: T, updateBlock: (T) -> Void) + func deleteData(data: T) } diff --git a/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/Protocols/RealmProviderProtocol.swift b/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/Protocols/RealmProviderProtocol.swift index d0dd6266..3e6bafba 100644 --- a/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/Protocols/RealmProviderProtocol.swift +++ b/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/Protocols/RealmProviderProtocol.swift @@ -1,7 +1,5 @@ -import Foundation +import SwiftData -import RealmSwift - -protocol RealmProviderProtocol { - func makeRealm() throws -> Realm +protocol SwiftDataContextProviding { + func makeContext() -> ModelContext } diff --git a/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/Providers/MockRealmProvider.swift b/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/Providers/MockRealmProvider.swift index a8775853..f58dd530 100644 --- a/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/Providers/MockRealmProvider.swift +++ b/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/Providers/MockRealmProvider.swift @@ -1,9 +1,25 @@ -import Foundation +import SwiftData -import RealmSwift +final class MockSwiftDataProvider: SwiftDataContextProviding { + private let container: ModelContainer -class MockRealmProvider: RealmProviderProtocol { - func makeRealm() throws -> Realm { - return try Realm(configuration: Realm.Configuration(inMemoryIdentifier: "testRealm")) + init() { + let configuration = ModelConfiguration(isStoredInMemoryOnly: true) + container = try! ModelContainer( + for: WorkOutDoneData.self, + FrameImage.self, + BodyInfo.self, + Routine.self, + WeightTraining.self, + WeightTrainingInfo.self, + MyRoutine.self, + MyWeightTraining.self, + TemporaryRoutine.self, + configurations: configuration + ) + } + + func makeContext() -> ModelContext { + return ModelContext(container) } } diff --git a/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/Providers/ProductionRealmProvider.swift b/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/Providers/ProductionRealmProvider.swift index 60cd1e39..3f4904c2 100644 --- a/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/Providers/ProductionRealmProvider.swift +++ b/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/Providers/ProductionRealmProvider.swift @@ -1,9 +1,7 @@ -import Foundation +import SwiftData -import RealmSwift - -class ProductionRealmProvider: RealmProviderProtocol { - func makeRealm() throws -> Realm { - return try Realm() +final class ProductionSwiftDataProvider: @MainActor SwiftDataContextProviding { + @MainActor func makeContext() -> ModelContext { + return SwiftDataStack.shared.context } } diff --git a/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/RealmManager.swift b/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/RealmManager.swift index feff44a2..4b2d1bdc 100644 --- a/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/RealmManager.swift +++ b/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/RealmManager.swift @@ -1,115 +1,59 @@ -import RealmSwift +import SwiftData +import Foundation -class RealmManager3 { - static let shared = RealmManager3() - let realm = try! Realm() - private init() {} - - ///create - func createData(data: T) { - do { - try realm.write { - if let dataArray = data as? [Object] { - realm.add(dataArray) - } else if let object = data as? Object { - realm.add(object) - } else { - print("Unsupported data type: \(type(of: data))") - } - } - } catch { - print("Error saving data: \(error)") - } - } - ///read - func readData(id: Int, type: T.Type) -> T? { - let data = realm.object(ofType: type, forPrimaryKey: id) - return data +final class SwiftDataManager: DataManager { + static let shared = SwiftDataManager(context: SwiftDataStack.shared.context) + + let context: ModelContext + + init(context: ModelContext) { + self.context = context } - ///update - func updateData(data: T) { - do { - try realm.write { - if let dataArray = data as? [Object] { - realm.add(dataArray, update: .modified) - } else if let object = data as? Object { - realm.add(object, update: .modified) - } - else { - print("Unsupported data type: \(type(of: data))") - } - } - } - catch { - print("Error saving data: \(error)") - } + + func createData(data: T) { + context.insert(data) + saveContext() } - ///delete - func deleteData(_ data: T) { - do { - let realm = try Realm() - try realm.write { - realm.delete(data) - } - } catch { - print("Error deleting data: \(error)") - } + + func createData(data: [T]) { + data.forEach { context.insert($0) } + saveContext() } -} + func readData(id: Int, type: T.Type) -> T? { + let predicate = #Predicate { $0.id == id } + var descriptor = FetchDescriptor(predicate: predicate) + descriptor.fetchLimit = 1 + return try? context.fetch(descriptor).first + } + func readData(id: String, type: T.Type) -> T? { + let predicate = #Predicate { $0.id == id } + var descriptor = FetchDescriptor(predicate: predicate) + descriptor.fetchLimit = 1 + return try? context.fetch(descriptor).first + } -class RealmManager: DataManager { - let realm: Realm - - init(realm: Realm) { - self.realm = realm + func updateData(data: T, updateBlock: (T) -> Void) { + updateBlock(data) + saveContext() } - - // create - func createData(data: T) { - do { - try realm.write { - if let dataArray = data as? [Object] { - realm.add(dataArray) - } else if let object = data as? Object { - realm.add(object) - } else { - print("Unsupported data type: \(type(of: data))") - } - } - } catch { - print("Error saving data: \(error)") - } + + func deleteData(data: T) { + context.delete(data) + saveContext() } - // read - func readData(id: Int, type: T.Type) -> T? { - let data = realm.object(ofType: type, forPrimaryKey: id) - return data + + func fetchAll(_ type: T.Type) -> [T] { + let descriptor = FetchDescriptor() + return (try? context.fetch(descriptor)) ?? [] } - // update - func updateData(data: T, updateBlock: (T) -> Void) { + + private func saveContext() { do { - try realm.write { - updateBlock(data) - } + try context.save() } catch { print("Error saving data: \(error)") } } - // delete - func deleteData(data: T) { - do { - let realm = try Realm() - try realm.write { - if let data = data as? Object { - realm.delete(data) - } else { - print("Unsupported data type: \(type(of: data))") - } - } - } catch { - print("Error deleting data: \(error)") - } - } } diff --git a/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/WorkoutDoneDataManager.swift b/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/WorkoutDoneDataManager.swift index 32efac81..84b0ba4b 100644 --- a/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/WorkoutDoneDataManager.swift +++ b/WorkoutDone/WorkoutDone/Sources/Classes/Manager/Realm/WorkoutDoneDataManager.swift @@ -1,13 +1,13 @@ import Foundation class WorkoutDoneDataManager { - let realmManager: RealmManager - - init(realmManager: RealmManager) { - self.realmManager = realmManager + let dataManager: SwiftDataManager + + init(dataManager: SwiftDataManager) { + self.dataManager = dataManager } func readWorkoutDoneData(id: Int) -> WorkOutDoneData? { - let workoutDoneData = realmManager.readData(id: id, type: WorkOutDoneData.self) + let workoutDoneData = dataManager.readData(id: id, type: WorkOutDoneData.self) return workoutDoneData } } diff --git a/WorkoutDone/WorkoutDone/Sources/Model/MyRoutine/MyRoutine.swift b/WorkoutDone/WorkoutDone/Sources/Model/MyRoutine/MyRoutine.swift index 6f33711a..c65705c7 100644 --- a/WorkoutDone/WorkoutDone/Sources/Model/MyRoutine/MyRoutine.swift +++ b/WorkoutDone/WorkoutDone/Sources/Model/MyRoutine/MyRoutine.swift @@ -6,22 +6,19 @@ // import Foundation -import RealmSwift +import SwiftData -class MyRoutine : Object { - @Persisted dynamic var id : String - @Persisted dynamic var name : String - @Persisted dynamic var stamp : String - @Persisted dynamic var myWeightTraining: List +@Model +final class MyRoutine: StringIdentifiable { + @Attribute(.unique) var id: String + var name: String + var stamp: String + var myWeightTraining: [MyWeightTraining] - convenience init(name: String, stamp: String, myWeightTraining: List) { - self.init() + init(id: String = UUID().uuidString, name: String = "", stamp: String = "", myWeightTraining: [MyWeightTraining] = []) { + self.id = id self.name = name self.stamp = stamp self.myWeightTraining = myWeightTraining } - - override class func primaryKey() -> String? { - return "id" - } } diff --git a/WorkoutDone/WorkoutDone/Sources/Model/MyRoutine/MyWeightTraining.swift b/WorkoutDone/WorkoutDone/Sources/Model/MyRoutine/MyWeightTraining.swift index 50d7db01..32a5f1ad 100644 --- a/WorkoutDone/WorkoutDone/Sources/Model/MyRoutine/MyWeightTraining.swift +++ b/WorkoutDone/WorkoutDone/Sources/Model/MyRoutine/MyWeightTraining.swift @@ -6,14 +6,14 @@ // import Foundation -import RealmSwift +import SwiftData -class MyWeightTraining : Object { - @Persisted dynamic var myBodyPart : String - @Persisted dynamic var myWeightTraining : String - - convenience init(myBodyPart: String, myWeightTraining: String) { - self.init() +@Model +final class MyWeightTraining { + var myBodyPart: String + var myWeightTraining: String + + init(myBodyPart: String = "", myWeightTraining: String = "") { self.myBodyPart = myBodyPart self.myWeightTraining = myWeightTraining } diff --git a/WorkoutDone/WorkoutDone/Sources/Model/TemporaryRoutine/TemporaryRoutine.swift b/WorkoutDone/WorkoutDone/Sources/Model/TemporaryRoutine/TemporaryRoutine.swift index 5b3d4198..e19f9f8f 100644 --- a/WorkoutDone/WorkoutDone/Sources/Model/TemporaryRoutine/TemporaryRoutine.swift +++ b/WorkoutDone/WorkoutDone/Sources/Model/TemporaryRoutine/TemporaryRoutine.swift @@ -5,23 +5,27 @@ // Created by 류창휘 on 2023/06/29. // -import RealmSwift +import SwiftData -class TemporaryRoutine : Object { - @Persisted dynamic var id : Int = 0 - @Persisted dynamic var name : String - @Persisted dynamic var stamp : String - @Persisted dynamic var intDate : Int - @Persisted dynamic var weightTraining : List +@Model +final class TemporaryRoutine: IntIdentifiable { + @Attribute(.unique) var id: Int + var name: String + var stamp: String + var intDate: Int + var weightTraining: [WeightTraining] - convenience init(name: String, stamp: String, intDate: Int ,weightTraining: List) { - self.init() + init( + id: Int = 0, + name: String = "", + stamp: String = "", + intDate: Int, + weightTraining: [WeightTraining] = [] + ) { + self.id = id self.name = name self.stamp = stamp self.intDate = intDate self.weightTraining = weightTraining } - override class func primaryKey() -> String? { - return "id" - } } diff --git a/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/BodyInfo.swift b/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/BodyInfo.swift index 2da511bf..d787a072 100644 --- a/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/BodyInfo.swift +++ b/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/BodyInfo.swift @@ -6,10 +6,17 @@ // import Foundation -import RealmSwift +import SwiftData -class BodyInfo : Object { - @Persisted dynamic var weight : Double? - @Persisted dynamic var skeletalMuscleMass : Double? - @Persisted dynamic var fatPercentage : Double? +@Model +final class BodyInfo { + var weight: Double? + var skeletalMuscleMass: Double? + var fatPercentage: Double? + + init(weight: Double? = nil, skeletalMuscleMass: Double? = nil, fatPercentage: Double? = nil) { + self.weight = weight + self.skeletalMuscleMass = skeletalMuscleMass + self.fatPercentage = fatPercentage + } } diff --git a/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/FrameImage.swift b/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/FrameImage.swift index be35d825..854cf37f 100644 --- a/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/FrameImage.swift +++ b/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/FrameImage.swift @@ -6,14 +6,14 @@ // import Foundation -import RealmSwift +import SwiftData -class FrameImage : Object { - @Persisted dynamic var frameType : Int = 0 - @Persisted dynamic var image : Data? - - convenience init(frameType: Int, image: Data) { - self.init() +@Model +final class FrameImage { + var frameType: Int + var image: Data? + + init(frameType: Int = 0, image: Data? = nil) { self.frameType = frameType self.image = image } diff --git a/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/Routine.swift b/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/Routine.swift index 1c44960c..babf6cff 100644 --- a/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/Routine.swift +++ b/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/Routine.swift @@ -6,15 +6,15 @@ // import Foundation -import RealmSwift +import SwiftData -class Routine : Object { - @Persisted dynamic var name : String - @Persisted dynamic var stamp : String - @Persisted dynamic var weightTraining : List +@Model +final class Routine { + var name: String + var stamp: String + var weightTraining: [WeightTraining] - convenience init(name: String, stamp: String, weightTraining: List) { - self.init() + init(name: String = "", stamp: String = "", weightTraining: [WeightTraining] = []) { self.name = name self.stamp = stamp self.weightTraining = weightTraining diff --git a/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/WeightTraining.swift b/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/WeightTraining.swift index 1bc48153..b356052f 100644 --- a/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/WeightTraining.swift +++ b/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/WeightTraining.swift @@ -6,23 +6,17 @@ // import Foundation -import RealmSwift +import SwiftData -class WeightTraining : Object { - @Persisted dynamic var bodyPart : String - @Persisted dynamic var weightTraining : String - @Persisted dynamic var weightTrainingInfo : List - - convenience init(bodyPart: String, weightTraining: String, weightTrainingInfo: List) { - self.init() +@Model +final class WeightTraining { + var bodyPart: String + var weightTraining: String + var weightTrainingInfo: [WeightTrainingInfo] + + init(bodyPart: String = "", weightTraining: String = "", weightTrainingInfo: [WeightTrainingInfo] = []) { self.bodyPart = bodyPart self.weightTraining = weightTraining self.weightTrainingInfo = weightTrainingInfo } - - convenience init(bodyPart: String, weightTraining: String) { - self.init() - self.bodyPart = bodyPart - self.weightTraining = weightTraining - } } diff --git a/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/WeightTrainingInfo.swift b/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/WeightTrainingInfo.swift index a1662dcf..8e290708 100644 --- a/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/WeightTrainingInfo.swift +++ b/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/WeightTrainingInfo.swift @@ -6,24 +6,17 @@ // import Foundation -import RealmSwift +import SwiftData -class WeightTrainingInfo : Object { - @Persisted dynamic var setCount : Int - @Persisted dynamic var weight : Double? - @Persisted dynamic var trainingCount : Int? - - convenience init(setCount: Int, weight: Double?, trainingCount: Int?) { - self.init() +@Model +final class WeightTrainingInfo { + var setCount: Int + var weight: Double? + var trainingCount: Int? + + init(setCount: Int, weight: Double? = nil, trainingCount: Int? = nil) { self.setCount = setCount self.weight = weight self.trainingCount = trainingCount } - - convenience init(setCount: Int, trainingCount: Int) { - self.init() - self.setCount = setCount - self.trainingCount = trainingCount - } } - diff --git a/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/WorkOutDoneData.swift b/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/WorkOutDoneData.swift index 3417ae9e..aacbe3c3 100644 --- a/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/WorkOutDoneData.swift +++ b/WorkoutDone/WorkoutDone/Sources/Model/WorkOutDone/WorkOutDoneData.swift @@ -6,25 +6,30 @@ // import Foundation -import RealmSwift +import SwiftData -class WorkOutDoneData : Object { - @Persisted dynamic var id : Int = 0 - @Persisted dynamic var date : String = Date().yyyyMMddToString() - @Persisted dynamic var frameImage : FrameImage? - @Persisted dynamic var bodyInfo : BodyInfo? - @Persisted dynamic var workOutTime : Int? - @Persisted dynamic var routine : Routine? - - convenience init(id: Int, date: String) { - self.init() +@Model +final class WorkOutDoneData: IntIdentifiable { + @Attribute(.unique) var id: Int + var date: String + var frameImage: FrameImage? + var bodyInfo: BodyInfo? + var workOutTime: Int? + var routine: Routine? + + init( + id: Int, + date: String = Date().yyyyMMddToString(), + frameImage: FrameImage? = nil, + bodyInfo: BodyInfo? = nil, + workOutTime: Int? = nil, + routine: Routine? = nil + ) { self.id = id self.date = date - } - - override class func primaryKey() -> String? { - return "id" + self.frameImage = frameImage + self.bodyInfo = bodyInfo + self.workOutTime = workOutTime + self.routine = routine } } - - diff --git a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/Camera/HomeButton/FrameImageViewModel.swift b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/Camera/HomeButton/FrameImageViewModel.swift index 40f04d1d..022f9c50 100644 --- a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/Camera/HomeButton/FrameImageViewModel.swift +++ b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/Camera/HomeButton/FrameImageViewModel.swift @@ -6,33 +6,31 @@ // import UIKit -import RealmSwift +import SwiftData class FrameImageViewModel { + private let dataManager = SwiftDataManager.shared + func saveImageToRealm(date: Date, frameType: Int, image: UIImage) { guard let imageData = image.pngData() else { return } - - let realm = try! Realm() - if let existingWorkOutDone = realm.object(ofType: WorkOutDoneData.self, forPrimaryKey: date.dateToInt()) { - try! realm.write { - existingWorkOutDone.frameImage = FrameImage(frameType: frameType, image: imageData) + let id = date.dateToInt() + if let existingWorkOutDone = dataManager.readData(id: id, type: WorkOutDoneData.self) { + dataManager.updateData(data: existingWorkOutDone) { updatedData in + updatedData.frameImage = FrameImage(frameType: frameType, image: imageData) } } else { - let workOutDone = WorkOutDoneData(id: date.dateToInt(), date: date.yyyyMMddToString()) - workOutDone.frameImage = FrameImage(frameType: frameType, image: imageData) - - try! realm.write { - realm.add(workOutDone) - } + let workOutDone = WorkOutDoneData(id: id, date: date.yyyyMMddToString(), frameImage: FrameImage(frameType: frameType, image: imageData)) + dataManager.createData(data: workOutDone) } } func loadImageFromRealm(date: Date) -> UIImage? { - let realm = try! Realm() - - let workOutDone = realm.objects(WorkOutDoneData.self).filter("date == %@", date.yyyyMMddToString()) - - guard let frameImage = workOutDone.first?.frameImage else { return nil } + let dateString = date.yyyyMMddToString() + let predicate = #Predicate { $0.date == dateString } + var descriptor = FetchDescriptor(predicate: predicate) + descriptor.fetchLimit = 1 + let workOutDone = try? dataManager.context.fetch(descriptor).first + guard let frameImage = workOutDone?.frameImage else { return nil } return UIImage(data: frameImage.image!) } } diff --git a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/Camera/HomeButtonLess/HomeButtonLessPressShutterViewModel.swift b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/Camera/HomeButtonLess/HomeButtonLessPressShutterViewModel.swift index 20376cf7..6678b6e5 100644 --- a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/Camera/HomeButtonLess/HomeButtonLessPressShutterViewModel.swift +++ b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/Camera/HomeButtonLess/HomeButtonLessPressShutterViewModel.swift @@ -8,16 +8,9 @@ import UIKit import RxCocoa import RxSwift -import RealmSwift class HomeButtonLessPressShutterViewModel { - let realm = try! Realm() - let realmManager = RealmManager3.shared - var workOutDoneData : Results? - init(workOutDoneData: Results? = nil) { - self.workOutDoneData = realm.objects(WorkOutDoneData.self) - - } + private let dataManager = SwiftDataManager.shared struct Input { let selectedData : Driver let selectedFrameType : Driver @@ -28,29 +21,27 @@ class HomeButtonLessPressShutterViewModel { let saveData : Driver } - ///Realm Create + ///SwiftData Create func createFrameImageData(image : UIImage, id : Int, date : String, frameType : Int) { let workoutDoneData = WorkOutDoneData(id: id, date: date) - let frameImage = FrameImage() - frameImage.image = image.pngData() - frameImage.frameType = frameType + let frameImage = FrameImage(frameType: frameType, image: image.pngData()) workoutDoneData.frameImage = frameImage - realmManager.createData(data: workoutDoneData) + dataManager.createData(data: workoutDoneData) } - ///Realm Read + ///SwiftData Read func readWorkoutDoneData(id : Int) -> WorkOutDoneData? { - let selectedWorkoutDoneData = realmManager.readData(id: id, type: WorkOutDoneData.self) + let selectedWorkoutDoneData = dataManager.readData(id: id, type: WorkOutDoneData.self) return selectedWorkoutDoneData } ///id값으로 데이터가 있는지 판별 func validFrameImageData(id : Int) -> Bool { - let selectedBodyInfoData = realm.object(ofType: WorkOutDoneData.self, forPrimaryKey: id) + let selectedBodyInfoData = readWorkoutDoneData(id: id) return selectedBodyInfoData?.frameImage == nil ? false : true } func validWorkoutDoneData(id : Int) -> Bool { - let selectedWorkoutDoneData = realm.object(ofType: WorkOutDoneData.self, forPrimaryKey: id) + let selectedWorkoutDoneData = readWorkoutDoneData(id: id) return selectedWorkoutDoneData == nil ? false : true } ///id 값(string) -> Date(string)으로 변경 @@ -76,22 +67,19 @@ class HomeButtonLessPressShutterViewModel { ///FrameImage 데이터 존재하는 경우 - update if self.validFrameImageData(id: id) { print("FrameImage 데이터 존재하는 경우 - update") - let workoutDoneData = self.readWorkoutDoneData(id: id) - try! self.realm.write { - workoutDoneData?.frameImage?.image = image.pngData() - workoutDoneData?.frameImage?.frameType = frame + guard let workoutDoneData = self.readWorkoutDoneData(id: id) else { return } + self.dataManager.updateData(data: workoutDoneData) { updatedData in + updatedData.frameImage?.image = image.pngData() + updatedData.frameImage?.frameType = frame } } ///FrameImage 데이터 존재하는 경우 - create else { print("FrameImage 데이터 존재하지 않는 경우 - create") guard let workOutDoneData = self.readWorkoutDoneData(id: id) else { return } - let frameImage = FrameImage() - frameImage.frameType = frame - frameImage.image = image.pngData() - try! self.realm.write { - workOutDoneData.frameImage = frameImage - self.realm.add(workOutDoneData) + let frameImage = FrameImage(frameType: frame, image: image.pngData()) + self.dataManager.updateData(data: workOutDoneData) { updatedData in + updatedData.frameImage = frameImage } } } diff --git a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/DuringEditRoutineViewModel.swift b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/DuringEditRoutineViewModel.swift index 55b877bb..3430361c 100644 --- a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/DuringEditRoutineViewModel.swift +++ b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/DuringEditRoutineViewModel.swift @@ -8,11 +8,9 @@ import UIKit import RxCocoa import RxSwift -import RealmSwift class DuringEditRoutineViewModel { - let realm = try! Realm() - let realmManager = RealmManager3.shared + private let dataManager = SwiftDataManager.shared let duringWorkoutRoutine = DuringWorkoutRoutine.shared struct Input { @@ -24,7 +22,7 @@ class DuringEditRoutineViewModel { } func readTemporaryRoutineData() -> TemporaryRoutine? { - let temporaryRoutineData = realmManager.readData(id: 0, type: TemporaryRoutine.self) + let temporaryRoutineData = dataManager.readData(id: 0, type: TemporaryRoutine.self) return temporaryRoutineData } @@ -35,7 +33,7 @@ class DuringEditRoutineViewModel { let weightTrainingValue = routine?.weightTraining if let weightTrainingValue = weightTrainingValue { - return Array(weightTrainingValue) + return weightTrainingValue } else { return [] diff --git a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/DuringSetViewModel.swift b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/DuringSetViewModel.swift index 81ee37f0..65a1ecd9 100644 --- a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/DuringSetViewModel.swift +++ b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/DuringSetViewModel.swift @@ -8,11 +8,9 @@ import UIKit import RxCocoa import RxSwift -import RealmSwift class DuringSetViewModel { - let realm = try! Realm() - let realmManager = RealmManager3.shared + private let dataManager = SwiftDataManager.shared let duringWorkoutRoutine = DuringWorkoutRoutine.shared struct Input { @@ -34,33 +32,27 @@ class DuringSetViewModel { } func readTemporaryRoutineData() -> TemporaryRoutine? { - let temporaryRoutineData = realmManager.readData(id: 0, type: TemporaryRoutine.self) + let temporaryRoutineData = dataManager.readData(id: 0, type: TemporaryRoutine.self) return temporaryRoutineData } func deleteTemporaryRoutineData(infoArrayIndex : Int, arrayIndex : Int) { - let temporaryRoutineData = readTemporaryRoutineData() - if let weightTrainingInfo = temporaryRoutineData?.weightTraining[arrayIndex].weightTrainingInfo[infoArrayIndex] { - realmManager.deleteData(weightTrainingInfo) + guard let temporaryRoutineData = readTemporaryRoutineData() else { return } + dataManager.updateData(data: temporaryRoutineData) { updatedRoutine in + guard updatedRoutine.weightTraining.indices.contains(arrayIndex) else { return } + guard updatedRoutine.weightTraining[arrayIndex].weightTrainingInfo.indices.contains(infoArrayIndex) else { return } + updatedRoutine.weightTraining[arrayIndex].weightTrainingInfo.remove(at: infoArrayIndex) } } func updateTemporaryRoutineSet(infoArrayIndex : Int, arrayIndex : Int) { - let temporaryRoutineData = readTemporaryRoutineData() - let weightTrainingInfoCount = temporaryRoutineData?.weightTraining[arrayIndex].weightTrainingInfo.count - if let weightTrainingInfoArray = temporaryRoutineData?.weightTraining[arrayIndex].weightTrainingInfo { - for (index, weightTrainingInfo) in weightTrainingInfoArray.enumerated() { - do { - try realm.write { - weightTrainingInfo.setCount = index + 1 - } - } - catch { - print(error) - } + guard let temporaryRoutineData = readTemporaryRoutineData() else { return } + dataManager.updateData(data: temporaryRoutineData) { updatedRoutine in + guard updatedRoutine.weightTraining.indices.contains(arrayIndex) else { return } + for (index, weightTrainingInfo) in updatedRoutine.weightTraining[arrayIndex].weightTrainingInfo.enumerated() { + weightTrainingInfo.setCount = index + 1 } } - } func transform(input : Input) -> Output { @@ -85,16 +77,12 @@ class DuringSetViewModel { let addData = Driver.zip( input.addWeightTrainingInfoTrigger, input.addWeightTrainingInfoIndexTrigger, resultSelector: { (_, index) in let routine = self.readTemporaryRoutineData() let count = routine?.weightTraining[index].weightTrainingInfo.count - do { - try self.realm.write { - let weightTrainingInfo = WeightTrainingInfo() - weightTrainingInfo.setCount = (count ?? 0) + 1 - weightTrainingInfo.trainingCount = nil - weightTrainingInfo.weight = nil - routine?.weightTraining[index].weightTrainingInfo.append(weightTrainingInfo) + if let routine = routine { + self.dataManager.updateData(data: routine) { updatedRoutine in + guard updatedRoutine.weightTraining.indices.contains(index) else { return } + let weightTrainingInfo = WeightTrainingInfo(setCount: (count ?? 0) + 1) + updatedRoutine.weightTraining[index].weightTrainingInfo.append(weightTrainingInfo) } - } catch { - print("Error saving new items, \(error)") } return true }) diff --git a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/DuringWorkoutResultViewModel.swift b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/DuringWorkoutResultViewModel.swift index 9076c901..097940e2 100644 --- a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/DuringWorkoutResultViewModel.swift +++ b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/DuringWorkoutResultViewModel.swift @@ -8,15 +8,13 @@ import UIKit import RxCocoa import RxSwift -import RealmSwift //제목이 있는지 없는지 확인하는 bool 타입 하나 만들기 //false 인 경우 label hidden //3 class DuringWorkoutResultViewModel { - let realm = try! Realm() - let realmManager = RealmManager3.shared + private let dataManager = SwiftDataManager.shared struct Input { let loadView : Driver @@ -32,16 +30,16 @@ class DuringWorkoutResultViewModel { } func readTemporaryRoutineData() -> TemporaryRoutine? { - let temporaryRoutineData = realmManager.readData(id: 0, type: TemporaryRoutine.self) + let temporaryRoutineData = dataManager.readData(id: 0, type: TemporaryRoutine.self) return temporaryRoutineData } func deleteTemporaryRoutineData() { guard let temporaryRoutineData = readTemporaryRoutineData() else { return } - realmManager.deleteData(temporaryRoutineData) + dataManager.deleteData(data: temporaryRoutineData) } func readWorkoutDoneData(id : Int) -> WorkOutDoneData? { - let workoutDoneData = RealmManager3.shared.readData(id: id, type: WorkOutDoneData.self) + let workoutDoneData = dataManager.readData(id: id, type: WorkOutDoneData.self) return workoutDoneData } @@ -69,11 +67,11 @@ class DuringWorkoutResultViewModel { guard let weightTraining = workoutData?.routine?.weightTraining else { return [] } let arrayWeightTraining = Array(weightTraining) - let letterCounts = arrayWeightTraining.reduce(into: [:]) { counts, word in - counts[word, default: 0] += 1 + let letterCounts = arrayWeightTraining.reduce(into: [String: Int]()) { counts, training in + counts[training.bodyPart, default: 0] += 1 } let sortedByCount = letterCounts.sorted { $0.value > $1.value } - let result = Array(sortedByCount.prefix(3).map { $0.key .bodyPart}) + let result = Array(sortedByCount.prefix(3).map { $0.key }) return result } diff --git a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/DuringWorkoutViewModel.swift b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/DuringWorkoutViewModel.swift index 0b8844e7..6b8a1ff9 100644 --- a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/DuringWorkoutViewModel.swift +++ b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/DuringWorkoutViewModel.swift @@ -6,13 +6,11 @@ // import UIKit -import RealmSwift import RxCocoa import RxSwift class DuringWorkoutViewModel { - let realm = try! Realm() - let realmManager = RealmManager3.shared + private let dataManager = SwiftDataManager.shared struct Input { let loadView : Driver let weightTrainingArrayIndex : Driver @@ -25,7 +23,7 @@ class DuringWorkoutViewModel { } func readTemporaryRoutineData() -> TemporaryRoutine? { - let temporaryRoutineData = realmManager.readData(id: 0, type: TemporaryRoutine.self) + let temporaryRoutineData = dataManager.readData(id: 0, type: TemporaryRoutine.self) return temporaryRoutineData } diff --git a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/EndWorkoutViewModel.swift b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/EndWorkoutViewModel.swift index 58a2bf04..60f03116 100644 --- a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/EndWorkoutViewModel.swift +++ b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/EndWorkoutViewModel.swift @@ -6,13 +6,11 @@ // import UIKit -import RealmSwift import RxCocoa import RxSwift class EndWorkoutViewModel { - let realm = try! Realm() - let realmManager = RealmManager3.shared + private let dataManager = SwiftDataManager.shared struct Input { let saveTrigger : Driver @@ -25,37 +23,40 @@ class EndWorkoutViewModel { } func validWorkoutDoneData(id : Int) -> Bool { - let selectedWorkoutDoneData = realm.object(ofType: WorkOutDoneData.self, forPrimaryKey: id) + let selectedWorkoutDoneData = dataManager.readData(id: id, type: WorkOutDoneData.self) return selectedWorkoutDoneData == nil ? false : true } ///id값으로 Routine데이터가 있는지 판별 func validRoutineData(id : Int) -> Bool { - let selectedBodyInfoData = realm.object(ofType: WorkOutDoneData.self, forPrimaryKey: id) + let selectedBodyInfoData = dataManager.readData(id: id, type: WorkOutDoneData.self) return selectedBodyInfoData?.routine == nil ? false : true } ///id값으로 workoutDoneData 가져오기 func readWorkoutDoneData(id : Int) -> WorkOutDoneData? { - let workoutDoneData = RealmManager3.shared.readData(id: id, type: WorkOutDoneData.self) + let workoutDoneData = dataManager.readData(id: id, type: WorkOutDoneData.self) return workoutDoneData } ///id값으로 temporaryRoutineData 가져오기 func readTemporaryRoutineData() -> TemporaryRoutine? { - let temporaryRoutineData = realmManager.readData(id: 0, type: TemporaryRoutine.self) + let temporaryRoutineData = dataManager.readData(id: 0, type: TemporaryRoutine.self) return temporaryRoutineData } func createRoutineData(id : Int, date : String, totalWorkoutTime : Int) { - let workoutDoneData = WorkOutDoneData(id: id, date: date) let temporaryRoutineData = readTemporaryRoutineData() - let routine = Routine() if let temporaryRoutineData = temporaryRoutineData { - routine.weightTraining = temporaryRoutineData.weightTraining - routine.name = temporaryRoutineData.name - routine.stamp = temporaryRoutineData.stamp - workoutDoneData.routine = routine - workoutDoneData.workOutTime = totalWorkoutTime - realmManager.createData(data: workoutDoneData) + let routine = Routine( + name: temporaryRoutineData.name, + stamp: temporaryRoutineData.stamp, + weightTraining: temporaryRoutineData.weightTraining + ) + let workoutDoneData = WorkOutDoneData( + id: id, + date: date, + workOutTime: totalWorkoutTime, routine: routine + ) + dataManager.createData(data: workoutDoneData) } } ///id 값(string) -> Date(string)으로 변경 @@ -88,28 +89,28 @@ class EndWorkoutViewModel { ///Routine 데이터 존재하는 경우 - update if self.validRoutineData(id: id) { print("Routine 데이터 존재하는 경우 - update") - let workoutDoneData = self.readWorkoutDoneData(id: id) + guard let workoutDoneData = self.readWorkoutDoneData(id: id) else { return false } if let temporaryRoutineData = self.readTemporaryRoutineData() { - try! self.realm.write { - workoutDoneData?.routine?.stamp = temporaryRoutineData.stamp - workoutDoneData?.routine?.name = temporaryRoutineData.name - workoutDoneData?.routine?.weightTraining = temporaryRoutineData.weightTraining - workoutDoneData?.workOutTime = time + self.dataManager.updateData(data: workoutDoneData) { updatedData in + updatedData.routine?.stamp = temporaryRoutineData.stamp + updatedData.routine?.name = temporaryRoutineData.name + updatedData.routine?.weightTraining = temporaryRoutineData.weightTraining + updatedData.workOutTime = time } } } else { print("나머지 케이스") - let workoutDoneData = self.readWorkoutDoneData(id: id) - let routine = Routine() + guard let workoutDoneData = self.readWorkoutDoneData(id: id) else { return false } if let temporaryRoutineData = self.readTemporaryRoutineData() { - routine.weightTraining = temporaryRoutineData.weightTraining - routine.name = temporaryRoutineData.name - routine.stamp = temporaryRoutineData.stamp - try! self.realm.write { - workoutDoneData?.routine = routine - workoutDoneData?.workOutTime = time - self.realm.add(workoutDoneData!) + let routine = Routine( + name: temporaryRoutineData.name, + stamp: temporaryRoutineData.stamp, + weightTraining: temporaryRoutineData.weightTraining + ) + self.dataManager.updateData(data: workoutDoneData) { updatedData in + updatedData.routine = routine + updatedData.workOutTime = time } } } diff --git a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/InputWorkoutDataViewModel.swift b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/InputWorkoutDataViewModel.swift index 0a057a15..2b099bfc 100644 --- a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/InputWorkoutDataViewModel.swift +++ b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/DuringWorkout/InputWorkoutDataViewModel.swift @@ -8,13 +8,10 @@ import UIKit import RxSwift import RxCocoa -import RealmSwift class InputWorkoutDataViewModel { let duringWorkoutRoutine = DuringWorkoutRoutine.shared - - let realm = try! Realm() - let realmManager = RealmManager3.shared + private let dataManager = SwiftDataManager.shared struct Input { let countInputText : Driver @@ -31,38 +28,29 @@ class InputWorkoutDataViewModel { } func readTemporaryRoutineData() -> TemporaryRoutine? { - let temporaryRoutineData = realmManager.readData(id: 0, type: TemporaryRoutine.self) + let temporaryRoutineData = dataManager.readData(id: 0, type: TemporaryRoutine.self) return temporaryRoutineData } func updateCalisthenicsTemporaryRoutineData(count : Int, infoArrayIndex : Int, arrayIndex : Int) { - let temporaryRoutineData = readTemporaryRoutineData() - if let weightTrainingInfo = temporaryRoutineData?.weightTraining[arrayIndex].weightTrainingInfo[infoArrayIndex] { - do { - try realm.write { - weightTrainingInfo.weight = nil - weightTrainingInfo.trainingCount = count - - } - } - catch { - print(error) - } + guard let temporaryRoutineData = readTemporaryRoutineData() else { return } + dataManager.updateData(data: temporaryRoutineData) { updatedRoutine in + guard updatedRoutine.weightTraining.indices.contains(arrayIndex) else { return } + guard updatedRoutine.weightTraining[arrayIndex].weightTrainingInfo.indices.contains(infoArrayIndex) else { return } + let weightTrainingInfo = updatedRoutine.weightTraining[arrayIndex].weightTrainingInfo[infoArrayIndex] + weightTrainingInfo.weight = nil + weightTrainingInfo.trainingCount = count } } func updateTemporaryRoutineData(count : Int, weight : Double, infoArrayIndex : Int, arrayIndex : Int) { - let temporaryRoutineData = readTemporaryRoutineData() - if let weightTrainingInfo = temporaryRoutineData?.weightTraining[arrayIndex].weightTrainingInfo[infoArrayIndex] { - do { - try realm.write { - weightTrainingInfo.weight = weight - weightTrainingInfo.trainingCount = count - } - } - catch { - print(error) - } + guard let temporaryRoutineData = readTemporaryRoutineData() else { return } + dataManager.updateData(data: temporaryRoutineData) { updatedRoutine in + guard updatedRoutine.weightTraining.indices.contains(arrayIndex) else { return } + guard updatedRoutine.weightTraining[arrayIndex].weightTrainingInfo.indices.contains(infoArrayIndex) else { return } + let weightTrainingInfo = updatedRoutine.weightTraining[arrayIndex].weightTrainingInfo[infoArrayIndex] + weightTrainingInfo.weight = weight + weightTrainingInfo.trainingCount = count } } diff --git a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/Home/CalendarViewModel.swift b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/Home/CalendarViewModel.swift index b8dc6fe3..5e8e6e50 100644 --- a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/Home/CalendarViewModel.swift +++ b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/Home/CalendarViewModel.swift @@ -6,13 +6,14 @@ // import Foundation -import RealmSwift +import SwiftData struct CalendarViewModel { func loadStampImage(date: String) -> [String: String] { - let realm = try! Realm() - - let workOutDoneData : [WorkOutDoneData] = realm.objects(WorkOutDoneData.self).sorted(byKeyPath: "date", ascending: false).filter("date CONTAINS %@", date).compactMap{$0} + let predicate = #Predicate { $0.date.contains(date) } + let sortByDate = [SortDescriptor(\WorkOutDoneData.date, order: .reverse)] + let descriptor = FetchDescriptor(predicate: predicate, sortBy: sortByDate) + let workOutDoneData: [WorkOutDoneData] = (try? SwiftDataManager.shared.context.fetch(descriptor)) ?? [] var dayStamp = [String: String]() for workOutDone in workOutDoneData { diff --git a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/Home/HomeViewModel.swift b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/Home/HomeViewModel.swift index b8e5c3bc..e6837986 100644 --- a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/Home/HomeViewModel.swift +++ b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/Home/HomeViewModel.swift @@ -7,23 +7,54 @@ import RxSwift import RxCocoa -import RealmSwift +import SwiftData import UIKit -class HomeViewModel { - let realm = try! Realm() - var workOutDoneData : Results? - init(workOutDoneData: Results? = nil) { - self.workOutDoneData = realm.objects(WorkOutDoneData.self) - +protocol WorkOutDoneDataProviding { + func workoutDoneData(for id: Int) -> WorkOutDoneData? +} + + +final class SwiftDataWorkOutDoneDataProvider: WorkOutDoneDataProviding { + private let context: ModelContext + + init(context: ModelContext = SwiftDataStack.shared.context) { + self.context = context + } + + func workoutDoneData(for id: Int) -> WorkOutDoneData? { + let predicate = #Predicate { $0.id == id } + var descriptor = FetchDescriptor(predicate: predicate) + descriptor.fetchLimit = 1 + return try? context.fetch(descriptor).first + } +} + +struct HomeWorkoutViewState { + let weightText: String + let skeletalMuscleMassText: String + let fatPercentageText: String + let image: UIImage + let workoutTimeText: String + let workoutRoutineTitleText: String + let isWorkout: Bool + let routineBodyParts: [String] + let hasRoutineTitle: Bool +} + +final class HomeViewModel { + private let dataProvider: WorkOutDoneDataProviding + + init(dataProvider: WorkOutDoneDataProviding = SwiftDataWorkOutDoneDataProvider()) { + self.dataProvider = dataProvider } struct Input { - let selectedDate : Driver - let loadView : Driver + let selectedDate: Driver + let loadView: Driver } struct Output { - let weightData : Driver - let skeletalMusleMassData : Driver + let weightData: Driver + let skeletalMusleMassData: Driver let fatPercentageData : Driver let imageData : Driver let workoutTimeData : Driver @@ -33,9 +64,8 @@ class HomeViewModel { let hasRoutineTitle: Driver } - func readWorkoutDoneData(id : Int) -> WorkOutDoneData? { - let selectedWorkoutDoneData = realm.object(ofType: WorkOutDoneData.self, forPrimaryKey: id) - return selectedWorkoutDoneData + func readWorkoutDoneData(id: Int) -> WorkOutDoneData? { + return dataProvider.workoutDoneData(for: id) } func convertIntToTimeValue(_ seconds: Int) -> String { @@ -62,115 +92,67 @@ class HomeViewModel { return result } + + func makeViewState(for id: Int) -> HomeWorkoutViewState { + let workoutDoneData = readWorkoutDoneData(id: id) + let bodyInfo = workoutDoneData?.bodyInfo + + let weightText = bodyInfo?.weight.map { String($0.truncateDecimalPoint()) } ?? "-" + let skeletalMuscleMassText = bodyInfo?.skeletalMuscleMass.map { String($0.truncateDecimalPoint()) } ?? "-" + let fatPercentageText = bodyInfo?.fatPercentage.map { String($0.truncateDecimalPoint()) } ?? "-" + let image = workoutDoneData?.frameImage?.image + .map { UIImage(data: $0)! } ?? UIImage() + let workoutTimeText = workoutDoneData?.workOutTime.map { convertIntToTimeValue($0) } ?? "00:00:00" + let workoutRoutineTitleText = workoutDoneData?.routine?.name ?? "-" + let isWorkout = workoutDoneData?.routine?.weightTraining != nil + + let routineTitle = workoutDoneData?.routine?.name ?? "" + let isRoutineTitleEmpty = workoutDoneData != nil && routineTitle == "" + let routineBodyParts = isRoutineTitleEmpty ? sortBodyPart(id: id) : [] + let hasRoutineTitle = !isRoutineTitleEmpty + + return HomeWorkoutViewState( + weightText: weightText, + skeletalMuscleMassText: skeletalMuscleMassText, + fatPercentageText: fatPercentageText, + image: image, + workoutTimeText: workoutTimeText, + workoutRoutineTitleText: workoutRoutineTitleText, + isWorkout: isWorkout, + routineBodyParts: routineBodyParts, + hasRoutineTitle: hasRoutineTitle + ) + } - func transform(input : Input) -> Output { - let weightData = Driver.combineLatest(input.loadView, input.selectedDate, resultSelector: { (load, date) in - let weight = self.readWorkoutDoneData(id: date)?.bodyInfo?.weight - if let validWeight = weight { - return String(validWeight.truncateDecimalPoint()) - } - else { - return "-" - } - }) - let skeletalMusleMassData = Driver.combineLatest(input.loadView, input.selectedDate, resultSelector: { (load, date) in - let skeletalMusleMass = self.readWorkoutDoneData(id: date)?.bodyInfo?.skeletalMuscleMass - if let validSkeletalMusleMass = skeletalMusleMass { - return String(validSkeletalMusleMass.truncateDecimalPoint()) - } - else { - return "-" - } - - }) - let fatPercentageData = Driver.combineLatest(input.loadView, input.selectedDate, resultSelector: { (load, date) in - let fatPercentage = self.readWorkoutDoneData(id: date)?.bodyInfo?.fatPercentage - if let validFatPercentage = fatPercentage { - return String(validFatPercentage.truncateDecimalPoint()) - } - else { - return "-" - } - }) - let imageData = Driver.combineLatest(input.loadView, input.selectedDate, resultSelector: { (load, date) in - let imageData = self.readWorkoutDoneData(id: date)?.frameImage?.image - if let validImageData = imageData { - return UIImage(data: validImageData)! - } - else { - return UIImage() - } - }) - - let workoutTimeData = Driver.combineLatest(input.loadView, input.selectedDate, resultSelector: { (_, date) in - let timeValue = self.readWorkoutDoneData(id: date)?.workOutTime - if let timeValue = timeValue { - let timeString = self.convertIntToTimeValue(timeValue) - return timeString - } - else { - return "00:00:00" - } - }) - - let workoutRoutineTitleData = Driver.combineLatest(input.loadView, input.selectedDate, resultSelector: { (_, date) in - let routineTitleValue = self.readWorkoutDoneData(id: date)?.routine?.name - - if let routineTitleValue = routineTitleValue { - ///비어있을때 해야함 todo - return routineTitleValue - } - else { - return "-" - } - }) - - let isWorkout = Driver.combineLatest(input.loadView, input.selectedDate, resultSelector: { (_, date) in - let weightTrainingData = self.readWorkoutDoneData(id: date)?.routine?.weightTraining - if let _ = weightTrainingData { - return true - - } - else { - return false - } - }) - - let routineBodyPartArray = Driver<[String]>.combineLatest(input.loadView, input.selectedDate, resultSelector: { (_, date) in - - let workoutDoneData = self.readWorkoutDoneData(id: date) - let workoutTimeData = self.readWorkoutDoneData(id: date)?.workOutTime - let routineTitleData = self.readWorkoutDoneData(id: date)?.routine?.name - - if workoutDoneData != nil && routineTitleData == "" { - return self.sortBodyPart(id: date) - } - else { - return [] - } - }) - - let hasRoutineTitle = Driver.combineLatest(input.loadView, input.selectedDate, resultSelector: { (_, date) in - let workoutDoneData = self.readWorkoutDoneData(id: date) - let workoutTimeData = self.readWorkoutDoneData(id: date)?.workOutTime - let routineTitleData = self.readWorkoutDoneData(id: date)?.routine?.name - if workoutDoneData != nil && routineTitleData == "" { - return false - } - else { - return true - } - }) + func transform(input: Input) -> Output { + let viewState = Driver.combineLatest( + input.loadView, + input.selectedDate, + resultSelector: { [weak self] _, date in + return self?.makeViewState(for: date) ?? HomeWorkoutViewState( + weightText: "-", + skeletalMuscleMassText: "-", + fatPercentageText: "-", + image: UIImage(), + workoutTimeText: "00:00:00", + workoutRoutineTitleText: "-", + isWorkout: false, + routineBodyParts: [], + hasRoutineTitle: true + ) + } + ) return Output( - weightData: weightData, - skeletalMusleMassData: skeletalMusleMassData, - fatPercentageData: fatPercentageData, - imageData: imageData, - workoutTimeData: workoutTimeData, - workoutRoutineTitleData: workoutRoutineTitleData, - isWorkout: isWorkout, - routineBodyPartArray: routineBodyPartArray, - hasRoutineTitle: hasRoutineTitle) + weightData: viewState.map { $0.weightText }, + skeletalMusleMassData: viewState.map { $0.skeletalMuscleMassText }, + fatPercentageData: viewState.map { $0.fatPercentageText }, + imageData: viewState.map { $0.image }, + workoutTimeData: viewState.map { $0.workoutTimeText }, + workoutRoutineTitleData: viewState.map { $0.workoutRoutineTitleText }, + isWorkout: viewState.map { $0.isWorkout }, + routineBodyPartArray: viewState.map { $0.routineBodyParts }, + hasRoutineTitle: viewState.map { $0.hasRoutineTitle } + ) } } diff --git a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/ImageSelection/ImageSelectionViewModel.swift b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/ImageSelection/ImageSelectionViewModel.swift index d2b39f28..61fedab0 100644 --- a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/ImageSelection/ImageSelectionViewModel.swift +++ b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/ImageSelection/ImageSelectionViewModel.swift @@ -7,10 +7,9 @@ import RxCocoa import RxSwift -import RealmSwift class ImageSelectionViewModel { - let realm = try! Realm() + private let dataManager = SwiftDataManager.shared struct Input { let loadView : Driver @@ -23,13 +22,14 @@ class ImageSelectionViewModel { } func validFrameImageData(id : Int) -> Bool { - let selectedBodyInfoData = realm.object(ofType: WorkOutDoneData.self, forPrimaryKey: id) + let selectedBodyInfoData = dataManager.readData(id: id, type: WorkOutDoneData.self) return selectedBodyInfoData?.frameImage == nil ? false : true } func deleteFrameImageData(id : Int) { - guard let workoutDoneData = realm.object(ofType: WorkOutDoneData.self, forPrimaryKey: id) else { return } - RealmManager3.shared.deleteData(workoutDoneData.frameImage!) + guard let workoutDoneData = dataManager.readData(id: id, type: WorkOutDoneData.self), + let frameImage = workoutDoneData.frameImage else { return } + dataManager.deleteData(data: frameImage) } func transform(input : Input) -> Output { diff --git a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/PhotoGallery/PhotoFrameType/PhotoFrameTypeViewModel.swift b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/PhotoGallery/PhotoFrameType/PhotoFrameTypeViewModel.swift index 279290f8..552b9dc8 100644 --- a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/PhotoGallery/PhotoFrameType/PhotoFrameTypeViewModel.swift +++ b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/PhotoGallery/PhotoFrameType/PhotoFrameTypeViewModel.swift @@ -8,11 +8,9 @@ import UIKit import RxCocoa import RxSwift -import RealmSwift class PhotoFrameTypeViewModel { - let realm = try! Realm() - let realmManager = RealmManager3.shared + private let dataManager = SwiftDataManager.shared struct Input { let frameTypeButtonStatus : Driver @@ -27,37 +25,35 @@ class PhotoFrameTypeViewModel { let saveData : Driver } - ///Realm Create + ///SwiftData Create func createFrameImageData(image : UIImage, id : Int, date : String, frameType : Int) { let workoutDoneData = WorkOutDoneData(id: id, date: date) - let frameImage = FrameImage() - frameImage.image = image.pngData() - frameImage.frameType = frameType + let frameImage = FrameImage(frameType: frameType, image: image.pngData()) workoutDoneData.frameImage = frameImage - realmManager.createData(data: workoutDoneData) + dataManager.createData(data: workoutDoneData) } - ///Realm Read + ///SwiftData Read func readWorkoutDoneData(id : Int) -> WorkOutDoneData? { - let selectedWorkoutDoneData = realmManager.readData(id: id, type: WorkOutDoneData.self) + let selectedWorkoutDoneData = dataManager.readData(id: id, type: WorkOutDoneData.self) return selectedWorkoutDoneData } - ///Realm Update + ///SwiftData Update func updateFrameImageData(image : UIImage, id : Int, date : String, frameType : Int) { let workoutDoneData = WorkOutDoneData(id: id, date: date) - let frameImage = FrameImage() - frameImage.image = image.pngData() - frameImage.frameType = frameType - realmManager.updateData(data: workoutDoneData) + let frameImage = FrameImage(frameType: frameType, image: image.pngData()) + dataManager.updateData(data: workoutDoneData) { updatedData in + updatedData.frameImage = frameImage + } } ///id값으로 데이터가 있는지 판별 func validFrameImageData(id : Int) -> Bool { - let selectedBodyInfoData = realm.object(ofType: WorkOutDoneData.self, forPrimaryKey: id) + let selectedBodyInfoData = readWorkoutDoneData(id: id) return selectedBodyInfoData?.frameImage == nil ? false : true } func validWorkoutDoneData(id : Int) -> Bool { - let selectedWorkoutDoneData = realm.object(ofType: WorkOutDoneData.self, forPrimaryKey: id) + let selectedWorkoutDoneData = readWorkoutDoneData(id: id) return selectedWorkoutDoneData == nil ? false : true } @@ -84,22 +80,19 @@ class PhotoFrameTypeViewModel { ///FrameImage 데이터 존재하는 경우 - update if self.validFrameImageData(id: id) { print("FrameImage 데이터 존재하는 경우 - update") - let workoutDoneData = self.readWorkoutDoneData(id: id) - try! self.realm.write { - workoutDoneData?.frameImage?.image = image.pngData() - workoutDoneData?.frameImage?.frameType = frame + guard let workoutDoneData = self.readWorkoutDoneData(id: id) else { return } + self.dataManager.updateData(data: workoutDoneData) { updatedData in + updatedData.frameImage?.image = image.pngData() + updatedData.frameImage?.frameType = frame } } ///FrameImage 데이터 존재하는 않는 경우 - create else { print("FrameImage 데이터 존재하는 않는 경우 - create") guard let workoutDoneData = self.readWorkoutDoneData(id: id) else { return } - let frameImage = FrameImage() - frameImage.frameType = frame - frameImage.image = image.pngData() - try! self.realm.write { - workoutDoneData.frameImage = frameImage - self.realm.add(workoutDoneData) + let frameImage = FrameImage(frameType: frame, image: image.pngData()) + self.dataManager.updateData(data: workoutDoneData) { updatedData in + updatedData.frameImage = frameImage } } } diff --git a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/RegisterMyBodyInfoScene/RegisterMyBodyInfoViewController.swift b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/RegisterMyBodyInfoScene/RegisterMyBodyInfoViewController.swift index 72fe513f..35e190e4 100644 --- a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/RegisterMyBodyInfoScene/RegisterMyBodyInfoViewController.swift +++ b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/RegisterMyBodyInfoScene/RegisterMyBodyInfoViewController.swift @@ -20,7 +20,7 @@ struct BodyInputData { class RegisterMyBodyInfoViewController: BaseViewController { // MARK: - Property - private var viewModel = RegisterMyBodyInfoViewModel(realmProvider: ProductionRealmProvider()) + private var viewModel = RegisterMyBodyInfoViewModel(contextProvider: ProductionSwiftDataProvider()) private var bodyInputData = PublishSubject() var selectedDate: Int? private var didLoad = PublishSubject() diff --git a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/RegisterMyBodyInfoScene/RegisterMyBodyInfoViewModel.swift b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/RegisterMyBodyInfoScene/RegisterMyBodyInfoViewModel.swift index 883c55d1..377abe60 100644 --- a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/RegisterMyBodyInfoScene/RegisterMyBodyInfoViewModel.swift +++ b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/RegisterMyBodyInfoScene/RegisterMyBodyInfoViewModel.swift @@ -4,14 +4,14 @@ import RxCocoa import RxSwift struct RegisterMyBodyInfoViewModel: ViewModelType { - let realmProvider: RealmProviderProtocol + let contextProvider: SwiftDataContextProviding let workoutdataManager: WorkoutDoneDataManager let bodyInfoDataManager: BodyInfoDataManager - init(realmProvider: RealmProviderProtocol) { - self.realmProvider = realmProvider - let realmManager = RealmManager(realm: try! realmProvider.makeRealm()) - self.workoutdataManager = WorkoutDoneDataManager(realmManager: realmManager) - self.bodyInfoDataManager = BodyInfoDataManager(realmManager: realmManager) + init(contextProvider: SwiftDataContextProviding) { + self.contextProvider = contextProvider + let dataManager = SwiftDataManager(context: contextProvider.makeContext()) + self.workoutdataManager = WorkoutDoneDataManager(dataManager: dataManager) + self.bodyInfoDataManager = BodyInfoDataManager(dataManager: dataManager) } struct Input { diff --git a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/WorkoutResutl/DeleteRecordAlertViewModel.swift b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/WorkoutResutl/DeleteRecordAlertViewModel.swift index 5d236f19..726c884e 100644 --- a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/WorkoutResutl/DeleteRecordAlertViewModel.swift +++ b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/WorkoutResutl/DeleteRecordAlertViewModel.swift @@ -6,33 +6,27 @@ // import UIKit -import RealmSwift import RxSwift import RxCocoa class DeleteRecordAlertViewModel { - let realm = try! Realm() - let realmManager = RealmManager3.shared - var workOutDoneData : Results? - init(workOutDoneData: Results? = nil) { - self.workOutDoneData = realm.objects(WorkOutDoneData.self) - } + private let dataManager = SwiftDataManager.shared func readWorkoutDoneData(id : Int) -> WorkOutDoneData? { - let workoutDoneData = RealmManager3.shared.readData(id: id, type: WorkOutDoneData.self) + let workoutDoneData = dataManager.readData(id: id, type: WorkOutDoneData.self) return workoutDoneData } func deleteRoutineData(id : Int) { guard let workoutDoneData = self.readWorkoutDoneData(id: id) else { return } if let routine = workoutDoneData.routine { - realmManager.deleteData(routine) + dataManager.deleteData(data: routine) } } func deleteWorkoutTimeData(id : Int) { guard let workoutDoneData = self.readWorkoutDoneData(id: id) else { return } - try! realm.write { - workoutDoneData.workOutTime = nil + dataManager.updateData(data: workoutDoneData) { updatedData in + updatedData.workOutTime = nil } } diff --git a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/WorkoutResutl/WorkoutResultViewModel.swift b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/WorkoutResutl/WorkoutResultViewModel.swift index c6f9b74e..9e19c122 100644 --- a/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/WorkoutResutl/WorkoutResultViewModel.swift +++ b/WorkoutDone/WorkoutDone/Sources/Presenter/HomeScene/WorkoutResutl/WorkoutResultViewModel.swift @@ -7,16 +7,9 @@ import RxSwift import RxCocoa -import RealmSwift class WorkoutResultViewModel { - let realm = try! Realm() - let realmManager = RealmManager3.shared - var workOutDoneData : Results? - init(workOutDoneData: Results? = nil) { - self.workOutDoneData = realm.objects(WorkOutDoneData.self) - - } + private let dataManager = SwiftDataManager.shared struct Input { let loadView : Driver @@ -31,7 +24,7 @@ class WorkoutResultViewModel { } func readWorkoutDoneData(id : Int) -> WorkOutDoneData? { - let workoutDoneData = realmManager.readData(id: id, type: WorkOutDoneData.self) + let workoutDoneData = dataManager.readData(id: id, type: WorkOutDoneData.self) return workoutDoneData } diff --git a/WorkoutDone/WorkoutDone/Sources/Presenter/MyRecordScene/MyRecord/Analyze/Graph/FatPercentageGraphViewModel.swift b/WorkoutDone/WorkoutDone/Sources/Presenter/MyRecordScene/MyRecord/Analyze/Graph/FatPercentageGraphViewModel.swift index 3bba08c6..458a86a0 100644 --- a/WorkoutDone/WorkoutDone/Sources/Presenter/MyRecordScene/MyRecord/Analyze/Graph/FatPercentageGraphViewModel.swift +++ b/WorkoutDone/WorkoutDone/Sources/Presenter/MyRecordScene/MyRecord/Analyze/Graph/FatPercentageGraphViewModel.swift @@ -6,15 +6,15 @@ // import SwiftUI -import RealmSwift +import SwiftData class FatPercentageGraphViewModel : ObservableObject { - let realm = try! Realm() @Published var fatPercentageData : [WorkOutDoneData] = [] func readFatPercentageData() { - let objects = realm.objects(WorkOutDoneData.self) - fatPercentageData = Array(objects) + let descriptor = FetchDescriptor() + let objects = (try? SwiftDataManager.shared.context.fetch(descriptor)) ?? [] + fatPercentageData = objects .sorted(by: { $0.date.yyMMddToDate() ?? Date() < $1.date.yyMMddToDate() ?? Date() }) .filter({ $0.bodyInfo?.fatPercentage != nil && $0.bodyInfo?.fatPercentage ?? 0 >= 0 diff --git a/WorkoutDone/WorkoutDone/Sources/Presenter/MyRecordScene/MyRecord/Analyze/Graph/SkeletalMuslemassViewModel.swift b/WorkoutDone/WorkoutDone/Sources/Presenter/MyRecordScene/MyRecord/Analyze/Graph/SkeletalMuslemassViewModel.swift index 7b3adfa8..f7094662 100644 --- a/WorkoutDone/WorkoutDone/Sources/Presenter/MyRecordScene/MyRecord/Analyze/Graph/SkeletalMuslemassViewModel.swift +++ b/WorkoutDone/WorkoutDone/Sources/Presenter/MyRecordScene/MyRecord/Analyze/Graph/SkeletalMuslemassViewModel.swift @@ -6,15 +6,15 @@ // import SwiftUI -import RealmSwift +import SwiftData class SkeletalMuslemassGraphViewModel : ObservableObject { - let realm = try! Realm() @Published var skeletalMusleMassData : [WorkOutDoneData] = [] func readSkeletalMusleMassData() { - let objects = realm.objects(WorkOutDoneData.self) - skeletalMusleMassData = Array(objects) + let descriptor = FetchDescriptor() + let objects = (try? SwiftDataManager.shared.context.fetch(descriptor)) ?? [] + skeletalMusleMassData = objects .sorted(by: { $0.date.yyMMddToDate() ?? Date() < $1.date.yyMMddToDate() ?? Date() }) .filter({ $0.bodyInfo?.skeletalMuscleMass != nil && $0.bodyInfo?.skeletalMuscleMass ?? 0 >= 0 diff --git a/WorkoutDone/WorkoutDone/Sources/Presenter/MyRecordScene/MyRecord/Analyze/Graph/WeightGraphViewModel.swift b/WorkoutDone/WorkoutDone/Sources/Presenter/MyRecordScene/MyRecord/Analyze/Graph/WeightGraphViewModel.swift index 1e494565..afe8d426 100644 --- a/WorkoutDone/WorkoutDone/Sources/Presenter/MyRecordScene/MyRecord/Analyze/Graph/WeightGraphViewModel.swift +++ b/WorkoutDone/WorkoutDone/Sources/Presenter/MyRecordScene/MyRecord/Analyze/Graph/WeightGraphViewModel.swift @@ -6,15 +6,15 @@ // import SwiftUI -import RealmSwift +import SwiftData class WeightGraphViewModel : ObservableObject { - let realm = try! Realm() @Published var weightData : [WorkOutDoneData] = [] func readWeightData() { - let objects = realm.objects(WorkOutDoneData.self) - weightData = Array(objects) + let descriptor = FetchDescriptor() + let objects = (try? SwiftDataManager.shared.context.fetch(descriptor)) ?? [] + weightData = objects .sorted(by: { $0.date.yyMMddToDate() ?? Date() < $1.date.yyMMddToDate() ?? Date() }) .filter({ $0.bodyInfo?.weight != nil && $0.bodyInfo?.weight ?? 0 >= 0 diff --git a/WorkoutDone/WorkoutDone/Sources/Presenter/MyRecordScene/MyRecord/Gallery/GalleryViewModel.swift b/WorkoutDone/WorkoutDone/Sources/Presenter/MyRecordScene/MyRecord/Gallery/GalleryViewModel.swift index 6ed95ecc..c81f4384 100644 --- a/WorkoutDone/WorkoutDone/Sources/Presenter/MyRecordScene/MyRecord/Gallery/GalleryViewModel.swift +++ b/WorkoutDone/WorkoutDone/Sources/Presenter/MyRecordScene/MyRecord/Gallery/GalleryViewModel.swift @@ -6,13 +6,13 @@ // import UIKit -import RealmSwift +import SwiftData struct GalleryViewModel { func loadImagesForMonth() -> [String: [(String, UIImage)]] { - let realm = try! Realm() - - let workOutDoneData : [WorkOutDoneData] = realm.objects(WorkOutDoneData.self).sorted(byKeyPath: "date", ascending: false).compactMap{$0} + let sortByDate = [SortDescriptor(\WorkOutDoneData.date, order: .reverse)] + let descriptor = FetchDescriptor(sortBy: sortByDate) + let workOutDoneData: [WorkOutDoneData] = (try? SwiftDataManager.shared.context.fetch(descriptor)) ?? [] var monthImages = [String: [(String, UIImage)]]() for workOutDone in workOutDoneData { @@ -25,9 +25,10 @@ struct GalleryViewModel { } func loadImagesForFrame(frameIndex: Int) -> [(String, UIImage)] { - let realm = try! Realm() - - let workOutDoneData = realm.objects(WorkOutDoneData.self).sorted(byKeyPath: "date", ascending: false).filter("frameImage.frameType == %@", frameIndex) + let predicate = #Predicate { $0.frameImage?.frameType == frameIndex } + let sortByDate = [SortDescriptor(\WorkOutDoneData.date, order: .reverse)] + let descriptor = FetchDescriptor(predicate: predicate, sortBy: sortByDate) + let workOutDoneData = (try? SwiftDataManager.shared.context.fetch(descriptor)) ?? [] var images = [(String, UIImage)]() for dateImage in workOutDoneData { @@ -48,14 +49,11 @@ struct GalleryViewModel { } func deleteImage(date: String) { - let realm = try! Realm() - - let imageData = realm.objects(WorkOutDoneData.self).filter("date == %@", date).first! - - guard let frameImage = imageData.frameImage else { return } - - try! realm.write { - realm.delete(frameImage) - } + let predicate = #Predicate { $0.date == date } + var descriptor = FetchDescriptor(predicate: predicate) + descriptor.fetchLimit = 1 + guard let imageData = try? SwiftDataManager.shared.context.fetch(descriptor).first, + let frameImage = imageData.frameImage else { return } + SwiftDataManager.shared.deleteData(data: frameImage) } } diff --git a/WorkoutDone/WorkoutDone/Sources/Presenter/RoutineScene/Routine/RoutineViewModel.swift b/WorkoutDone/WorkoutDone/Sources/Presenter/RoutineScene/Routine/RoutineViewModel.swift index a06fbb75..a18d2ecf 100644 --- a/WorkoutDone/WorkoutDone/Sources/Presenter/RoutineScene/Routine/RoutineViewModel.swift +++ b/WorkoutDone/WorkoutDone/Sources/Presenter/RoutineScene/Routine/RoutineViewModel.swift @@ -6,20 +6,16 @@ // import UIKit -import RealmSwift +import SwiftData struct RoutineViewModel { func loadMyRoutine() -> [MyRoutine] { - let realm = try! Realm() - let myRoutine = realm.objects(MyRoutine.self) - - return Array(myRoutine) + let descriptor = FetchDescriptor() + return (try? SwiftDataManager.shared.context.fetch(descriptor)) ?? [] } func loadMyRoutineName(id: String) -> String { - let realm = try! Realm() - - guard let myRoutine = realm.objects(MyRoutine.self).filter("id == %@", id).first else { + guard let myRoutine = SwiftDataManager.shared.readData(id: id, type: MyRoutine.self) else { return "" } @@ -29,9 +25,7 @@ struct RoutineViewModel { } func loadMyRoutineStamp(id: String) -> String { - let realm = try! Realm() - - guard let myRoutine = realm.objects(MyRoutine.self).filter("id == %@", id).first else { + guard let myRoutine = SwiftDataManager.shared.readData(id: id, type: MyRoutine.self) else { return "" } @@ -41,39 +35,26 @@ struct RoutineViewModel { } func saveMyRoutine(id: String?, name: String, stamp: String, weightTraining: [MyWeightTraining]) { - let realm = try! Realm() - - if let id = id, let existingMyRoutine = realm.object(ofType: MyRoutine.self, forPrimaryKey: id) { - - try! realm.write { - existingMyRoutine.name = name - existingMyRoutine.stamp = stamp - existingMyRoutine.myWeightTraining.removeAll() - existingMyRoutine.myWeightTraining.append(objectsIn: weightTraining) - - realm.add(existingMyRoutine) + if let id = id, let existingMyRoutine = SwiftDataManager.shared.readData(id: id, type: MyRoutine.self) { + SwiftDataManager.shared.updateData(data: existingMyRoutine) { updatedRoutine in + updatedRoutine.name = name + updatedRoutine.stamp = stamp + updatedRoutine.myWeightTraining.removeAll() + updatedRoutine.myWeightTraining.append(contentsOf: weightTraining) } } else { - let myRoutine = MyRoutine() - - myRoutine.id = UUID().uuidString - myRoutine.name = name - myRoutine.stamp = stamp - myRoutine.myWeightTraining.append(objectsIn: weightTraining) - - try! realm.write { - realm.add(myRoutine) - } + let myRoutine = MyRoutine(id: UUID().uuidString, name: name, stamp: stamp, myWeightTraining: weightTraining) + SwiftDataManager.shared.createData(data: myRoutine) } } func setRoutine(routineIndex: Int?, weightTraining: [WeightTraining], id: Int) { - let temporaryRoutine = TemporaryRoutine() + let temporaryRoutine = TemporaryRoutine(id: 0, intDate: id) if let index = routineIndex { - let realm = try! Realm() - - let myRoutine = realm.objects(MyRoutine.self)[index] + let myRoutines = loadMyRoutine() + guard myRoutines.indices.contains(index) else { return } + let myRoutine = myRoutines[index] temporaryRoutine.name = myRoutine.name temporaryRoutine.stamp = myRoutine.stamp } else { @@ -81,28 +62,22 @@ struct RoutineViewModel { temporaryRoutine.stamp = "" } temporaryRoutine.intDate = id - temporaryRoutine.weightTraining.append(objectsIn: setWeightTraining(weightTraining)) - let realmManager = RealmManager3.shared - realmManager.createData(data: temporaryRoutine) + temporaryRoutine.weightTraining.append(contentsOf: setWeightTraining(weightTraining)) + SwiftDataManager.shared.createData(data: temporaryRoutine) } func setWeightTraining(_ weightTraining: [WeightTraining]) -> [WeightTraining] { for training in weightTraining { - training.weightTrainingInfo.append(objectsIn: [WeightTrainingInfo(setCount: 1, weight: nil, trainingCount: nil)]) + training.weightTrainingInfo.append(WeightTrainingInfo(setCount: 1, weight: nil, trainingCount: nil)) } return weightTraining } func deleteRoutine(id: [String]) { - let realm = try! Realm() - for myRoutineId in id { - if let myRoutine = realm.object(ofType: MyRoutine.self, forPrimaryKey: myRoutineId) { - try! realm.write { - realm.delete(myRoutine) - } + if let myRoutine = SwiftDataManager.shared.readData(id: myRoutineId, type: MyRoutine.self) { + SwiftDataManager.shared.deleteData(data: myRoutine) } } } } - diff --git a/WorkoutDone/WorkoutDone/Utils/RealmManager.swift b/WorkoutDone/WorkoutDone/Utils/RealmManager.swift deleted file mode 100644 index 8bc36a59..00000000 --- a/WorkoutDone/WorkoutDone/Utils/RealmManager.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// RealmManager.swift -// WorkoutDone -// -// Created by 류창휘 on 2023/04/30. -// - -import Foundation -import RealmSwift - -class RealmManager { - static let shared = RealmManager() - let realm = try! Realm() - private init() {} - - ///create - func createData(data: T) { - do { - try realm.write { - if let dataArray = data as? [Object] { - realm.add(dataArray) - } else if let object = data as? Object { - realm.add(object) - } else { - print("Unsupported data type: \(type(of: data))") - } - } - } catch { - print("Error saving data: \(error)") - } - } -} diff --git a/WorkoutDone/WorkoutDone/Utils/SwiftDataIdentifiers.swift b/WorkoutDone/WorkoutDone/Utils/SwiftDataIdentifiers.swift new file mode 100644 index 00000000..d3d00e19 --- /dev/null +++ b/WorkoutDone/WorkoutDone/Utils/SwiftDataIdentifiers.swift @@ -0,0 +1,14 @@ +// +// SwiftDataIdentifiers.swift +// WorkoutDone +// +// Created by Codex on 2025/02/14. +// + +protocol IntIdentifiable { + var id: Int { get set } +} + +protocol StringIdentifiable { + var id: String { get set } +} diff --git a/WorkoutDone/WorkoutDone/Utils/SwiftDataStack.swift b/WorkoutDone/WorkoutDone/Utils/SwiftDataStack.swift new file mode 100644 index 00000000..04c31f47 --- /dev/null +++ b/WorkoutDone/WorkoutDone/Utils/SwiftDataStack.swift @@ -0,0 +1,30 @@ +// +// SwiftDataStack.swift +// WorkoutDone +// +// Created by Codex on 2025/02/14. +// + +import SwiftData + +final class SwiftDataStack { + static let shared = SwiftDataStack() + + let container: ModelContainer + let context: ModelContext + + private init() { + container = try! ModelContainer( + for: WorkOutDoneData.self, + FrameImage.self, + BodyInfo.self, + Routine.self, + WeightTraining.self, + WeightTrainingInfo.self, + MyRoutine.self, + MyWeightTraining.self, + TemporaryRoutine.self + ) + context = ModelContext(container) + } +} diff --git a/WorkoutDone/WorkoutDoneTests/BodyInfoDataManagerTest/BodyInfoDataManagerTest.swift b/WorkoutDone/WorkoutDoneTests/BodyInfoDataManagerTest/BodyInfoDataManagerTest.swift index a08cae78..f7293a19 100644 --- a/WorkoutDone/WorkoutDoneTests/BodyInfoDataManagerTest/BodyInfoDataManagerTest.swift +++ b/WorkoutDone/WorkoutDoneTests/BodyInfoDataManagerTest/BodyInfoDataManagerTest.swift @@ -1,5 +1,4 @@ import XCTest -import RealmSwift @testable import WorkoutDone struct ExpectedBodyInfoData { @@ -22,13 +21,11 @@ struct ExpectedBodyInfoData { final class BodyInputDataValidatorTest: XCTestCase { var sut: BodyInfoDataManager! - var realmProvider: MockRealmProvider! - var testRealm: Realm! + var contextProvider: MockSwiftDataProvider! override func setUp() { - realmProvider = MockRealmProvider() // Mock Realm을 통해 테스트 - testRealm = try! realmProvider.makeRealm() - let realmManager = RealmManager(realm: testRealm) - sut = BodyInfoDataManager(realmManager: realmManager) + contextProvider = MockSwiftDataProvider() + let dataManager = SwiftDataManager(context: contextProvider.makeContext()) + sut = BodyInfoDataManager(dataManager: dataManager) sut.createBodyInfoData(weight: ExpectedBodyInfoData.weight, skeletalMusleMass: ExpectedBodyInfoData.skeletalMusleMass, fatPercentage: ExpectedBodyInfoData.fatPercentage, @@ -37,8 +34,7 @@ final class BodyInputDataValidatorTest: XCTestCase { } override func tearDown() { sut = nil - realmProvider = nil - testRealm = nil + contextProvider = nil } // MARK: - BodyInfoDataManager createBodyInfoData 메서드 테스트 diff --git a/WorkoutDone/WorkoutDoneTests/HomeViewModelTests.swift b/WorkoutDone/WorkoutDoneTests/HomeViewModelTests.swift new file mode 100644 index 00000000..ea3f694a --- /dev/null +++ b/WorkoutDone/WorkoutDoneTests/HomeViewModelTests.swift @@ -0,0 +1,106 @@ +// +// HomeViewModelTests.swift +// WorkoutDoneTests +// +// Created by Codex on 2025/02/14. +// + +import XCTest +@testable import WorkoutDone + +final class HomeViewModelTests: XCTestCase { + private final class StubWorkOutDoneDataProvider: WorkOutDoneDataProviding { + private var dataById: [Int: WorkOutDoneData] + + init(dataById: [Int: WorkOutDoneData]) { + self.dataById = dataById + } + + func workoutDoneData(for id: Int) -> WorkOutDoneData? { + return dataById[id] + } + } + + func test_makeViewState_whenNoData_thenReturnsPlaceholders() { + let sut = HomeViewModel(dataProvider: StubWorkOutDoneDataProvider(dataById: [:])) + + let viewState = sut.makeViewState(for: 20240101) + + XCTAssertEqual(viewState.weightText, "-") + XCTAssertEqual(viewState.skeletalMuscleMassText, "-") + XCTAssertEqual(viewState.fatPercentageText, "-") + XCTAssertEqual(viewState.workoutTimeText, "00:00:00") + XCTAssertEqual(viewState.workoutRoutineTitleText, "-") + XCTAssertEqual(viewState.isWorkout, false) + XCTAssertEqual(viewState.routineBodyParts, []) + XCTAssertEqual(viewState.hasRoutineTitle, true) + XCTAssertEqual(viewState.image.size, .zero) + } + + func test_makeViewState_whenBodyInfoExists_thenFormatsValues() { + let data = makeWorkOutDoneData( + id: 20240102, + routineName: "Leg Day", + bodyParts: ["Leg"], + weight: 70.1, + skeletalMuscleMass: 32.4, + fatPercentage: 18.9, + workOutTime: 3661 + ) + let sut = HomeViewModel(dataProvider: StubWorkOutDoneDataProvider(dataById: [20240102: data])) + + let viewState = sut.makeViewState(for: 20240102) + + XCTAssertEqual(viewState.weightText, "70.1") + XCTAssertEqual(viewState.skeletalMuscleMassText, "32.4") + XCTAssertEqual(viewState.fatPercentageText, "18.9") + XCTAssertEqual(viewState.workoutTimeText, "01:01:01") + XCTAssertEqual(viewState.workoutRoutineTitleText, "Leg Day") + XCTAssertEqual(viewState.isWorkout, true) + XCTAssertEqual(viewState.routineBodyParts, []) + XCTAssertEqual(viewState.hasRoutineTitle, true) + } + + func test_makeViewState_whenRoutineTitleEmpty_thenReturnsBodyPartsAndHasRoutineTitleFalse() { + let data = makeWorkOutDoneData( + id: 20240103, + routineName: "", + bodyParts: ["Leg", "Back", "Leg"] + ) + let sut = HomeViewModel(dataProvider: StubWorkOutDoneDataProvider(dataById: [20240103: data])) + + let viewState = sut.makeViewState(for: 20240103) + + XCTAssertEqual(viewState.isWorkout, true) + XCTAssertEqual(viewState.routineBodyParts, ["Leg", "Back"]) + XCTAssertEqual(viewState.hasRoutineTitle, false) + } + + private func makeWorkOutDoneData( + id: Int, + routineName: String? = nil, + bodyParts: [String] = [], + weight: Double? = nil, + skeletalMuscleMass: Double? = nil, + fatPercentage: Double? = nil, + workOutTime: Int? = nil + ) -> WorkOutDoneData { + let data = WorkOutDoneData(id: id, date: "20240101") + let bodyInfo = BodyInfo() + bodyInfo.weight = weight + bodyInfo.skeletalMuscleMass = skeletalMuscleMass + bodyInfo.fatPercentage = fatPercentage + data.bodyInfo = bodyInfo + + if let routineName = routineName { + var weightTrainingList: [WeightTraining] = [] + for bodyPart in bodyParts { + weightTrainingList.append(WeightTraining(bodyPart: bodyPart, weightTraining: "Dummy")) + } + data.routine = Routine(name: routineName, stamp: "", weightTraining: weightTrainingList) + } + + data.workOutTime = workOutTime + return data + } +} diff --git a/WorkoutDone/WorkoutDoneTests/SwiftDataManagerTests.swift b/WorkoutDone/WorkoutDoneTests/SwiftDataManagerTests.swift new file mode 100644 index 00000000..75c46edf --- /dev/null +++ b/WorkoutDone/WorkoutDoneTests/SwiftDataManagerTests.swift @@ -0,0 +1,34 @@ +// +// SwiftDataManagerTests.swift +// WorkoutDoneTests +// +// Created by Codex on 2025/02/14. +// + +import XCTest +@testable import WorkoutDone + +final class SwiftDataManagerTests: XCTestCase { + func test_createReadUpdateDelete_workoutDoneData() { + let provider = MockSwiftDataProvider() + let manager = SwiftDataManager(context: provider.makeContext()) + + let workout = WorkOutDoneData(id: 20240101, date: "2024.01.01") + manager.createData(data: workout) + + let readWorkout = manager.readData(id: 20240101, type: WorkOutDoneData.self) + XCTAssertNotNil(readWorkout) + + manager.updateData(data: workout) { updated in + updated.workOutTime = 3600 + } + let updatedWorkout = manager.readData(id: 20240101, type: WorkOutDoneData.self) + XCTAssertEqual(updatedWorkout?.workOutTime, 3600) + + if let updatedWorkout = updatedWorkout { + manager.deleteData(data: updatedWorkout) + } + let deletedWorkout = manager.readData(id: 20240101, type: WorkOutDoneData.self) + XCTAssertNil(deletedWorkout) + } +} diff --git a/WorkoutDone/WorkoutDoneTests/WorkoutDoneTests.swift b/WorkoutDone/WorkoutDoneTests/WorkoutDoneTests.swift index 34750f8e..15caf559 100644 --- a/WorkoutDone/WorkoutDoneTests/WorkoutDoneTests.swift +++ b/WorkoutDone/WorkoutDoneTests/WorkoutDoneTests.swift @@ -6,7 +6,6 @@ // import XCTest -import RealmSwift import RxCocoa @testable import WorkoutDone