From 8cb9f8c3823163aa560f2215a81e258d74f5aa4b Mon Sep 17 00:00:00 2001 From: kimkyuchul Date: Fri, 22 Nov 2024 12:41:01 +0900 Subject: [PATCH 01/16] =?UTF-8?q?[FIX]=20Realm=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=9D=B4=EC=8A=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makgulli.xcodeproj/project.pbxproj | 40 +++++++++++++----------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/Makgulli.xcodeproj/project.pbxproj b/Makgulli.xcodeproj/project.pbxproj index a035d46..c0383a6 100644 --- a/Makgulli.xcodeproj/project.pbxproj +++ b/Makgulli.xcodeproj/project.pbxproj @@ -378,17 +378,17 @@ 5A0A984B2AE689C600545939 /* RxReachability in Frameworks */, 5A0A98582AE817BE00545939 /* RxDataSources in Frameworks */, C2773C612CEF26C400942765 /* RxBlocking in Frameworks */, - 5A70BA722AC179A500506846 /* RealmSwift in Frameworks */, C2773C632CEF26C400942765 /* RxCocoa in Frameworks */, 5A6827BC2AD6BDCE00C37B17 /* RxGesture in Frameworks */, C2773C652CEF26C400942765 /* RxRelay in Frameworks */, + C2C2FECC2CF02DD50000AC4F /* RealmSwift in Frameworks */, 5A9DA1E52AC2B4DF006040D2 /* Alamofire in Frameworks */, 5A0A98562AE817BE00545939 /* Differentiator in Frameworks */, C2773C5E2CEF25E500942765 /* FirebaseMessaging in Frameworks */, 5A70BA662AC178E600506846 /* SnapKit in Frameworks */, - 5A70BA702AC179A500506846 /* Realm in Frameworks */, C2773C5C2CEF25E500942765 /* FirebaseCrashlytics in Frameworks */, 5AAB8E8D2ADD780E005969FA /* RxKeyboard in Frameworks */, + C652817663168A57FCC2F4F3 /* Pods_Makgulli.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1288,8 +1288,6 @@ name = Makgulli; packageProductDependencies = ( 5A70BA652AC178E600506846 /* SnapKit */, - 5A70BA6F2AC179A500506846 /* Realm */, - 5A70BA712AC179A500506846 /* RealmSwift */, 5A9DA1E42AC2B4DF006040D2 /* Alamofire */, 5A6827BB2AD6BDCE00C37B17 /* RxGesture */, 5AAB8E8C2ADD780E005969FA /* RxKeyboard */, @@ -1304,6 +1302,7 @@ C2773C622CEF26C400942765 /* RxCocoa */, C2773C642CEF26C400942765 /* RxRelay */, C2773C662CEF26C400942765 /* RxSwift */, + C2C2FECB2CF02DD50000AC4F /* RealmSwift */, ); productName = Makgulli; productReference = 5A70BA2F2AC174E600506846 /* Makgulli.app */; @@ -1382,7 +1381,6 @@ mainGroup = 5A70BA262AC174E600506846; packageReferences = ( 5A70BA642AC178E600506846 /* XCRemoteSwiftPackageReference "SnapKit" */, - 5A70BA6E2AC179A500506846 /* XCRemoteSwiftPackageReference "realm-swift" */, 5A9DA1E32AC2B4DF006040D2 /* XCRemoteSwiftPackageReference "Alamofire" */, 5A6827BA2AD6BDCE00C37B17 /* XCRemoteSwiftPackageReference "RxGesture" */, 5AAB8E8B2ADD780D005969FA /* XCRemoteSwiftPackageReference "RxKeyboard" */, @@ -1390,6 +1388,7 @@ 5A0A98542AE817BE00545939 /* XCRemoteSwiftPackageReference "RxDataSources" */, C2773C562CEF25E500942765 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, C2773C5F2CEF26C400942765 /* XCRemoteSwiftPackageReference "RxSwift" */, + C2C2FECA2CF02DD50000AC4F /* XCRemoteSwiftPackageReference "realm-swift" */, ); productRefGroup = 5A70BA302AC174E600506846 /* Products */; projectDirPath = ""; @@ -2110,14 +2109,6 @@ minimumVersion = 5.0.0; }; }; - 5A70BA6E2AC179A500506846 /* XCRemoteSwiftPackageReference "realm-swift" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/realm/realm-swift"; - requirement = { - branch = master; - kind = branch; - }; - }; 5A9DA1E32AC2B4DF006040D2 /* XCRemoteSwiftPackageReference "Alamofire" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Alamofire/Alamofire"; @@ -2150,6 +2141,14 @@ minimumVersion = 6.8.0; }; }; + C2C2FECA2CF02DD50000AC4F /* XCRemoteSwiftPackageReference "realm-swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/realm/realm-swift"; + requirement = { + branch = master; + kind = branch; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -2178,16 +2177,6 @@ package = 5A70BA642AC178E600506846 /* XCRemoteSwiftPackageReference "SnapKit" */; productName = SnapKit; }; - 5A70BA6F2AC179A500506846 /* Realm */ = { - isa = XCSwiftPackageProductDependency; - package = 5A70BA6E2AC179A500506846 /* XCRemoteSwiftPackageReference "realm-swift" */; - productName = Realm; - }; - 5A70BA712AC179A500506846 /* RealmSwift */ = { - isa = XCSwiftPackageProductDependency; - package = 5A70BA6E2AC179A500506846 /* XCRemoteSwiftPackageReference "realm-swift" */; - productName = RealmSwift; - }; 5A9DA1E42AC2B4DF006040D2 /* Alamofire */ = { isa = XCSwiftPackageProductDependency; package = 5A9DA1E32AC2B4DF006040D2 /* XCRemoteSwiftPackageReference "Alamofire" */; @@ -2238,6 +2227,11 @@ package = C2773C5F2CEF26C400942765 /* XCRemoteSwiftPackageReference "RxSwift" */; productName = RxSwift; }; + C2C2FECB2CF02DD50000AC4F /* RealmSwift */ = { + isa = XCSwiftPackageProductDependency; + package = C2C2FECA2CF02DD50000AC4F /* XCRemoteSwiftPackageReference "realm-swift" */; + productName = RealmSwift; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 5A70BA272AC174E600506846 /* Project object */; From c6964038ab48fe4c59d61ea07ad1e9cfc4bd83de Mon Sep 17 00:00:00 2001 From: kimkyuchul Date: Sat, 23 Nov 2024 17:24:15 +0900 Subject: [PATCH 02/16] =?UTF-8?q?[ADD]=20Coordinator=20=EC=A0=95=EC=9D=98?= =?UTF-8?q?=20(#30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makgulli.xcodeproj/project.pbxproj | 14 ++-- .../Application/Coordinator/Coordinator.swift | 71 +++++++++++++++++++ 2 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 Makgulli/Application/Coordinator/Coordinator.swift diff --git a/Makgulli.xcodeproj/project.pbxproj b/Makgulli.xcodeproj/project.pbxproj index c0383a6..ff97aae 100644 --- a/Makgulli.xcodeproj/project.pbxproj +++ b/Makgulli.xcodeproj/project.pbxproj @@ -175,6 +175,7 @@ 5AE652982B9268FB0033CED5 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5A36F1B62B027A9900039ADA /* GoogleService-Info.plist */; }; 97E4E67B233AA4926A368385 /* libPods-MakgulliTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C6D596DBBF462B7A2EB0ABAA /* libPods-MakgulliTests.a */; }; D5B862DDBE4105DA235A3EDC /* libPods-Makgulli.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B35E14DFFC9313A7EEBC2771 /* libPods-Makgulli.a */; }; + C2773C6C2CF0087300942765 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2773C6B2CF0087300942765 /* Coordinator.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -365,6 +366,7 @@ C6D596DBBF462B7A2EB0ABAA /* libPods-MakgulliTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MakgulliTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; E264EB7DCC1E4513F31412DC /* Pods-MakgulliTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MakgulliTests.debug.xcconfig"; path = "Target Support Files/Pods-MakgulliTests/Pods-MakgulliTests.debug.xcconfig"; sourceTree = ""; }; F42A819738E82936BB69AC5F /* Pods-Makgulli.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Makgulli.debug.xcconfig"; path = "Target Support Files/Pods-Makgulli/Pods-Makgulli.debug.xcconfig"; sourceTree = ""; }; + C2773C6B2CF0087300942765 /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -864,6 +866,7 @@ 5A9DA1C02AC18F9D006040D2 /* Application */ = { isa = PBXGroup; children = ( + C2773C682CF007AF00942765 /* Coordinator */, 5A70BA322AC174E600506846 /* AppDelegate.swift */, 5A70BA342AC174E600506846 /* SceneDelegate.swift */, 5A0406E52AF50CF900A4BA84 /* DIContainer */, @@ -1257,14 +1260,14 @@ path = APIKey; sourceTree = ""; }; - 64AEBEDA30B71C9D4154194F /* Frameworks */ = { + C2773C682CF007AF00942765 /* Coordinator */ = { isa = PBXGroup; children = ( - B35E14DFFC9313A7EEBC2771 /* libPods-Makgulli.a */, - 1611F00CAC988DA4A2FFB986 /* libPods-Makgulli-MakgulliUITests.a */, - C6D596DBBF462B7A2EB0ABAA /* libPods-MakgulliTests.a */, + C2773C6B2CF0087300942765 /* Coordinator.swift */, + C2773C692CF007BC00942765 /* AppCoordinator.swift */, ); - name = Frameworks; + path = Coordinator; + sourceTree = ""; sourceTree = ""; }; /* End PBXGroup section */ @@ -1646,6 +1649,7 @@ 5A9DA20A2AC3CE9F006040D2 /* SearchLocationRepository.swift in Sources */, 5AAB8EA92AE14607005969FA /* UserDefaultHandler.swift in Sources */, 5AAB8E952ADE824F005969FA /* FilterHeaderView.swift in Sources */, + C2773C6C2CF0087300942765 /* Coordinator.swift in Sources */, 5ADC00342AC5F2720024D7F8 /* CategoryType.swift in Sources */, 5A6827842ACFEB6D00C37B17 /* UIStackView+.swift in Sources */, 5A9DA1F62AC2F51C006040D2 /* LocationAPI.swift in Sources */, diff --git a/Makgulli/Application/Coordinator/Coordinator.swift b/Makgulli/Application/Coordinator/Coordinator.swift new file mode 100644 index 0000000..edf76f2 --- /dev/null +++ b/Makgulli/Application/Coordinator/Coordinator.swift @@ -0,0 +1,71 @@ +// +// Coordinator.swift +// Makgulli +// +// Created by kyuchul on 11/22/24. +// + +import UIKit + +protocol Coordinatable { + associatedtype CoordinatorType: Coordinator + + var coordinator: CoordinatorType? { get } +} + +protocol Coordinator: AnyObject { + // 부모 코디네이터 + var parentCoordinator: Coordinator? { get set } + // 모든 코디네이터는 자신의 자식 코디네이터를 관리 + var childCoordinators: [Coordinator] { get set } + // 뷰컨트롤러를 보여줄 때 사용될 내비게이션 컨트롤러를 저장 + var navigationController: UINavigationController { get set } + // 해당 코디네이터가 제어권을 갖도록 하는 메서드. 완전히 만들고 준비되었을 때만 코디네이터를 활성화 + func start() +} + +extension Coordinator { + func addDependency(_ coordinator: Coordinator) { + for element in childCoordinators { + if element === coordinator { return } + } + childCoordinators.append(coordinator) + } + + func removeDependency(_ coordinator: Coordinator?) { + for (index, element) in childCoordinators.enumerated() where element === coordinator { + childCoordinators.remove(at: index) + break + } + } +} + +extension Coordinator { + func push(viewController: UIViewController, navibarHidden: Bool = true, swipe: Bool = true, animated: Bool = true) { + navigationController.setNavigationBarHidden(navibarHidden, animated: true) + + if swipe { + self.navigationController.interactivePopGestureRecognizer?.isEnabled = swipe + self.navigationController.interactivePopGestureRecognizer?.delegate = nil + } + + navigationController.pushViewController(viewController, animated: animated) + } + + func present(_ viewController: UIViewController, style: UIModalPresentationStyle) { + navigationController.modalPresentationStyle = style + navigationController.present(viewController, animated: true) + } + + func pop(_ viewController: UIViewController) { + navigationController.popViewController(animated: true) + } + + func dismiss(animated: Bool = true,completion: (() -> Void)?) { + navigationController.dismiss(animated: animated, completion: completion) + } + + func setViewController(viewController: UIViewController, animated: Bool = true) { + navigationController.setViewControllers([viewController], animated: animated) + } +} From 812bf8efa2bbec21a7829dc3dde3cd1ec104e008 Mon Sep 17 00:00:00 2001 From: kimkyuchul Date: Mon, 25 Nov 2024 14:22:23 +0900 Subject: [PATCH 03/16] =?UTF-8?q?[FIX]=20=EC=BD=94=EB=94=94=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EC=88=98=EC=A0=95=20(#30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makgulli/Application/Coordinator/Coordinator.swift | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Makgulli/Application/Coordinator/Coordinator.swift b/Makgulli/Application/Coordinator/Coordinator.swift index edf76f2..fe157f8 100644 --- a/Makgulli/Application/Coordinator/Coordinator.swift +++ b/Makgulli/Application/Coordinator/Coordinator.swift @@ -38,6 +38,13 @@ extension Coordinator { break } } + + func printStack() { + let viewControllers = navigationController.viewControllers + for (index, viewController) in viewControllers.enumerated() { + print("\(index): \(Swift.type(of: viewController))") + } + } } extension Coordinator { @@ -53,15 +60,15 @@ extension Coordinator { } func present(_ viewController: UIViewController, style: UIModalPresentationStyle) { - navigationController.modalPresentationStyle = style + viewController.modalPresentationStyle = style navigationController.present(viewController, animated: true) } - func pop(_ viewController: UIViewController) { + func pop() { navigationController.popViewController(animated: true) } - func dismiss(animated: Bool = true,completion: (() -> Void)?) { + func dismiss(animated: Bool = true,completion: (() -> Void)? = nil) { navigationController.dismiss(animated: animated, completion: completion) } From 7a074301c04639b85df1503afb6a7ec163aa21c5 Mon Sep 17 00:00:00 2001 From: kimkyuchul Date: Mon, 25 Nov 2024 14:26:19 +0900 Subject: [PATCH 04/16] =?UTF-8?q?[FIX]=20RealmSwift=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EC=9D=B4=EC=8A=88=20=EC=88=98=EC=A0=95=20(#30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makgulli.xcodeproj/project.pbxproj | 18 ++++++++++++++++-- .../xcshareddata/swiftpm/Package.resolved | 16 ++++++++-------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/Makgulli.xcodeproj/project.pbxproj b/Makgulli.xcodeproj/project.pbxproj index ff97aae..0de3d1c 100644 --- a/Makgulli.xcodeproj/project.pbxproj +++ b/Makgulli.xcodeproj/project.pbxproj @@ -97,8 +97,6 @@ 5A70BA562AC174EC00506846 /* MakgulliUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A70BA552AC174EC00506846 /* MakgulliUITestsLaunchTests.swift */; }; 5A70BA632AC1788E00506846 /* LocationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A70BA622AC1788E00506846 /* LocationViewController.swift */; }; 5A70BA662AC178E600506846 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5A70BA652AC178E600506846 /* SnapKit */; }; - 5A70BA702AC179A500506846 /* Realm in Frameworks */ = {isa = PBXBuildFile; productRef = 5A70BA6F2AC179A500506846 /* Realm */; }; - 5A70BA722AC179A500506846 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 5A70BA712AC179A500506846 /* RealmSwift */; }; 5A9DA1C82AC19119006040D2 /* Bundle+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9DA1C72AC19119006040D2 /* Bundle+.swift */; }; 5A9DA1D32AC19AFB006040D2 /* UIFont+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9DA1D22AC19AFB006040D2 /* UIFont+.swift */; }; 5A9DA1D52AC19D30006040D2 /* UIColor+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9DA1D42AC19D30006040D2 /* UIColor+.swift */; }; @@ -176,6 +174,8 @@ 97E4E67B233AA4926A368385 /* libPods-MakgulliTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C6D596DBBF462B7A2EB0ABAA /* libPods-MakgulliTests.a */; }; D5B862DDBE4105DA235A3EDC /* libPods-Makgulli.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B35E14DFFC9313A7EEBC2771 /* libPods-Makgulli.a */; }; C2773C6C2CF0087300942765 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2773C6B2CF0087300942765 /* Coordinator.swift */; }; + C2C2FECC2CF02DD50000AC4F /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = C2C2FECB2CF02DD50000AC4F /* RealmSwift */; }; + C2C2FECD2CF02DE80000AC4F /* RealmSwift in Embed Frameworks */ = {isa = PBXBuildFile; productRef = C2C2FECB2CF02DD50000AC4F /* RealmSwift */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -195,6 +195,20 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + C2C2FEC82CF02D000000AC4F /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + C2C2FECD2CF02DE80000AC4F /* RealmSwift in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 1611F00CAC988DA4A2FFB986 /* libPods-Makgulli-MakgulliUITests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Makgulli-MakgulliUITests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 463D854F776FDADD2658D9BD /* Pods-Makgulli.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Makgulli.release.xcconfig"; path = "Target Support Files/Pods-Makgulli/Pods-Makgulli.release.xcconfig"; sourceTree = ""; }; diff --git a/Makgulli.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Makgulli.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6e75d80..61dcd9b 100644 --- a/Makgulli.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Makgulli.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "dcee1f64719827fe357ae6a446c89603385ee9415dc75343f62f976875b3bec0", + "originHash" : "2e12ae3ccf19a62525d5bb352166d7187485cc2d9ffd159d0d55f722a0854c89", "pins" : [ { "identity" : "abseil-cpp-binary", @@ -15,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Alamofire/Alamofire", "state" : { - "revision" : "3dc6a42c7727c49bf26508e29b0a0b35f9c7e1ad", - "version" : "5.8.1" + "revision" : "e16d3481f5ed35f0472cb93350085853d754913f", + "version" : "5.10.1" } }, { @@ -123,8 +123,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/ashleymills/Reachability.swift", "state" : { - "revision" : "c01127cb51f591045696128effe43c16840d08bf", - "version" : "5.2.0" + "revision" : "21d1dc412cfecbe6e34f1f4c4eb88d3f912654a6", + "version" : "5.2.4" } }, { @@ -132,8 +132,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/realm/realm-core.git", "state" : { - "revision" : "a5e87a39cffdcc591f3203c11cfca68100d0b9a6", - "version" : "13.26.0" + "revision" : "85eeca41654cc9070ad81a21a4b1d153ad467c33", + "version" : "14.13.1" } }, { @@ -142,7 +142,7 @@ "location" : "https://github.com/realm/realm-swift", "state" : { "branch" : "master", - "revision" : "a56145813ef5e7c4b5c5450fabfa1b1a098247b0" + "revision" : "5553cfd1c789efdb3d6daf7f0cc0733cfe601846" } }, { From c22868a6b2d713170449d889b5005546b1f0b45d Mon Sep 17 00:00:00 2001 From: kimkyuchul Date: Mon, 25 Nov 2024 14:28:01 +0900 Subject: [PATCH 05/16] =?UTF-8?q?[ADD]=20AppCoordinator=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makgulli.xcodeproj/project.pbxproj | 3 + .../Coordinator/AppCoordinator.swift | 68 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 Makgulli/Application/Coordinator/AppCoordinator.swift diff --git a/Makgulli.xcodeproj/project.pbxproj b/Makgulli.xcodeproj/project.pbxproj index 0de3d1c..c1293f7 100644 --- a/Makgulli.xcodeproj/project.pbxproj +++ b/Makgulli.xcodeproj/project.pbxproj @@ -173,6 +173,7 @@ 5AE652982B9268FB0033CED5 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5A36F1B62B027A9900039ADA /* GoogleService-Info.plist */; }; 97E4E67B233AA4926A368385 /* libPods-MakgulliTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C6D596DBBF462B7A2EB0ABAA /* libPods-MakgulliTests.a */; }; D5B862DDBE4105DA235A3EDC /* libPods-Makgulli.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B35E14DFFC9313A7EEBC2771 /* libPods-Makgulli.a */; }; + C2773C6A2CF007BC00942765 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2773C692CF007BC00942765 /* AppCoordinator.swift */; }; C2773C6C2CF0087300942765 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2773C6B2CF0087300942765 /* Coordinator.swift */; }; C2C2FECC2CF02DD50000AC4F /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = C2C2FECB2CF02DD50000AC4F /* RealmSwift */; }; C2C2FECD2CF02DE80000AC4F /* RealmSwift in Embed Frameworks */ = {isa = PBXBuildFile; productRef = C2C2FECB2CF02DD50000AC4F /* RealmSwift */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; @@ -380,6 +381,7 @@ C6D596DBBF462B7A2EB0ABAA /* libPods-MakgulliTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MakgulliTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; E264EB7DCC1E4513F31412DC /* Pods-MakgulliTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MakgulliTests.debug.xcconfig"; path = "Target Support Files/Pods-MakgulliTests/Pods-MakgulliTests.debug.xcconfig"; sourceTree = ""; }; F42A819738E82936BB69AC5F /* Pods-Makgulli.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Makgulli.debug.xcconfig"; path = "Target Support Files/Pods-Makgulli/Pods-Makgulli.debug.xcconfig"; sourceTree = ""; }; + C2773C692CF007BC00942765 /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = ""; }; C2773C6B2CF0087300942765 /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -1625,6 +1627,7 @@ 5AAB8E862ADC286D005969FA /* EpisodeDetailRepository.swift in Sources */, 5A9DA1FE2AC3B070006040D2 /* Encodable+.swift in Sources */, 5AAB8E6A2AD7F0FB005969FA /* QuantityType.swift in Sources */, + C2773C6A2CF007BC00942765 /* AppCoordinator.swift in Sources */, 5A0A98372AE5894D00545939 /* SplashViewController.swift in Sources */, 5A04070F2AF91BB200A4BA84 /* DefaultWriteEpisodeLocalRepository.swift in Sources */, 5A6827AD2AD52F4D00C37B17 /* WriteEpisodeViewModel.swift in Sources */, diff --git a/Makgulli/Application/Coordinator/AppCoordinator.swift b/Makgulli/Application/Coordinator/AppCoordinator.swift new file mode 100644 index 0000000..ba037e8 --- /dev/null +++ b/Makgulli/Application/Coordinator/AppCoordinator.swift @@ -0,0 +1,68 @@ +// +// AppCoordinator.swift +// Makgulli +// +// Created by kyuchul on 11/22/24. +// + +import UIKit + +import Combine + +enum AppFlow { + case main + case login +} + +final class AppCoordinator: Coordinator { + weak var parentCoordinator: (any Coordinator)? + var childCoordinators: [any Coordinator] = [] + var navigationController: UINavigationController + let flow = PassthroughSubject() + private var cancellable = Set() + + init(navigationController: UINavigationController) { + self.navigationController = navigationController + bindState() + } + + func bindState() { + flow + .sink { [weak self] flow in + switch flow { + case .main: + self?.startTabBar() + case .login: + break + } + } + .store(in: &cancellable) + } + + func start() { + startSplash() + } +} + +extension AppCoordinator { + private func startSplash() { + let splashViewController = SplashViewController(coordinator: self) + navigationController.setNavigationBarHidden(true, animated: false) + setViewController(viewController: splashViewController, animated: false) + } + + private func startTabBar() { + guard let window = UIApplication.shared.keyWindowInConnectedScenes else { return } + + let tabBarCoordinator = TabBarCoordinator(navigationController: navigationController) + tabBarCoordinator.parentCoordinator = self + + UIView.transition(with: window, + duration: 0.5, + options: .transitionCrossDissolve, + animations: { + tabBarCoordinator.start() + self.addDependency(tabBarCoordinator) + }, completion: nil) + } +} From 983b62570b3d258e340290042c2277a3cb7fd4ee Mon Sep 17 00:00:00 2001 From: kimkyuchul Date: Mon, 25 Nov 2024 14:29:26 +0900 Subject: [PATCH 06/16] =?UTF-8?q?[CHORE]=20AppCoordinator=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0=20(#30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makgulli/Application/SceneDelegate.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Makgulli/Application/SceneDelegate.swift b/Makgulli/Application/SceneDelegate.swift index c7cb945..975b622 100644 --- a/Makgulli/Application/SceneDelegate.swift +++ b/Makgulli/Application/SceneDelegate.swift @@ -12,13 +12,20 @@ import Reachability class SceneDelegate: UIResponder, UIWindowSceneDelegate { + private var coordinator: AppCoordinator? var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } window = UIWindow(windowScene: windowScene) - window?.rootViewController = SplashViewController() + + let navigationController = UINavigationController() + coordinator = AppCoordinator(navigationController: navigationController) + + window?.rootViewController = navigationController window?.makeKeyAndVisible() + + coordinator?.start() } func sceneDidDisconnect(_ scene: UIScene) { From acded2b3e8603f701c83a39b362f01e095067fe1 Mon Sep 17 00:00:00 2001 From: kimkyuchul Date: Mon, 25 Nov 2024 14:31:26 +0900 Subject: [PATCH 07/16] [ADD] AppInfoDIContainer (#30) --- Makgulli.xcodeproj/project.pbxproj | 4 ++++ .../Application/DIContainer/AppDIContainer.swift | 4 ++++ .../DIContainer/AppInfoDIContainer.swift | 15 +++++++++++++++ .../DIContainer/EpisodeDIContainer.swift | 8 ++++---- 4 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 Makgulli/Application/DIContainer/AppInfoDIContainer.swift diff --git a/Makgulli.xcodeproj/project.pbxproj b/Makgulli.xcodeproj/project.pbxproj index c1293f7..78c6969 100644 --- a/Makgulli.xcodeproj/project.pbxproj +++ b/Makgulli.xcodeproj/project.pbxproj @@ -177,6 +177,7 @@ C2773C6C2CF0087300942765 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2773C6B2CF0087300942765 /* Coordinator.swift */; }; C2C2FECC2CF02DD50000AC4F /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = C2C2FECB2CF02DD50000AC4F /* RealmSwift */; }; C2C2FECD2CF02DE80000AC4F /* RealmSwift in Embed Frameworks */ = {isa = PBXBuildFile; productRef = C2C2FECB2CF02DD50000AC4F /* RealmSwift */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + C2C2FEEB2CF416DF0000AC4F /* AppInfoDIContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FEEA2CF416DF0000AC4F /* AppInfoDIContainer.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -383,6 +384,7 @@ F42A819738E82936BB69AC5F /* Pods-Makgulli.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Makgulli.debug.xcconfig"; path = "Target Support Files/Pods-Makgulli/Pods-Makgulli.debug.xcconfig"; sourceTree = ""; }; C2773C692CF007BC00942765 /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = ""; }; C2773C6B2CF0087300942765 /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; + C2C2FEEA2CF416DF0000AC4F /* AppInfoDIContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoDIContainer.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -449,6 +451,7 @@ 5A0406E82AF5145400A4BA84 /* LocationDIContainer.swift */, 5A0407132AF91E8500A4BA84 /* EpisodeDIContainer.swift */, 5A0407212AF9275F00A4BA84 /* FavoriteDIContainer.swift */, + C2C2FEEA2CF416DF0000AC4F /* AppInfoDIContainer.swift */, ); path = DIContainer; sourceTree = ""; @@ -1640,6 +1643,7 @@ 5A0A98332AE4BBBE00545939 /* IndicatorView.swift in Sources */, 5A9DA1E72AC2B4F2006040D2 /* TargetType.swift in Sources */, 5A6827962AD15EAA00C37B17 /* StoreTable.swift in Sources */, + C2C2FEEB2CF416DF0000AC4F /* AppInfoDIContainer.swift in Sources */, 5A0A98622AE917A300545939 /* InquiryButtonView.swift in Sources */, 5AAB8E752ADA70BD005969FA /* DefaultLocationDetailRepository.swift in Sources */, 5A9DA20F2AC3D739006040D2 /* ViewModelType.swift in Sources */, diff --git a/Makgulli/Application/DIContainer/AppDIContainer.swift b/Makgulli/Application/DIContainer/AppDIContainer.swift index 28dc604..d490e0b 100644 --- a/Makgulli/Application/DIContainer/AppDIContainer.swift +++ b/Makgulli/Application/DIContainer/AppDIContainer.swift @@ -30,4 +30,8 @@ final class AppDIContainer { func makeFavoriteDIContainer() -> FavoriteDIContainer { return FavoriteDIContainer() } + + func makeAppInfoDIContainer() -> AppInfoDIContainer { + return AppInfoDIContainer() + } } diff --git a/Makgulli/Application/DIContainer/AppInfoDIContainer.swift b/Makgulli/Application/DIContainer/AppInfoDIContainer.swift new file mode 100644 index 0000000..3064b97 --- /dev/null +++ b/Makgulli/Application/DIContainer/AppInfoDIContainer.swift @@ -0,0 +1,15 @@ +// +// SettingDIContainer.swift +// Makgulli +// +// Created by kyuchul on 11/25/24. +// + +import Foundation + +final class AppInfoDIContainer { + // MARK: - ViewModel + func makeAppInfoViewModel() -> AppInfoViewModel { + AppInfoViewModel() + } +} diff --git a/Makgulli/Application/DIContainer/EpisodeDIContainer.swift b/Makgulli/Application/DIContainer/EpisodeDIContainer.swift index d33c427..8914ae6 100644 --- a/Makgulli/Application/DIContainer/EpisodeDIContainer.swift +++ b/Makgulli/Application/DIContainer/EpisodeDIContainer.swift @@ -45,7 +45,7 @@ final class EpisodeDIContainer { } // MARK: - UseCases - private func makeLocationUseCase() -> WriteEpisodeUseCase { + private func makeWriteEpisodeUseCase() -> WriteEpisodeUseCase { DefaultWriteEpisodeUseCase( writeEpisodeRepository: makeWriteEpisodeRepository(), writeEpisodeLocalRepository: makeWriteEpisodeLocalRepository() @@ -60,14 +60,14 @@ final class EpisodeDIContainer { } // MARK: - ViewModel - func makeLocationViewModel(store: StoreVO) -> WriteEpisodeViewModel { + func makeWriteEpisodeViewModel(store: StoreVO) -> WriteEpisodeViewModel { WriteEpisodeViewModel( storeVO: store, - writeEpisodeUseCase: makeLocationUseCase() + writeEpisodeUseCase: makeWriteEpisodeUseCase() ) } - func makeLocationViewModel(episode: Episode, storeId: String) -> EpisodeDetailViewModel { + func makeEpisodeDetailViewModel(episode: Episode, storeId: String) -> EpisodeDetailViewModel { EpisodeDetailViewModel( episode: episode, storeId: storeId, From df90721a52f9dd67c12caa1eda63dd5dff5bf532 Mon Sep 17 00:00:00 2001 From: kimkyuchul Date: Mon, 25 Nov 2024 14:33:25 +0900 Subject: [PATCH 08/16] =?UTF-8?q?[ADD]=20TabBarCoordinator=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makgulli.xcodeproj/project.pbxproj | 11 ++- .../Presentation/Common/Tabbar/TabBar.swift | 31 ++++++++ .../Common/Tabbar/TabBarController.swift | 62 ---------------- .../Common/Tabbar/TabBarCoordinator.swift | 72 +++++++++++++++++++ 4 files changed, 111 insertions(+), 65 deletions(-) create mode 100644 Makgulli/Presentation/Common/Tabbar/TabBar.swift delete mode 100644 Makgulli/Presentation/Common/Tabbar/TabBarController.swift create mode 100644 Makgulli/Presentation/Common/Tabbar/TabBarCoordinator.swift diff --git a/Makgulli.xcodeproj/project.pbxproj b/Makgulli.xcodeproj/project.pbxproj index 78c6969..d965b0c 100644 --- a/Makgulli.xcodeproj/project.pbxproj +++ b/Makgulli.xcodeproj/project.pbxproj @@ -177,7 +177,9 @@ C2773C6C2CF0087300942765 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2773C6B2CF0087300942765 /* Coordinator.swift */; }; C2C2FECC2CF02DD50000AC4F /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = C2C2FECB2CF02DD50000AC4F /* RealmSwift */; }; C2C2FECD2CF02DE80000AC4F /* RealmSwift in Embed Frameworks */ = {isa = PBXBuildFile; productRef = C2C2FECB2CF02DD50000AC4F /* RealmSwift */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + C2C2FECF2CF1BEB70000AC4F /* TabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FECE2CF1BEB70000AC4F /* TabBar.swift */; }; C2C2FEEB2CF416DF0000AC4F /* AppInfoDIContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FEEA2CF416DF0000AC4F /* AppInfoDIContainer.swift */; }; + C2F7418C2CF0270700C423D5 /* TabBarCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2F7418B2CF0270700C423D5 /* TabBarCoordinator.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -369,7 +371,6 @@ 5ADC00352AC5F8EC0024D7F8 /* BaseCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseCollectionViewCell.swift; sourceTree = ""; }; 5ADC00382AC5F9370024D7F8 /* CategoryCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryCollectionViewCell.swift; sourceTree = ""; }; 5ADC003A2AC8450C0024D7F8 /* CLLocationCoordinate2D+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CLLocationCoordinate2D+.swift"; sourceTree = ""; }; - 5ADC003D2AC8693A0024D7F8 /* TabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; 5ADC00402AC86DA00024D7F8 /* FavoriteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteViewController.swift; sourceTree = ""; }; 5ADC00422AC879060024D7F8 /* StoreCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreCollectionViewCell.swift; sourceTree = ""; }; 5ADC004C2ACA9DAE0024D7F8 /* LocationDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationDetailViewController.swift; sourceTree = ""; }; @@ -384,7 +385,9 @@ F42A819738E82936BB69AC5F /* Pods-Makgulli.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Makgulli.debug.xcconfig"; path = "Target Support Files/Pods-Makgulli/Pods-Makgulli.debug.xcconfig"; sourceTree = ""; }; C2773C692CF007BC00942765 /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = ""; }; C2773C6B2CF0087300942765 /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; + C2C2FECE2CF1BEB70000AC4F /* TabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBar.swift; sourceTree = ""; }; C2C2FEEA2CF416DF0000AC4F /* AppInfoDIContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoDIContainer.swift; sourceTree = ""; }; + C2F7418B2CF0270700C423D5 /* TabBarCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarCoordinator.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1246,7 +1249,8 @@ 5ADC003C2AC869060024D7F8 /* Tabbar */ = { isa = PBXGroup; children = ( - 5ADC003D2AC8693A0024D7F8 /* TabBarController.swift */, + C2F7418B2CF0270700C423D5 /* TabBarCoordinator.swift */, + C2C2FECE2CF1BEB70000AC4F /* TabBar.swift */, ); path = Tabbar; sourceTree = ""; @@ -1588,6 +1592,7 @@ files = ( 5A70BA332AC174E600506846 /* AppDelegate.swift in Sources */, 5A9DA2012AC3C227006040D2 /* SearchLocationVO.swift in Sources */, + C2F7418C2CF0270700C423D5 /* TabBarCoordinator.swift in Sources */, 5A6827A72AD50D5600C37B17 /* WriteEpisodeViewController.swift in Sources */, 5A0A98462AE66AB200545939 /* UIButton+.swift in Sources */, 5A9DA1DE2AC1A393006040D2 /* MetricLiteral.swift in Sources */, @@ -1679,7 +1684,6 @@ 5A9DA1D82AC1A1F2006040D2 /* ImageLiteral.swift in Sources */, 5A6827A32AD44DD900C37B17 /* PaddingLabel.swift in Sources */, 5A0A98532AE79F4000545939 /* AppInfoViewController.swift in Sources */, - 5ADC003E2AC8693A0024D7F8 /* TabBarController.swift in Sources */, 5A6827AB2AD5265100C37B17 /* EpisodeButton.swift in Sources */, 5ADC003B2AC8450C0024D7F8 /* CLLocationCoordinate2D+.swift in Sources */, 5A68278C2AD060FA00C37B17 /* DetailBottomView.swift in Sources */, @@ -1718,6 +1722,7 @@ 5A6827752ACE9A1D00C37B17 /* DetailRateView.swift in Sources */, 5A0406E92AF5145400A4BA84 /* LocationDIContainer.swift in Sources */, 5AAB8EA72AE145F0005969FA /* UserDefault.swift in Sources */, + C2C2FECF2CF1BEB70000AC4F /* TabBar.swift in Sources */, 5AAB8E882ADC36F2005969FA /* EpisodeDetailView.swift in Sources */, 5AAB8E822ADC0418005969FA /* BaseAlert.swift in Sources */, 5A0407042AF8FF7F00A4BA84 /* DefaultLocationDetailLocalRepository.swift in Sources */, diff --git a/Makgulli/Presentation/Common/Tabbar/TabBar.swift b/Makgulli/Presentation/Common/Tabbar/TabBar.swift new file mode 100644 index 0000000..f481095 --- /dev/null +++ b/Makgulli/Presentation/Common/Tabbar/TabBar.swift @@ -0,0 +1,31 @@ +// +// TabBar.swift +// Makgulli +// +// Created by kyuchul on 11/23/24. +// + +import UIKit + +enum TabBar: CaseIterable { + case makgulli + case favorite + + var tabBarItem: UITabBarItem { + switch self { + case .makgulli: + return UITabBarItem( + title: StringLiteral.location, + image: ImageLiteral.mapTabIcon, + selectedImage: nil + ) + + case .favorite: + return UITabBarItem( + title: StringLiteral.Favorite, + image: ImageLiteral.heartIcon, + selectedImage: nil + ) + } + } +} diff --git a/Makgulli/Presentation/Common/Tabbar/TabBarController.swift b/Makgulli/Presentation/Common/Tabbar/TabBarController.swift deleted file mode 100644 index 4477189..0000000 --- a/Makgulli/Presentation/Common/Tabbar/TabBarController.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// TabBarController.swift -// Makgulli -// -// Created by 김규철 on 2023/09/30. -// - -import UIKit - -final class TabBarController: UITabBarController { - - override func viewDidLoad() { - super.viewDidLoad() - setViewControllers() - setUpTabBar() - } - - private func setUpTabBar() { - self.tabBar.tintColor = .brown - self.tabBar.unselectedItemTintColor = .darkGray - - let appearance = UITabBarAppearance() - appearance.backgroundColor = .white - appearance.stackedLayoutAppearance.normal.titleTextAttributes = [.foregroundColor: UIColor.darkGray, .font: UIFont.boldLineSeed(size: ._12)] - appearance.stackedLayoutAppearance.selected.titleTextAttributes = [.foregroundColor: UIColor.brown, .font: UIFont.boldLineSeed(size: ._12)] - tabBar.standardAppearance = appearance - tabBar.scrollEdgeAppearance = appearance - } -} - -extension TabBarController { - func setViewControllers() { - let locationViewController = UINavigationController( - rootViewController: LocationViewController( - viewModel: AppDIContainer.shared - .makeLocationDIContainer() - .makeLocationViewModel()) - ) - - locationViewController.tabBarItem = UITabBarItem( - title: StringLiteral.location, - image: ImageLiteral.mapTabIcon, - selectedImage: nil) - - let favoriteViewController = UINavigationController( - rootViewController: FavoriteViewController( - viewModel: AppDIContainer.shared - .makeFavoriteDIContainer() - .makeFavoriteViewModel()) - ) - - favoriteViewController.tabBarItem = UITabBarItem( - title: StringLiteral.Favorite, - image: ImageLiteral.heartIcon, - selectedImage: nil) - - super.setViewControllers([ - locationViewController, - favoriteViewController - ], animated: true) - } -} diff --git a/Makgulli/Presentation/Common/Tabbar/TabBarCoordinator.swift b/Makgulli/Presentation/Common/Tabbar/TabBarCoordinator.swift new file mode 100644 index 0000000..e01a00a --- /dev/null +++ b/Makgulli/Presentation/Common/Tabbar/TabBarCoordinator.swift @@ -0,0 +1,72 @@ +// +// TabBarCoordinator.swift +// Makgulli +// +// Created by kyuchul on 11/22/24. +// + +import UIKit + +final class TabBarCoordinator: Coordinator { + weak var parentCoordinator: (any Coordinator)? + var childCoordinators: [any Coordinator] = [] + var navigationController: UINavigationController + private var tabBarController = UITabBarController() + + init(navigationController: UINavigationController) { + self.navigationController = navigationController + } + + func start() { + // 탭 별 뷰컨트롤러 생성 + let viewControllers = TabBar.allCases.map { + createTabNavigationController(of: $0) + } + + // 탭바컨트롤러 설정 + configureTabbarController(with: viewControllers) + } +} + +extension TabBarCoordinator { + private func configureTabbarController(with tabViewControllers: [UIViewController]) { + self.tabBarController.setViewControllers(tabViewControllers, animated: true) + + self.tabBarController.tabBar.tintColor = .brown + self.tabBarController.tabBar.unselectedItemTintColor = .darkGray + let appearance = UITabBarAppearance() + appearance.backgroundColor = .white + appearance.stackedLayoutAppearance.normal.titleTextAttributes = [.foregroundColor: UIColor.darkGray, .font: UIFont.boldLineSeed(size: ._12)] + appearance.stackedLayoutAppearance.selected.titleTextAttributes = [.foregroundColor: UIColor.brown, .font: UIFont.boldLineSeed(size: ._12)] + self.tabBarController.tabBar.standardAppearance = appearance + self.tabBarController.tabBar.scrollEdgeAppearance = appearance + + navigationController.viewControllers = [tabBarController] + } + + private func createTabNavigationController(of tabBar: TabBar) -> UINavigationController { + let tabBarNavigationController = UINavigationController() + tabBarNavigationController.tabBarItem = tabBar.tabBarItem + setTabBarFlow(of: tabBar, to: tabBarNavigationController) + return tabBarNavigationController + } + + private func setTabBarFlow(of tabBar: TabBar, to tabNavigationController: UINavigationController) { + switch tabBar { + case .makgulli: + let locationCoordinator = LocationCoordinator(navigationController: tabNavigationController) + locationCoordinator.parentCoordinator = self + locationCoordinator.start() + + addDependency(locationCoordinator) + + + case .favorite: + let favoriteCoordinator = FavoriteCoordinator(navigationController: tabNavigationController) + favoriteCoordinator.parentCoordinator = self + favoriteCoordinator.start() + + addDependency(favoriteCoordinator) + } + } +} From eb6727c7935abcc3d461e8408770886d7a87aa18 Mon Sep 17 00:00:00 2001 From: kimkyuchul Date: Mon, 25 Nov 2024 14:35:24 +0900 Subject: [PATCH 09/16] =?UTF-8?q?[CHORE]=20SplashViewController=20Coordina?= =?UTF-8?q?tor=20=EC=97=B0=EA=B2=B0=20(#30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makgulli.xcodeproj/project.pbxproj | 27 +++++-------------- .../{ => Splash}/SplashViewController.swift | 17 ++++++++---- 2 files changed, 19 insertions(+), 25 deletions(-) rename Makgulli/Presentation/Common/{ => Splash}/SplashViewController.swift (80%) diff --git a/Makgulli.xcodeproj/project.pbxproj b/Makgulli.xcodeproj/project.pbxproj index d965b0c..ae7b6db 100644 --- a/Makgulli.xcodeproj/project.pbxproj +++ b/Makgulli.xcodeproj/project.pbxproj @@ -641,31 +641,12 @@ 5A0A98502AE6D9A000545939 /* Common */ = { isa = PBXGroup; children = ( - 5A0A98362AE5894D00545939 /* SplashViewController.swift */, + C2F741882CF019D900C423D5 /* Splash */, 5ADC003C2AC869060024D7F8 /* Tabbar */, ); path = Common; sourceTree = ""; }; - 5A0A98512AE79DF200545939 /* AppInfo */ = { - isa = PBXGroup; - children = ( - 5A0A98632AE9201D00545939 /* View */, - 5A0A98522AE79F4000545939 /* AppInfoViewController.swift */, - 5A0A985F2AE9159F00545939 /* InquiryViewController.swift */, - ); - path = AppInfo; - sourceTree = ""; - }; - 5A0A98632AE9201D00545939 /* View */ = { - isa = PBXGroup; - children = ( - 5A0A98612AE917A300545939 /* InquiryButtonView.swift */, - 5A0A98642AE9202400545939 /* Cell */, - ); - path = View; - sourceTree = ""; - }; 5A0A98642AE9202400545939 /* Cell */ = { isa = PBXGroup; children = ( @@ -1291,6 +1272,12 @@ ); path = Coordinator; sourceTree = ""; + C2F741882CF019D900C423D5 /* Splash */ = { + isa = PBXGroup; + children = ( + 5A0A98362AE5894D00545939 /* SplashViewController.swift */, + ); + path = Splash; sourceTree = ""; }; /* End PBXGroup section */ diff --git a/Makgulli/Presentation/Common/SplashViewController.swift b/Makgulli/Presentation/Common/Splash/SplashViewController.swift similarity index 80% rename from Makgulli/Presentation/Common/SplashViewController.swift rename to Makgulli/Presentation/Common/Splash/SplashViewController.swift index 0688787..8e20304 100644 --- a/Makgulli/Presentation/Common/SplashViewController.swift +++ b/Makgulli/Presentation/Common/Splash/SplashViewController.swift @@ -9,7 +9,13 @@ import UIKit import RxSwift -final class SplashViewController: BaseViewController { +final class SplashViewController: BaseViewController, Coordinatable { + weak var coordinator: AppCoordinator? + + init(coordinator: AppCoordinator) { + self.coordinator = coordinator + super.init() + } private let imageView: UIImageView = { let imageVIew = UIImageView() @@ -35,10 +41,11 @@ final class SplashViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() reachability?.rx.isConnected - .withUnretained(self) - .subscribe(onNext: { owner, _ in - RootHandler.shard.update(.main) - }) + .bind(with: self) { owner, _ in + DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) { + owner.coordinator?.flow.send(.main) + } + } .disposed(by: disposeBag) } From 96d6b6bc3282cd5363e41d781dc7e121f11b3a44 Mon Sep 17 00:00:00 2001 From: kimkyuchul Date: Mon, 25 Nov 2024 14:38:29 +0900 Subject: [PATCH 10/16] =?UTF-8?q?[ADD]=20Location,=20LocationDetail=20Coor?= =?UTF-8?q?dinator=20=EC=A0=81=EC=9A=A9=20(#30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makgulli.xcodeproj/project.pbxproj | 25 +++++++ .../Coordinator/LocationCoordinator.swift | 45 ++++++++++++ .../View/LocationViewController.swift | 26 ++----- .../ViewModel/LocationViewModel.swift | 71 ++++++++++--------- .../LocationDetailCoordinator.swift | 65 +++++++++++++++++ .../View/LocationDetailView.swift | 23 ++++-- .../View/LocationDetailViewController.swift | 31 +++----- .../ViewModel/LocationDetailViewModel.swift | 24 +++++-- 8 files changed, 226 insertions(+), 84 deletions(-) create mode 100644 Makgulli/Presentation/Location/Coordinator/LocationCoordinator.swift create mode 100644 Makgulli/Presentation/LocationDetail/Coordinator/LocationDetailCoordinator.swift diff --git a/Makgulli.xcodeproj/project.pbxproj b/Makgulli.xcodeproj/project.pbxproj index ae7b6db..635452a 100644 --- a/Makgulli.xcodeproj/project.pbxproj +++ b/Makgulli.xcodeproj/project.pbxproj @@ -178,6 +178,8 @@ C2C2FECC2CF02DD50000AC4F /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = C2C2FECB2CF02DD50000AC4F /* RealmSwift */; }; C2C2FECD2CF02DE80000AC4F /* RealmSwift in Embed Frameworks */ = {isa = PBXBuildFile; productRef = C2C2FECB2CF02DD50000AC4F /* RealmSwift */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; C2C2FECF2CF1BEB70000AC4F /* TabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FECE2CF1BEB70000AC4F /* TabBar.swift */; }; + C2C2FED22CF1C2720000AC4F /* LocationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FED12CF1C2720000AC4F /* LocationCoordinator.swift */; }; + C2C2FED82CF310930000AC4F /* LocationDetailCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FED72CF310930000AC4F /* LocationDetailCoordinator.swift */; }; C2C2FEEB2CF416DF0000AC4F /* AppInfoDIContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FEEA2CF416DF0000AC4F /* AppInfoDIContainer.swift */; }; C2F7418C2CF0270700C423D5 /* TabBarCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2F7418B2CF0270700C423D5 /* TabBarCoordinator.swift */; }; /* End PBXBuildFile section */ @@ -386,6 +388,8 @@ C2773C692CF007BC00942765 /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = ""; }; C2773C6B2CF0087300942765 /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; C2C2FECE2CF1BEB70000AC4F /* TabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBar.swift; sourceTree = ""; }; + C2C2FED12CF1C2720000AC4F /* LocationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationCoordinator.swift; sourceTree = ""; }; + C2C2FED72CF310930000AC4F /* LocationDetailCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationDetailCoordinator.swift; sourceTree = ""; }; C2C2FEEA2CF416DF0000AC4F /* AppInfoDIContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoDIContainer.swift; sourceTree = ""; }; C2F7418B2CF0270700C423D5 /* TabBarCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarCoordinator.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -1188,6 +1192,7 @@ 5ADC002E2AC5E2C60024D7F8 /* Location */ = { isa = PBXGroup; children = ( + C2C2FED02CF1C25D0000AC4F /* Coordinator */, 5ADC002F2AC5E2DA0024D7F8 /* View */, 5A35E5782AEB482E0074C8EC /* ViewModel */, ); @@ -1248,6 +1253,7 @@ 5ADC004B2ACA9D970024D7F8 /* LocationDetail */ = { isa = PBXGroup; children = ( + C2C2FED62CF310820000AC4F /* Coordinator */, 5A68277A2ACEC78300C37B17 /* View */, 5A35E5792AEB48460074C8EC /* ViewModel */, ); @@ -1272,6 +1278,23 @@ ); path = Coordinator; sourceTree = ""; + }; + C2C2FED02CF1C25D0000AC4F /* Coordinator */ = { + isa = PBXGroup; + children = ( + C2C2FED12CF1C2720000AC4F /* LocationCoordinator.swift */, + ); + path = Coordinator; + sourceTree = ""; + }; + C2C2FED62CF310820000AC4F /* Coordinator */ = { + isa = PBXGroup; + children = ( + C2C2FED72CF310930000AC4F /* LocationDetailCoordinator.swift */, + ); + path = Coordinator; + sourceTree = ""; + }; C2F741882CF019D900C423D5 /* Splash */ = { isa = PBXGroup; children = ( @@ -1643,6 +1666,7 @@ 5A9DA2052AC3CABB006040D2 /* LocationUseCase.swift in Sources */, 5A0406FC2AF7C20D00A4BA84 /* SearchLocationLocalRepository.swift in Sources */, 5A6827B22AD6760600C37B17 /* EpisodeDateView.swift in Sources */, + C2C2FED22CF1C2720000AC4F /* LocationCoordinator.swift in Sources */, 5AAB8E772ADA7107005969FA /* LocationDetailRepository.swift in Sources */, 5A0406F22AF617BE00A4BA84 /* FavoriteStorage.swift in Sources */, 5A6827B42AD6906700C37B17 /* Date+.swift in Sources */, @@ -1714,6 +1738,7 @@ 5AAB8E822ADC0418005969FA /* BaseAlert.swift in Sources */, 5A0407042AF8FF7F00A4BA84 /* DefaultLocationDetailLocalRepository.swift in Sources */, 5A9DA21E2AC419D5006040D2 /* LocationView.swift in Sources */, + C2C2FED82CF310930000AC4F /* LocationDetailCoordinator.swift in Sources */, 5A68278E2AD08A2A00C37B17 /* StoreVO.swift in Sources */, 5A9DA2172AC3F629006040D2 /* LocationService.swift in Sources */, 5A9DA1D52AC19D30006040D2 /* UIColor+.swift in Sources */, diff --git a/Makgulli/Presentation/Location/Coordinator/LocationCoordinator.swift b/Makgulli/Presentation/Location/Coordinator/LocationCoordinator.swift new file mode 100644 index 0000000..142ee2a --- /dev/null +++ b/Makgulli/Presentation/Location/Coordinator/LocationCoordinator.swift @@ -0,0 +1,45 @@ +// +// LocationCoordinator.swift +// Makgulli +// +// Created by kyuchul on 11/23/24. +// + +import UIKit + +final class LocationCoordinator: Coordinator { + weak var parentCoordinator: (any Coordinator)? + var childCoordinators: [any Coordinator] = [] + var navigationController: UINavigationController + + init(navigationController: UINavigationController) { + self.navigationController = navigationController + } + + func start() { + let viewModel = AppDIContainer.shared + .makeLocationDIContainer() + .makeLocationViewModel() + viewModel.coordinator = self + + let locationViewController = LocationViewController(viewModel: viewModel) + + push(viewController: locationViewController, navibarHidden: false, animated: false) + } +} + +extension LocationCoordinator { + func startLocationDetail(_ store: StoreVO) { + let locationDetailCoordinator = LocationDetailCoordinator(navigationController: navigationController) + locationDetailCoordinator.parentCoordinator = self + locationDetailCoordinator.store = store + locationDetailCoordinator.start() + + addDependency(locationDetailCoordinator) + } + + func startQuestion() { + let questionViewController = QuestionViewController() + present(questionViewController, style: .automatic) + } +} diff --git a/Makgulli/Presentation/Location/View/LocationViewController.swift b/Makgulli/Presentation/Location/View/LocationViewController.swift index 68cdd93..1e21d58 100644 --- a/Makgulli/Presentation/Location/View/LocationViewController.swift +++ b/Makgulli/Presentation/Location/View/LocationViewController.swift @@ -20,6 +20,7 @@ final class LocationViewController: BaseViewController { private var selectCategoryType: CategoryType = .makgulli private var selectMarkerRelay = PublishRelay() private var changeMapLocation = PublishRelay() + private let didSelectStoreItem = PublishRelay() init(viewModel: LocationViewModel) { self.viewModel = viewModel @@ -43,14 +44,17 @@ final class LocationViewController: BaseViewController { override func bind() { let input = LocationViewModel .Input(viewDidLoadEvent: Observable.just(()).asObservable(), - viewWillAppearEvent: self.rx.viewWillAppear.map { _ in }, + viewWillAppearEvent: self.rx.viewWillAppear.map { _ in }, + didSelectQuestionButton: locationView.questionButton.rx.tap.asObservable().throttle(.seconds(1), scheduler: MainScheduler.instance), willDisplayCell: locationView.storeCollectionView.rx.willDisplayCell.map { $0.at }, didSelectMarker: selectMarkerRelay.asObservable(), didSelectCategoryCell: locationView.categoryCollectionView.rx.itemSelected.asObservable().throttle(.seconds(1), scheduler: MainScheduler.instance), changeMapLocation: changeMapLocation.asObservable(), didSelectRefreshButton: locationView.researchButton.rx.tap.asObservable().throttle(.seconds(1), scheduler: MainScheduler.instance), didSelectUserLocationButton: locationView.userLocationButton.rx.tap.asObservable().throttle(.seconds(1), scheduler: MainScheduler.instance), - didScrollStoreCollectionView: locationView.visibleItemsRelay.asObservable().debounce(.milliseconds(250), scheduler: MainScheduler.instance)) + didScrollStoreCollectionView: locationView.visibleItemsRelay.asObservable().debounce(.milliseconds(250), scheduler: MainScheduler.instance), + didSelectStoreItem: didSelectStoreItem.asObservable().throttle(.seconds(1), scheduler: MainScheduler.instance) + ) let output = viewModel.transform(input: input) output.storeList @@ -144,7 +148,6 @@ final class LocationViewController: BaseViewController { output.showErrorAlert .withUnretained(self) .flatMap { owner, error in - dump(error) return owner.rx.makeErrorAlert(title: "네트워크 에러", message: "네트워크 에러가 발생했습니다.", cancelButtonTitle: "확인") } .subscribe() @@ -156,26 +159,11 @@ final class LocationViewController: BaseViewController { } override func bindAction() { - locationView.questionButton.rx.tap - .asDriver(onErrorJustReturn: Void()) - .drive(with: self) { owner, _ in - owner.present(QuestionViewController(), animated: true) - } - .disposed(by: disposeBag) - Observable.zip(locationView.storeCollectionView.rx.modelSelected(StoreVO.self), locationView.storeCollectionView.rx.itemSelected) .withUnretained(self) .bind(onNext: { [weak self] data in if let updatedItem = self?.viewModel.updateStoreCell(data.1.0) { - - let locationDetilViewController = LocationDetailViewController( - viewModel: AppDIContainer.shared - .makeLocationDIContainer() - .makeLocationDetailViewModel(store: updatedItem) - ) - - locationDetilViewController.hidesBottomBarWhenPushed = true - data.0.navigationController?.pushViewController(locationDetilViewController, animated: true) + data.0.didSelectStoreItem.accept(updatedItem) } }) .disposed(by: disposeBag) diff --git a/Makgulli/Presentation/Location/ViewModel/LocationViewModel.swift b/Makgulli/Presentation/Location/ViewModel/LocationViewModel.swift index 779afc0..7acb1e9 100644 --- a/Makgulli/Presentation/Location/ViewModel/LocationViewModel.swift +++ b/Makgulli/Presentation/Location/ViewModel/LocationViewModel.swift @@ -11,7 +11,8 @@ import CoreLocation import RxSwift import RxRelay -final class LocationViewModel: ViewModelType { +final class LocationViewModel: ViewModelType, Coordinatable { + weak var coordinator: LocationCoordinator? var disposeBag: DisposeBag = .init() private let searchLocationUseCase: SearchLocationUseCase @@ -32,6 +33,7 @@ final class LocationViewModel: ViewModelType { struct Input { let viewDidLoadEvent: Observable let viewWillAppearEvent: Observable + let didSelectQuestionButton: Observable let willDisplayCell: Observable let didSelectMarker: Observable let didSelectCategoryCell: Observable @@ -39,6 +41,7 @@ final class LocationViewModel: ViewModelType { let didSelectRefreshButton: Observable let didSelectUserLocationButton: Observable let didScrollStoreCollectionView: Observable + let didSelectStoreItem: Observable } struct Output { @@ -59,20 +62,24 @@ final class LocationViewModel: ViewModelType { let output = Output() input.viewDidLoadEvent - .withUnretained(self) - .subscribe(onNext: { owner, _ in + .subscribe(with: self) { owner, _ in owner.locationUseCase.checkLocationAuthorization() owner.locationUseCase.checkAuthorization() owner.locationUseCase.observeUserLocation() - }) + } .disposed(by: disposeBag) input.viewWillAppearEvent .withLatestFrom(input.willDisplayCell) - .withUnretained(self) - .bind(onNext: { owner, indexPath in + .bind(with: self) { owner, indexPath in owner.searchLocationUseCase.updateWillDisplayStoreCell(index: indexPath.row, storeList: owner.storeList) - }) + } + .disposed(by: disposeBag) + + input.didSelectQuestionButton + .bind(with: self) { owner, _ in + owner.coordinator?.startQuestion() + } .disposed(by: disposeBag) let didSelectMarker = input.didSelectMarker @@ -83,34 +90,31 @@ final class LocationViewModel: ViewModelType { .disposed(by: disposeBag) didSelectMarker - .withUnretained(self) - .bind(onNext: { owner, index in + .bind(with: self) { owner, index in guard let index = index else { return } let store = owner.storeList[index] output.setCameraPosition.accept((store.y, store.x)) - }) + } .disposed(by: disposeBag) input.didSelectCategoryCell .withLatestFrom(currentLocation) { index, location in return (index, location) } - .withUnretained(self) - .bind(onNext: { owner, indexLocation in + .bind(with: self) { owner, indexLocation in let categoryType = CategoryType.allCases[indexLocation.0.row] output.reSearchButtonHidden.accept(true) owner.categoryType.accept(categoryType) owner.searchLocationUseCase.fetchLocation(query: categoryType.rawValue, x: indexLocation.1.x, y: indexLocation.1.y, page: 1, display: 30) - }) + } .disposed(by: disposeBag) input.changeMapLocation - .withUnretained(self) - .bind(onNext: { owner, coordinate in + .bind(with: self) { owner, coordinate in output.reSearchButtonHidden.accept(false) owner.currentLocation.accept(coordinate) - }) + } .disposed(by: disposeBag) input.didSelectRefreshButton @@ -129,21 +133,26 @@ final class LocationViewModel: ViewModelType { input.didSelectUserLocationButton .withUnretained(self) - .bind(onNext: { owner, _ in + .bind(with: self) { owner, _ in output.reSearchButtonHidden.accept(true) owner.locationUseCase.startUpdatingLocation() - }) + } .disposed(by: disposeBag) input.didScrollStoreCollectionView .distinctUntilChanged() - .withUnretained(self) - .bind(onNext: { owner, percentVisible in + .bind(with: self) { owner, percentVisible in guard let index = percentVisible else { return } let store = owner.storeList[index] output.setCameraPosition.accept((store.y, store.x)) output.selectedMarkerIndex.accept(index) - }) + } + .disposed(by: disposeBag) + + input.didSelectStoreItem + .bind(with: self) { owner, store in + owner.coordinator?.startLocationDetail(store) + } .disposed(by: disposeBag) createOutput(input: input, output: output) @@ -155,12 +164,11 @@ final class LocationViewModel: ViewModelType { private func createOutput(input: Input, output: Output) { locationUseCase.locationUpdateSubject .withLatestFrom(Observable.combineLatest(output.currentUserLocation, self.categoryType)) - .withUnretained(self) - .bind(onNext: { owner, userLocationAndCategoryType in + .bind(with: self) { owner, userLocationAndCategoryType in let (userLocation, categoryType) = userLocationAndCategoryType owner.searchLocationUseCase.fetchLocation(query: categoryType.rawValue, x: userLocation.x, y: userLocation.y, page: 1, display: 30) - }) + } .disposed(by: disposeBag) searchLocationUseCase.storeVO @@ -177,20 +185,18 @@ final class LocationViewModel: ViewModelType { .disposed(by: disposeBag) output.storeList - .withUnretained(self) - .subscribe(onNext: { owner, storeVO in + .subscribe(with: self) { owner, storeVO in owner.storeList = storeVO - }) + } .disposed(by: disposeBag) let locationCoordinate = locationUseCase.locationCoordinate .share() locationCoordinate - .withUnretained(self) - .bind(onNext: { owner, coordinate in + .bind(with: self) { owner, coordinate in owner.currentLocation.accept(coordinate) - }) + } .disposed(by: disposeBag) locationCoordinate @@ -214,15 +220,14 @@ final class LocationViewModel: ViewModelType { .withLatestFrom(input.willDisplayCell) { storeVO, willDisplayCell in return (storeVO, willDisplayCell) } - .withUnretained(self) - .bind(onNext: { owner, visibleStore in + .bind(with: self) { owner, visibleStore in let willDisplayStore = owner.storeList[visibleStore.1.row] if owner.shouldUpdateStore(store: visibleStore.0, visibleStore: willDisplayStore) { owner.storeList[visibleStore.1.row] = visibleStore.0 output.storeList.accept(owner.storeList) } - }) + } .disposed(by: disposeBag) searchLocationUseCase.isLoding diff --git a/Makgulli/Presentation/LocationDetail/Coordinator/LocationDetailCoordinator.swift b/Makgulli/Presentation/LocationDetail/Coordinator/LocationDetailCoordinator.swift new file mode 100644 index 0000000..c0e452a --- /dev/null +++ b/Makgulli/Presentation/LocationDetail/Coordinator/LocationDetailCoordinator.swift @@ -0,0 +1,65 @@ +// +// LocationDetailCoordinator.swift +// Makgulli +// +// Created by kyuchul on 11/24/24. +// + +import UIKit + +final class LocationDetailCoordinator: Coordinator { + weak var parentCoordinator: (any Coordinator)? + var childCoordinators: [any Coordinator] = [] + var navigationController: UINavigationController + var store: StoreVO? + + init(navigationController: UINavigationController) { + self.navigationController = navigationController + } + + deinit { + debugPrint("deinit Coordinator: \(self)") + } + + func start() { + guard let store else { return } + + let viewModel = AppDIContainer.shared + .makeLocationDIContainer() + .makeLocationDetailViewModel(store: store) + viewModel.coordinator = self + + let locationDetilViewController = LocationDetailViewController(viewModel: viewModel) + + locationDetilViewController.hidesBottomBarWhenPushed = true + push(viewController: locationDetilViewController, navibarHidden: true, swipe: false) + } +} + +extension LocationDetailCoordinator { + func popLocationDetail() { + parentCoordinator?.removeDependency(self) + pop() + } + + func startWriteEpisode(store: StoreVO) { + let writeEpisodeCoordinator = WriteEpisodeCoordinator(navigationController: navigationController) + writeEpisodeCoordinator.parentCoordinator = self + writeEpisodeCoordinator.store = store + writeEpisodeCoordinator.start() + + addDependency(writeEpisodeCoordinator) + } + + func startEpisodeDetail(episode: Episode, storeId: String) { + let episodeDetailCoordinator = EpisodeDetailCoordinator(navigationController: navigationController) + episodeDetailCoordinator.parentCoordinator = self + episodeDetailCoordinator.episode = episode + episodeDetailCoordinator.storeId = storeId + episodeDetailCoordinator.start() + + addDependency(episodeDetailCoordinator) + } +} + + diff --git a/Makgulli/Presentation/LocationDetail/View/LocationDetailView.swift b/Makgulli/Presentation/LocationDetail/View/LocationDetailView.swift index b48053a..a4b3e5a 100644 --- a/Makgulli/Presentation/LocationDetail/View/LocationDetailView.swift +++ b/Makgulli/Presentation/LocationDetail/View/LocationDetailView.swift @@ -12,7 +12,12 @@ import RxSwift import RxCocoa final class LocationDetailView: BaseView { - + + fileprivate let navigationBar: JumakNavigationBar = { + let navigationBar = JumakNavigationBar() + navigationBar.setTitle("상세 정보") + return navigationBar + }() private let scrollView = UIScrollView() private let contentView = UIView() private let topCornerView: UIView = { @@ -56,7 +61,7 @@ final class LocationDetailView: BaseView { func itemIdentifier(for indexPath: IndexPath) -> Episode? { return episodeView.dataSource?.itemIdentifier(for: indexPath) } - + fileprivate func moveCamera(latitude: Double, longitude: Double) { let cameraPosition = NMFCameraPosition( NMGLatLng(lat: latitude,lng: longitude), @@ -91,6 +96,7 @@ final class LocationDetailView: BaseView { } override func setHierarchy() { + self.addSubview(navigationBar) self.addSubview(scrollView) self.addSubview(bottomView) scrollView.addSubview(contentView) @@ -110,8 +116,13 @@ final class LocationDetailView: BaseView { } override func setConstraints() { - scrollView.snp.makeConstraints { make in + navigationBar.snp.makeConstraints { make in make.top.leading.trailing.equalTo(self.safeAreaLayoutGuide) + } + + scrollView.snp.makeConstraints { make in + make.top.equalTo(navigationBar.snp.bottom) + make.horizontalEdges.equalTo(navigationBar.snp.horizontalEdges) make.bottom.equalTo(bottomView.snp.top) } @@ -178,9 +189,13 @@ final class LocationDetailView: BaseView { } extension Reactive where Base: LocationDetailView { + var backButtonTap: Observable { + return self.base.navigationBar.backButtonAction() + } + var selectedItem: ControlEvent { return self.base.episodeView.episodeCollectionView.rx.itemSelected - } + } var storeCameraPosition: Binder<(Double, Double)> { return Binder(self.base) { view, cameraPosition in diff --git a/Makgulli/Presentation/LocationDetail/View/LocationDetailViewController.swift b/Makgulli/Presentation/LocationDetail/View/LocationDetailViewController.swift index 93748ef..5f90af5 100644 --- a/Makgulli/Presentation/LocationDetail/View/LocationDetailViewController.swift +++ b/Makgulli/Presentation/LocationDetail/View/LocationDetailViewController.swift @@ -15,6 +15,7 @@ final class LocationDetailViewController: BaseViewController { private let locationDetailView = LocationDetailView() private let viewModel: LocationDetailViewModel private let didSelectFindRouteType = PublishRelay() + private let didSelectEpisodeItem = PublishRelay() init(viewModel: LocationDetailViewModel) { self.viewModel = viewModel @@ -27,23 +28,22 @@ final class LocationDetailViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() - self.title = "상세 정보" self.view.backgroundColor = .white - self.navigationController?.navigationBar.isHidden = false - self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false } override func bind() { let input = LocationDetailViewModel .Input(viewDidLoadEvent: Observable.just(()).asObservable(), viewWillAppearEvent: self.rx.viewWillAppear.map { _ in }, - viewWillDisappearEvent: self.rx.viewWillDisappear.map { _ in }, + viewWillDisappearEvent: self.rx.viewWillDisappear.map { _ in }, + didSelectBackButton: locationDetailView.rx.backButtonTap.throttle(.milliseconds(300), scheduler: MainScheduler.instance), didSelectRate: locationDetailView.rateView.currentStarSubject.asObserver(), didSelectBookmark: locationDetailView.titleView.bookMarkButton.rx.isSelected.asObservable(), didSelectFindRouteType: didSelectFindRouteType.asObservable(), didSelectUserLocationButton: locationDetailView.storeLocationButton.rx.tap.asObservable().throttle(.seconds(1), scheduler: MainScheduler.asyncInstance), didSelectCopyAddressButton: locationDetailView.infoView.rx.tapCopyAddress.asObservable().throttle(.milliseconds(300), scheduler: MainScheduler.instance), - didSelectMakeEpisodeButton: locationDetailView.bottomView.rx.tapMakeEpisode.asObservable().throttle(.milliseconds(300), scheduler: MainScheduler.instance)) + didSelectMakeEpisodeButton: locationDetailView.bottomView.rx.tapMakeEpisode.asObservable().throttle(.milliseconds(300), scheduler: MainScheduler.instance), + didSelectEpisodeItem: didSelectEpisodeItem.asObservable().throttle(.milliseconds(300), scheduler: MainScheduler.instance)) let output = viewModel.transform(input: input) output.hashTag @@ -119,21 +119,7 @@ final class LocationDetailViewController: BaseViewController { } .subscribe() .disposed(by: disposeBag) - - output.presentWriteEpisode - .withUnretained(self) - .bind(onNext: { owner, storeVO in - let writeEpisodeViewController = WriteEpisodeViewController( - viewModel: AppDIContainer.shared - .makeEpisodeDIContainer() - .makeLocationViewModel(store: storeVO) - ) - - writeEpisodeViewController.modalPresentationStyle = .fullScreen - owner.present(writeEpisodeViewController, animated: true) - }) - .disposed(by: disposeBag) - + let episodeList = output.episodeList .share() .distinctUntilChanged() @@ -164,7 +150,7 @@ final class LocationDetailViewController: BaseViewController { .disposed(by: disposeBag) } - override func bindAction() { + override func bindAction() { locationDetailView.titleView.rx.tapFindRoute .withUnretained(self) .bind(onNext: { owner, _ in @@ -176,8 +162,7 @@ final class LocationDetailViewController: BaseViewController { .withUnretained(self) .subscribe(onNext: { owner, indexPath in guard let episode = owner.locationDetailView.itemIdentifier(for: indexPath) else { return } - let episodeDetailViewController = EpisodeDetailViewController(viewModel: AppDIContainer.shared.makeEpisodeDIContainer().makeLocationViewModel(episode: episode, storeId: owner.viewModel.storeVO.id)) - owner.navigationController?.pushViewController(episodeDetailViewController, animated: true) + owner.didSelectEpisodeItem.accept(episode) }) .disposed(by: disposeBag) diff --git a/Makgulli/Presentation/LocationDetail/ViewModel/LocationDetailViewModel.swift b/Makgulli/Presentation/LocationDetail/ViewModel/LocationDetailViewModel.swift index c7b4084..81dba6a 100644 --- a/Makgulli/Presentation/LocationDetail/ViewModel/LocationDetailViewModel.swift +++ b/Makgulli/Presentation/LocationDetail/ViewModel/LocationDetailViewModel.swift @@ -21,7 +21,8 @@ struct Episode: Hashable { let imageData: Data } -final class LocationDetailViewModel: ViewModelType { +final class LocationDetailViewModel: ViewModelType, Coordinatable { + weak var coordinator: LocationDetailCoordinator? var disposeBag: DisposeBag = .init() var storeVO: StoreVO @@ -39,12 +40,14 @@ final class LocationDetailViewModel: ViewModelType { let viewDidLoadEvent: Observable let viewWillAppearEvent: Observable let viewWillDisappearEvent: Observable + let didSelectBackButton: Observable let didSelectRate: Observable let didSelectBookmark: Observable let didSelectFindRouteType: Observable let didSelectUserLocationButton: Observable let didSelectCopyAddressButton : Observable let didSelectMakeEpisodeButton: Observable + let didSelectEpisodeItem: Observable } struct Output { @@ -94,6 +97,12 @@ final class LocationDetailViewModel: ViewModelType { }) .disposed(by: disposeBag) + input.didSelectBackButton + .subscribe(with: self) { owner, _ in + owner.coordinator?.popLocationDetail() + } + .disposed(by: disposeBag) + let didSelectRate = input.didSelectRate .share() @@ -158,10 +167,15 @@ final class LocationDetailViewModel: ViewModelType { .disposed(by: disposeBag) input.didSelectMakeEpisodeButton - .withUnretained(self) - .bind(onNext: { owner, _ in - output.presentWriteEpisode.accept(owner.storeVO) - }) + .bind(with: self) { owner, _ in + owner.coordinator?.startWriteEpisode(store: owner.storeVO) + } + .disposed(by: disposeBag) + + input.didSelectEpisodeItem + .bind(with: self) { owner, episode in + owner.coordinator?.startEpisodeDetail(episode: episode, storeId: owner.storeVO.id) + } .disposed(by: disposeBag) createOutput(output: output) From 523277d30c76dc0bdeded1a4e1c0177a85ffa1e7 Mon Sep 17 00:00:00 2001 From: kimkyuchul Date: Mon, 25 Nov 2024 14:40:54 +0900 Subject: [PATCH 11/16] =?UTF-8?q?[ADD]=20WriteEpisode,=20EpisodeDetail=20C?= =?UTF-8?q?oordinator=20=EC=A0=81=EC=9A=A9=20(#30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makgulli.xcodeproj/project.pbxproj | 30 ++++++++++++-- .../EpisodeDetailCoordinator.swift | 41 +++++++++++++++++++ .../View/EpisodeDetailView.swift | 27 +++++++++++- .../View/EpisodeDetailViewController.swift | 26 ++++-------- .../ViewModel/EpisodeDetailViewModel.swift | 24 +++++++---- .../Coordinator/WriteEpisodeCoordinator.swift | 40 ++++++++++++++++++ .../View/EpisodeContentView.swift | 0 .../View/EpisodeDateView.swift | 0 .../View/EpisodeDrinkCountView.swift | 0 .../View/EpisodeDrinkNameView.swift | 0 .../View/WriteEpisodeView.swift | 0 .../View/WriteEpisodeViewController.swift | 10 +---- .../ViewModel/WriteEpisodeViewModel.swift | 11 ++++- 13 files changed, 168 insertions(+), 41 deletions(-) create mode 100644 Makgulli/Presentation/EpisodeDetail/Coordinator/EpisodeDetailCoordinator.swift create mode 100644 Makgulli/Presentation/WriteEpisode/Coordinator/WriteEpisodeCoordinator.swift rename Makgulli/Presentation/{Episode => WriteEpisode}/View/EpisodeContentView.swift (100%) rename Makgulli/Presentation/{Episode => WriteEpisode}/View/EpisodeDateView.swift (100%) rename Makgulli/Presentation/{Episode => WriteEpisode}/View/EpisodeDrinkCountView.swift (100%) rename Makgulli/Presentation/{Episode => WriteEpisode}/View/EpisodeDrinkNameView.swift (100%) rename Makgulli/Presentation/{Episode => WriteEpisode}/View/WriteEpisodeView.swift (100%) rename Makgulli/Presentation/{Episode => WriteEpisode}/View/WriteEpisodeViewController.swift (95%) rename Makgulli/Presentation/{Episode => WriteEpisode}/ViewModel/WriteEpisodeViewModel.swift (94%) diff --git a/Makgulli.xcodeproj/project.pbxproj b/Makgulli.xcodeproj/project.pbxproj index 635452a..c1c9ada 100644 --- a/Makgulli.xcodeproj/project.pbxproj +++ b/Makgulli.xcodeproj/project.pbxproj @@ -180,6 +180,8 @@ C2C2FECF2CF1BEB70000AC4F /* TabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FECE2CF1BEB70000AC4F /* TabBar.swift */; }; C2C2FED22CF1C2720000AC4F /* LocationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FED12CF1C2720000AC4F /* LocationCoordinator.swift */; }; C2C2FED82CF310930000AC4F /* LocationDetailCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FED72CF310930000AC4F /* LocationDetailCoordinator.swift */; }; + C2C2FEDD2CF363A30000AC4F /* WriteEpisodeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FEDC2CF363A30000AC4F /* WriteEpisodeCoordinator.swift */; }; + C2C2FEE02CF369830000AC4F /* EpisodeDetailCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FEDF2CF369830000AC4F /* EpisodeDetailCoordinator.swift */; }; C2C2FEEB2CF416DF0000AC4F /* AppInfoDIContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FEEA2CF416DF0000AC4F /* AppInfoDIContainer.swift */; }; C2F7418C2CF0270700C423D5 /* TabBarCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2F7418B2CF0270700C423D5 /* TabBarCoordinator.swift */; }; /* End PBXBuildFile section */ @@ -390,6 +392,8 @@ C2C2FECE2CF1BEB70000AC4F /* TabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBar.swift; sourceTree = ""; }; C2C2FED12CF1C2720000AC4F /* LocationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationCoordinator.swift; sourceTree = ""; }; C2C2FED72CF310930000AC4F /* LocationDetailCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationDetailCoordinator.swift; sourceTree = ""; }; + C2C2FEDC2CF363A30000AC4F /* WriteEpisodeCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteEpisodeCoordinator.swift; sourceTree = ""; }; + C2C2FEDF2CF369830000AC4F /* EpisodeDetailCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeDetailCoordinator.swift; sourceTree = ""; }; C2C2FEEA2CF416DF0000AC4F /* AppInfoDIContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoDIContainer.swift; sourceTree = ""; }; C2F7418B2CF0270700C423D5 /* TabBarCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarCoordinator.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -792,13 +796,14 @@ path = Label; sourceTree = ""; }; - 5A6827A52AD50D3A00C37B17 /* Episode */ = { + 5A6827A52AD50D3A00C37B17 /* WriteEpisode */ = { isa = PBXGroup; children = ( + C2C2FEDB2CF363920000AC4F /* Coordinator */, 5AAB8E7D2ADBE246005969FA /* View */, 5A35E57A2AEB48B30074C8EC /* ViewModel */, ); - path = Episode; + path = WriteEpisode; sourceTree = ""; }; 5A6827AE2AD56AD700C37B17 /* Episode */ = { @@ -923,7 +928,7 @@ 5A0A98502AE6D9A000545939 /* Common */, 5ADC002E2AC5E2C60024D7F8 /* Location */, 5ADC004B2ACA9D970024D7F8 /* LocationDetail */, - 5A6827A52AD50D3A00C37B17 /* Episode */, + 5A6827A52AD50D3A00C37B17 /* WriteEpisode */, 5AAB8E782ADBE152005969FA /* EpisodeDetail */, 5ADC003F2AC86D8A0024D7F8 /* Favorite */, 5A0A98512AE79DF200545939 /* AppInfo */, @@ -1144,6 +1149,7 @@ 5AAB8E782ADBE152005969FA /* EpisodeDetail */ = { isa = PBXGroup; children = ( + C2C2FEDE2CF369710000AC4F /* Coordinator */, 5A35E57B2AEB4A620074C8EC /* View */, 5A35E57C2AEB4A690074C8EC /* ViewModel */, ); @@ -1295,6 +1301,22 @@ path = Coordinator; sourceTree = ""; }; + C2C2FEDB2CF363920000AC4F /* Coordinator */ = { + isa = PBXGroup; + children = ( + C2C2FEDC2CF363A30000AC4F /* WriteEpisodeCoordinator.swift */, + ); + path = Coordinator; + sourceTree = ""; + }; + C2C2FEDE2CF369710000AC4F /* Coordinator */ = { + isa = PBXGroup; + children = ( + C2C2FEDF2CF369830000AC4F /* EpisodeDetailCoordinator.swift */, + ); + path = Coordinator; + sourceTree = ""; + }; C2F741882CF019D900C423D5 /* Splash */ = { isa = PBXGroup; children = ( @@ -1611,12 +1633,14 @@ 5A35E57F2AEBDFCD0074C8EC /* NetworkError.swift in Sources */, 5A9DA20C2AC3D6E5006040D2 /* LocationViewModel.swift in Sources */, 5A9DA21C2AC415EF006040D2 /* ViewSetupable.swift in Sources */, + C2C2FEE02CF369830000AC4F /* EpisodeDetailCoordinator.swift in Sources */, 5AAB8EA22ADFEB85005969FA /* NotificationCenterHandler.swift in Sources */, 5A6827A12AD446D700C37B17 /* UIViewController+.swift in Sources */, 5A0407172AF922C500A4BA84 /* EpisodeDetailStorage.swift in Sources */, 5A6827BE2AD6D44E00C37B17 /* ImageSelectionView.swift in Sources */, 5A6827772ACEA7B200C37B17 /* RateButton.swift in Sources */, 5A0406F62AF7922900A4BA84 /* BaseRealmStorage.swift in Sources */, + C2C2FEDD2CF363A30000AC4F /* WriteEpisodeCoordinator.swift in Sources */, 5A68279F2AD2B64E00C37B17 /* StoreBadge.swift in Sources */, 5A0406F92AF7C15800A4BA84 /* DefaultSearchLocationLocalRepository.swift in Sources */, 5ADC00412AC86DA00024D7F8 /* FavoriteViewController.swift in Sources */, diff --git a/Makgulli/Presentation/EpisodeDetail/Coordinator/EpisodeDetailCoordinator.swift b/Makgulli/Presentation/EpisodeDetail/Coordinator/EpisodeDetailCoordinator.swift new file mode 100644 index 0000000..4712fd5 --- /dev/null +++ b/Makgulli/Presentation/EpisodeDetail/Coordinator/EpisodeDetailCoordinator.swift @@ -0,0 +1,41 @@ +// +// EpisodeDetailCoordinator.swift +// Makgulli +// +// Created by kyuchul on 11/24/24. +// + +import UIKit + +final class EpisodeDetailCoordinator: Coordinator { + var parentCoordinator: (any Coordinator)? + var childCoordinators: [any Coordinator] = [] + var navigationController: UINavigationController + var episode: Episode? + var storeId: String? + + init(navigationController: UINavigationController) { + self.navigationController = navigationController + } + + deinit { + debugPrint("deinit Coordinator: \(self)") + } + + func start() { + guard let episode, let storeId else { return } + + let viewModel = AppDIContainer.shared.makeEpisodeDIContainer().makeEpisodeDetailViewModel(episode: episode, storeId: storeId) + viewModel.coordinator = self + + let viewController = EpisodeDetailViewController(viewModel: viewModel) + push(viewController: viewController, swipe: false) + } +} + +extension EpisodeDetailCoordinator { + func popEpisodeDetail() { + parentCoordinator?.removeDependency(self) + pop() + } +} diff --git a/Makgulli/Presentation/EpisodeDetail/View/EpisodeDetailView.swift b/Makgulli/Presentation/EpisodeDetail/View/EpisodeDetailView.swift index 361e2b8..2931847 100644 --- a/Makgulli/Presentation/EpisodeDetail/View/EpisodeDetailView.swift +++ b/Makgulli/Presentation/EpisodeDetail/View/EpisodeDetailView.swift @@ -12,6 +12,17 @@ import RxCocoa final class EpisodeDetailView: BaseView { + fileprivate lazy var navigationBar: JumakNavigationBar = { + let navigationBar = JumakNavigationBar(rightItems: [deleteBarButton]) + navigationBar.backgroundColor = .lightGray + return navigationBar + }() + fileprivate let deleteBarButton: UIButton = { + let button = UIButton() + button.setImage(ImageLiteral.deleteEpisodeIcon, for: .normal) + button.tintColor = .darkGray + return button + }() private let titleLabel: UILabel = { let label = UILabel() label.text = "에피소드" @@ -77,14 +88,18 @@ final class EpisodeDetailView: BaseView { } override func setHierarchy() { - [titleLabel, dateLabel, episodeImageView, commentTitleLabel, commentTextField, drinkNmaeTitleLabel, drinkNameTextField, drinkCountTitleLabel, drinkCountTextField].forEach { + [navigationBar, titleLabel, dateLabel, episodeImageView, commentTitleLabel, commentTextField, drinkNmaeTitleLabel, drinkNameTextField, drinkCountTitleLabel, drinkCountTextField].forEach { addSubview($0) } } override func setConstraints() { + navigationBar.snp.makeConstraints { make in + make.top.leading.trailing.equalTo(self.safeAreaLayoutGuide) + } + titleLabel.snp.makeConstraints { make in - make.top.equalTo(self.safeAreaLayoutGuide.snp.top).offset(20) + make.top.equalTo(navigationBar.snp.bottom).offset(20) make.leading.equalToSuperview().inset(24) make.trailing.equalToSuperview().inset(24).priority(.high) } @@ -146,6 +161,14 @@ final class EpisodeDetailView: BaseView { } extension Reactive where Base: EpisodeDetailView { + var backButtonTap: Observable { + return self.base.navigationBar.backButtonAction() + } + + var deleteButtonTap: Observable { + return self.base.deleteBarButton.rx.tap.asObservable() + } + var episode: Binder { return Binder(self.base) { view, episode in view.dateLabel.text = episode.date.formattedDate() diff --git a/Makgulli/Presentation/EpisodeDetail/View/EpisodeDetailViewController.swift b/Makgulli/Presentation/EpisodeDetail/View/EpisodeDetailViewController.swift index b456dc7..4a3aecb 100644 --- a/Makgulli/Presentation/EpisodeDetail/View/EpisodeDetailViewController.swift +++ b/Makgulli/Presentation/EpisodeDetail/View/EpisodeDetailViewController.swift @@ -13,7 +13,6 @@ import RxSwift final class EpisodeDetailViewController: BaseViewController { private let detailView = EpisodeDetailView() - private lazy var episodeDeleteBarButtonItem = UIBarButtonItem(image: ImageLiteral.deleteEpisodeIcon, style: .plain, target: self, action: nil) private let viewModel: EpisodeDetailViewModel private let deleteButtonAction = PublishRelay() @@ -37,34 +36,23 @@ final class EpisodeDetailViewController: BaseViewController { override func bind() { let input = EpisodeDetailViewModel.Input( viewDidLoadEvent: Observable.just(()).asObservable(), - didSeletDeleteButton: deleteButtonAction.asObservable()) + didSelectBackButton: detailView.rx.backButtonTap.throttle(.milliseconds(300), scheduler: MainScheduler.instance), + didSelectDeleteButton: deleteButtonAction.asObservable()) let output = viewModel.transform(input: input) output.episode .bind(to: detailView.rx.episode) .disposed(by: disposeBag) - - output.deleteStoreEpisodeState - .withUnretained(self) - .bind(onNext: { owner, _ in - owner.navigationController?.popViewController(animated: true) - }) - .disposed(by: disposeBag) } override func bindAction() { - episodeDeleteBarButtonItem.rx.tap.throttle(.milliseconds(300), scheduler: MainScheduler.instance) - .withUnretained(self) - .bind(onNext: { owner, _ in + detailView.rx.deleteButtonTap + .throttle(.milliseconds(300), scheduler: MainScheduler.instance) + .bind(with: self) { owner, _ in owner.presentAlert(type: .deleteEpisode, leftButtonAction: nil) { - owner.deleteButtonAction.accept(Void()) + owner.deleteButtonAction.accept(()) } - }) + } .disposed(by: disposeBag) } - - override func setNavigationBar() { - episodeDeleteBarButtonItem.tintColor = .black - self.navigationItem.rightBarButtonItem = episodeDeleteBarButtonItem - } } diff --git a/Makgulli/Presentation/EpisodeDetail/ViewModel/EpisodeDetailViewModel.swift b/Makgulli/Presentation/EpisodeDetail/ViewModel/EpisodeDetailViewModel.swift index 68e589c..8c58386 100644 --- a/Makgulli/Presentation/EpisodeDetail/ViewModel/EpisodeDetailViewModel.swift +++ b/Makgulli/Presentation/EpisodeDetail/ViewModel/EpisodeDetailViewModel.swift @@ -10,7 +10,8 @@ import Foundation import RxRelay import RxSwift -final class EpisodeDetailViewModel: ViewModelType { +final class EpisodeDetailViewModel: ViewModelType, Coordinatable { + weak var coordinator: EpisodeDetailCoordinator? var disposeBag: DisposeBag = .init() private let episodeDetailUseCase: EpisodeDetailUseCase @@ -29,12 +30,12 @@ final class EpisodeDetailViewModel: ViewModelType { struct Input { let viewDidLoadEvent: Observable - let didSeletDeleteButton: Observable + let didSelectBackButton: Observable + let didSelectDeleteButton: Observable } struct Output { let episode = PublishRelay() - let deleteStoreEpisodeState = PublishRelay() } func transform(input: Input) -> Output { @@ -47,13 +48,18 @@ final class EpisodeDetailViewModel: ViewModelType { owner.episodeDetailUseCase.fetchEpisodeDetail(episode: owner.episode) }) .disposed(by: disposeBag) + + input.didSelectBackButton + .bind(with: self) { owner, _ in + owner.coordinator?.popEpisodeDetail() + } + .disposed(by: disposeBag) - input.didSeletDeleteButton - .withUnretained(self) - .bind(onNext: { owner, _ in + input.didSelectDeleteButton + .bind(with: self) { owner, _ in owner.episodeDetailUseCase.deleteEpisodeImage(fileName: "\(owner.episode.id).jpg".trimmingWhitespace()) owner.episodeDetailUseCase.deleteEpisode(storeId: owner.storeId, episodeId: owner.episode.id) - }) + } .disposed(by: disposeBag) @@ -69,7 +75,9 @@ final class EpisodeDetailViewModel: ViewModelType { Observable.combineLatest(episodeDetailUseCase.deleteEpisodeState, episodeDetailUseCase.deleteEpisodeImageState) .map { _, _ in () } - .bind(to: output.deleteStoreEpisodeState) + .bind(with: self) { owner, _ in + owner.coordinator?.popEpisodeDetail() + } .disposed(by: disposeBag) } } diff --git a/Makgulli/Presentation/WriteEpisode/Coordinator/WriteEpisodeCoordinator.swift b/Makgulli/Presentation/WriteEpisode/Coordinator/WriteEpisodeCoordinator.swift new file mode 100644 index 0000000..df28b3b --- /dev/null +++ b/Makgulli/Presentation/WriteEpisode/Coordinator/WriteEpisodeCoordinator.swift @@ -0,0 +1,40 @@ +// +// EpisodeCoordinator.swift +// Makgulli +// +// Created by kyuchul on 11/24/24. +// + +import UIKit + +final class WriteEpisodeCoordinator: Coordinator { + var parentCoordinator: (any Coordinator)? + var childCoordinators: [any Coordinator] = [] + var navigationController: UINavigationController + var store: StoreVO? + + init(navigationController: UINavigationController) { + self.navigationController = navigationController + } + + deinit { + debugPrint("deinit Coordinator: \(self)") + } + + func start() { + guard let store else { return } + + let viewModel = AppDIContainer.shared.makeEpisodeDIContainer().makeWriteEpisodeViewModel(store: store) + viewModel.coordinator = self + + let viewController = WriteEpisodeViewController(viewModel: viewModel) + present(viewController, style: .fullScreen) + } +} + +extension WriteEpisodeCoordinator { + func dismissWriteEpisode() { + parentCoordinator?.removeDependency(self) + dismiss() + } +} diff --git a/Makgulli/Presentation/Episode/View/EpisodeContentView.swift b/Makgulli/Presentation/WriteEpisode/View/EpisodeContentView.swift similarity index 100% rename from Makgulli/Presentation/Episode/View/EpisodeContentView.swift rename to Makgulli/Presentation/WriteEpisode/View/EpisodeContentView.swift diff --git a/Makgulli/Presentation/Episode/View/EpisodeDateView.swift b/Makgulli/Presentation/WriteEpisode/View/EpisodeDateView.swift similarity index 100% rename from Makgulli/Presentation/Episode/View/EpisodeDateView.swift rename to Makgulli/Presentation/WriteEpisode/View/EpisodeDateView.swift diff --git a/Makgulli/Presentation/Episode/View/EpisodeDrinkCountView.swift b/Makgulli/Presentation/WriteEpisode/View/EpisodeDrinkCountView.swift similarity index 100% rename from Makgulli/Presentation/Episode/View/EpisodeDrinkCountView.swift rename to Makgulli/Presentation/WriteEpisode/View/EpisodeDrinkCountView.swift diff --git a/Makgulli/Presentation/Episode/View/EpisodeDrinkNameView.swift b/Makgulli/Presentation/WriteEpisode/View/EpisodeDrinkNameView.swift similarity index 100% rename from Makgulli/Presentation/Episode/View/EpisodeDrinkNameView.swift rename to Makgulli/Presentation/WriteEpisode/View/EpisodeDrinkNameView.swift diff --git a/Makgulli/Presentation/Episode/View/WriteEpisodeView.swift b/Makgulli/Presentation/WriteEpisode/View/WriteEpisodeView.swift similarity index 100% rename from Makgulli/Presentation/Episode/View/WriteEpisodeView.swift rename to Makgulli/Presentation/WriteEpisode/View/WriteEpisodeView.swift diff --git a/Makgulli/Presentation/Episode/View/WriteEpisodeViewController.swift b/Makgulli/Presentation/WriteEpisode/View/WriteEpisodeViewController.swift similarity index 95% rename from Makgulli/Presentation/Episode/View/WriteEpisodeViewController.swift rename to Makgulli/Presentation/WriteEpisode/View/WriteEpisodeViewController.swift index caa9193..4641e31 100644 --- a/Makgulli/Presentation/Episode/View/WriteEpisodeViewController.swift +++ b/Makgulli/Presentation/WriteEpisode/View/WriteEpisodeViewController.swift @@ -34,7 +34,8 @@ final class WriteEpisodeViewController: BaseViewController { override func bind() { let input = WriteEpisodeViewModel.Input( - viewDidLoadEvent: Observable.just(()).asObservable(), + viewDidLoadEvent: Observable.just(()).asObservable(), + didSelectBackButton: episodeView.rx.tapDismiss.asObservable(), didSelectDatePicker: episodeView.episodeDateView.rx.date.asObservable(), commentTextFieldDidEditEvent: episodeView.episodeContentView.rx.comment, didSelectImage: episodeView.episodeContentView.rx.hasImage, @@ -81,13 +82,6 @@ final class WriteEpisodeViewController: BaseViewController { } override func bindAction() { - episodeView.rx.tapDismiss - .withUnretained(self) - .bind(onNext: { owner, _ in - owner.dismiss(animated: true) - }) - .disposed(by: disposeBag) - episodeView.episodeContentView.rx.imageViewTapGesture .emit(with: self, onNext: { owner, _ in owner.showPhotoGallery() diff --git a/Makgulli/Presentation/Episode/ViewModel/WriteEpisodeViewModel.swift b/Makgulli/Presentation/WriteEpisode/ViewModel/WriteEpisodeViewModel.swift similarity index 94% rename from Makgulli/Presentation/Episode/ViewModel/WriteEpisodeViewModel.swift rename to Makgulli/Presentation/WriteEpisode/ViewModel/WriteEpisodeViewModel.swift index 3c9255f..9e836c8 100644 --- a/Makgulli/Presentation/Episode/ViewModel/WriteEpisodeViewModel.swift +++ b/Makgulli/Presentation/WriteEpisode/ViewModel/WriteEpisodeViewModel.swift @@ -10,7 +10,8 @@ import Foundation import RxRelay import RxSwift -final class WriteEpisodeViewModel: ViewModelType { +final class WriteEpisodeViewModel: ViewModelType, Coordinatable { + weak var coordinator: WriteEpisodeCoordinator? var disposeBag: DisposeBag = .init() private let writeEpisodeUseCase: WriteEpisodeUseCase @@ -29,6 +30,7 @@ final class WriteEpisodeViewModel: ViewModelType { struct Input { let viewDidLoadEvent: Observable + let didSelectBackButton: Observable let didSelectDatePicker: Observable let commentTextFieldDidEditEvent: Observable let didSelectImage: Observable @@ -75,6 +77,13 @@ final class WriteEpisodeViewModel: ViewModelType { }) .disposed(by: disposeBag) + input.didSelectBackButton + .observe(on: MainScheduler.asyncInstance) + .bind(with: self) { owner, _ in + owner.coordinator?.dismissWriteEpisode() + } + .disposed(by: disposeBag) + input.didSelectDatePicker .bind(to: output.date) .disposed(by: disposeBag) From 1f12b840491db140b73b2c82635a1a9a773b8fe3 Mon Sep 17 00:00:00 2001 From: kimkyuchul Date: Mon, 25 Nov 2024 14:43:13 +0900 Subject: [PATCH 12/16] =?UTF-8?q?[ADD]=20Favorite,=20AppInfo=20Coordinator?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9=20(#30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makgulli.xcodeproj/project.pbxproj | 69 ++++++++++- .../Coordinator/AppInfoCoordinator.swift | 50 ++++++++ .../AppInfo/Enum/AppInfoType.swift | 37 ++++++ .../{ => View}/AppInfoViewController.swift | 107 ++++++------------ .../{ => View}/InquiryViewController.swift | 15 ++- .../AppInfo/ViewModel/AppInfoViewModel.swift | 62 ++++++++++ .../Coordinator/FavoriteCoordinator.swift | 48 ++++++++ .../View/FavoriteViewController.swift | 33 ++---- .../ViewModel/FavoriteViewModel.swift | 17 ++- 9 files changed, 338 insertions(+), 100 deletions(-) create mode 100644 Makgulli/Presentation/AppInfo/Coordinator/AppInfoCoordinator.swift create mode 100644 Makgulli/Presentation/AppInfo/Enum/AppInfoType.swift rename Makgulli/Presentation/AppInfo/{ => View}/AppInfoViewController.swift (57%) rename Makgulli/Presentation/AppInfo/{ => View}/InquiryViewController.swift (86%) create mode 100644 Makgulli/Presentation/AppInfo/ViewModel/AppInfoViewModel.swift create mode 100644 Makgulli/Presentation/Favorite/Coordinator/FavoriteCoordinator.swift diff --git a/Makgulli.xcodeproj/project.pbxproj b/Makgulli.xcodeproj/project.pbxproj index c1c9ada..edc19e8 100644 --- a/Makgulli.xcodeproj/project.pbxproj +++ b/Makgulli.xcodeproj/project.pbxproj @@ -179,10 +179,14 @@ C2C2FECD2CF02DE80000AC4F /* RealmSwift in Embed Frameworks */ = {isa = PBXBuildFile; productRef = C2C2FECB2CF02DD50000AC4F /* RealmSwift */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; C2C2FECF2CF1BEB70000AC4F /* TabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FECE2CF1BEB70000AC4F /* TabBar.swift */; }; C2C2FED22CF1C2720000AC4F /* LocationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FED12CF1C2720000AC4F /* LocationCoordinator.swift */; }; + C2C2FED52CF1C37A0000AC4F /* FavoriteCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FED42CF1C37A0000AC4F /* FavoriteCoordinator.swift */; }; C2C2FED82CF310930000AC4F /* LocationDetailCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FED72CF310930000AC4F /* LocationDetailCoordinator.swift */; }; C2C2FEDD2CF363A30000AC4F /* WriteEpisodeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FEDC2CF363A30000AC4F /* WriteEpisodeCoordinator.swift */; }; C2C2FEE02CF369830000AC4F /* EpisodeDetailCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FEDF2CF369830000AC4F /* EpisodeDetailCoordinator.swift */; }; + C2C2FEE92CF4167D0000AC4F /* AppInfoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FEE82CF4167D0000AC4F /* AppInfoViewModel.swift */; }; C2C2FEEB2CF416DF0000AC4F /* AppInfoDIContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FEEA2CF416DF0000AC4F /* AppInfoDIContainer.swift */; }; + C2C2FEEE2CF42E560000AC4F /* AppInfoType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FEED2CF42E560000AC4F /* AppInfoType.swift */; }; + C2C2FEF02CF42FD10000AC4F /* AppInfoCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FEEF2CF42FD10000AC4F /* AppInfoCoordinator.swift */; }; C2F7418C2CF0270700C423D5 /* TabBarCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2F7418B2CF0270700C423D5 /* TabBarCoordinator.swift */; }; /* End PBXBuildFile section */ @@ -391,10 +395,14 @@ C2773C6B2CF0087300942765 /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; C2C2FECE2CF1BEB70000AC4F /* TabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBar.swift; sourceTree = ""; }; C2C2FED12CF1C2720000AC4F /* LocationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationCoordinator.swift; sourceTree = ""; }; + C2C2FED42CF1C37A0000AC4F /* FavoriteCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteCoordinator.swift; sourceTree = ""; }; C2C2FED72CF310930000AC4F /* LocationDetailCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationDetailCoordinator.swift; sourceTree = ""; }; C2C2FEDC2CF363A30000AC4F /* WriteEpisodeCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteEpisodeCoordinator.swift; sourceTree = ""; }; C2C2FEDF2CF369830000AC4F /* EpisodeDetailCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeDetailCoordinator.swift; sourceTree = ""; }; + C2C2FEE82CF4167D0000AC4F /* AppInfoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoViewModel.swift; sourceTree = ""; }; C2C2FEEA2CF416DF0000AC4F /* AppInfoDIContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoDIContainer.swift; sourceTree = ""; }; + C2C2FEED2CF42E560000AC4F /* AppInfoType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoType.swift; sourceTree = ""; }; + C2C2FEEF2CF42FD10000AC4F /* AppInfoCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoCoordinator.swift; sourceTree = ""; }; C2F7418B2CF0270700C423D5 /* TabBarCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarCoordinator.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -931,7 +939,7 @@ 5A6827A52AD50D3A00C37B17 /* WriteEpisode */, 5AAB8E782ADBE152005969FA /* EpisodeDetail */, 5ADC003F2AC86D8A0024D7F8 /* Favorite */, - 5A0A98512AE79DF200545939 /* AppInfo */, + C2C2FEE42CF416590000AC4F /* AppInfo */, ); path = Presentation; sourceTree = ""; @@ -1250,6 +1258,7 @@ 5ADC003F2AC86D8A0024D7F8 /* Favorite */ = { isa = PBXGroup; children = ( + C2C2FED32CF1C3550000AC4F /* Coordinator */, 5A0A982A2AE2657000545939 /* View */, 5A35E57D2AEB4A8D0074C8EC /* ViewModel */, ); @@ -1293,6 +1302,14 @@ path = Coordinator; sourceTree = ""; }; + C2C2FED32CF1C3550000AC4F /* Coordinator */ = { + isa = PBXGroup; + children = ( + C2C2FED42CF1C37A0000AC4F /* FavoriteCoordinator.swift */, + ); + path = Coordinator; + sourceTree = ""; + }; C2C2FED62CF310820000AC4F /* Coordinator */ = { isa = PBXGroup; children = ( @@ -1317,6 +1334,52 @@ path = Coordinator; sourceTree = ""; }; + C2C2FEE42CF416590000AC4F /* AppInfo */ = { + isa = PBXGroup; + children = ( + C2C2FEEC2CF42E460000AC4F /* Enum */, + C2C2FEE72CF4166D0000AC4F /* Coordinator */, + C2C2FEE52CF416640000AC4F /* View */, + C2C2FEE62CF416690000AC4F /* ViewModel */, + ); + path = AppInfo; + sourceTree = ""; + }; + C2C2FEE52CF416640000AC4F /* View */ = { + isa = PBXGroup; + children = ( + 5A0A98522AE79F4000545939 /* AppInfoViewController.swift */, + 5A0A985F2AE9159F00545939 /* InquiryViewController.swift */, + 5A0A98612AE917A300545939 /* InquiryButtonView.swift */, + 5A0A98642AE9202400545939 /* Cell */, + ); + path = View; + sourceTree = ""; + }; + C2C2FEE62CF416690000AC4F /* ViewModel */ = { + isa = PBXGroup; + children = ( + C2C2FEE82CF4167D0000AC4F /* AppInfoViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + C2C2FEE72CF4166D0000AC4F /* Coordinator */ = { + isa = PBXGroup; + children = ( + C2C2FEEF2CF42FD10000AC4F /* AppInfoCoordinator.swift */, + ); + path = Coordinator; + sourceTree = ""; + }; + C2C2FEEC2CF42E460000AC4F /* Enum */ = { + isa = PBXGroup; + children = ( + C2C2FEED2CF42E560000AC4F /* AppInfoType.swift */, + ); + path = Enum; + sourceTree = ""; + }; C2F741882CF019D900C423D5 /* Splash */ = { isa = PBXGroup; children = ( @@ -1639,6 +1702,7 @@ 5A0407172AF922C500A4BA84 /* EpisodeDetailStorage.swift in Sources */, 5A6827BE2AD6D44E00C37B17 /* ImageSelectionView.swift in Sources */, 5A6827772ACEA7B200C37B17 /* RateButton.swift in Sources */, + C2C2FEEE2CF42E560000AC4F /* AppInfoType.swift in Sources */, 5A0406F62AF7922900A4BA84 /* BaseRealmStorage.swift in Sources */, C2C2FEDD2CF363A30000AC4F /* WriteEpisodeCoordinator.swift in Sources */, 5A68279F2AD2B64E00C37B17 /* StoreBadge.swift in Sources */, @@ -1712,6 +1776,7 @@ 5AAB8E952ADE824F005969FA /* FilterHeaderView.swift in Sources */, C2773C6C2CF0087300942765 /* Coordinator.swift in Sources */, 5ADC00342AC5F2720024D7F8 /* CategoryType.swift in Sources */, + C2C2FEF02CF42FD10000AC4F /* AppInfoCoordinator.swift in Sources */, 5A6827842ACFEB6D00C37B17 /* UIStackView+.swift in Sources */, 5A9DA1F62AC2F51C006040D2 /* LocationAPI.swift in Sources */, 5A6827B02AD56B1300C37B17 /* WriteEpisodeUseCase.swift in Sources */, @@ -1745,6 +1810,7 @@ 5ADC00362AC5F8EC0024D7F8 /* BaseCollectionViewCell.swift in Sources */, 5AAB8EAB2AE15CC7005969FA /* PasteboardService.swift in Sources */, 5ADC002B2AC5D72C0024D7F8 /* UIView+.swift in Sources */, + C2C2FEE92CF4167D0000AC4F /* AppInfoViewModel.swift in Sources */, 5AAB8EAD2AE1645F005969FA /* EpisodeEmptyView.swift in Sources */, 5A70BA632AC1788E00506846 /* LocationViewController.swift in Sources */, 5A6827C22AD6E59300C37B17 /* CheckBoxView.swift in Sources */, @@ -1770,6 +1836,7 @@ 5A0407082AF9028500A4BA84 /* LocationDetailLocalRepository.swift in Sources */, 5A68279D2AD2AC5C00C37B17 /* UIImage+.swift in Sources */, 5A6827702ACE7A6C00C37B17 /* EpisodeCollectionViewCell.swift in Sources */, + C2C2FED52CF1C37A0000AC4F /* FavoriteCoordinator.swift in Sources */, 5AAB8E7F2ADBEEAE005969FA /* EpisodeDetailUseCase.swift in Sources */, 5A0A985A2AE8A1BC00545939 /* AppInfoTableViewCell.swift in Sources */, 5A0A98482AE67F4200545939 /* RootHandler.swift in Sources */, diff --git a/Makgulli/Presentation/AppInfo/Coordinator/AppInfoCoordinator.swift b/Makgulli/Presentation/AppInfo/Coordinator/AppInfoCoordinator.swift new file mode 100644 index 0000000..13cf8a5 --- /dev/null +++ b/Makgulli/Presentation/AppInfo/Coordinator/AppInfoCoordinator.swift @@ -0,0 +1,50 @@ +// +// AppInfoCoordinator.swift +// Makgulli +// +// Created by kyuchul on 11/25/24. +// + +import UIKit +import SafariServices + +final class AppInfoCoordinator: Coordinator { + var parentCoordinator: (any Coordinator)? + var childCoordinators: [any Coordinator] = [] + var navigationController: UINavigationController + + init(navigationController: UINavigationController) { + self.navigationController = navigationController + } + + deinit { + debugPrint("deinit Coordinator: \(self)") + } + + func start() { + let viewModel = AppDIContainer.shared + .makeAppInfoDIContainer() + .makeAppInfoViewModel() + viewModel.coordinator = self + + let viewController = AppInfoViewController(viewModel: viewModel) + push(viewController: viewController, navibarHidden: true, swipe: false) + } +} + +extension AppInfoCoordinator { + func popAppInfo() { + parentCoordinator?.removeDependency(self) + pop() + } + + func startInquiry() { + let viewController = InquiryViewController() + push(viewController: viewController) + } + + func startSafariWebView(_ url: URL) { + let safariViewController = SFSafariViewController(url: url) + present(safariViewController, style: .automatic) + } +} diff --git a/Makgulli/Presentation/AppInfo/Enum/AppInfoType.swift b/Makgulli/Presentation/AppInfo/Enum/AppInfoType.swift new file mode 100644 index 0000000..47d1331 --- /dev/null +++ b/Makgulli/Presentation/AppInfo/Enum/AppInfoType.swift @@ -0,0 +1,37 @@ +// +// AppInfoType.swift +// Makgulli +// +// Created by kyuchul on 11/25/24. +// + +import Foundation + +enum AppInfoType: CaseIterable, CustomStringConvertible { + case appInfo + + var contents: [AppInfoSection] { + switch self { + case .appInfo: + return [.inquiry, .privacyPolicy, .openSourceInfo, .versionInfo] + } + } + + var description: String { + switch self { + case .appInfo: + return "앱 정보" + } + } +} + +enum AppInfoSection: String, CustomStringConvertible { + case inquiry = "문의하기" + case privacyPolicy = "개인정보 처리방침" + case openSourceInfo = "오픈소스 사용정보" + case versionInfo = "버전정보" + + var description: String { + return self.rawValue + } +} diff --git a/Makgulli/Presentation/AppInfo/AppInfoViewController.swift b/Makgulli/Presentation/AppInfo/View/AppInfoViewController.swift similarity index 57% rename from Makgulli/Presentation/AppInfo/AppInfoViewController.swift rename to Makgulli/Presentation/AppInfo/View/AppInfoViewController.swift index 42ff3d8..d559603 100644 --- a/Makgulli/Presentation/AppInfo/AppInfoViewController.swift +++ b/Makgulli/Presentation/AppInfo/View/AppInfoViewController.swift @@ -6,43 +6,25 @@ // import UIKit +import SafariServices import RxSwift import RxCocoa import RxDataSources -import SafariServices final class AppInfoViewController: BaseViewController { - - enum AppInfoType: CaseIterable, CustomStringConvertible { - case appInfo - - var contents: [AppInfoSection] { - switch self { - case .appInfo: - return [.inquiry, .privacyPolicy, .openSourceInfo, .versionInfo] - } - } - - var description: String { - switch self { - case .appInfo: - return "앱 정보" - } - } + private let viewModel: AppInfoViewModel + private let sections: [SectionModel] = AppInfoType.allCases.map { section in + SectionModel(model: section, items: section.contents) } + private typealias SectionOfAppInfo = SectionModel - enum AppInfoSection: String, CustomStringConvertible { - case inquiry = "문의하기" - case privacyPolicy = "개인정보 처리방침" - case openSourceInfo = "오픈소스 사용정보" - case versionInfo = "버전정보" - - var description: String { - return self.rawValue - } + init(viewModel: AppInfoViewModel) { + self.viewModel = viewModel + super.init() } + private let navigationBar = JumakNavigationBar() private lazy var appInfoTableView: UITableView = { let tableView = UITableView(frame: .zero, style: .grouped) tableView.rowHeight = 55 @@ -53,7 +35,6 @@ final class AppInfoViewController: BaseViewController { return tableView }() - private typealias SectionOfAppInfo = SectionModel private var dataSource = RxTableViewSectionedReloadDataSource( configureCell: { (dataSource, tableView, indexPath, item) in guard let cell = tableView.dequeueReusableCell(withIdentifier: "AppInfoTableViewCell", for: indexPath) as? AppInfoTableViewCell else { return UITableViewCell() } @@ -68,23 +49,26 @@ final class AppInfoViewController: BaseViewController { return cell }, titleForHeaderInSection: { dataSource, sectionIndex in return dataSource[sectionIndex].model.description - } - ) - - private let sections: [SectionModel] = AppInfoType.allCases.map { section in - SectionModel(model: section, items: section.contents) - } - + }) + override func viewDidLoad() { super.viewDidLoad() } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - self.navigationController?.navigationBar.isHidden = false - } - + override func bind() { + let input = AppInfoViewModel.Input( + didSelectBackButton: navigationBar.backButtonAction(), + didSelectTablbViewItem: appInfoTableView.rx.itemSelected.asObservable() + ) + let output = viewModel.transform(input: input) + + output.showToast + .asDriver(onErrorJustReturn: ()) + .drive(with: self) { owner, _ in + owner.showToast(message: "항상 최신 버전의 주막을 이용해보세요.") + } + .disposed(by: disposeBag) + Observable.just(sections) .bind(to: appInfoTableView.rx.items(dataSource: dataSource)) .disposed(by: disposeBag) @@ -92,42 +76,21 @@ final class AppInfoViewController: BaseViewController { appInfoTableView.rx.setDelegate(self) .disposed(by: disposeBag) } - - override func bindAction() { - appInfoTableView.rx.itemSelected - .withUnretained(self) - .bind(onNext: { owner, indexPath in - let section = AppInfoType.appInfo - - switch section.contents[indexPath.row] { - case .inquiry: - owner.navigationController?.pushViewController(InquiryViewController(), animated: true) - case .privacyPolicy: - guard - let url = URL(string: URLLiteral.policy.trimmingWhitespace()), UIApplication.shared.canOpenURL(url) - else { return } - let safariViewController = SFSafariViewController(url: url) - owner.present(safariViewController, animated: true) - case .openSourceInfo: - guard - let url = URL(string: URLLiteral.openSourceInfo.trimmingWhitespace()), UIApplication.shared.canOpenURL(url) - else { return } - let safariViewController = SFSafariViewController(url: url) - owner.present(safariViewController, animated: true) - case .versionInfo: - owner.showToast(message: "최신 버전의 주막입니다.") - } - }) - .disposed(by: disposeBag) - } - + override func setHierarchy() { - view.addSubview(appInfoTableView) + [navigationBar, appInfoTableView] + .forEach { view.addSubview($0) } } override func setConstraints() { + navigationBar.snp.makeConstraints { make in + make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide) + } + appInfoTableView.snp.makeConstraints { make in - make.edges.equalTo(view.safeAreaLayoutGuide.snp.edges) + make.top.equalTo(navigationBar.snp.bottom) + make.horizontalEdges.equalTo(navigationBar.snp.horizontalEdges) + make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom) } } diff --git a/Makgulli/Presentation/AppInfo/InquiryViewController.swift b/Makgulli/Presentation/AppInfo/View/InquiryViewController.swift similarity index 86% rename from Makgulli/Presentation/AppInfo/InquiryViewController.swift rename to Makgulli/Presentation/AppInfo/View/InquiryViewController.swift index a1650ad..4bb529d 100644 --- a/Makgulli/Presentation/AppInfo/InquiryViewController.swift +++ b/Makgulli/Presentation/AppInfo/View/InquiryViewController.swift @@ -14,6 +14,7 @@ import RxCocoa final class InquiryViewController: BaseViewController { + private let navigationBar = JumakNavigationBar() private let titleLabel: UILabel = { let label = UILabel() label.text = "궁금한 점이 있으시다면\n아래 연락처를 통해 문의해주세요." @@ -33,6 +34,12 @@ final class InquiryViewController: BaseViewController { }() override func bindAction() { + navigationBar.backButtonAction() + .bind(with: self) { owner, _ in + owner.navigationController?.popViewController(animated: true) + } + .disposed(by: disposeBag) + emailInquiryView.rx.tapInquiry .withUnretained(self) .bind(onNext: { owner, _ in @@ -63,14 +70,18 @@ final class InquiryViewController: BaseViewController { } override func setHierarchy() { - [titleLabel, stackView].forEach { + [navigationBar, titleLabel, stackView].forEach { view.addSubview($0) } } override func setConstraints() { + navigationBar.snp.makeConstraints { make in + make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide) + } + titleLabel.snp.makeConstraints { make in - make.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(20) + make.top.equalTo(navigationBar.snp.bottom).offset(20) make.leading.equalToSuperview().inset(18) make.trailing.equalToSuperview().inset(18).priority(.high) } diff --git a/Makgulli/Presentation/AppInfo/ViewModel/AppInfoViewModel.swift b/Makgulli/Presentation/AppInfo/ViewModel/AppInfoViewModel.swift new file mode 100644 index 0000000..6c58095 --- /dev/null +++ b/Makgulli/Presentation/AppInfo/ViewModel/AppInfoViewModel.swift @@ -0,0 +1,62 @@ +// +// AppInfoViewModel.swift +// Makgulli +// +// Created by kyuchul on 11/25/24. +// + +import Foundation + +import RxRelay +import RxSwift + +final class AppInfoViewModel: ViewModelType, Coordinatable { + weak var coordinator: AppInfoCoordinator? + var disposeBag: DisposeBag = .init() + + struct Input { + let didSelectBackButton: Observable + let didSelectTablbViewItem: Observable + } + + struct Output { + let showToast = PublishRelay() + } + + func transform(input: Input) -> Output { + let output = Output() + + input.didSelectBackButton + .bind(with: self) { owner, _ in + owner.coordinator?.popAppInfo() + } + .disposed(by: disposeBag) + + input.didSelectTablbViewItem + .map { ($0, AppInfoType.appInfo) } + .bind(with: self) { owner, items in + let indexPath = items.0 + let section = items.1 + + switch section.contents[indexPath.row] { + case .inquiry: + owner.coordinator?.startInquiry() + + case .privacyPolicy: + guard let url = URL(string: URLLiteral.policy.trimmingWhitespace()) else { return } + owner.coordinator?.startSafariWebView(url) + + case .openSourceInfo: + guard let url = URL(string: URLLiteral.openSourceInfo.trimmingWhitespace()) else { return } + owner.coordinator?.startSafariWebView(url) + + case .versionInfo: + output.showToast.accept(()) + } + } + .disposed(by: disposeBag) + + return output + } +} + diff --git a/Makgulli/Presentation/Favorite/Coordinator/FavoriteCoordinator.swift b/Makgulli/Presentation/Favorite/Coordinator/FavoriteCoordinator.swift new file mode 100644 index 0000000..c46811a --- /dev/null +++ b/Makgulli/Presentation/Favorite/Coordinator/FavoriteCoordinator.swift @@ -0,0 +1,48 @@ +// +// FavoriteCoordinator.swift +// Makgulli +// +// Created by kyuchul on 11/23/24. +// + +import UIKit + +final class FavoriteCoordinator: Coordinator { + weak var parentCoordinator: (any Coordinator)? + var childCoordinators: [any Coordinator] = [] + var navigationController: UINavigationController + + init(navigationController: UINavigationController) { + self.navigationController = navigationController + } + + func start() { + let viewModel = AppDIContainer.shared + .makeFavoriteDIContainer() + .makeFavoriteViewModel() + viewModel.coordinator = self + + let favoriteViewController = FavoriteViewController(viewModel: viewModel) + + push(viewController: favoriteViewController, navibarHidden: false, animated: false) + } +} + +extension FavoriteCoordinator { + func startLocationDetail(_ store: StoreVO) { + let locationDetailCoordinator = LocationDetailCoordinator(navigationController: navigationController) + locationDetailCoordinator.parentCoordinator = self + locationDetailCoordinator.store = store + locationDetailCoordinator.start() + + addDependency(locationDetailCoordinator) + } + + func startAppInfo() { + let appInfoCoordinator = AppInfoCoordinator(navigationController: navigationController) + appInfoCoordinator.parentCoordinator = self + appInfoCoordinator.start() + + addDependency(appInfoCoordinator) + } +} diff --git a/Makgulli/Presentation/Favorite/View/FavoriteViewController.swift b/Makgulli/Presentation/Favorite/View/FavoriteViewController.swift index fb422c3..bd6e11f 100644 --- a/Makgulli/Presentation/Favorite/View/FavoriteViewController.swift +++ b/Makgulli/Presentation/Favorite/View/FavoriteViewController.swift @@ -24,6 +24,7 @@ final class FavoriteViewController: BaseViewController { private let viewModel: FavoriteViewModel private let didSelectReverseFilterButton = PublishRelay() private let didSelectCategoryFilterButton = PublishSubject() + private let didSelectStoreItem = PublishRelay() fileprivate let categoryButton = { var configuration = UIButton.Configuration.plain() @@ -81,9 +82,11 @@ final class FavoriteViewController: BaseViewController { override func bind() { let input = FavoriteViewModel.Input(viewWillAppearEvent: self.rx.viewWillAppear.map { _ in }, - viewDidAppearEvent: self.rx.viewDidAppear.map { _ in }, + viewDidAppearEvent: self.rx.viewDidAppear.map { _ in }, + didSelectAppInfoButton: appInfoButton.rx.tap.asObservable().throttle(.seconds(1), scheduler: MainScheduler.instance), didSelectCategoryFilterButton: didSelectCategoryFilterButton.asObservable().throttle(.seconds(1), scheduler: MainScheduler.instance), - didSelectReverseFilterButton: didSelectReverseFilterButton.asObservable().throttle(.seconds(1), scheduler: MainScheduler.instance)) + didSelectReverseFilterButton: didSelectReverseFilterButton.asObservable().throttle(.seconds(1), scheduler: MainScheduler.instance), + didSelectStoreItem: didSelectStoreItem.asObservable().throttle(.seconds(1), scheduler: MainScheduler.instance)) let output = viewModel.transform(input: input) let storeList = output.storeList @@ -128,30 +131,13 @@ final class FavoriteViewController: BaseViewController { owner.presentActionSheet(actionType: CategoryFilterType.allCases, inputSubject: owner.didSelectCategoryFilterButton) }) .disposed(by: disposeBag) - - appInfoButton.rx.tap - .withUnretained(self) - .bind(onNext: { owner, _ in - let appInfoViewController = AppInfoViewController() - appInfoViewController.hidesBottomBarWhenPushed = true - owner.navigationController?.pushViewController(appInfoViewController, animated: true) - }) - .disposed(by: disposeBag) - + collectionView.rx.itemSelected - .withUnretained(self) - .bind(onNext: { owner, indexPath in + .bind(with: self) { owner, indexPath in guard let storeVO = owner.itemIdentifier(for: indexPath) else { return } - let locationDetilViewController = LocationDetailViewController( - viewModel: AppDIContainer.shared - .makeLocationDIContainer() - .makeLocationDetailViewModel(store: storeVO) - ) - - locationDetilViewController.hidesBottomBarWhenPushed = true - owner.navigationController?.pushViewController(locationDetilViewController, animated: true) - }) + owner.didSelectStoreItem.accept(storeVO) + } .disposed(by: disposeBag) } @@ -165,7 +151,6 @@ final class FavoriteViewController: BaseViewController { snapshot.reconfigureItems(viewModels) configureHeader(countTitle: countTitle, filterType: filterType, reverseFilter: reverseFilterType) - // dataSource?.apply(snapshot, animatingDifferences: false) dataSource?.applySnapshotUsingReloadData(snapshot) } diff --git a/Makgulli/Presentation/Favorite/ViewModel/FavoriteViewModel.swift b/Makgulli/Presentation/Favorite/ViewModel/FavoriteViewModel.swift index f2af017..971a670 100644 --- a/Makgulli/Presentation/Favorite/ViewModel/FavoriteViewModel.swift +++ b/Makgulli/Presentation/Favorite/ViewModel/FavoriteViewModel.swift @@ -10,7 +10,8 @@ import Foundation import RxRelay import RxSwift -final class FavoriteViewModel: ViewModelType { +final class FavoriteViewModel: ViewModelType, Coordinatable { + weak var coordinator: FavoriteCoordinator? var disposeBag: DisposeBag = .init() private let favoriteUseCase: FavoriteUseCase @@ -24,8 +25,10 @@ final class FavoriteViewModel: ViewModelType { struct Input { let viewWillAppearEvent: Observable let viewDidAppearEvent: Observable + let didSelectAppInfoButton: Observable let didSelectCategoryFilterButton: Observable let didSelectReverseFilterButton: Observable + let didSelectStoreItem: Observable } struct Output { @@ -63,6 +66,12 @@ final class FavoriteViewModel: ViewModelType { }) .disposed(by: disposeBag) + input.didSelectAppInfoButton + .bind(with: self) { owner, _ in + owner.coordinator?.startAppInfo() + } + .disposed(by: disposeBag) + let didSelectCategoryFilterButton = input.didSelectCategoryFilterButton .share() @@ -87,6 +96,12 @@ final class FavoriteViewModel: ViewModelType { owner.favoriteUseCase.fetchFilterStore(filterType: filterType, reverseFilter: UserDefaultHandler.reverseFilter, categoryFilter: categoryFilter) }) .disposed(by: disposeBag) + + input.didSelectStoreItem + .bind(with: self) { owner, store in + owner.coordinator?.startLocationDetail(store) + } + .disposed(by: disposeBag) createOutput(output: output) From d1564a41172dc90804987490391f45160f52a389 Mon Sep 17 00:00:00 2001 From: kimkyuchul Date: Mon, 25 Nov 2024 14:44:02 +0900 Subject: [PATCH 13/16] [ADD] UIApplication Extension (#30) --- Makgulli.xcodeproj/project.pbxproj | 4 ++++ Makgulli/Common/Extension/UIApplication+.swift | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 Makgulli/Common/Extension/UIApplication+.swift diff --git a/Makgulli.xcodeproj/project.pbxproj b/Makgulli.xcodeproj/project.pbxproj index edc19e8..ee61b74 100644 --- a/Makgulli.xcodeproj/project.pbxproj +++ b/Makgulli.xcodeproj/project.pbxproj @@ -187,6 +187,7 @@ C2C2FEEB2CF416DF0000AC4F /* AppInfoDIContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FEEA2CF416DF0000AC4F /* AppInfoDIContainer.swift */; }; C2C2FEEE2CF42E560000AC4F /* AppInfoType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FEED2CF42E560000AC4F /* AppInfoType.swift */; }; C2C2FEF02CF42FD10000AC4F /* AppInfoCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FEEF2CF42FD10000AC4F /* AppInfoCoordinator.swift */; }; + C2C2FEF22CF43FD30000AC4F /* UIApplication+.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FEF12CF43FD30000AC4F /* UIApplication+.swift */; }; C2F7418C2CF0270700C423D5 /* TabBarCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2F7418B2CF0270700C423D5 /* TabBarCoordinator.swift */; }; /* End PBXBuildFile section */ @@ -403,6 +404,7 @@ C2C2FEEA2CF416DF0000AC4F /* AppInfoDIContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoDIContainer.swift; sourceTree = ""; }; C2C2FEED2CF42E560000AC4F /* AppInfoType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoType.swift; sourceTree = ""; }; C2C2FEEF2CF42FD10000AC4F /* AppInfoCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoCoordinator.swift; sourceTree = ""; }; + C2C2FEF12CF43FD30000AC4F /* UIApplication+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+.swift"; sourceTree = ""; }; C2F7418B2CF0270700C423D5 /* TabBarCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarCoordinator.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -974,6 +976,7 @@ 5A6827B32AD6906700C37B17 /* Date+.swift */, 5A0A983F2AE64A2E00545939 /* UIDevice+.swift */, 5A0A98452AE66AB200545939 /* UIButton+.swift */, + C2C2FEF12CF43FD30000AC4F /* UIApplication+.swift */, ); path = Extension; sourceTree = ""; @@ -1746,6 +1749,7 @@ 5A0A98332AE4BBBE00545939 /* IndicatorView.swift in Sources */, 5A9DA1E72AC2B4F2006040D2 /* TargetType.swift in Sources */, 5A6827962AD15EAA00C37B17 /* StoreTable.swift in Sources */, + C2C2FEF22CF43FD30000AC4F /* UIApplication+.swift in Sources */, C2C2FEEB2CF416DF0000AC4F /* AppInfoDIContainer.swift in Sources */, 5A0A98622AE917A300545939 /* InquiryButtonView.swift in Sources */, 5AAB8E752ADA70BD005969FA /* DefaultLocationDetailRepository.swift in Sources */, diff --git a/Makgulli/Common/Extension/UIApplication+.swift b/Makgulli/Common/Extension/UIApplication+.swift new file mode 100644 index 0000000..226db6a --- /dev/null +++ b/Makgulli/Common/Extension/UIApplication+.swift @@ -0,0 +1,17 @@ +// +// UIApplication+.swift +// Makgulli +// +// Created by kyuchul on 11/25/24. +// + +import UIKit + +extension UIApplication { + var keyWindowInConnectedScenes: UIWindow? { + return connectedScenes + .compactMap { $0 as? UIWindowScene } + .flatMap { $0.windows } + .first { $0.isKeyWindow } + } +} From 2101b2fb9907bdb807a01a9b870f353b877d545c Mon Sep 17 00:00:00 2001 From: kimkyuchul Date: Mon, 25 Nov 2024 14:46:47 +0900 Subject: [PATCH 14/16] [ADD] JumakNavigationBar (#30) --- Makgulli.xcodeproj/project.pbxproj | 17 ++- Makgulli/Common/Literal/ImageLiteral.swift | 1 + .../NavigationBar/JumakNavigationBar.swift | 109 ++++++++++++++++++ Makgulli/Common/Util/RootHandler.swift | 39 ------- 4 files changed, 122 insertions(+), 44 deletions(-) create mode 100644 Makgulli/Common/UIComponent/NavigationBar/JumakNavigationBar.swift delete mode 100644 Makgulli/Common/Util/RootHandler.swift diff --git a/Makgulli.xcodeproj/project.pbxproj b/Makgulli.xcodeproj/project.pbxproj index ee61b74..162689a 100644 --- a/Makgulli.xcodeproj/project.pbxproj +++ b/Makgulli.xcodeproj/project.pbxproj @@ -37,7 +37,6 @@ 5A0A98422AE6617D00545939 /* ActionTitleable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A0A98412AE6617D00545939 /* ActionTitleable.swift */; }; 5A0A98442AE664F100545939 /* CategoryFilterType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A0A98432AE664F100545939 /* CategoryFilterType.swift */; }; 5A0A98462AE66AB200545939 /* UIButton+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A0A98452AE66AB200545939 /* UIButton+.swift */; }; - 5A0A98482AE67F4200545939 /* RootHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A0A98472AE67F4200545939 /* RootHandler.swift */; }; 5A0A984B2AE689C600545939 /* RxReachability in Frameworks */ = {isa = PBXBuildFile; productRef = 5A0A984A2AE689C600545939 /* RxReachability */; }; 5A0A984D2AE6BEF200545939 /* NetworkErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A0A984C2AE6BEF200545939 /* NetworkErrorView.swift */; }; 5A0A984F2AE6C6FA00545939 /* FavoriteEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A0A984E2AE6C6FA00545939 /* FavoriteEmptyView.swift */; }; @@ -161,7 +160,6 @@ 5ADC00362AC5F8EC0024D7F8 /* BaseCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ADC00352AC5F8EC0024D7F8 /* BaseCollectionViewCell.swift */; }; 5ADC00392AC5F9370024D7F8 /* CategoryCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ADC00382AC5F9370024D7F8 /* CategoryCollectionViewCell.swift */; }; 5ADC003B2AC8450C0024D7F8 /* CLLocationCoordinate2D+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ADC003A2AC8450C0024D7F8 /* CLLocationCoordinate2D+.swift */; }; - 5ADC003E2AC8693A0024D7F8 /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ADC003D2AC8693A0024D7F8 /* TabBarController.swift */; }; 5ADC00412AC86DA00024D7F8 /* FavoriteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ADC00402AC86DA00024D7F8 /* FavoriteViewController.swift */; }; 5ADC00432AC879060024D7F8 /* StoreCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ADC00422AC879060024D7F8 /* StoreCollectionViewCell.swift */; }; 5ADC004D2ACA9DAE0024D7F8 /* LocationDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ADC004C2ACA9DAE0024D7F8 /* LocationDetailViewController.swift */; }; @@ -183,6 +181,7 @@ C2C2FED82CF310930000AC4F /* LocationDetailCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FED72CF310930000AC4F /* LocationDetailCoordinator.swift */; }; C2C2FEDD2CF363A30000AC4F /* WriteEpisodeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FEDC2CF363A30000AC4F /* WriteEpisodeCoordinator.swift */; }; C2C2FEE02CF369830000AC4F /* EpisodeDetailCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FEDF2CF369830000AC4F /* EpisodeDetailCoordinator.swift */; }; + C2C2FEE22CF3F8970000AC4F /* JumakNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FEE12CF3F8970000AC4F /* JumakNavigationBar.swift */; }; C2C2FEE92CF4167D0000AC4F /* AppInfoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FEE82CF4167D0000AC4F /* AppInfoViewModel.swift */; }; C2C2FEEB2CF416DF0000AC4F /* AppInfoDIContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FEEA2CF416DF0000AC4F /* AppInfoDIContainer.swift */; }; C2C2FEEE2CF42E560000AC4F /* AppInfoType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C2FEED2CF42E560000AC4F /* AppInfoType.swift */; }; @@ -255,7 +254,6 @@ 5A0A98412AE6617D00545939 /* ActionTitleable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionTitleable.swift; sourceTree = ""; }; 5A0A98432AE664F100545939 /* CategoryFilterType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryFilterType.swift; sourceTree = ""; }; 5A0A98452AE66AB200545939 /* UIButton+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+.swift"; sourceTree = ""; }; - 5A0A98472AE67F4200545939 /* RootHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootHandler.swift; sourceTree = ""; }; 5A0A984C2AE6BEF200545939 /* NetworkErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkErrorView.swift; sourceTree = ""; }; 5A0A984E2AE6C6FA00545939 /* FavoriteEmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteEmptyView.swift; sourceTree = ""; }; 5A0A98522AE79F4000545939 /* AppInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoViewController.swift; sourceTree = ""; }; @@ -400,6 +398,7 @@ C2C2FED72CF310930000AC4F /* LocationDetailCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationDetailCoordinator.swift; sourceTree = ""; }; C2C2FEDC2CF363A30000AC4F /* WriteEpisodeCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteEpisodeCoordinator.swift; sourceTree = ""; }; C2C2FEDF2CF369830000AC4F /* EpisodeDetailCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeDetailCoordinator.swift; sourceTree = ""; }; + C2C2FEE12CF3F8970000AC4F /* JumakNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JumakNavigationBar.swift; sourceTree = ""; }; C2C2FEE82CF4167D0000AC4F /* AppInfoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoViewModel.swift; sourceTree = ""; }; C2C2FEEA2CF416DF0000AC4F /* AppInfoDIContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoDIContainer.swift; sourceTree = ""; }; C2C2FEED2CF42E560000AC4F /* AppInfoType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoType.swift; sourceTree = ""; }; @@ -1139,6 +1138,7 @@ 5A9DA2212AC57E20006040D2 /* UIComponent */ = { isa = PBXGroup; children = ( + C2C2FEE32CF3F89D0000AC4F /* NavigationBar */, 5AAB8E802ADC034B005969FA /* Alert */, 5A6827B72AD6AC3B00C37B17 /* TextField */, 5A6827A42AD44DDE00C37B17 /* Label */, @@ -1201,7 +1201,6 @@ children = ( 5AAB8EA62AE145F0005969FA /* UserDefault.swift */, 5AAB8EA82AE14607005969FA /* UserDefaultHandler.swift */, - 5A0A98472AE67F4200545939 /* RootHandler.swift */, ); path = Util; sourceTree = ""; @@ -1337,6 +1336,14 @@ path = Coordinator; sourceTree = ""; }; + C2C2FEE32CF3F89D0000AC4F /* NavigationBar */ = { + isa = PBXGroup; + children = ( + C2C2FEE12CF3F8970000AC4F /* JumakNavigationBar.swift */, + ); + path = NavigationBar; + sourceTree = ""; + }; C2C2FEE42CF416590000AC4F /* AppInfo */ = { isa = PBXGroup; children = ( @@ -1695,6 +1702,7 @@ 5A0A98462AE66AB200545939 /* UIButton+.swift in Sources */, 5A9DA1DE2AC1A393006040D2 /* MetricLiteral.swift in Sources */, 5AAB8E982ADFC203005969FA /* FilterBottomSheetViewController.swift in Sources */, + C2C2FEE22CF3F8970000AC4F /* JumakNavigationBar.swift in Sources */, 5A6827792ACEC68000C37B17 /* UILabel+.swift in Sources */, 5A35E57F2AEBDFCD0074C8EC /* NetworkError.swift in Sources */, 5A9DA20C2AC3D6E5006040D2 /* LocationViewModel.swift in Sources */, @@ -1843,7 +1851,6 @@ C2C2FED52CF1C37A0000AC4F /* FavoriteCoordinator.swift in Sources */, 5AAB8E7F2ADBEEAE005969FA /* EpisodeDetailUseCase.swift in Sources */, 5A0A985A2AE8A1BC00545939 /* AppInfoTableViewCell.swift in Sources */, - 5A0A98482AE67F4200545939 /* RootHandler.swift in Sources */, 5A9DA2202AC41A19006040D2 /* BaseView.swift in Sources */, 5A0407142AF91E8500A4BA84 /* EpisodeDIContainer.swift in Sources */, ); diff --git a/Makgulli/Common/Literal/ImageLiteral.swift b/Makgulli/Common/Literal/ImageLiteral.swift index 6ae6cea..649e1a8 100644 --- a/Makgulli/Common/Literal/ImageLiteral.swift +++ b/Makgulli/Common/Literal/ImageLiteral.swift @@ -22,6 +22,7 @@ enum ImageLiteral { //MARK: - Favorite //MARK: - System image + static var back: UIImage { .load(systemName: "chevron.backward") } static var checkIcon: UIImage { .load(systemName: "checkmark")} static var bookMarkIcon: UIImage { .load(systemName: "bookmark") } static var fillBookMarkIcon: UIImage { .load(systemName: "bookmark.fill") } diff --git a/Makgulli/Common/UIComponent/NavigationBar/JumakNavigationBar.swift b/Makgulli/Common/UIComponent/NavigationBar/JumakNavigationBar.swift new file mode 100644 index 0000000..5c3313b --- /dev/null +++ b/Makgulli/Common/UIComponent/NavigationBar/JumakNavigationBar.swift @@ -0,0 +1,109 @@ +// +// JumakNavigationBar.swift +// Makgulli +// +// Created by kyuchul on 11/25/24. +// + +import UIKit + +import RxSwift +import RxCocoa + +final class JumakNavigationBar: BaseView { + + private let containerView = UIView() + private let backButton: UIButton = { + let button = UIButton() + button.setImage(ImageLiteral.back, for: .normal) + button.tintColor = .black + return button + }() + + private let titleLabel: UILabel = { + let label = UILabel() + label.textColor = .black + label.font = UIFont.boldLineSeed(size: ._16) + label.textAlignment = .center + return label + }() + + private var rightItems: [UIView] = [] + private let rightStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.spacing = 8 + stackView.alignment = .center + return stackView + }() + + init(rightItems: [UIView] = []) { + self.rightItems = rightItems + super.init(frame: .zero) + configureRightItems() + } + + override func setHierarchy() { + addSubview(containerView) + + [backButton, titleLabel, rightStackView] + .forEach { + containerView.addSubview($0) + } + } + + override func setConstraints() { + self.snp.makeConstraints { make in + make.height.equalTo(52) + } + + containerView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + backButton.snp.makeConstraints { make in + make.leading.equalToSuperview().offset(12) + make.centerY.equalToSuperview() + make.size.equalTo(26) + } + + titleLabel.snp.makeConstraints { make in + make.center.equalToSuperview() + } + + rightStackView.snp.makeConstraints { make in + make.trailing.equalToSuperview().inset(12) + make.centerY.equalToSuperview() + } + } + + override func setLayout() { + self.backgroundColor = .white + } +} + +private extension JumakNavigationBar { + func configureRightItems() { + rightItems.forEach { + $0.snp.makeConstraints { make in + make.size.equalTo(26) + } + } + + rightItems.forEach { rightStackView.addArrangedSubview($0) } + } +} + +extension JumakNavigationBar { + func setTitle(_ title: String) { + titleLabel.text = title + } + + func hideBackButton() { + backButton.isHidden = true + } + + func backButtonAction() -> Observable { + return backButton.rx.tap.asObservable() + } +} diff --git a/Makgulli/Common/Util/RootHandler.swift b/Makgulli/Common/Util/RootHandler.swift deleted file mode 100644 index 81a3ae4..0000000 --- a/Makgulli/Common/Util/RootHandler.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// RootHandler.swift -// Makgulli -// -// Created by 김규철 on 2023/10/23. -// - -import UIKit - -final class RootHandler { - static let shard = RootHandler() - - enum Destination { - case main - } - - private init() {} - - func update(_ destination: Destination) { - guard let delegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate else { - return - } - switch destination { - case .main: - let mainViewController = TabBarController() - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - UIView.transition(with: delegate.window!, - duration: 0.5, - options: .transitionCrossDissolve, - animations: { - delegate.window?.rootViewController = mainViewController - delegate.window?.makeKeyAndVisible() - }, - completion: nil) - } - } - } -} From d0656834ccb1b095c4c80e4276be9ca760ff2db2 Mon Sep 17 00:00:00 2001 From: kimkyuchul Date: Mon, 25 Nov 2024 14:55:09 +0900 Subject: [PATCH 15/16] =?UTF-8?q?[CHORE]=20=EB=9D=BC=EC=9D=B4=EB=B8=8C?= =?UTF-8?q?=EB=9F=AC=EB=A6=AC=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makgulli.xcodeproj/project.pbxproj | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Makgulli.xcodeproj/project.pbxproj b/Makgulli.xcodeproj/project.pbxproj index 162689a..76baddd 100644 --- a/Makgulli.xcodeproj/project.pbxproj +++ b/Makgulli.xcodeproj/project.pbxproj @@ -171,6 +171,13 @@ 5AE652982B9268FB0033CED5 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5A36F1B62B027A9900039ADA /* GoogleService-Info.plist */; }; 97E4E67B233AA4926A368385 /* libPods-MakgulliTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C6D596DBBF462B7A2EB0ABAA /* libPods-MakgulliTests.a */; }; D5B862DDBE4105DA235A3EDC /* libPods-Makgulli.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B35E14DFFC9313A7EEBC2771 /* libPods-Makgulli.a */; }; + C2773C5A2CEF25E500942765 /* FirebaseCore in Frameworks */ = {isa = PBXBuildFile; productRef = C2773C592CEF25E500942765 /* FirebaseCore */; }; + C2773C5C2CEF25E500942765 /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = C2773C5B2CEF25E500942765 /* FirebaseCrashlytics */; }; + C2773C5E2CEF25E500942765 /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = C2773C5D2CEF25E500942765 /* FirebaseMessaging */; }; + C2773C612CEF26C400942765 /* RxBlocking in Frameworks */ = {isa = PBXBuildFile; productRef = C2773C602CEF26C400942765 /* RxBlocking */; }; + C2773C632CEF26C400942765 /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = C2773C622CEF26C400942765 /* RxCocoa */; }; + C2773C652CEF26C400942765 /* RxRelay in Frameworks */ = {isa = PBXBuildFile; productRef = C2773C642CEF26C400942765 /* RxRelay */; }; + C2773C672CEF26C400942765 /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = C2773C662CEF26C400942765 /* RxSwift */; }; C2773C6A2CF007BC00942765 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2773C692CF007BC00942765 /* AppCoordinator.swift */; }; C2773C6C2CF0087300942765 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2773C6B2CF0087300942765 /* Coordinator.swift */; }; C2C2FECC2CF02DD50000AC4F /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = C2C2FECB2CF02DD50000AC4F /* RealmSwift */; }; From 5263d2bfc4f4a0d77351676c24de61a5617bff57 Mon Sep 17 00:00:00 2001 From: kimkyuchul Date: Mon, 25 Nov 2024 15:19:06 +0900 Subject: [PATCH 16/16] =?UTF-8?q?[FIX]=20=EC=95=B1=20=EC=B4=88=EA=B8=B0=20?= =?UTF-8?q?=EC=A7=84=EC=9E=85=20=EC=8B=9C=20=EC=95=A0=EB=8B=88=EB=A9=94?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=A0=9C=EA=B1=B0=20(#30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Application/Coordinator/AppCoordinator.swift | 13 +++---------- .../Common/Splash/SplashViewController.swift | 2 +- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/Makgulli/Application/Coordinator/AppCoordinator.swift b/Makgulli/Application/Coordinator/AppCoordinator.swift index ba037e8..9164423 100644 --- a/Makgulli/Application/Coordinator/AppCoordinator.swift +++ b/Makgulli/Application/Coordinator/AppCoordinator.swift @@ -52,17 +52,10 @@ extension AppCoordinator { } private func startTabBar() { - guard let window = UIApplication.shared.keyWindowInConnectedScenes else { return } - let tabBarCoordinator = TabBarCoordinator(navigationController: navigationController) tabBarCoordinator.parentCoordinator = self - - UIView.transition(with: window, - duration: 0.5, - options: .transitionCrossDissolve, - animations: { - tabBarCoordinator.start() - self.addDependency(tabBarCoordinator) - }, completion: nil) + tabBarCoordinator.start() + + self.addDependency(tabBarCoordinator) } } diff --git a/Makgulli/Presentation/Common/Splash/SplashViewController.swift b/Makgulli/Presentation/Common/Splash/SplashViewController.swift index 8e20304..85a5eea 100644 --- a/Makgulli/Presentation/Common/Splash/SplashViewController.swift +++ b/Makgulli/Presentation/Common/Splash/SplashViewController.swift @@ -42,7 +42,7 @@ final class SplashViewController: BaseViewController, Coordinatable { super.viewDidLoad() reachability?.rx.isConnected .bind(with: self) { owner, _ in - DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) { + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { owner.coordinator?.flow.send(.main) } }