├── .DS_Store ├── .gitignore ├── LICENSE ├── MBA-Demo.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── swiftpm │ └── Package.resolved ├── MBA-Demo ├── .DS_Store ├── DB │ └── db.json ├── MBA-Demo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── MBA-Demo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Domain │ └── APIController.swift │ ├── Entity │ └── UserInfo.swift │ ├── Info.plist │ ├── Presentation │ ├── TableView │ │ └── TableViewDelegate.swift │ ├── ViewController.swift │ ├── ViewInteractor.swift │ └── ViewModel.swift │ └── SceneDelegate.swift ├── MBA-book-browser ├── .DS_Store ├── MBA-book-browser.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── swiftpm │ │ └── Package.resolved └── MBA-book-browser │ ├── .DS_Store │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Entity │ ├── APIOpenLibrary.swift │ └── APISearchDataModel.swift │ ├── Info.plist │ ├── SceneDelegate.swift │ └── source files │ ├── ViewController.swift │ ├── ViewInteractor.swift │ ├── ViewModel.swift │ ├── detail view │ ├── BookImageCell.swift │ └── DetailViewController.swift │ └── table view │ └── TableViewDelegate.swift ├── MBA-calculator ├── MBA-calculator.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── swiftpm │ │ └── Package.resolved └── MBA-calculator │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── SceneDelegate.swift │ └── source files │ ├── CalcDataManager.swift │ ├── ViewController.swift │ ├── ViewInteractor.swift │ └── ViewModel.swift └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MBKwon/MBA-Demo/3e55c0ba0cc7796b3887cc0c0df6d7265671d05a/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Brad MB 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MBA-Demo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /MBA-Demo.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "cbb569fc3b1db94f0e278b59d5ae70351c0411dcddd7ef135555e39ae9a49b66", 3 | "pins" : [ 4 | { 5 | "identity" : "mba-kit", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/MBKwon/MBA-kit", 8 | "state" : { 9 | "revision" : "5905bd6d0adf9fe21bc318010a2710eb6ef801ea", 10 | "version" : "0.9.9" 11 | } 12 | }, 13 | { 14 | "identity" : "resultextensions", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/MBKwon/ResultExtensions", 17 | "state" : { 18 | "revision" : "60c252f7a57c8b64f51bb566722e5654756901db", 19 | "version" : "0.9.5" 20 | } 21 | } 22 | ], 23 | "version" : 3 24 | } 25 | -------------------------------------------------------------------------------- /MBA-Demo/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MBKwon/MBA-Demo/3e55c0ba0cc7796b3887cc0c0df6d7265671d05a/MBA-Demo/.DS_Store -------------------------------------------------------------------------------- /MBA-Demo/DB/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "user_info": [ 3 | { 4 | "name": "Dana", 5 | "age": 30 6 | }, 7 | { 8 | "name": "Alice", 9 | "age": 34 10 | }, 11 | { 12 | "name": "Nate", 13 | "age": 33 14 | }, 15 | { 16 | "name": "Brad", 17 | "age": 33 18 | }, 19 | { 20 | "name": "Coyote", 21 | "age": 33 22 | }, 23 | { 24 | "name": "Ella", 25 | "age": 30 26 | }, 27 | { 28 | "name": "Kim", 29 | "age": 24 30 | }, 31 | { 32 | "name": "Lee", 33 | "age": 42 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /MBA-Demo/MBA-Demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 018B6B962D65DF4700ECB5EE /* MBAkit in Frameworks */ = {isa = PBXBuildFile; productRef = 018B6B952D65DF4700ECB5EE /* MBAkit */; }; 11 | FEE527402AC5A6F300BC682F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEE5273F2AC5A6F300BC682F /* AppDelegate.swift */; }; 12 | FEE527422AC5A6F300BC682F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEE527412AC5A6F300BC682F /* SceneDelegate.swift */; }; 13 | FEE527442AC5A6F300BC682F /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEE527432AC5A6F300BC682F /* ViewController.swift */; }; 14 | FEE527472AC5A6F300BC682F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FEE527452AC5A6F300BC682F /* Main.storyboard */; }; 15 | FEE527492AC5A6F400BC682F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FEE527482AC5A6F400BC682F /* Assets.xcassets */; }; 16 | FEE5274C2AC5A6F400BC682F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FEE5274A2AC5A6F400BC682F /* LaunchScreen.storyboard */; }; 17 | FEE5275A2AC5A7D500BC682F /* APIController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEE527592AC5A7D500BC682F /* APIController.swift */; }; 18 | FEE5275C2AC5A7F900BC682F /* UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEE5275B2AC5A7F900BC682F /* UserInfo.swift */; }; 19 | FEE5275E2AC5A98F00BC682F /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEE5275D2AC5A98F00BC682F /* ViewModel.swift */; }; 20 | FEE527602AC5A99B00BC682F /* ViewInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEE5275F2AC5A99B00BC682F /* ViewInteractor.swift */; }; 21 | FEE527632AC5C5BC00BC682F /* TableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEE527622AC5C5BC00BC682F /* TableViewDelegate.swift */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | FEE5273C2AC5A6F300BC682F /* MBA-Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "MBA-Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 26 | FEE5273F2AC5A6F300BC682F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 27 | FEE527412AC5A6F300BC682F /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 28 | FEE527432AC5A6F300BC682F /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 29 | FEE527462AC5A6F300BC682F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 30 | FEE527482AC5A6F400BC682F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 31 | FEE5274B2AC5A6F400BC682F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 32 | FEE5274D2AC5A6F400BC682F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 33 | FEE527592AC5A7D500BC682F /* APIController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIController.swift; sourceTree = ""; }; 34 | FEE5275B2AC5A7F900BC682F /* UserInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserInfo.swift; sourceTree = ""; }; 35 | FEE5275D2AC5A98F00BC682F /* ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = ""; }; 36 | FEE5275F2AC5A99B00BC682F /* ViewInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewInteractor.swift; sourceTree = ""; }; 37 | FEE527622AC5C5BC00BC682F /* TableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewDelegate.swift; sourceTree = ""; }; 38 | /* End PBXFileReference section */ 39 | 40 | /* Begin PBXFrameworksBuildPhase section */ 41 | FEE527392AC5A6F300BC682F /* Frameworks */ = { 42 | isa = PBXFrameworksBuildPhase; 43 | buildActionMask = 2147483647; 44 | files = ( 45 | 018B6B962D65DF4700ECB5EE /* MBAkit in Frameworks */, 46 | ); 47 | runOnlyForDeploymentPostprocessing = 0; 48 | }; 49 | /* End PBXFrameworksBuildPhase section */ 50 | 51 | /* Begin PBXGroup section */ 52 | C3B654EE2ADE85B7005FBFAB /* Frameworks */ = { 53 | isa = PBXGroup; 54 | children = ( 55 | ); 56 | name = Frameworks; 57 | sourceTree = ""; 58 | }; 59 | FEE527332AC5A6F300BC682F = { 60 | isa = PBXGroup; 61 | children = ( 62 | FEE5273E2AC5A6F300BC682F /* MBA-Demo */, 63 | FEE5273D2AC5A6F300BC682F /* Products */, 64 | C3B654EE2ADE85B7005FBFAB /* Frameworks */, 65 | ); 66 | sourceTree = ""; 67 | }; 68 | FEE5273D2AC5A6F300BC682F /* Products */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | FEE5273C2AC5A6F300BC682F /* MBA-Demo.app */, 72 | ); 73 | name = Products; 74 | sourceTree = ""; 75 | }; 76 | FEE5273E2AC5A6F300BC682F /* MBA-Demo */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | FEE5273F2AC5A6F300BC682F /* AppDelegate.swift */, 80 | FEE527412AC5A6F300BC682F /* SceneDelegate.swift */, 81 | FEE527562AC5A77A00BC682F /* Domain */, 82 | FEE527572AC5A78300BC682F /* Entity */, 83 | FEE527582AC5A78B00BC682F /* Presentation */, 84 | FEE527452AC5A6F300BC682F /* Main.storyboard */, 85 | FEE527482AC5A6F400BC682F /* Assets.xcassets */, 86 | FEE5274A2AC5A6F400BC682F /* LaunchScreen.storyboard */, 87 | FEE5274D2AC5A6F400BC682F /* Info.plist */, 88 | ); 89 | path = "MBA-Demo"; 90 | sourceTree = ""; 91 | }; 92 | FEE527562AC5A77A00BC682F /* Domain */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | FEE527592AC5A7D500BC682F /* APIController.swift */, 96 | ); 97 | path = Domain; 98 | sourceTree = ""; 99 | }; 100 | FEE527572AC5A78300BC682F /* Entity */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | FEE5275B2AC5A7F900BC682F /* UserInfo.swift */, 104 | ); 105 | path = Entity; 106 | sourceTree = ""; 107 | }; 108 | FEE527582AC5A78B00BC682F /* Presentation */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | FEE527612AC5C59800BC682F /* TableView */, 112 | FEE527432AC5A6F300BC682F /* ViewController.swift */, 113 | FEE5275F2AC5A99B00BC682F /* ViewInteractor.swift */, 114 | FEE5275D2AC5A98F00BC682F /* ViewModel.swift */, 115 | ); 116 | path = Presentation; 117 | sourceTree = ""; 118 | }; 119 | FEE527612AC5C59800BC682F /* TableView */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | FEE527622AC5C5BC00BC682F /* TableViewDelegate.swift */, 123 | ); 124 | path = TableView; 125 | sourceTree = ""; 126 | }; 127 | /* End PBXGroup section */ 128 | 129 | /* Begin PBXNativeTarget section */ 130 | FEE5273B2AC5A6F300BC682F /* MBA-Demo */ = { 131 | isa = PBXNativeTarget; 132 | buildConfigurationList = FEE527502AC5A6F400BC682F /* Build configuration list for PBXNativeTarget "MBA-Demo" */; 133 | buildPhases = ( 134 | FEE527382AC5A6F300BC682F /* Sources */, 135 | FEE527392AC5A6F300BC682F /* Frameworks */, 136 | FEE5273A2AC5A6F300BC682F /* Resources */, 137 | ); 138 | buildRules = ( 139 | ); 140 | dependencies = ( 141 | ); 142 | name = "MBA-Demo"; 143 | packageProductDependencies = ( 144 | 018B6B952D65DF4700ECB5EE /* MBAkit */, 145 | ); 146 | productName = "MBA-Demo"; 147 | productReference = FEE5273C2AC5A6F300BC682F /* MBA-Demo.app */; 148 | productType = "com.apple.product-type.application"; 149 | }; 150 | /* End PBXNativeTarget section */ 151 | 152 | /* Begin PBXProject section */ 153 | FEE527342AC5A6F300BC682F /* Project object */ = { 154 | isa = PBXProject; 155 | attributes = { 156 | BuildIndependentTargetsInParallel = 1; 157 | LastSwiftUpdateCheck = 1430; 158 | LastUpgradeCheck = 1430; 159 | TargetAttributes = { 160 | FEE5273B2AC5A6F300BC682F = { 161 | CreatedOnToolsVersion = 14.3.1; 162 | }; 163 | }; 164 | }; 165 | buildConfigurationList = FEE527372AC5A6F300BC682F /* Build configuration list for PBXProject "MBA-Demo" */; 166 | compatibilityVersion = "Xcode 14.0"; 167 | developmentRegion = en; 168 | hasScannedForEncodings = 0; 169 | knownRegions = ( 170 | en, 171 | Base, 172 | ); 173 | mainGroup = FEE527332AC5A6F300BC682F; 174 | packageReferences = ( 175 | 018B6B942D65DF4700ECB5EE /* XCRemoteSwiftPackageReference "MBA-kit" */, 176 | ); 177 | productRefGroup = FEE5273D2AC5A6F300BC682F /* Products */; 178 | projectDirPath = ""; 179 | projectRoot = ""; 180 | targets = ( 181 | FEE5273B2AC5A6F300BC682F /* MBA-Demo */, 182 | ); 183 | }; 184 | /* End PBXProject section */ 185 | 186 | /* Begin PBXResourcesBuildPhase section */ 187 | FEE5273A2AC5A6F300BC682F /* Resources */ = { 188 | isa = PBXResourcesBuildPhase; 189 | buildActionMask = 2147483647; 190 | files = ( 191 | FEE5274C2AC5A6F400BC682F /* LaunchScreen.storyboard in Resources */, 192 | FEE527492AC5A6F400BC682F /* Assets.xcassets in Resources */, 193 | FEE527472AC5A6F300BC682F /* Main.storyboard in Resources */, 194 | ); 195 | runOnlyForDeploymentPostprocessing = 0; 196 | }; 197 | /* End PBXResourcesBuildPhase section */ 198 | 199 | /* Begin PBXSourcesBuildPhase section */ 200 | FEE527382AC5A6F300BC682F /* Sources */ = { 201 | isa = PBXSourcesBuildPhase; 202 | buildActionMask = 2147483647; 203 | files = ( 204 | FEE527632AC5C5BC00BC682F /* TableViewDelegate.swift in Sources */, 205 | FEE5275C2AC5A7F900BC682F /* UserInfo.swift in Sources */, 206 | FEE5275A2AC5A7D500BC682F /* APIController.swift in Sources */, 207 | FEE527442AC5A6F300BC682F /* ViewController.swift in Sources */, 208 | FEE527602AC5A99B00BC682F /* ViewInteractor.swift in Sources */, 209 | FEE527402AC5A6F300BC682F /* AppDelegate.swift in Sources */, 210 | FEE5275E2AC5A98F00BC682F /* ViewModel.swift in Sources */, 211 | FEE527422AC5A6F300BC682F /* SceneDelegate.swift in Sources */, 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | }; 215 | /* End PBXSourcesBuildPhase section */ 216 | 217 | /* Begin PBXVariantGroup section */ 218 | FEE527452AC5A6F300BC682F /* Main.storyboard */ = { 219 | isa = PBXVariantGroup; 220 | children = ( 221 | FEE527462AC5A6F300BC682F /* Base */, 222 | ); 223 | name = Main.storyboard; 224 | sourceTree = ""; 225 | }; 226 | FEE5274A2AC5A6F400BC682F /* LaunchScreen.storyboard */ = { 227 | isa = PBXVariantGroup; 228 | children = ( 229 | FEE5274B2AC5A6F400BC682F /* Base */, 230 | ); 231 | name = LaunchScreen.storyboard; 232 | sourceTree = ""; 233 | }; 234 | /* End PBXVariantGroup section */ 235 | 236 | /* Begin XCBuildConfiguration section */ 237 | FEE5274E2AC5A6F400BC682F /* Debug */ = { 238 | isa = XCBuildConfiguration; 239 | buildSettings = { 240 | ALWAYS_SEARCH_USER_PATHS = NO; 241 | CLANG_ANALYZER_NONNULL = YES; 242 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 243 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 244 | CLANG_ENABLE_MODULES = YES; 245 | CLANG_ENABLE_OBJC_ARC = YES; 246 | CLANG_ENABLE_OBJC_WEAK = YES; 247 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 248 | CLANG_WARN_BOOL_CONVERSION = YES; 249 | CLANG_WARN_COMMA = YES; 250 | CLANG_WARN_CONSTANT_CONVERSION = YES; 251 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 252 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 253 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 254 | CLANG_WARN_EMPTY_BODY = YES; 255 | CLANG_WARN_ENUM_CONVERSION = YES; 256 | CLANG_WARN_INFINITE_RECURSION = YES; 257 | CLANG_WARN_INT_CONVERSION = YES; 258 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 259 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 260 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 261 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 262 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 263 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 264 | CLANG_WARN_STRICT_PROTOTYPES = YES; 265 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 266 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 267 | CLANG_WARN_UNREACHABLE_CODE = YES; 268 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 269 | COPY_PHASE_STRIP = NO; 270 | DEBUG_INFORMATION_FORMAT = dwarf; 271 | ENABLE_STRICT_OBJC_MSGSEND = YES; 272 | ENABLE_TESTABILITY = YES; 273 | GCC_C_LANGUAGE_STANDARD = gnu11; 274 | GCC_DYNAMIC_NO_PIC = NO; 275 | GCC_NO_COMMON_BLOCKS = YES; 276 | GCC_OPTIMIZATION_LEVEL = 0; 277 | GCC_PREPROCESSOR_DEFINITIONS = ( 278 | "DEBUG=1", 279 | "$(inherited)", 280 | ); 281 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 282 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 283 | GCC_WARN_UNDECLARED_SELECTOR = YES; 284 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 285 | GCC_WARN_UNUSED_FUNCTION = YES; 286 | GCC_WARN_UNUSED_VARIABLE = YES; 287 | IPHONEOS_DEPLOYMENT_TARGET = 16.4; 288 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 289 | MTL_FAST_MATH = YES; 290 | ONLY_ACTIVE_ARCH = YES; 291 | SDKROOT = iphoneos; 292 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 293 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 294 | }; 295 | name = Debug; 296 | }; 297 | FEE5274F2AC5A6F400BC682F /* Release */ = { 298 | isa = XCBuildConfiguration; 299 | buildSettings = { 300 | ALWAYS_SEARCH_USER_PATHS = NO; 301 | CLANG_ANALYZER_NONNULL = YES; 302 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 303 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 304 | CLANG_ENABLE_MODULES = YES; 305 | CLANG_ENABLE_OBJC_ARC = YES; 306 | CLANG_ENABLE_OBJC_WEAK = YES; 307 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 308 | CLANG_WARN_BOOL_CONVERSION = YES; 309 | CLANG_WARN_COMMA = YES; 310 | CLANG_WARN_CONSTANT_CONVERSION = YES; 311 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 312 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 313 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 314 | CLANG_WARN_EMPTY_BODY = YES; 315 | CLANG_WARN_ENUM_CONVERSION = YES; 316 | CLANG_WARN_INFINITE_RECURSION = YES; 317 | CLANG_WARN_INT_CONVERSION = YES; 318 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 319 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 320 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 321 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 322 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 323 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 324 | CLANG_WARN_STRICT_PROTOTYPES = YES; 325 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 326 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 327 | CLANG_WARN_UNREACHABLE_CODE = YES; 328 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 329 | COPY_PHASE_STRIP = NO; 330 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 331 | ENABLE_NS_ASSERTIONS = NO; 332 | ENABLE_STRICT_OBJC_MSGSEND = YES; 333 | GCC_C_LANGUAGE_STANDARD = gnu11; 334 | GCC_NO_COMMON_BLOCKS = YES; 335 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 336 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 337 | GCC_WARN_UNDECLARED_SELECTOR = YES; 338 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 339 | GCC_WARN_UNUSED_FUNCTION = YES; 340 | GCC_WARN_UNUSED_VARIABLE = YES; 341 | IPHONEOS_DEPLOYMENT_TARGET = 16.4; 342 | MTL_ENABLE_DEBUG_INFO = NO; 343 | MTL_FAST_MATH = YES; 344 | SDKROOT = iphoneos; 345 | SWIFT_COMPILATION_MODE = wholemodule; 346 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 347 | VALIDATE_PRODUCT = YES; 348 | }; 349 | name = Release; 350 | }; 351 | FEE527512AC5A6F400BC682F /* Debug */ = { 352 | isa = XCBuildConfiguration; 353 | buildSettings = { 354 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 355 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 356 | CODE_SIGN_STYLE = Automatic; 357 | CURRENT_PROJECT_VERSION = 1; 358 | GENERATE_INFOPLIST_FILE = YES; 359 | INFOPLIST_FILE = "MBA-Demo/Info.plist"; 360 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 361 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 362 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 363 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 364 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 365 | LD_RUNPATH_SEARCH_PATHS = ( 366 | "$(inherited)", 367 | "@executable_path/Frameworks", 368 | ); 369 | MARKETING_VERSION = 1.0; 370 | PRODUCT_BUNDLE_IDENTIFIER = "com.mbk.architecture.MBA-Demo"; 371 | PRODUCT_NAME = "$(TARGET_NAME)"; 372 | SWIFT_EMIT_LOC_STRINGS = YES; 373 | SWIFT_VERSION = 5.0; 374 | TARGETED_DEVICE_FAMILY = "1,2"; 375 | }; 376 | name = Debug; 377 | }; 378 | FEE527522AC5A6F400BC682F /* Release */ = { 379 | isa = XCBuildConfiguration; 380 | buildSettings = { 381 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 382 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 383 | CODE_SIGN_STYLE = Automatic; 384 | CURRENT_PROJECT_VERSION = 1; 385 | GENERATE_INFOPLIST_FILE = YES; 386 | INFOPLIST_FILE = "MBA-Demo/Info.plist"; 387 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 388 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 389 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 390 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 391 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 392 | LD_RUNPATH_SEARCH_PATHS = ( 393 | "$(inherited)", 394 | "@executable_path/Frameworks", 395 | ); 396 | MARKETING_VERSION = 1.0; 397 | PRODUCT_BUNDLE_IDENTIFIER = "com.mbk.architecture.MBA-Demo"; 398 | PRODUCT_NAME = "$(TARGET_NAME)"; 399 | SWIFT_EMIT_LOC_STRINGS = YES; 400 | SWIFT_VERSION = 5.0; 401 | TARGETED_DEVICE_FAMILY = "1,2"; 402 | }; 403 | name = Release; 404 | }; 405 | /* End XCBuildConfiguration section */ 406 | 407 | /* Begin XCConfigurationList section */ 408 | FEE527372AC5A6F300BC682F /* Build configuration list for PBXProject "MBA-Demo" */ = { 409 | isa = XCConfigurationList; 410 | buildConfigurations = ( 411 | FEE5274E2AC5A6F400BC682F /* Debug */, 412 | FEE5274F2AC5A6F400BC682F /* Release */, 413 | ); 414 | defaultConfigurationIsVisible = 0; 415 | defaultConfigurationName = Release; 416 | }; 417 | FEE527502AC5A6F400BC682F /* Build configuration list for PBXNativeTarget "MBA-Demo" */ = { 418 | isa = XCConfigurationList; 419 | buildConfigurations = ( 420 | FEE527512AC5A6F400BC682F /* Debug */, 421 | FEE527522AC5A6F400BC682F /* Release */, 422 | ); 423 | defaultConfigurationIsVisible = 0; 424 | defaultConfigurationName = Release; 425 | }; 426 | /* End XCConfigurationList section */ 427 | 428 | /* Begin XCRemoteSwiftPackageReference section */ 429 | 018B6B942D65DF4700ECB5EE /* XCRemoteSwiftPackageReference "MBA-kit" */ = { 430 | isa = XCRemoteSwiftPackageReference; 431 | repositoryURL = "https://github.com/MBKwon/MBA-kit"; 432 | requirement = { 433 | kind = upToNextMajorVersion; 434 | minimumVersion = 0.9.5; 435 | }; 436 | }; 437 | /* End XCRemoteSwiftPackageReference section */ 438 | 439 | /* Begin XCSwiftPackageProductDependency section */ 440 | 018B6B952D65DF4700ECB5EE /* MBAkit */ = { 441 | isa = XCSwiftPackageProductDependency; 442 | package = 018B6B942D65DF4700ECB5EE /* XCRemoteSwiftPackageReference "MBA-kit" */; 443 | productName = MBAkit; 444 | }; 445 | /* End XCSwiftPackageProductDependency section */ 446 | }; 447 | rootObject = FEE527342AC5A6F300BC682F /* Project object */; 448 | } 449 | -------------------------------------------------------------------------------- /MBA-Demo/MBA-Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MBA-Demo/MBA-Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MBA-Demo/MBA-Demo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "cd9f379f6c98550e9fb9d601c21d2878bff84ee876003fb089e2baa6f6cfee04", 3 | "pins" : [ 4 | { 5 | "identity" : "mba-kit", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/MBKwon/MBA-kit", 8 | "state" : { 9 | "revision" : "f773887698fcf0a17d2828c89be320df348f9ecd", 10 | "version" : "0.9.5" 11 | } 12 | }, 13 | { 14 | "identity" : "resultextensions", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/MBKwon/ResultExtensions", 17 | "state" : { 18 | "revision" : "60c252f7a57c8b64f51bb566722e5654756901db", 19 | "version" : "0.9.5" 20 | } 21 | } 22 | ], 23 | "version" : 3 24 | } 25 | -------------------------------------------------------------------------------- /MBA-Demo/MBA-Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MBA-Demo 4 | // 5 | // Created by Moonbeom KWON on 2023/09/28. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /MBA-Demo/MBA-Demo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /MBA-Demo/MBA-Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /MBA-Demo/MBA-Demo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /MBA-Demo/MBA-Demo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /MBA-Demo/MBA-Demo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 33 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /MBA-Demo/MBA-Demo/Domain/APIController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APIController.swift 3 | // MBA-Demo 4 | // 5 | // Created by Moonbeom KWON on 2023/09/28. 6 | // 7 | 8 | import Foundation 9 | import MBAkit 10 | 11 | // JSON-server : https://www.npmjs.com/package/json-server 12 | // json-server --watch db.json --port 8888 13 | enum APIController { 14 | static let shared = API(with: API.APIDomainInfo(scheme: "http", 15 | host: "localhost", 16 | port: 8888)) 17 | } 18 | 19 | extension APIController { 20 | enum Path: APIPath { 21 | case userInfoList 22 | 23 | var pathString: String { 24 | switch self { 25 | case .userInfoList: 26 | return "/user_info" 27 | } 28 | } 29 | 30 | var parameters: [String: String]? { 31 | switch self { 32 | case .userInfoList: 33 | return nil 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /MBA-Demo/MBA-Demo/Entity/UserInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserInfo.swift 3 | // MBA-Demo 4 | // 5 | // Created by Moonbeom KWON on 2023/09/28. 6 | // 7 | 8 | import Foundation 9 | 10 | struct UserInfo: Decodable { 11 | let name: String 12 | let age: Int 13 | 14 | enum CodingKeys: CodingKey { 15 | case name 16 | case age 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /MBA-Demo/MBA-Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | UISceneStoryboardFile 19 | Main 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /MBA-Demo/MBA-Demo/Presentation/TableView/TableViewDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewDelegate.swift 3 | // MBA-Demo 4 | // 5 | // Created by Moonbeom KWON on 2023/09/28. 6 | // 7 | 8 | import UIKit 9 | 10 | class TableViewDelegate: NSObject { 11 | private let data: [UserInfo] 12 | 13 | init(with data: [UserInfo]) { 14 | self.data = data 15 | } 16 | } 17 | 18 | extension TableViewDelegate: UITableViewDataSource { 19 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 20 | return data.count 21 | } 22 | 23 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 24 | let userInfo = data[indexPath.row] 25 | let cell = tableView.dequeueReusableCell(withIdentifier: "default", for: indexPath) 26 | 27 | var content = cell.defaultContentConfiguration() 28 | content.text = userInfo.name 29 | content.secondaryText = "\(userInfo.age) years old" 30 | cell.contentConfiguration = content 31 | 32 | return cell 33 | } 34 | } 35 | 36 | extension TableViewDelegate: UITableViewDelegate { 37 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 38 | return 100.0 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /MBA-Demo/MBA-Demo/Presentation/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // MBA-Demo 4 | // 5 | // Created by Moonbeom KWON on 2023/09/28. 6 | // 7 | 8 | import Combine 9 | import MBAkit 10 | import UIKit 11 | 12 | class ViewController: UITableViewController { 13 | 14 | private(set) var microBean: MicroBean? 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | // Do any additional setup after loading the view. 19 | 20 | self.microBean = MicroBean(withVC: self, 21 | viewModel: ViewModel(), 22 | viewInteractor: ViewInteractor(), 23 | observeMessage: { result in 24 | print(result) 25 | }) 26 | 27 | self.microBean?.handle(inputMessage: .requestUserInfo) 28 | } 29 | } 30 | 31 | // MARK: - VC-VM : input message -> output messaage 32 | extension ViewController: ViewControllerConfigurable { 33 | 34 | typealias VM = ViewModel 35 | 36 | typealias I = ViewInputMessage 37 | enum ViewInputMessage: InputMessage { 38 | case requestUserInfo 39 | } 40 | 41 | typealias O = ViewOutputMessage 42 | enum ViewOutputMessage: OutputMessage { 43 | case respondToUserInfo(userInfoList: [UserInfo]) 44 | } 45 | } 46 | 47 | // MARK: - VM-VI : output messaage -> interaction message 48 | extension ViewController: ViewContollerInteractable { 49 | 50 | typealias VI = ViewInteractor 51 | 52 | typealias IM = ViewInteractionMessage 53 | enum ViewInteractionMessage: InteractionMessage { 54 | case reloadUserInfoView(tableView: UITableView, userInfoList: [UserInfo]) 55 | } 56 | 57 | 58 | func convertToInteraction(from outputMessage: ViewOutputMessage) -> ViewInteractionMessage { 59 | switch outputMessage { 60 | case .respondToUserInfo(let userInfoList): 61 | return .reloadUserInfoView(tableView: self.tableView, userInfoList: userInfoList) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /MBA-Demo/MBA-Demo/Presentation/ViewInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewInteractor.swift 3 | // MBA-Demo 4 | // 5 | // Created by Moonbeom KWON on 2023/09/28. 6 | // 7 | 8 | import Foundation 9 | import MBAkit 10 | 11 | class ViewInteractor: ViewInteractorConfigurable { 12 | 13 | typealias VC = ViewController 14 | private var tableViewDelegate: TableViewDelegate? 15 | 16 | func handleMessage(_ interactionMessage: ViewController.ViewInteractionMessage) { 17 | switch interactionMessage { 18 | case .reloadUserInfoView(let tableView, let userInfoList): 19 | let tableViewDelegate = TableViewDelegate(with: userInfoList) 20 | tableView.dataSource = tableViewDelegate 21 | tableView.delegate = tableViewDelegate 22 | tableView.reloadData() 23 | 24 | self.tableViewDelegate = tableViewDelegate 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /MBA-Demo/MBA-Demo/Presentation/ViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewModel.swift 3 | // MBA-Demo 4 | // 5 | // Created by Moonbeom KWON on 2023/09/28. 6 | // 7 | 8 | import Combine 9 | import Foundation 10 | import MBAkit 11 | 12 | class ViewModel: ViewModelConfigurable { 13 | 14 | typealias VC = ViewController 15 | private(set) var outputSubject = PassthroughSubject, Never>() 16 | 17 | func handleMessage(_ inputMessage: VC.I) { 18 | switch inputMessage { 19 | case .requestUserInfo: 20 | self.requestUserInfo() 21 | } 22 | } 23 | } 24 | 25 | extension ViewModel { 26 | private func requestUserInfo() { 27 | Task { 28 | await APIController.shared 29 | .request(path: APIController.Path.userInfoList, method: .get) 30 | .decode(decoder: [UserInfo].self) 31 | .map(VC.O.respondToUserInfo(userInfoList:)) 32 | .send(through: self.outputSubject) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /MBA-Demo/MBA-Demo/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // MBA-Demo 4 | // 5 | // Created by Moonbeom KWON on 2023/09/28. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 19 | guard let _ = (scene as? UIWindowScene) else { return } 20 | } 21 | 22 | func sceneDidDisconnect(_ scene: UIScene) { 23 | // Called as the scene is being released by the system. 24 | // This occurs shortly after the scene enters the background, or when its session is discarded. 25 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 27 | } 28 | 29 | func sceneDidBecomeActive(_ scene: UIScene) { 30 | // Called when the scene has moved from an inactive state to an active state. 31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 32 | } 33 | 34 | func sceneWillResignActive(_ scene: UIScene) { 35 | // Called when the scene will move from an active state to an inactive state. 36 | // This may occur due to temporary interruptions (ex. an incoming phone call). 37 | } 38 | 39 | func sceneWillEnterForeground(_ scene: UIScene) { 40 | // Called as the scene transitions from the background to the foreground. 41 | // Use this method to undo the changes made on entering the background. 42 | } 43 | 44 | func sceneDidEnterBackground(_ scene: UIScene) { 45 | // Called as the scene transitions from the foreground to the background. 46 | // Use this method to save data, release shared resources, and store enough scene-specific state information 47 | // to restore the scene back to its current state. 48 | } 49 | 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /MBA-book-browser/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MBKwon/MBA-Demo/3e55c0ba0cc7796b3887cc0c0df6d7265671d05a/MBA-book-browser/.DS_Store -------------------------------------------------------------------------------- /MBA-book-browser/MBA-book-browser.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 77; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0150A35A2D6C60840041C8F2 /* MBAkit in Frameworks */ = {isa = PBXBuildFile; productRef = 0150A3592D6C60840041C8F2 /* MBAkit */; }; 11 | /* End PBXBuildFile section */ 12 | 13 | /* Begin PBXFileReference section */ 14 | 01AEF5382D6C46FC00962B6E /* MBA-book-browser.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "MBA-book-browser.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 15 | /* End PBXFileReference section */ 16 | 17 | /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ 18 | 01AEF54A2D6C46FD00962B6E /* Exceptions for "MBA-book-browser" folder in "MBA-book-browser" target */ = { 19 | isa = PBXFileSystemSynchronizedBuildFileExceptionSet; 20 | membershipExceptions = ( 21 | Info.plist, 22 | ); 23 | target = 01AEF5372D6C46FC00962B6E /* MBA-book-browser */; 24 | }; 25 | /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ 26 | 27 | /* Begin PBXFileSystemSynchronizedRootGroup section */ 28 | 01AEF53A2D6C46FC00962B6E /* MBA-book-browser */ = { 29 | isa = PBXFileSystemSynchronizedRootGroup; 30 | exceptions = ( 31 | 01AEF54A2D6C46FD00962B6E /* Exceptions for "MBA-book-browser" folder in "MBA-book-browser" target */, 32 | ); 33 | path = "MBA-book-browser"; 34 | sourceTree = ""; 35 | }; 36 | /* End PBXFileSystemSynchronizedRootGroup section */ 37 | 38 | /* Begin PBXFrameworksBuildPhase section */ 39 | 01AEF5352D6C46FC00962B6E /* Frameworks */ = { 40 | isa = PBXFrameworksBuildPhase; 41 | buildActionMask = 2147483647; 42 | files = ( 43 | 0150A35A2D6C60840041C8F2 /* MBAkit in Frameworks */, 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | /* End PBXFrameworksBuildPhase section */ 48 | 49 | /* Begin PBXGroup section */ 50 | 01AEF52F2D6C46FC00962B6E = { 51 | isa = PBXGroup; 52 | children = ( 53 | 01AEF53A2D6C46FC00962B6E /* MBA-book-browser */, 54 | 01AEF5392D6C46FC00962B6E /* Products */, 55 | ); 56 | sourceTree = ""; 57 | }; 58 | 01AEF5392D6C46FC00962B6E /* Products */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 01AEF5382D6C46FC00962B6E /* MBA-book-browser.app */, 62 | ); 63 | name = Products; 64 | sourceTree = ""; 65 | }; 66 | /* End PBXGroup section */ 67 | 68 | /* Begin PBXNativeTarget section */ 69 | 01AEF5372D6C46FC00962B6E /* MBA-book-browser */ = { 70 | isa = PBXNativeTarget; 71 | buildConfigurationList = 01AEF54B2D6C46FD00962B6E /* Build configuration list for PBXNativeTarget "MBA-book-browser" */; 72 | buildPhases = ( 73 | 01AEF5342D6C46FC00962B6E /* Sources */, 74 | 01AEF5352D6C46FC00962B6E /* Frameworks */, 75 | 01AEF5362D6C46FC00962B6E /* Resources */, 76 | ); 77 | buildRules = ( 78 | ); 79 | dependencies = ( 80 | ); 81 | fileSystemSynchronizedGroups = ( 82 | 01AEF53A2D6C46FC00962B6E /* MBA-book-browser */, 83 | ); 84 | name = "MBA-book-browser"; 85 | packageProductDependencies = ( 86 | 0150A3592D6C60840041C8F2 /* MBAkit */, 87 | ); 88 | productName = "MBA-book-browser"; 89 | productReference = 01AEF5382D6C46FC00962B6E /* MBA-book-browser.app */; 90 | productType = "com.apple.product-type.application"; 91 | }; 92 | /* End PBXNativeTarget section */ 93 | 94 | /* Begin PBXProject section */ 95 | 01AEF5302D6C46FC00962B6E /* Project object */ = { 96 | isa = PBXProject; 97 | attributes = { 98 | BuildIndependentTargetsInParallel = 1; 99 | LastSwiftUpdateCheck = 1620; 100 | LastUpgradeCheck = 1620; 101 | TargetAttributes = { 102 | 01AEF5372D6C46FC00962B6E = { 103 | CreatedOnToolsVersion = 16.2; 104 | }; 105 | }; 106 | }; 107 | buildConfigurationList = 01AEF5332D6C46FC00962B6E /* Build configuration list for PBXProject "MBA-book-browser" */; 108 | developmentRegion = en; 109 | hasScannedForEncodings = 0; 110 | knownRegions = ( 111 | en, 112 | Base, 113 | ); 114 | mainGroup = 01AEF52F2D6C46FC00962B6E; 115 | minimizedProjectReferenceProxies = 1; 116 | packageReferences = ( 117 | 0150A3582D6C60840041C8F2 /* XCRemoteSwiftPackageReference "MBA-kit" */, 118 | ); 119 | preferredProjectObjectVersion = 77; 120 | productRefGroup = 01AEF5392D6C46FC00962B6E /* Products */; 121 | projectDirPath = ""; 122 | projectRoot = ""; 123 | targets = ( 124 | 01AEF5372D6C46FC00962B6E /* MBA-book-browser */, 125 | ); 126 | }; 127 | /* End PBXProject section */ 128 | 129 | /* Begin PBXResourcesBuildPhase section */ 130 | 01AEF5362D6C46FC00962B6E /* Resources */ = { 131 | isa = PBXResourcesBuildPhase; 132 | buildActionMask = 2147483647; 133 | files = ( 134 | ); 135 | runOnlyForDeploymentPostprocessing = 0; 136 | }; 137 | /* End PBXResourcesBuildPhase section */ 138 | 139 | /* Begin PBXSourcesBuildPhase section */ 140 | 01AEF5342D6C46FC00962B6E /* Sources */ = { 141 | isa = PBXSourcesBuildPhase; 142 | buildActionMask = 2147483647; 143 | files = ( 144 | ); 145 | runOnlyForDeploymentPostprocessing = 0; 146 | }; 147 | /* End PBXSourcesBuildPhase section */ 148 | 149 | /* Begin XCBuildConfiguration section */ 150 | 01AEF54C2D6C46FD00962B6E /* Debug */ = { 151 | isa = XCBuildConfiguration; 152 | buildSettings = { 153 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 154 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 155 | CODE_SIGN_STYLE = Automatic; 156 | CURRENT_PROJECT_VERSION = 1; 157 | DEVELOPMENT_TEAM = 92F796HA5V; 158 | GENERATE_INFOPLIST_FILE = YES; 159 | INFOPLIST_FILE = "MBA-book-browser/Info.plist"; 160 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 161 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 162 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 163 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 164 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 165 | LD_RUNPATH_SEARCH_PATHS = ( 166 | "$(inherited)", 167 | "@executable_path/Frameworks", 168 | ); 169 | MARKETING_VERSION = 1.0; 170 | PRODUCT_BUNDLE_IDENTIFIER = "com.mbkwon.MBA-book-browser"; 171 | PRODUCT_NAME = "$(TARGET_NAME)"; 172 | SWIFT_EMIT_LOC_STRINGS = YES; 173 | SWIFT_VERSION = 5.0; 174 | TARGETED_DEVICE_FAMILY = "1,2"; 175 | }; 176 | name = Debug; 177 | }; 178 | 01AEF54D2D6C46FD00962B6E /* Release */ = { 179 | isa = XCBuildConfiguration; 180 | buildSettings = { 181 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 182 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 183 | CODE_SIGN_STYLE = Automatic; 184 | CURRENT_PROJECT_VERSION = 1; 185 | DEVELOPMENT_TEAM = 92F796HA5V; 186 | GENERATE_INFOPLIST_FILE = YES; 187 | INFOPLIST_FILE = "MBA-book-browser/Info.plist"; 188 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 189 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 190 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 191 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 192 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 193 | LD_RUNPATH_SEARCH_PATHS = ( 194 | "$(inherited)", 195 | "@executable_path/Frameworks", 196 | ); 197 | MARKETING_VERSION = 1.0; 198 | PRODUCT_BUNDLE_IDENTIFIER = "com.mbkwon.MBA-book-browser"; 199 | PRODUCT_NAME = "$(TARGET_NAME)"; 200 | SWIFT_EMIT_LOC_STRINGS = YES; 201 | SWIFT_VERSION = 5.0; 202 | TARGETED_DEVICE_FAMILY = "1,2"; 203 | }; 204 | name = Release; 205 | }; 206 | 01AEF54E2D6C46FD00962B6E /* Debug */ = { 207 | isa = XCBuildConfiguration; 208 | buildSettings = { 209 | ALWAYS_SEARCH_USER_PATHS = NO; 210 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 211 | CLANG_ANALYZER_NONNULL = YES; 212 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 213 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 214 | CLANG_ENABLE_MODULES = YES; 215 | CLANG_ENABLE_OBJC_ARC = YES; 216 | CLANG_ENABLE_OBJC_WEAK = YES; 217 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 218 | CLANG_WARN_BOOL_CONVERSION = YES; 219 | CLANG_WARN_COMMA = YES; 220 | CLANG_WARN_CONSTANT_CONVERSION = YES; 221 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 222 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 223 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 224 | CLANG_WARN_EMPTY_BODY = YES; 225 | CLANG_WARN_ENUM_CONVERSION = YES; 226 | CLANG_WARN_INFINITE_RECURSION = YES; 227 | CLANG_WARN_INT_CONVERSION = YES; 228 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 229 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 230 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 231 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 232 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 233 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 234 | CLANG_WARN_STRICT_PROTOTYPES = YES; 235 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 236 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 237 | CLANG_WARN_UNREACHABLE_CODE = YES; 238 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 239 | COPY_PHASE_STRIP = NO; 240 | DEBUG_INFORMATION_FORMAT = dwarf; 241 | ENABLE_STRICT_OBJC_MSGSEND = YES; 242 | ENABLE_TESTABILITY = YES; 243 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 244 | GCC_C_LANGUAGE_STANDARD = gnu17; 245 | GCC_DYNAMIC_NO_PIC = NO; 246 | GCC_NO_COMMON_BLOCKS = YES; 247 | GCC_OPTIMIZATION_LEVEL = 0; 248 | GCC_PREPROCESSOR_DEFINITIONS = ( 249 | "DEBUG=1", 250 | "$(inherited)", 251 | ); 252 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 253 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 254 | GCC_WARN_UNDECLARED_SELECTOR = YES; 255 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 256 | GCC_WARN_UNUSED_FUNCTION = YES; 257 | GCC_WARN_UNUSED_VARIABLE = YES; 258 | IPHONEOS_DEPLOYMENT_TARGET = 18.2; 259 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 260 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 261 | MTL_FAST_MATH = YES; 262 | ONLY_ACTIVE_ARCH = YES; 263 | SDKROOT = iphoneos; 264 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 265 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 266 | }; 267 | name = Debug; 268 | }; 269 | 01AEF54F2D6C46FD00962B6E /* Release */ = { 270 | isa = XCBuildConfiguration; 271 | buildSettings = { 272 | ALWAYS_SEARCH_USER_PATHS = NO; 273 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 274 | CLANG_ANALYZER_NONNULL = YES; 275 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 276 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 277 | CLANG_ENABLE_MODULES = YES; 278 | CLANG_ENABLE_OBJC_ARC = YES; 279 | CLANG_ENABLE_OBJC_WEAK = YES; 280 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 281 | CLANG_WARN_BOOL_CONVERSION = YES; 282 | CLANG_WARN_COMMA = YES; 283 | CLANG_WARN_CONSTANT_CONVERSION = YES; 284 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 285 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 286 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 287 | CLANG_WARN_EMPTY_BODY = YES; 288 | CLANG_WARN_ENUM_CONVERSION = YES; 289 | CLANG_WARN_INFINITE_RECURSION = YES; 290 | CLANG_WARN_INT_CONVERSION = YES; 291 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 292 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 293 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 294 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 295 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 296 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 297 | CLANG_WARN_STRICT_PROTOTYPES = YES; 298 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 299 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 300 | CLANG_WARN_UNREACHABLE_CODE = YES; 301 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 302 | COPY_PHASE_STRIP = NO; 303 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 304 | ENABLE_NS_ASSERTIONS = NO; 305 | ENABLE_STRICT_OBJC_MSGSEND = YES; 306 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 307 | GCC_C_LANGUAGE_STANDARD = gnu17; 308 | GCC_NO_COMMON_BLOCKS = YES; 309 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 310 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 311 | GCC_WARN_UNDECLARED_SELECTOR = YES; 312 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 313 | GCC_WARN_UNUSED_FUNCTION = YES; 314 | GCC_WARN_UNUSED_VARIABLE = YES; 315 | IPHONEOS_DEPLOYMENT_TARGET = 18.2; 316 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 317 | MTL_ENABLE_DEBUG_INFO = NO; 318 | MTL_FAST_MATH = YES; 319 | SDKROOT = iphoneos; 320 | SWIFT_COMPILATION_MODE = wholemodule; 321 | VALIDATE_PRODUCT = YES; 322 | }; 323 | name = Release; 324 | }; 325 | /* End XCBuildConfiguration section */ 326 | 327 | /* Begin XCConfigurationList section */ 328 | 01AEF5332D6C46FC00962B6E /* Build configuration list for PBXProject "MBA-book-browser" */ = { 329 | isa = XCConfigurationList; 330 | buildConfigurations = ( 331 | 01AEF54E2D6C46FD00962B6E /* Debug */, 332 | 01AEF54F2D6C46FD00962B6E /* Release */, 333 | ); 334 | defaultConfigurationIsVisible = 0; 335 | defaultConfigurationName = Release; 336 | }; 337 | 01AEF54B2D6C46FD00962B6E /* Build configuration list for PBXNativeTarget "MBA-book-browser" */ = { 338 | isa = XCConfigurationList; 339 | buildConfigurations = ( 340 | 01AEF54C2D6C46FD00962B6E /* Debug */, 341 | 01AEF54D2D6C46FD00962B6E /* Release */, 342 | ); 343 | defaultConfigurationIsVisible = 0; 344 | defaultConfigurationName = Release; 345 | }; 346 | /* End XCConfigurationList section */ 347 | 348 | /* Begin XCRemoteSwiftPackageReference section */ 349 | 0150A3582D6C60840041C8F2 /* XCRemoteSwiftPackageReference "MBA-kit" */ = { 350 | isa = XCRemoteSwiftPackageReference; 351 | repositoryURL = "https://github.com/MBKwon/MBA-kit"; 352 | requirement = { 353 | kind = upToNextMajorVersion; 354 | minimumVersion = 0.9.9; 355 | }; 356 | }; 357 | /* End XCRemoteSwiftPackageReference section */ 358 | 359 | /* Begin XCSwiftPackageProductDependency section */ 360 | 0150A3592D6C60840041C8F2 /* MBAkit */ = { 361 | isa = XCSwiftPackageProductDependency; 362 | package = 0150A3582D6C60840041C8F2 /* XCRemoteSwiftPackageReference "MBA-kit" */; 363 | productName = MBAkit; 364 | }; 365 | /* End XCSwiftPackageProductDependency section */ 366 | }; 367 | rootObject = 01AEF5302D6C46FC00962B6E /* Project object */; 368 | } 369 | -------------------------------------------------------------------------------- /MBA-book-browser/MBA-book-browser.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MBA-book-browser/MBA-book-browser.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "cd9f379f6c98550e9fb9d601c21d2878bff84ee876003fb089e2baa6f6cfee04", 3 | "pins" : [ 4 | { 5 | "identity" : "mba-kit", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/MBKwon/MBA-kit", 8 | "state" : { 9 | "revision" : "5905bd6d0adf9fe21bc318010a2710eb6ef801ea", 10 | "version" : "0.9.9" 11 | } 12 | }, 13 | { 14 | "identity" : "resultextensions", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/MBKwon/ResultExtensions", 17 | "state" : { 18 | "revision" : "60c252f7a57c8b64f51bb566722e5654756901db", 19 | "version" : "0.9.5" 20 | } 21 | } 22 | ], 23 | "version" : 3 24 | } 25 | -------------------------------------------------------------------------------- /MBA-book-browser/MBA-book-browser/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MBKwon/MBA-Demo/3e55c0ba0cc7796b3887cc0c0df6d7265671d05a/MBA-book-browser/MBA-book-browser/.DS_Store -------------------------------------------------------------------------------- /MBA-book-browser/MBA-book-browser/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MBA-book-browser 4 | // 5 | // Created by Moonbeom KWON on 2/24/25. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /MBA-book-browser/MBA-book-browser/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /MBA-book-browser/MBA-book-browser/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "dark" 13 | } 14 | ], 15 | "idiom" : "universal", 16 | "platform" : "ios", 17 | "size" : "1024x1024" 18 | }, 19 | { 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "tinted" 24 | } 25 | ], 26 | "idiom" : "universal", 27 | "platform" : "ios", 28 | "size" : "1024x1024" 29 | } 30 | ], 31 | "info" : { 32 | "author" : "xcode", 33 | "version" : 1 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /MBA-book-browser/MBA-book-browser/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /MBA-book-browser/MBA-book-browser/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /MBA-book-browser/MBA-book-browser/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 45 | 54 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 177 | 183 | 189 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | -------------------------------------------------------------------------------- /MBA-book-browser/MBA-book-browser/Entity/APIOpenLibrary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APIOpenLibrary.swift 3 | // MBA-book-browser 4 | // 5 | // Created by Moonbeom KWON on 2/25/25. 6 | // 7 | 8 | import Foundation 9 | import MBAkit_url_session 10 | 11 | class APIOpenLibrary { 12 | 13 | // document : "https://developers.google.com/books/docs/v1/using?hl=en#PerformingSearch" 14 | private lazy var session: API = { 15 | API(with: .init(scheme: "https", 16 | host: "www.googleapis.com")) 17 | }() 18 | 19 | enum APIType: APIPath { 20 | 21 | case search(text: String, startIndex: Int?) 22 | 23 | var method: API.HTTPMethod { 24 | switch self { 25 | case .search: 26 | return .get 27 | } 28 | } 29 | 30 | var pathString: String { 31 | switch self { 32 | case .search: 33 | return "/books/v1/volumes" 34 | } 35 | } 36 | 37 | var parameters: [String: String]? { 38 | switch self { 39 | case .search(let text, let startIndex): 40 | var paramDic = [ 41 | "q": text, 42 | "maxResults": "40" 43 | ] 44 | if let startIndex = startIndex { 45 | paramDic["startIndex"] = "\(startIndex)" 46 | } 47 | return paramDic 48 | } 49 | } 50 | } 51 | } 52 | 53 | extension APIOpenLibrary { 54 | func request(with type: APIType) async -> Result { 55 | return await session.request(path: type, method: type.method) 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /MBA-book-browser/MBA-book-browser/Entity/APISearchDataModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APISearchDataModel.swift 3 | // MBA-book-browser 4 | // 5 | // Created by Moonbeom KWON on 2/24/25. 6 | // 7 | 8 | struct APISearchDataModel: Codable { 9 | 10 | let kind: String 11 | let totalItems: Int 12 | let items: [APIBookDataModel] 13 | 14 | enum CodingKeys: String, CodingKey { 15 | case kind 16 | case totalItems 17 | case items 18 | } 19 | } 20 | 21 | struct APIBookDataModel: Codable { 22 | let id: String 23 | let etag: String 24 | let volumeInfo: APIVolumeInfoModel 25 | let saleInfo: APISaleInfoModel 26 | 27 | enum CodingKeys: String, CodingKey { 28 | case id 29 | case etag 30 | case volumeInfo 31 | case saleInfo 32 | } 33 | } 34 | 35 | struct APIVolumeInfoModel: Codable { 36 | let title: String 37 | let subtitle: String? 38 | let authors: [String]? 39 | let publisher: String? 40 | let publishedDate: String? 41 | let description: String? 42 | let imageLinks: APIVolumeImageModel? 43 | 44 | enum CodingKeys: String, CodingKey { 45 | case title 46 | case subtitle 47 | case authors 48 | case publisher 49 | case publishedDate 50 | case description 51 | case imageLinks 52 | } 53 | } 54 | 55 | struct APIVolumeImageModel: Codable { 56 | let smallThumbnail: String 57 | let thumbnail: String 58 | 59 | enum CodingKeys: String, CodingKey { 60 | case smallThumbnail 61 | case thumbnail 62 | } 63 | } 64 | 65 | struct APISaleInfoModel: Codable { 66 | let country: String 67 | let saleability: String 68 | let isEbook: Bool 69 | let listPrice: APISalePriceModel? 70 | let retailPrice: APISalePriceModel? 71 | let buyLink: String? 72 | 73 | enum CodingKeys: String, CodingKey { 74 | case country 75 | case saleability 76 | case isEbook 77 | case listPrice 78 | case retailPrice 79 | case buyLink 80 | } 81 | } 82 | 83 | struct APISalePriceModel: Codable { 84 | let amount: Float 85 | let currencyCode: String 86 | 87 | enum CodingKeys: String, CodingKey { 88 | case amount 89 | case currencyCode 90 | } 91 | } 92 | 93 | /* 94 | { 95 | "kind": "books#volume", 96 | "id": "RKAFCwAAQBAJ", 97 | "etag": "Hno3Vm0oc1I", 98 | "selfLink": "https://www.googleapis.com/books/v1/volumes/RKAFCwAAQBAJ", 99 | "volumeInfo": { 100 | "title": "Swift for Beginners", 101 | "subtitle": "Develop and Design", 102 | "authors": [ 103 | "Boisy G. Pitre" 104 | ], 105 | "publisher": "Peachpit Press", 106 | "publishedDate": "2015-11-26", 107 | "description": "LEARNING A NEW PROGRAMMING LANGUAGE can be daunting. With Swift, Apple has lowered the barrier of entry for developing iOS and OS X apps by giving developers an innovative programming language for Cocoa and Cocoa Touch. Now in its second edition, Swift for Beginners has been updated to accommodate the evolving features of this rapidly adopted language. If you are new to Swift, this book is for you. If you have never used C, C++, or Objective-C, this book is definitely for you. With this handson guide, you’ll quickly be writing Swift code, using Playgrounds to instantly see the results of your work. Author Boisy G. Pitre gives you a solid grounding in key Swift language concepts—including variables, constants, types, arrays, and dictionaries—before he shows you how to use Swift’s innovative Xcode integrated development environment to create apps for iOS and OS X. THIS BOOK INCLUDES: Detailed instruction, ample illustrations, and clear examples Best practices from an experienced Mac and iOS developer Emphasis on how to use Xcode, Playgrounds, and the REPL COMPANION WEBSITE: www.peachpit.com/swiftbeginners2 includes additional resources.", 108 | "industryIdentifiers": [ 109 | { 110 | "type": "ISBN_13", 111 | "identifier": "9780134289786" 112 | }, 113 | { 114 | "type": "ISBN_10", 115 | "identifier": "0134289781" 116 | } 117 | ], 118 | "readingModes": { 119 | "text": true, 120 | "image": true 121 | }, 122 | "pageCount": 702, 123 | "printType": "BOOK", 124 | "categories": [ 125 | "Computers" 126 | ], 127 | "maturityRating": "NOT_MATURE", 128 | "allowAnonLogging": true, 129 | "contentVersion": "1.12.11.0.preview.3", 130 | "panelizationSummary": { 131 | "containsEpubBubbles": false, 132 | "containsImageBubbles": false 133 | }, 134 | "imageLinks": { 135 | "smallThumbnail": "http://books.google.com/books/content?id=RKAFCwAAQBAJ&printsec=frontcover&img=1&zoom=5&edge=curl&source=gbs_api", 136 | "thumbnail": "http://books.google.com/books/content?id=RKAFCwAAQBAJ&printsec=frontcover&img=1&zoom=1&edge=curl&source=gbs_api" 137 | }, 138 | "language": "en", 139 | "previewLink": "http://books.google.co.kr/books?id=RKAFCwAAQBAJ&printsec=frontcover&dq=intitle:swift&hl=&cd=1&source=gbs_api", 140 | "infoLink": "https://play.google.com/store/books/details?id=RKAFCwAAQBAJ&source=gbs_api", 141 | "canonicalVolumeLink": "https://play.google.com/store/books/details?id=RKAFCwAAQBAJ" 142 | }, 143 | "saleInfo": { 144 | "country": "KR", 145 | "saleability": "FOR_SALE", 146 | "isEbook": true, 147 | "listPrice": { 148 | "amount": 28280, 149 | "currencyCode": "KRW" 150 | }, 151 | "retailPrice": { 152 | "amount": 25452, 153 | "currencyCode": "KRW" 154 | }, 155 | "buyLink": "https://play.google.com/store/books/details?id=RKAFCwAAQBAJ&rdid=book-RKAFCwAAQBAJ&rdot=1&source=gbs_api", 156 | "offers": [ 157 | { 158 | "finskyOfferType": 1, 159 | "listPrice": { 160 | "amountInMicros": 28280000000, 161 | "currencyCode": "KRW" 162 | }, 163 | "retailPrice": { 164 | "amountInMicros": 25452000000, 165 | "currencyCode": "KRW" 166 | } 167 | } 168 | ] 169 | }, 170 | "accessInfo": { 171 | "country": "KR", 172 | "viewability": "PARTIAL", 173 | "embeddable": true, 174 | "publicDomain": false, 175 | "textToSpeechPermission": "ALLOWED_FOR_ACCESSIBILITY", 176 | "epub": { 177 | "isAvailable": true, 178 | "acsTokenLink": "http://books.google.co.kr/books/download/Swift_for_Beginners-sample-epub.acsm?id=RKAFCwAAQBAJ&format=epub&output=acs4_fulfillment_token&dl_type=sample&source=gbs_api" 179 | }, 180 | "pdf": { 181 | "isAvailable": true, 182 | "acsTokenLink": "http://books.google.co.kr/books/download/Swift_for_Beginners-sample-pdf.acsm?id=RKAFCwAAQBAJ&format=pdf&output=acs4_fulfillment_token&dl_type=sample&source=gbs_api" 183 | }, 184 | "webReaderLink": "http://play.google.com/books/reader?id=RKAFCwAAQBAJ&hl=&source=gbs_api", 185 | "accessViewStatus": "SAMPLE", 186 | "quoteSharingAllowed": false 187 | }, 188 | "searchInfo": { 189 | "textSnippet": "If you are new to Swift, this book is for you. If you have never used C, C++, or Objective-C, this book is definitely for you." 190 | } 191 | } */ 192 | 193 | -------------------------------------------------------------------------------- /MBA-book-browser/MBA-book-browser/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | UIApplicationSceneManifest 11 | 12 | UIApplicationSupportsMultipleScenes 13 | 14 | UISceneConfigurations 15 | 16 | UIWindowSceneSessionRoleApplication 17 | 18 | 19 | UISceneConfigurationName 20 | Default Configuration 21 | UISceneDelegateClassName 22 | $(PRODUCT_MODULE_NAME).SceneDelegate 23 | UISceneStoryboardFile 24 | Main 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /MBA-book-browser/MBA-book-browser/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // MBA-book-browser 4 | // 5 | // Created by Moonbeom KWON on 2/24/25. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 19 | guard let _ = (scene as? UIWindowScene) else { return } 20 | } 21 | 22 | func sceneDidDisconnect(_ scene: UIScene) { 23 | // Called as the scene is being released by the system. 24 | // This occurs shortly after the scene enters the background, or when its session is discarded. 25 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 27 | } 28 | 29 | func sceneDidBecomeActive(_ scene: UIScene) { 30 | // Called when the scene has moved from an inactive state to an active state. 31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 32 | } 33 | 34 | func sceneWillResignActive(_ scene: UIScene) { 35 | // Called when the scene will move from an active state to an inactive state. 36 | // This may occur due to temporary interruptions (ex. an incoming phone call). 37 | } 38 | 39 | func sceneWillEnterForeground(_ scene: UIScene) { 40 | // Called as the scene transitions from the background to the foreground. 41 | // Use this method to undo the changes made on entering the background. 42 | } 43 | 44 | func sceneDidEnterBackground(_ scene: UIScene) { 45 | // Called as the scene transitions from the foreground to the background. 46 | // Use this method to save data, release shared resources, and store enough scene-specific state information 47 | // to restore the scene back to its current state. 48 | } 49 | 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /MBA-book-browser/MBA-book-browser/source files/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // MBA-book-browser 4 | // 5 | // Created by Moonbeom KWON on 2/24/25. 6 | // 7 | 8 | import MBAkit_core 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | enum SegueIdentifier: String { 14 | case showBookDetail 15 | } 16 | 17 | @IBOutlet private weak var tableView: UITableView! 18 | @IBOutlet private weak var loadingBackgroundView: UIView! 19 | @IBOutlet private weak var loadingIndicatorView: UIActivityIndicatorView! 20 | 21 | private var datasource: TableViewDelegate.BookListData? 22 | 23 | private var isPrefetching: Bool = false { 24 | didSet { 25 | loadingBackgroundView.isHidden = !isPrefetching 26 | if isPrefetching { 27 | loadingIndicatorView.startAnimating() 28 | } else { 29 | loadingIndicatorView.stopAnimating() 30 | } 31 | } 32 | } 33 | 34 | private(set) var microBean: MicroBean? 35 | 36 | override func viewDidLoad() { 37 | super.viewDidLoad() 38 | // Do any additional setup after loading the view. 39 | tableView.isPrefetchingEnabled = true 40 | tableView.prefetchDataSource = self 41 | 42 | microBean = MicroBean(withVC: self, 43 | viewModel: ViewModel(), 44 | viewInteractor: ViewInteractor()) 45 | } 46 | 47 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 48 | if segue.identifier == SegueIdentifier.showBookDetail.rawValue, 49 | let detailVC = segue.destination as? BookDetailViewController, 50 | let selectedBook = sender as? APIBookDataModel { 51 | detailVC.selectedBook = selectedBook 52 | } 53 | } 54 | 55 | private func loadBookList(with keyword: String) { 56 | let message = { 57 | if let datasource = self.datasource, datasource.keyword == keyword { 58 | return I.pageBookListData(keyword: keyword, 59 | startIndex: datasource.itemList.count) 60 | } else { 61 | return I.searchBookListData(keyword: keyword) 62 | } 63 | }() 64 | microBean?.handle(inputMessage: message) 65 | } 66 | 67 | private func reloadTableView(with result: Result) { 68 | result.fold(success: { _ in tableView.reloadData() }, 69 | failure: { print($0.localizedDescription) }) 70 | 71 | isPrefetching = false 72 | } 73 | } 74 | 75 | extension ViewController: ViewControllerConfigurable { 76 | 77 | typealias VM = ViewModel 78 | 79 | typealias I = ViewInputMessage 80 | enum ViewInputMessage: InputMessage { 81 | case searchBookListData(keyword: String) 82 | case pageBookListData(keyword: String, startIndex: Int) 83 | } 84 | 85 | typealias O = ViewOutputMessage 86 | enum ViewOutputMessage: OutputMessage { 87 | case searchBookList(keyword:String, result: APISearchDataModel) 88 | case pageBookList(result: APISearchDataModel) 89 | } 90 | } 91 | 92 | extension ViewController: ViewContollerInteractable { 93 | 94 | typealias VI = ViewInteractor 95 | 96 | typealias IM = ViewInteractionMessage 97 | enum ViewInteractionMessage: InteractionMessage { 98 | case reloadBookList(tableView: UITableView, 99 | datasource: TableViewDelegate.BookListData, 100 | vc: ViewController) 101 | } 102 | 103 | func convertToInteraction(from outputMessage: ViewOutputMessage) -> ViewInteractionMessage { 104 | let datasource: TableViewDelegate.BookListData 105 | switch outputMessage { 106 | case .searchBookList(let keyword, let result): 107 | datasource = TableViewDelegate.BookListData(keyword: keyword, 108 | latestResponse: result, 109 | itemList: result.items) 110 | case .pageBookList(let result): 111 | let keyword = self.datasource?.keyword ?? "" 112 | let exList = self.datasource?.itemList ?? [] 113 | datasource = TableViewDelegate.BookListData(keyword: keyword, 114 | latestResponse: result, 115 | itemList: exList + result.items) 116 | } 117 | isPrefetching = false 118 | self.datasource = datasource 119 | return .reloadBookList(tableView: tableView, datasource: datasource, vc: self) 120 | } 121 | } 122 | 123 | extension ViewController: UITextFieldDelegate { 124 | func textFieldShouldReturn(_ textField: UITextField) -> Bool { 125 | if let text = textField.text, text.isEmpty == false { 126 | loadBookList(with: text) 127 | } else { 128 | print("Empty text") 129 | } 130 | textField.resignFirstResponder() 131 | return true 132 | } 133 | } 134 | 135 | extension ViewController: UITableViewDataSourcePrefetching { 136 | 137 | func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) { 138 | guard let datasource, isPrefetching == false else { return } 139 | let indexForPrefetching = indexPaths.first?.row ?? 0 140 | guard datasource.latestResponse.totalItems >= indexForPrefetching + 1, 141 | datasource.itemList.count < indexForPrefetching else { return } 142 | 143 | isPrefetching = true 144 | loadBookList(with: datasource.keyword) 145 | } 146 | } 147 | 148 | extension ViewController { 149 | func selectBookCell(at indexPath: IndexPath) { 150 | guard let datasource = datasource, datasource.itemList.count > indexPath.row else { return } 151 | performSegue(withIdentifier: SegueIdentifier.showBookDetail.rawValue, 152 | sender: datasource.itemList[indexPath.row]) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /MBA-book-browser/MBA-book-browser/source files/ViewInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewInteractor.swift 3 | // MBA-book-browser 4 | // 5 | // Created by Moonbeom KWON on 2/26/25. 6 | // 7 | 8 | import MBAkit_core 9 | 10 | class ViewInteractor: ViewInteractorConfigurable { 11 | 12 | typealias VC = ViewController 13 | private var tableViewDelegate: TableViewDelegate? 14 | 15 | func handleMessage(_ interactionMessage: ViewController.ViewInteractionMessage) { 16 | switch interactionMessage { 17 | case .reloadBookList(let tableView, let datasource, let vc): 18 | let tableViewDelegate = TableViewDelegate(with: datasource, vc: vc) 19 | tableView.dataSource = tableViewDelegate 20 | tableView.delegate = tableViewDelegate 21 | tableView.reloadData() 22 | 23 | self.tableViewDelegate = tableViewDelegate 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /MBA-book-browser/MBA-book-browser/source files/ViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewModel.swift 3 | // MBA-book-browser 4 | // 5 | // Created by Moonbeom KWON on 2/26/25. 6 | // 7 | 8 | import Combine 9 | import MBAkit_core 10 | 11 | class ViewModel: ViewModelConfigurable { 12 | 13 | typealias VC = ViewController 14 | private(set) var outputSubject = PassthroughSubject, Never>() 15 | 16 | private lazy var apiLibrary: APIOpenLibrary = .init() 17 | 18 | func handleMessage(_ inputMessage: VC.I) { 19 | Task { 20 | switch inputMessage { 21 | case .searchBookListData(let keyword): 22 | await searchBookListData(with: keyword) 23 | case .pageBookListData(let keyword, let startIndex): 24 | await pageBookListData(with: keyword, from: startIndex) 25 | } 26 | } 27 | } 28 | } 29 | 30 | extension ViewModel { 31 | private func searchBookListData(with keyword: String) async { 32 | await apiLibrary 33 | .request(with: .search(text: keyword, startIndex: 0)) 34 | .decode(decoder: APISearchDataModel.self) 35 | .map({ VC.O.searchBookList(keyword: keyword, result: $0) }) 36 | .send(through: outputSubject) 37 | } 38 | 39 | private func pageBookListData(with keyword: String, from startIndex: Int) async { 40 | await apiLibrary 41 | .request(with: .search(text: keyword, startIndex: startIndex)) 42 | .decode(decoder: APISearchDataModel.self) 43 | .map({ VC.O.pageBookList(result: $0) }) 44 | .send(through: outputSubject) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /MBA-book-browser/MBA-book-browser/source files/detail view/BookImageCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BookImageCell.swift 3 | // MBA-book-browser 4 | // 5 | // Created by Moonbeom KWON on 2/24/25. 6 | // 7 | 8 | import UIKit 9 | 10 | class BookImageCell: UITableViewCell { 11 | @IBOutlet weak var photoImageView: UIImageView! 12 | @IBOutlet weak var bookTitleLabel: UILabel! 13 | @IBOutlet weak var bookSubtitleLabel: UILabel! 14 | @IBOutlet weak var saleStatusLabel: UILabel! 15 | 16 | var photoImageURL: String? 17 | 18 | override func prepareForReuse() { 19 | super.prepareForReuse() 20 | 21 | photoImageView.image = nil 22 | bookTitleLabel.text = nil 23 | bookSubtitleLabel.text = nil 24 | saleStatusLabel.text = nil 25 | photoImageURL = nil 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /MBA-book-browser/MBA-book-browser/source files/detail view/DetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailViewController.swift 3 | // MBA-book-browser 4 | // 5 | // Created by Moonbeom KWON on 2/24/25. 6 | // 7 | 8 | import MBAkit_image_loader 9 | import SafariServices 10 | import UIKit 11 | 12 | class BookDetailViewController: UIViewController { 13 | 14 | @IBOutlet private weak var bookImageView: UIImageView! 15 | @IBOutlet private weak var titleLabel: UILabel! 16 | @IBOutlet private weak var subtitleLabel: UILabel! 17 | @IBOutlet private weak var authorLabel: UILabel! 18 | @IBOutlet private weak var publishDateLabel: UILabel! 19 | 20 | @IBOutlet private weak var bookInfoTextView: UITextView! 21 | @IBOutlet private weak var buyLinkButton: UIButton! 22 | 23 | var selectedBook: APIBookDataModel? 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | } 28 | 29 | override func viewWillAppear(_ animated: Bool) { 30 | super.viewWillAppear(animated) 31 | 32 | guard let bookInfo = selectedBook else { return } 33 | if let imageUrlString = bookInfo.volumeInfo.imageLinks?.thumbnail, 34 | let imageURL = URL(string: imageUrlString) { 35 | Task { 36 | let imageData = try? await ImageLoader.loadImage(with: imageURL).get().data 37 | if let imageData = imageData { 38 | DispatchQueue.main.async { 39 | self.bookImageView.image = UIImage(data: imageData) 40 | } 41 | } 42 | } 43 | } 44 | 45 | titleLabel.text = bookInfo.volumeInfo.title 46 | subtitleLabel.text = bookInfo.volumeInfo.subtitle 47 | 48 | let authors = bookInfo.volumeInfo.authors?.joined(separator: " / ") 49 | authorLabel.text = authors 50 | 51 | let publishDate = [bookInfo.volumeInfo.publishedDate, bookInfo.volumeInfo.publisher] 52 | .compactMap({ $0 }) 53 | .joined(separator: " / ") 54 | publishDateLabel.text = publishDate 55 | bookInfoTextView.text = bookInfo.volumeInfo.description 56 | 57 | var priceInfo: [String] = [] 58 | if let listPrice = bookInfo.saleInfo.listPrice { 59 | priceInfo.append("\(listPrice.currencyCode) \(listPrice.amount)") 60 | } 61 | if let retailPrice = bookInfo.saleInfo.retailPrice { 62 | priceInfo.append("\(retailPrice.currencyCode) \(retailPrice.amount)") 63 | } 64 | let priceInfoText: [String] = [ 65 | priceInfo.joined(separator: " -> "), 66 | bookInfo.saleInfo.saleability 67 | ] 68 | 69 | buyLinkButton.titleLabel?.textAlignment = .center 70 | buyLinkButton.setTitle("\(priceInfoText.joined(separator: "\n"))", for: .normal) 71 | } 72 | } 73 | 74 | extension BookDetailViewController { 75 | @IBAction private func touchButLinkButton() { 76 | guard let linkString = selectedBook?.saleInfo.buyLink, 77 | let linkURL = URL(string: linkString) else { 78 | let alert = UIAlertController(title: "No Link", message: nil, preferredStyle: .alert) 79 | alert.addAction(UIAlertAction(title: "OK", style: .default)) 80 | show(alert, sender: nil) 81 | return 82 | } 83 | 84 | show(SFSafariViewController(url: linkURL), sender: nil) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /MBA-book-browser/MBA-book-browser/source files/table view/TableViewDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewDelegate.swift 3 | // MBA-book-browser 4 | // 5 | // Created by Moonbeom KWON on 2/26/25. 6 | // 7 | 8 | import MBAkit_image_loader 9 | import UIKit 10 | 11 | class TableViewDelegate: NSObject { 12 | struct BookListData { 13 | let keyword: String 14 | let latestResponse: APISearchDataModel 15 | let itemList: [APIBookDataModel] 16 | } 17 | 18 | private let data: BookListData 19 | private let vc: ViewController 20 | 21 | init(with data: BookListData, vc: ViewController) { 22 | self.data = data 23 | self.vc = vc 24 | } 25 | } 26 | 27 | extension TableViewDelegate: UITableViewDataSource { 28 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 29 | return data.latestResponse.totalItems 30 | } 31 | 32 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 33 | guard let cell = tableView.dequeueReusableCell(withIdentifier: "BookImageCell") as? BookImageCell else { 34 | return UITableViewCell() 35 | } 36 | 37 | cell.selectionStyle = .none 38 | 39 | if data.itemList.count > indexPath.row { 40 | let bookDataModel = data.itemList[indexPath.row] 41 | cell.bookTitleLabel.text = bookDataModel.volumeInfo.title 42 | cell.bookSubtitleLabel.text = bookDataModel.volumeInfo.subtitle 43 | cell.saleStatusLabel.text = bookDataModel.saleInfo.saleability 44 | 45 | 46 | if let thumbnailURL = bookDataModel.volumeInfo.imageLinks?.smallThumbnail, 47 | let imageURL = URL(string: thumbnailURL) { 48 | 49 | cell.photoImageURL = thumbnailURL 50 | Task { 51 | switch await ImageLoader.loadImage(with: imageURL) { 52 | case .success(let imageData): 53 | guard imageData.url == cell.photoImageURL else { return } 54 | DispatchQueue.main.async { 55 | cell.photoImageView.image = UIImage(data: imageData.data) 56 | } 57 | 58 | case .failure(let error): 59 | print("ImageLoad Error: \(error)") 60 | } 61 | } 62 | } else { 63 | cell.photoImageView.image = nil 64 | } 65 | } else { 66 | cell.photoImageView.image = nil 67 | } 68 | return cell 69 | } 70 | } 71 | 72 | extension TableViewDelegate: UITableViewDelegate { 73 | 74 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 75 | return 80 76 | } 77 | 78 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 79 | vc.selectBookCell(at: indexPath) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /MBA-calculator/MBA-calculator.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 77; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0172633B2D6C500A000D577A /* MBAkit-core in Frameworks */ = {isa = PBXBuildFile; productRef = 0172633A2D6C500A000D577A /* MBAkit-core */; }; 11 | /* End PBXBuildFile section */ 12 | 13 | /* Begin PBXFileReference section */ 14 | 018B6BA02D699DE600ECB5EE /* MBA-calculator.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "MBA-calculator.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 15 | /* End PBXFileReference section */ 16 | 17 | /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ 18 | 018B6BB22D699DE800ECB5EE /* Exceptions for "MBA-calculator" folder in "MBA-calculator" target */ = { 19 | isa = PBXFileSystemSynchronizedBuildFileExceptionSet; 20 | membershipExceptions = ( 21 | Info.plist, 22 | ); 23 | target = 018B6B9F2D699DE600ECB5EE /* MBA-calculator */; 24 | }; 25 | /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ 26 | 27 | /* Begin PBXFileSystemSynchronizedRootGroup section */ 28 | 018B6BA22D699DE600ECB5EE /* MBA-calculator */ = { 29 | isa = PBXFileSystemSynchronizedRootGroup; 30 | exceptions = ( 31 | 018B6BB22D699DE800ECB5EE /* Exceptions for "MBA-calculator" folder in "MBA-calculator" target */, 32 | ); 33 | path = "MBA-calculator"; 34 | sourceTree = ""; 35 | }; 36 | /* End PBXFileSystemSynchronizedRootGroup section */ 37 | 38 | /* Begin PBXFrameworksBuildPhase section */ 39 | 018B6B9D2D699DE600ECB5EE /* Frameworks */ = { 40 | isa = PBXFrameworksBuildPhase; 41 | buildActionMask = 2147483647; 42 | files = ( 43 | 0172633B2D6C500A000D577A /* MBAkit-core in Frameworks */, 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | /* End PBXFrameworksBuildPhase section */ 48 | 49 | /* Begin PBXGroup section */ 50 | 018B6B972D699DE600ECB5EE = { 51 | isa = PBXGroup; 52 | children = ( 53 | 018B6BA22D699DE600ECB5EE /* MBA-calculator */, 54 | 018B6BA12D699DE600ECB5EE /* Products */, 55 | ); 56 | sourceTree = ""; 57 | }; 58 | 018B6BA12D699DE600ECB5EE /* Products */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 018B6BA02D699DE600ECB5EE /* MBA-calculator.app */, 62 | ); 63 | name = Products; 64 | sourceTree = ""; 65 | }; 66 | /* End PBXGroup section */ 67 | 68 | /* Begin PBXNativeTarget section */ 69 | 018B6B9F2D699DE600ECB5EE /* MBA-calculator */ = { 70 | isa = PBXNativeTarget; 71 | buildConfigurationList = 018B6BB32D699DE800ECB5EE /* Build configuration list for PBXNativeTarget "MBA-calculator" */; 72 | buildPhases = ( 73 | 018B6B9C2D699DE600ECB5EE /* Sources */, 74 | 018B6B9D2D699DE600ECB5EE /* Frameworks */, 75 | 018B6B9E2D699DE600ECB5EE /* Resources */, 76 | ); 77 | buildRules = ( 78 | ); 79 | dependencies = ( 80 | ); 81 | fileSystemSynchronizedGroups = ( 82 | 018B6BA22D699DE600ECB5EE /* MBA-calculator */, 83 | ); 84 | name = "MBA-calculator"; 85 | packageProductDependencies = ( 86 | 0172633A2D6C500A000D577A /* MBAkit-core */, 87 | ); 88 | productName = "MBA-calculator"; 89 | productReference = 018B6BA02D699DE600ECB5EE /* MBA-calculator.app */; 90 | productType = "com.apple.product-type.application"; 91 | }; 92 | /* End PBXNativeTarget section */ 93 | 94 | /* Begin PBXProject section */ 95 | 018B6B982D699DE600ECB5EE /* Project object */ = { 96 | isa = PBXProject; 97 | attributes = { 98 | BuildIndependentTargetsInParallel = 1; 99 | LastSwiftUpdateCheck = 1620; 100 | LastUpgradeCheck = 1620; 101 | TargetAttributes = { 102 | 018B6B9F2D699DE600ECB5EE = { 103 | CreatedOnToolsVersion = 16.2; 104 | }; 105 | }; 106 | }; 107 | buildConfigurationList = 018B6B9B2D699DE600ECB5EE /* Build configuration list for PBXProject "MBA-calculator" */; 108 | developmentRegion = en; 109 | hasScannedForEncodings = 0; 110 | knownRegions = ( 111 | en, 112 | Base, 113 | ); 114 | mainGroup = 018B6B972D699DE600ECB5EE; 115 | minimizedProjectReferenceProxies = 1; 116 | packageReferences = ( 117 | 017263392D6C500A000D577A /* XCRemoteSwiftPackageReference "MBA-kit" */, 118 | ); 119 | preferredProjectObjectVersion = 77; 120 | productRefGroup = 018B6BA12D699DE600ECB5EE /* Products */; 121 | projectDirPath = ""; 122 | projectRoot = ""; 123 | targets = ( 124 | 018B6B9F2D699DE600ECB5EE /* MBA-calculator */, 125 | ); 126 | }; 127 | /* End PBXProject section */ 128 | 129 | /* Begin PBXResourcesBuildPhase section */ 130 | 018B6B9E2D699DE600ECB5EE /* Resources */ = { 131 | isa = PBXResourcesBuildPhase; 132 | buildActionMask = 2147483647; 133 | files = ( 134 | ); 135 | runOnlyForDeploymentPostprocessing = 0; 136 | }; 137 | /* End PBXResourcesBuildPhase section */ 138 | 139 | /* Begin PBXSourcesBuildPhase section */ 140 | 018B6B9C2D699DE600ECB5EE /* Sources */ = { 141 | isa = PBXSourcesBuildPhase; 142 | buildActionMask = 2147483647; 143 | files = ( 144 | ); 145 | runOnlyForDeploymentPostprocessing = 0; 146 | }; 147 | /* End PBXSourcesBuildPhase section */ 148 | 149 | /* Begin XCBuildConfiguration section */ 150 | 018B6BB42D699DE800ECB5EE /* Debug */ = { 151 | isa = XCBuildConfiguration; 152 | buildSettings = { 153 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 154 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 155 | CODE_SIGN_STYLE = Automatic; 156 | CURRENT_PROJECT_VERSION = 1; 157 | DEVELOPMENT_TEAM = 92F796HA5V; 158 | GENERATE_INFOPLIST_FILE = YES; 159 | INFOPLIST_FILE = "MBA-calculator/Info.plist"; 160 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 161 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 162 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 163 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 164 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 165 | IPHONEOS_DEPLOYMENT_TARGET = 16.6; 166 | LD_RUNPATH_SEARCH_PATHS = ( 167 | "$(inherited)", 168 | "@executable_path/Frameworks", 169 | ); 170 | MARKETING_VERSION = 1.0; 171 | PRODUCT_BUNDLE_IDENTIFIER = "com.mbkwon.MBA-calculator"; 172 | PRODUCT_NAME = "$(TARGET_NAME)"; 173 | SWIFT_EMIT_LOC_STRINGS = YES; 174 | SWIFT_VERSION = 5.0; 175 | TARGETED_DEVICE_FAMILY = "1,2"; 176 | }; 177 | name = Debug; 178 | }; 179 | 018B6BB52D699DE800ECB5EE /* Release */ = { 180 | isa = XCBuildConfiguration; 181 | buildSettings = { 182 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 183 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 184 | CODE_SIGN_STYLE = Automatic; 185 | CURRENT_PROJECT_VERSION = 1; 186 | DEVELOPMENT_TEAM = 92F796HA5V; 187 | GENERATE_INFOPLIST_FILE = YES; 188 | INFOPLIST_FILE = "MBA-calculator/Info.plist"; 189 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 190 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 191 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 192 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 193 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 194 | IPHONEOS_DEPLOYMENT_TARGET = 16.6; 195 | LD_RUNPATH_SEARCH_PATHS = ( 196 | "$(inherited)", 197 | "@executable_path/Frameworks", 198 | ); 199 | MARKETING_VERSION = 1.0; 200 | PRODUCT_BUNDLE_IDENTIFIER = "com.mbkwon.MBA-calculator"; 201 | PRODUCT_NAME = "$(TARGET_NAME)"; 202 | SWIFT_EMIT_LOC_STRINGS = YES; 203 | SWIFT_VERSION = 5.0; 204 | TARGETED_DEVICE_FAMILY = "1,2"; 205 | }; 206 | name = Release; 207 | }; 208 | 018B6BB62D699DE800ECB5EE /* Debug */ = { 209 | isa = XCBuildConfiguration; 210 | buildSettings = { 211 | ALWAYS_SEARCH_USER_PATHS = NO; 212 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 213 | CLANG_ANALYZER_NONNULL = YES; 214 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 215 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 216 | CLANG_ENABLE_MODULES = YES; 217 | CLANG_ENABLE_OBJC_ARC = YES; 218 | CLANG_ENABLE_OBJC_WEAK = YES; 219 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 220 | CLANG_WARN_BOOL_CONVERSION = YES; 221 | CLANG_WARN_COMMA = YES; 222 | CLANG_WARN_CONSTANT_CONVERSION = YES; 223 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 224 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 225 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 226 | CLANG_WARN_EMPTY_BODY = YES; 227 | CLANG_WARN_ENUM_CONVERSION = YES; 228 | CLANG_WARN_INFINITE_RECURSION = YES; 229 | CLANG_WARN_INT_CONVERSION = YES; 230 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 231 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 232 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 233 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 234 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 235 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 236 | CLANG_WARN_STRICT_PROTOTYPES = YES; 237 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 238 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 239 | CLANG_WARN_UNREACHABLE_CODE = YES; 240 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 241 | COPY_PHASE_STRIP = NO; 242 | DEBUG_INFORMATION_FORMAT = dwarf; 243 | ENABLE_STRICT_OBJC_MSGSEND = YES; 244 | ENABLE_TESTABILITY = YES; 245 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 246 | GCC_C_LANGUAGE_STANDARD = gnu17; 247 | GCC_DYNAMIC_NO_PIC = NO; 248 | GCC_NO_COMMON_BLOCKS = YES; 249 | GCC_OPTIMIZATION_LEVEL = 0; 250 | GCC_PREPROCESSOR_DEFINITIONS = ( 251 | "DEBUG=1", 252 | "$(inherited)", 253 | ); 254 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 255 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 256 | GCC_WARN_UNDECLARED_SELECTOR = YES; 257 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 258 | GCC_WARN_UNUSED_FUNCTION = YES; 259 | GCC_WARN_UNUSED_VARIABLE = YES; 260 | IPHONEOS_DEPLOYMENT_TARGET = 18.2; 261 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 262 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 263 | MTL_FAST_MATH = YES; 264 | ONLY_ACTIVE_ARCH = YES; 265 | SDKROOT = iphoneos; 266 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 267 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 268 | }; 269 | name = Debug; 270 | }; 271 | 018B6BB72D699DE800ECB5EE /* Release */ = { 272 | isa = XCBuildConfiguration; 273 | buildSettings = { 274 | ALWAYS_SEARCH_USER_PATHS = NO; 275 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 276 | CLANG_ANALYZER_NONNULL = YES; 277 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 278 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 279 | CLANG_ENABLE_MODULES = YES; 280 | CLANG_ENABLE_OBJC_ARC = YES; 281 | CLANG_ENABLE_OBJC_WEAK = YES; 282 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 283 | CLANG_WARN_BOOL_CONVERSION = YES; 284 | CLANG_WARN_COMMA = YES; 285 | CLANG_WARN_CONSTANT_CONVERSION = YES; 286 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 287 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 288 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 289 | CLANG_WARN_EMPTY_BODY = YES; 290 | CLANG_WARN_ENUM_CONVERSION = YES; 291 | CLANG_WARN_INFINITE_RECURSION = YES; 292 | CLANG_WARN_INT_CONVERSION = YES; 293 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 294 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 295 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 296 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 297 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 298 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 299 | CLANG_WARN_STRICT_PROTOTYPES = YES; 300 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 301 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 302 | CLANG_WARN_UNREACHABLE_CODE = YES; 303 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 304 | COPY_PHASE_STRIP = NO; 305 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 306 | ENABLE_NS_ASSERTIONS = NO; 307 | ENABLE_STRICT_OBJC_MSGSEND = YES; 308 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 309 | GCC_C_LANGUAGE_STANDARD = gnu17; 310 | GCC_NO_COMMON_BLOCKS = YES; 311 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 312 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 313 | GCC_WARN_UNDECLARED_SELECTOR = YES; 314 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 315 | GCC_WARN_UNUSED_FUNCTION = YES; 316 | GCC_WARN_UNUSED_VARIABLE = YES; 317 | IPHONEOS_DEPLOYMENT_TARGET = 18.2; 318 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 319 | MTL_ENABLE_DEBUG_INFO = NO; 320 | MTL_FAST_MATH = YES; 321 | SDKROOT = iphoneos; 322 | SWIFT_COMPILATION_MODE = wholemodule; 323 | VALIDATE_PRODUCT = YES; 324 | }; 325 | name = Release; 326 | }; 327 | /* End XCBuildConfiguration section */ 328 | 329 | /* Begin XCConfigurationList section */ 330 | 018B6B9B2D699DE600ECB5EE /* Build configuration list for PBXProject "MBA-calculator" */ = { 331 | isa = XCConfigurationList; 332 | buildConfigurations = ( 333 | 018B6BB62D699DE800ECB5EE /* Debug */, 334 | 018B6BB72D699DE800ECB5EE /* Release */, 335 | ); 336 | defaultConfigurationIsVisible = 0; 337 | defaultConfigurationName = Release; 338 | }; 339 | 018B6BB32D699DE800ECB5EE /* Build configuration list for PBXNativeTarget "MBA-calculator" */ = { 340 | isa = XCConfigurationList; 341 | buildConfigurations = ( 342 | 018B6BB42D699DE800ECB5EE /* Debug */, 343 | 018B6BB52D699DE800ECB5EE /* Release */, 344 | ); 345 | defaultConfigurationIsVisible = 0; 346 | defaultConfigurationName = Release; 347 | }; 348 | /* End XCConfigurationList section */ 349 | 350 | /* Begin XCRemoteSwiftPackageReference section */ 351 | 017263392D6C500A000D577A /* XCRemoteSwiftPackageReference "MBA-kit" */ = { 352 | isa = XCRemoteSwiftPackageReference; 353 | repositoryURL = "https://github.com/MBKwon/MBA-kit"; 354 | requirement = { 355 | kind = upToNextMajorVersion; 356 | minimumVersion = 0.9.6; 357 | }; 358 | }; 359 | /* End XCRemoteSwiftPackageReference section */ 360 | 361 | /* Begin XCSwiftPackageProductDependency section */ 362 | 0172633A2D6C500A000D577A /* MBAkit-core */ = { 363 | isa = XCSwiftPackageProductDependency; 364 | package = 017263392D6C500A000D577A /* XCRemoteSwiftPackageReference "MBA-kit" */; 365 | productName = "MBAkit-core"; 366 | }; 367 | /* End XCSwiftPackageProductDependency section */ 368 | }; 369 | rootObject = 018B6B982D699DE600ECB5EE /* Project object */; 370 | } 371 | -------------------------------------------------------------------------------- /MBA-calculator/MBA-calculator.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MBA-calculator/MBA-calculator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "cd9f379f6c98550e9fb9d601c21d2878bff84ee876003fb089e2baa6f6cfee04", 3 | "pins" : [ 4 | { 5 | "identity" : "mba-kit", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/MBKwon/MBA-kit", 8 | "state" : { 9 | "revision" : "a2c47f82f56af376504985ebdf558f554bc7c947", 10 | "version" : "0.9.6" 11 | } 12 | }, 13 | { 14 | "identity" : "resultextensions", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/MBKwon/ResultExtensions", 17 | "state" : { 18 | "revision" : "60c252f7a57c8b64f51bb566722e5654756901db", 19 | "version" : "0.9.5" 20 | } 21 | } 22 | ], 23 | "version" : 3 24 | } 25 | -------------------------------------------------------------------------------- /MBA-calculator/MBA-calculator/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MBA-calculator 4 | // 5 | // Created by Moonbeom KWON on 2/22/25. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /MBA-calculator/MBA-calculator/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /MBA-calculator/MBA-calculator/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "dark" 13 | } 14 | ], 15 | "idiom" : "universal", 16 | "platform" : "ios", 17 | "size" : "1024x1024" 18 | }, 19 | { 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "tinted" 24 | } 25 | ], 26 | "idiom" : "universal", 27 | "platform" : "ios", 28 | "size" : "1024x1024" 29 | } 30 | ], 31 | "info" : { 32 | "author" : "xcode", 33 | "version" : 1 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /MBA-calculator/MBA-calculator/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /MBA-calculator/MBA-calculator/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /MBA-calculator/MBA-calculator/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 43 | 55 | 67 | 75 | 76 | 77 | 78 | 79 | 80 | 92 | 104 | 116 | 128 | 129 | 130 | 131 | 132 | 133 | 145 | 157 | 169 | 177 | 178 | 179 | 180 | 181 | 182 | 190 | 198 | 206 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | -------------------------------------------------------------------------------- /MBA-calculator/MBA-calculator/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | UISceneStoryboardFile 19 | Main 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /MBA-calculator/MBA-calculator/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // MBA-calculator 4 | // 5 | // Created by Moonbeom KWON on 2/22/25. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 19 | guard let _ = (scene as? UIWindowScene) else { return } 20 | } 21 | 22 | func sceneDidDisconnect(_ scene: UIScene) { 23 | // Called as the scene is being released by the system. 24 | // This occurs shortly after the scene enters the background, or when its session is discarded. 25 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 27 | } 28 | 29 | func sceneDidBecomeActive(_ scene: UIScene) { 30 | // Called when the scene has moved from an inactive state to an active state. 31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 32 | } 33 | 34 | func sceneWillResignActive(_ scene: UIScene) { 35 | // Called when the scene will move from an active state to an inactive state. 36 | // This may occur due to temporary interruptions (ex. an incoming phone call). 37 | } 38 | 39 | func sceneWillEnterForeground(_ scene: UIScene) { 40 | // Called as the scene transitions from the background to the foreground. 41 | // Use this method to undo the changes made on entering the background. 42 | } 43 | 44 | func sceneDidEnterBackground(_ scene: UIScene) { 45 | // Called as the scene transitions from the foreground to the background. 46 | // Use this method to save data, release shared resources, and store enough scene-specific state information 47 | // to restore the scene back to its current state. 48 | } 49 | 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /MBA-calculator/MBA-calculator/source files/CalcDataManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CalcDataManager.swift 3 | // MBA-calculator 4 | // 5 | // Created by Moonbeom KWON on 2/22/25. 6 | // 7 | 8 | import Foundation 9 | 10 | actor CalcDataManager { 11 | enum CalcElement { 12 | case number(num: Int) 13 | case `operator`(text: String) 14 | case clear 15 | case equal 16 | } 17 | 18 | enum CalcResponse { 19 | case error 20 | case result(text: String) 21 | } 22 | 23 | private var currentText: String = "" 24 | } 25 | 26 | extension CalcDataManager { 27 | func push(_ element: CalcElement) -> CalcResponse { 28 | switch element { 29 | case .number(num: let num): 30 | currentText += "\(num)" 31 | case .operator(text: let text): 32 | currentText += text 33 | case .clear: 34 | currentText.removeLast() 35 | case .equal: 36 | let expression = NSExpression(format: currentText) 37 | if let result = expression.expressionValue(with: nil, context: nil) { 38 | currentText = "\(result)" 39 | } else { 40 | return .error 41 | } 42 | } 43 | 44 | return .result(text: currentText) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /MBA-calculator/MBA-calculator/source files/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // MBA-calculator 4 | // 5 | // Created by Moonbeom KWON on 2/22/25. 6 | // 7 | 8 | import MBAkit_core 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | @IBOutlet private weak var resultLabel: UILabel! 14 | 15 | private(set) var microBean: MicroBean? 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | // Do any additional setup after loading the view. 20 | 21 | self.microBean = MicroBean(withVC: self, 22 | viewModel: ViewModel(), 23 | viewInteractor: ViewInteractor(with: resultLabel)) 24 | } 25 | } 26 | 27 | extension ViewController { 28 | @IBAction private func pushNumberButton(_ sender: UIButton) { 29 | let number = sender.tag 30 | self.microBean?.handle(inputMessage: .pushNumber(number: number)) 31 | } 32 | 33 | @IBAction private func pushClearButton(_ sender: UIButton) { 34 | self.microBean?.handle(inputMessage: .pushClearButton) 35 | } 36 | 37 | @IBAction private func pushEqualButton(_ sender: UIButton) { 38 | self.microBean?.handle(inputMessage: .pushEqualButton) 39 | } 40 | 41 | @IBAction private func pushPlusButton(_ sender: UIButton) { 42 | self.microBean?.handle(inputMessage: .pushPlusButton) 43 | } 44 | 45 | @IBAction private func pushSubstractButton(_ sender: UIButton) { 46 | self.microBean?.handle(inputMessage: .pushSubstractButton) 47 | } 48 | 49 | @IBAction private func pushMultiplyButton(_ sender: UIButton) { 50 | self.microBean?.handle(inputMessage: .pushMultiplyButton) 51 | } 52 | 53 | @IBAction private func pushDevideButton(_ sender: UIButton) { 54 | self.microBean?.handle(inputMessage: .pushDevideButton) 55 | } 56 | } 57 | 58 | extension ViewController: ViewControllerConfigurable { 59 | typealias VM = ViewModel 60 | 61 | typealias I = CalcMessage 62 | enum CalcMessage: InputMessage { 63 | case pushNumber(number: Int) 64 | case pushClearButton 65 | case pushEqualButton 66 | case pushPlusButton 67 | case pushSubstractButton 68 | case pushMultiplyButton 69 | case pushDevideButton 70 | } 71 | 72 | typealias O = ResultMessage 73 | enum ResultMessage: OutputMessage { 74 | case showResult(text: String) 75 | case showError(text: String) 76 | } 77 | } 78 | 79 | extension ViewController: ViewContollerInteractable { 80 | typealias VI = ViewInteractor 81 | 82 | typealias IM = PresentationMessage 83 | enum PresentationMessage: InteractionMessage { 84 | case updateResult(text: String) 85 | } 86 | 87 | func convertToInteraction(from outputMessage: ResultMessage) -> PresentationMessage { 88 | switch outputMessage { 89 | case .showResult(let text): 90 | return .updateResult(text: text) 91 | case .showError(let text): 92 | return .updateResult(text: text) 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /MBA-calculator/MBA-calculator/source files/ViewInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewInteractor.swift 3 | // MBA-calculator 4 | // 5 | // Created by Moonbeom KWON on 2/24/25. 6 | // 7 | 8 | import MBAkit_core 9 | import UIKit 10 | 11 | class ViewInteractor: ViewInteractorConfigurable { 12 | 13 | typealias VC = ViewController 14 | private let resultLabel: UILabel 15 | 16 | init(with label: UILabel) { 17 | resultLabel = label 18 | } 19 | 20 | func handleMessage(_ interactionMessage: ViewController.IM) { 21 | switch interactionMessage { 22 | case .updateResult(let text): 23 | resultLabel.text = text 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /MBA-calculator/MBA-calculator/source files/ViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewModel.swift 3 | // MBA-calculator 4 | // 5 | // Created by Moonbeom KWON on 2/22/25. 6 | // 7 | 8 | import Combine 9 | import Foundation 10 | import MBAkit_core 11 | 12 | class ViewModel: ViewModelConfigurable { 13 | 14 | typealias VC = ViewController 15 | private(set) var outputSubject = PassthroughSubject, Never>() 16 | 17 | private let dataManager: CalcDataManager = CalcDataManager() 18 | 19 | func handleMessage(_ inputMessage: VC.I) { 20 | Task { 21 | let result: CalcDataManager.CalcResponse 22 | switch inputMessage { 23 | case .pushNumber(let number): 24 | result = await dataManager.push(.number(num: number)) 25 | case .pushClearButton: 26 | result = await dataManager.push(.clear) 27 | case .pushEqualButton: 28 | result = await dataManager.push(.equal) 29 | case .pushPlusButton: 30 | result = await dataManager.push(.operator(text: "+")) 31 | case .pushSubstractButton: 32 | result = await dataManager.push(.operator(text: "-")) 33 | case .pushMultiplyButton: 34 | result = await dataManager.push(.operator(text: "*")) 35 | case .pushDevideButton: 36 | result = await dataManager.push(.operator(text: "/")) 37 | } 38 | 39 | switch result { 40 | case .error: 41 | outputSubject.send(.success(.showError(text: "[Error Message]"))) 42 | case .result(let text): 43 | outputSubject.send(.success(.showResult(text: text))) 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MBA-Demo --------------------------------------------------------------------------------