├── .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 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
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
--------------------------------------------------------------------------------