diff --git a/project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/DeploymentSettings.swift b/project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/DeploymentSettings.swift index 6f076fb7..ad6ddcd5 100644 --- a/project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/DeploymentSettings.swift +++ b/project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/DeploymentSettings.swift @@ -10,7 +10,8 @@ import ProjectDescription public enum DeploymentSettings { /// SceneDelegate를 지원하는 iOS 13이상 버전을 요구합니다. - public static let deployment_version = DeploymentTargets.iOS("13.0") - public static let platform = Destinations.iOS + public static let productName = "Caremeet" + public static let deployment_version = DeploymentTargets.iOS("15.0") + public static let platforms: Set = [Destination.iPad, Destination.iPhone] public static let workspace_name = "idle_workspace" } diff --git a/project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/InfoPlist.swift b/project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/InfoPlist.swift index 65b84508..cbf692c6 100644 --- a/project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/InfoPlist.swift +++ b/project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/InfoPlist.swift @@ -10,11 +10,13 @@ import ProjectDescription public enum IdleInfoPlist { public static let appDefault: InfoPlist = .extendingDefault(with: [ + + "CFBundleDisplayName": "케어밋", + "NSAppTransportSecurity" : [ "NSAllowsArbitraryLoads" : true ], "UILaunchStoryboardName": "LaunchScreen.storyboard", - "CFBundleDisplayName" : "$(BUNDLE_DISPLAY_NAME)", "UIApplicationSceneManifest": [ "UIApplicationSupportsMultipleScenes": false, "UISceneConfigurations": [ @@ -27,7 +29,16 @@ public enum IdleInfoPlist { ] ], - "NMFClientId": "$(NAVER_API_CLIENT_ID)" + // 멀티 스크린 미지원 + "UIRequiresFullScreen": true, + + "NMFClientId": "$(NAVER_API_CLIENT_ID)", + + // 앱추적 허용 메세지 + "NSUserTrackingUsageDescription": "사용자 맞춤 서비스 제공을 위해 권한을 허용해 주세요. 권한을 허용하지 않을 경우, 앱 사용에 제약이 있을 수 있습니다.", + + // 네트워크 사용 메세지 + "NSLocalNetworkUsageDescription": "이 앱은 로컬 네트워크를 통해 서버에 연결하여 데이터를 주고받기 위해 로컬 네트워크 접근 권한이 필요합니다." ]) public static let exampleAppDefault: InfoPlist = .extendingDefault(with: [ diff --git a/project/Projects/App/Project.swift b/project/Projects/App/Project.swift index 960dec0d..2f9f9405 100644 --- a/project/Projects/App/Project.swift +++ b/project/Projects/App/Project.swift @@ -19,8 +19,9 @@ let project = Project( /// Application .target( name: "Idle-iOS", - destinations: DeploymentSettings.platform, + destinations: DeploymentSettings.platforms, product: .app, + productName: DeploymentSettings.productName, bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", deploymentTargets: DeploymentSettings.deployment_version, infoPlist: IdleInfoPlist.appDefault, @@ -48,9 +49,9 @@ let project = Project( /// UnitTests .target( name: "IdleAppTests", - destinations: DeploymentSettings.platform, + destinations: DeploymentSettings.platforms, product: .unitTests, - bundleId: "com.idle-application.test", + bundleId: "com.idleApplication.test", deploymentTargets: DeploymentSettings.deployment_version, sources: ["Tests/**"], dependencies: [.target(name: "Idle-iOS")] diff --git a/project/Projects/App/Sources/AppDelegate.swift b/project/Projects/App/Sources/AppDelegate.swift index 00267bb5..2a996576 100644 --- a/project/Projects/App/Sources/AppDelegate.swift +++ b/project/Projects/App/Sources/AppDelegate.swift @@ -6,6 +6,9 @@ // import UIKit +import AppTrackingTransparency +import AdSupport +import PresentationCore @main class AppDelegate: UIResponder, UIApplicationDelegate { @@ -14,6 +17,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: { [weak self] in + self?.requestTrackingAuthorization() + }) + return true } @@ -31,6 +38,28 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } - + private func requestTrackingAuthorization() { + ATTrackingManager.requestTrackingAuthorization(completionHandler: { status in + switch status { + case .authorized: + // Tracking authorization dialog was shown + // and we are authorized + printIfDebug("앱추적권한: Authorized") + + // 추적을 허용한 사용자 식별자 + printIfDebug(ASIdentifierManager.shared().advertisingIdentifier) + case .denied: + // Tracking authorization dialog was + // shown and permission is denied + printIfDebug("앱추적권한: Denied") + case .notDetermined: + // Tracking authorization dialog has not been shown + printIfDebug("앱추적권한: Not Determined") + case .restricted: + printIfDebug("앱추적권한: Restricted") + @unknown default: + printIfDebug("앱추적권한: Unknown") + } + }) + } } - diff --git a/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift b/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift index ac34de68..434021fe 100644 --- a/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift +++ b/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift @@ -367,8 +367,6 @@ extension ApplyType { switch self { case .phoneCall: "CALLING" - case .message: - "MESSAGE" case .app: "APP" } diff --git a/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecruitmentPostDTO.swift b/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecruitmentPostDTO.swift index fdd77b1e..01d2d58f 100644 --- a/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecruitmentPostDTO.swift +++ b/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecruitmentPostDTO.swift @@ -189,8 +189,6 @@ extension ApplyType { switch text { case "CALLING": return .phoneCall - case "MESSAGE": - return .message case "APP": return .app default: diff --git a/project/Projects/Data/Project.swift b/project/Projects/Data/Project.swift index ce1e31f0..3745174c 100644 --- a/project/Projects/Data/Project.swift +++ b/project/Projects/Data/Project.swift @@ -20,7 +20,7 @@ let project = Project( /// RepositoryConcrete .target( name: "ConcreteRepository", - destinations: DeploymentSettings.platform, + destinations: DeploymentSettings.platforms, product: .staticLibrary, bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", deploymentTargets: DeploymentSettings.deployment_version, @@ -40,7 +40,7 @@ let project = Project( /// ConcreteTests .target( name: "ConcretesTests", - destinations: DeploymentSettings.platform, + destinations: DeploymentSettings.platforms, product: .unitTests, bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", deploymentTargets: DeploymentSettings.deployment_version, @@ -57,7 +57,7 @@ let project = Project( /// DataSource .target( name: "DataSource", - destinations: DeploymentSettings.platform, + destinations: DeploymentSettings.platforms, product: .staticLibrary, bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", deploymentTargets: DeploymentSettings.deployment_version, diff --git a/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift b/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift index 356d7b53..f02a905f 100644 --- a/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift +++ b/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift @@ -163,8 +163,9 @@ public class DefaultRecruitmentPostUseCase: RecruitmentPostUseCase { requestCnt: postCount ) case .thirdParty: + // TODO: ‼️ ‼️워크넷 가져오기 미구현 - fatalError() + return .just(.failure(.notImplemented)) } } diff --git a/project/Projects/Domain/Entity/Error/DomainError.swift b/project/Projects/Domain/Entity/Error/DomainError.swift index c465cd9b..210c3aea 100644 --- a/project/Projects/Domain/Entity/Error/DomainError.swift +++ b/project/Projects/Domain/Entity/Error/DomainError.swift @@ -60,6 +60,9 @@ public enum DomainError: Error { case undefinedCode case undefinedError + // Not Implemented + case notImplemented + public init(code: String) { switch code { case "API-001": @@ -198,6 +201,9 @@ public enum DomainError: Error { case .undefinedCode, .undefinedError: return "예기치 않은 오류가 발생했습니다. 잠시 후 다시 시도해주세요." + + case .notImplemented: + return "아직 개발되지 않은 기능입니다." } } } diff --git a/project/Projects/Domain/Entity/State/RecruitmentPost/AdditionalApplicationInfoStateObject.swift b/project/Projects/Domain/Entity/State/RecruitmentPost/AdditionalApplicationInfoStateObject.swift index 33083cb1..13f2a2f5 100644 --- a/project/Projects/Domain/Entity/State/RecruitmentPost/AdditionalApplicationInfoStateObject.swift +++ b/project/Projects/Domain/Entity/State/RecruitmentPost/AdditionalApplicationInfoStateObject.swift @@ -27,7 +27,6 @@ public class ApplicationDetailStateObject: NSCopying { mockObject.experiencePreferenceType = .beginnerPossible mockObject.applyType = [ .app : true, - .message : false, .phoneCall : false ] mockObject.applyDeadlineType = .untilApplicationFinished diff --git a/project/Projects/Domain/Entity/State/RecruitmentPost/RecruitmentDeadline.swift b/project/Projects/Domain/Entity/State/RecruitmentPost/RecruitmentDeadline.swift index cc472224..b6ddf370 100644 --- a/project/Projects/Domain/Entity/State/RecruitmentPost/RecruitmentDeadline.swift +++ b/project/Projects/Domain/Entity/State/RecruitmentPost/RecruitmentDeadline.swift @@ -23,15 +23,12 @@ public enum ExperiencePreferenceType: Int, CaseIterable { public enum ApplyType: Int, CaseIterable { case phoneCall - case message case app public var korTextForBtn: String { switch self { case .phoneCall: "전화 지원" - case .message: - "문자 지원" case .app: "어플 지원" } @@ -41,8 +38,6 @@ public enum ApplyType: Int, CaseIterable { switch self { case .phoneCall: "전화" - case .message: - "문자" case .app: "어플" } diff --git a/project/Projects/Domain/Project.swift b/project/Projects/Domain/Project.swift index 0441aa3b..917afb40 100644 --- a/project/Projects/Domain/Project.swift +++ b/project/Projects/Domain/Project.swift @@ -20,7 +20,7 @@ let project = Project( /// UseCaseConcrete type .target( name: "ConcreteUseCase", - destinations: DeploymentSettings.platform, + destinations: DeploymentSettings.platforms, product: .staticLibrary, bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", deploymentTargets: DeploymentSettings.deployment_version, @@ -40,7 +40,7 @@ let project = Project( /// Concrete type Test .target( name: "ConcreteUseCaseTests", - destinations: DeploymentSettings.platform, + destinations: DeploymentSettings.platforms, product: .unitTests, bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", deploymentTargets: DeploymentSettings.deployment_version, @@ -61,7 +61,7 @@ let project = Project( /// Domain interface .target( name: "UseCaseInterface", - destinations: DeploymentSettings.platform, + destinations: DeploymentSettings.platforms, product: .framework, bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", deploymentTargets: DeploymentSettings.deployment_version, @@ -80,7 +80,7 @@ let project = Project( /// Repository interface .target( name: "RepositoryInterface", - destinations: DeploymentSettings.platform, + destinations: DeploymentSettings.platforms, product: .framework, bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", deploymentTargets: DeploymentSettings.deployment_version, @@ -100,7 +100,7 @@ let project = Project( /// Entity .target( name: "Entity", - destinations: DeploymentSettings.platform, + destinations: DeploymentSettings.platforms, product: .framework, bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", deploymentTargets: DeploymentSettings.deployment_version, diff --git a/project/Projects/Presentation/DSKit/Project.swift b/project/Projects/Presentation/DSKit/Project.swift index 008a48ec..edfbc565 100644 --- a/project/Projects/Presentation/DSKit/Project.swift +++ b/project/Projects/Presentation/DSKit/Project.swift @@ -18,7 +18,7 @@ let proejct = Project( targets: [ .target( name: "DSKit", - destinations: DeploymentSettings.platform, + destinations: DeploymentSettings.platforms, product: .framework, bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", deploymentTargets: DeploymentSettings.deployment_version, @@ -42,7 +42,7 @@ let proejct = Project( // Component를 테스트하는 Example타겟 .target( name: "DSKitExampleApp", - destinations: DeploymentSettings.platform, + destinations: DeploymentSettings.platforms, product: .app, bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", deploymentTargets: DeploymentSettings.deployment_version, diff --git a/project/Projects/Presentation/Feature/Auth/ExampleApp/Resources/LaunchScreen.storyboard b/project/Projects/Presentation/Feature/Auth/ExampleApp/Resources/LaunchScreen.storyboard index a2157a3e..2cba5fa8 100644 --- a/project/Projects/Presentation/Feature/Auth/ExampleApp/Resources/LaunchScreen.storyboard +++ b/project/Projects/Presentation/Feature/Auth/ExampleApp/Resources/LaunchScreen.storyboard @@ -1,9 +1,9 @@ - + - + diff --git a/project/Projects/Presentation/Feature/Auth/ExampleApp/Sources/SceneDelegate.swift b/project/Projects/Presentation/Feature/Auth/ExampleApp/Sources/SceneDelegate.swift index 0b9fca5d..b67dab5d 100644 --- a/project/Projects/Presentation/Feature/Auth/ExampleApp/Sources/SceneDelegate.swift +++ b/project/Projects/Presentation/Feature/Auth/ExampleApp/Sources/SceneDelegate.swift @@ -7,8 +7,6 @@ import UIKit import AuthFeature -import ConcreteUseCase -import ConcreteRepository class SceneDelegate: UIResponder, UIWindowSceneDelegate { diff --git a/project/Projects/Presentation/Feature/Auth/Project.swift b/project/Projects/Presentation/Feature/Auth/Project.swift index 39630662..82be4acb 100644 --- a/project/Projects/Presentation/Feature/Auth/Project.swift +++ b/project/Projects/Presentation/Feature/Auth/Project.swift @@ -20,7 +20,7 @@ let project = Project( /// FeatureConcrete .target( name: "AuthFeature", - destinations: DeploymentSettings.platform, + destinations: DeploymentSettings.platforms, product: .staticFramework, bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", deploymentTargets: DeploymentSettings.deployment_version, @@ -37,7 +37,7 @@ let project = Project( /// FeatureConcrete ExampleApp .target( name: "Auth_ExampleApp", - destinations: DeploymentSettings.platform, + destinations: DeploymentSettings.platforms, product: .app, bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", deploymentTargets: DeploymentSettings.deployment_version, diff --git a/project/Projects/Presentation/Feature/Base/Project.swift b/project/Projects/Presentation/Feature/Base/Project.swift index fe112ca1..c1d364b0 100644 --- a/project/Projects/Presentation/Feature/Base/Project.swift +++ b/project/Projects/Presentation/Feature/Base/Project.swift @@ -20,7 +20,7 @@ let project = Project( /// FeatureConcrete .target( name: "BaseFeature", - destinations: DeploymentSettings.platform, + destinations: DeploymentSettings.platforms, product: .staticFramework, bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", deploymentTargets: DeploymentSettings.deployment_version, @@ -34,10 +34,6 @@ let project = Project( // Domain D.Domain.UseCaseInterface, D.Domain.RepositoryInterface, - - // For Test - D.Domain.ConcreteUseCase, - D.Data.ConcreteRepository, // ThirdParty D.ThirdParty.RxSwift, @@ -52,7 +48,7 @@ let project = Project( /// FeatureConcrete ExampleApp .target( name: "Base_ExampleApp", - destinations: DeploymentSettings.platform, + destinations: DeploymentSettings.platforms, product: .app, bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", deploymentTargets: DeploymentSettings.deployment_version, diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/Edit/ApplicationDetailViewContentView.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/Edit/ApplicationDetailViewContentView.swift index edf9b38f..ebca7d17 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/Edit/ApplicationDetailViewContentView.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/Edit/ApplicationDetailViewContentView.swift @@ -245,6 +245,7 @@ public class ApplicationDetailViewContentView: UIView { .deadlineString? .drive(onNext: { [calendarOpenButton] str in calendarOpenButton.textLabel.textString = str + calendarOpenButton.textLabel.attrTextColor = DSColor.gray900.color }) .disposed(by: disposeBag) diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/NativePostDetailForWorkerVM.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/NativePostDetailForWorkerVM.swift index f31632cd..6d093724 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/NativePostDetailForWorkerVM.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/NativePostDetailForWorkerVM.swift @@ -197,7 +197,7 @@ public class NativePostDetailForWorkerVM: BaseViewModel ,NativePostDetailForWork getPostDetailFailureAlert, applyRequestFailureAlert ) - .subscribe(alert) + .subscribe(self.alert) .disposed(by: disposeBag) diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Base/BaseViewController.swift b/project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Base/BaseViewController.swift index d9bb2399..4d6d3455 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Base/BaseViewController.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Base/BaseViewController.swift @@ -60,6 +60,9 @@ open class BaseViewController: UIViewController { public extension BaseViewController { func showAlert(vo: DefaultAlertContentVO, onClose: (() -> ())? = nil) { + + guard let _ = self.parent else { return } + let alert = UIAlertController( title: vo.title, message: vo.message, @@ -87,6 +90,8 @@ public extension BaseViewController { func showDefaultLoadingScreen() { + guard let _ = self.parent else { return } + let vc = DefaultLoadingVC() loadingVC = vc vc.modalPresentationStyle = .overFullScreen diff --git a/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/SceneDelegate.swift b/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/SceneDelegate.swift index f4bb042a..22c76b62 100644 --- a/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/SceneDelegate.swift +++ b/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/SceneDelegate.swift @@ -7,8 +7,6 @@ import UIKit import CenterFeature -import ConcreteUseCase -import ConcreteRepository class SceneDelegate: UIResponder, UIWindowSceneDelegate { @@ -20,13 +18,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window = UIWindow(windowScene: windowScene) - let store = TestStore() - - try! store.saveAuthToken( - accessToken: "", - refreshToken: "" - ) - // let useCase = DefaultCenterProfileUseCase( // repository: DefaultUserProfileRepository(store) // ) diff --git a/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/Testing.swift b/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/Testing.swift deleted file mode 100644 index 5f3b073d..00000000 --- a/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/Testing.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// Testing.swift -// Center_ExampleApp -// -// Created by choijunios on 7/20/24. -// - -import Foundation -import DataSource - -class TestStore: KeyValueStore { - func save(key: String, value: String) throws { - UserDefaults.standard.setValue(value, forKey: key) - } - - func get(key: String) -> String? { - UserDefaults.standard.string(forKey: key) - } - - func delete(key: String) throws { - - } - - func removeAll() throws { - - } - -} diff --git a/project/Projects/Presentation/Feature/Center/Project.swift b/project/Projects/Presentation/Feature/Center/Project.swift index cafb24a6..be9af7d4 100644 --- a/project/Projects/Presentation/Feature/Center/Project.swift +++ b/project/Projects/Presentation/Feature/Center/Project.swift @@ -20,7 +20,7 @@ let project = Project( /// FeatureConcrete .target( name: "CenterFeature", - destinations: DeploymentSettings.platform, + destinations: DeploymentSettings.platforms, product: .staticFramework, bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", deploymentTargets: DeploymentSettings.deployment_version, @@ -37,7 +37,7 @@ let project = Project( /// FeatureConcrete ExampleApp .target( name: "Center_ExampleApp", - destinations: DeploymentSettings.platform, + destinations: DeploymentSettings.platforms, product: .app, bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", deploymentTargets: DeploymentSettings.deployment_version, diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/Profile/CenterProfileViewController.swift b/project/Projects/Presentation/Feature/Center/Sources/View/Profile/CenterProfileViewController.swift index 9e01d47c..e3d3c06f 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/View/Profile/CenterProfileViewController.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/View/Profile/CenterProfileViewController.swift @@ -263,17 +263,16 @@ public class CenterProfileViewController: BaseViewController { } - public func bind(viewModel: any CenterProfileViewModelable) { + public func bind(viewModel: CenterProfileViewModelable) { super.bind(viewModel: viewModel) // input - let input = viewModel.input let bindFinished = PublishRelay() bindFinished - .bind(to: input.readyToFetch) + .bind(to: viewModel.readyToFetch) .disposed(by: disposeBag) // 내 센터보기 상태인 경우(수정가능한 프로필 상태) @@ -281,88 +280,87 @@ public class CenterProfileViewController: BaseViewController { profileEditButton .eventPublisher - .bind(to: input.editingButtonPressed) + .bind(to: viewModel.editingButtonPressed) .disposed(by: disposeBag) editingCompleteButton .eventPublisher - .bind(to: input.editingFinishButtonPressed) + .bind(to: viewModel.editingFinishButtonPressed) .disposed(by: disposeBag) centerPhoneNumeberField.rx.text .compactMap { $0 } - .bind(to: input.editingPhoneNumber) + .bind(to: viewModel.editingPhoneNumber) .disposed(by: disposeBag) centerIntroductionField.rx.text .compactMap { $0 } - .bind(to: input.editingInstruction) + .bind(to: viewModel.editingInstruction) .disposed(by: disposeBag) centerImageView .selectedImage .compactMap { $0 } - .bind(to: input.selectedImage) + .bind(to: viewModel.selectedImage) .disposed(by: disposeBag) } navigationBar .backButton .rx.tap - .bind(to: input.exitButtonClicked) + .bind(to: viewModel.exitButtonClicked) .disposed(by: disposeBag) // output - guard let output = viewModel.output else { fatalError() } - navigationBar.titleLabel.textString = output.navigationBarTitle + navigationBar.titleLabel.textString = viewModel.navigationBarTitle - output - .centerName + viewModel + .centerName? .drive(centerNameLabel.rx.textString) .disposed(by: disposeBag) - output - .centerLocation + viewModel + .centerLocation? .drive(centerLocationLabel.rx.textString) .disposed(by: disposeBag) - output - .centerPhoneNumber + viewModel + .centerPhoneNumber? .drive(centerPhoneNumeberLabel.rx.textString) .disposed(by: disposeBag) - output - .centerPhoneNumber + viewModel + .centerPhoneNumber? .drive(centerPhoneNumeberField.rx.textString) .disposed(by: disposeBag) - output - .centerIntroduction + viewModel + .centerIntroduction? .drive(centerIntroductionLabel.rx.textString) .disposed(by: disposeBag) - output - .centerIntroduction + viewModel + .centerIntroduction? .drive(centerIntroductionField.rx.textString) .disposed(by: disposeBag) - output - .displayingImage + viewModel + .displayingImage? .drive(centerImageView.displayingImage) .disposed(by: disposeBag) // MARK: Edit Mode if case .myProfile = viewModel.profileMode { - output - .isEditingMode + viewModel + .isEditingMode? .map { isEditing -> ImageSelectView.State in isEditing ? .editing : .normal } .drive(centerImageView.state) .disposed(by: disposeBag) - output - .isEditingMode + viewModel + .isEditingMode? .drive { [weak self] in guard let self else { return } @@ -377,8 +375,8 @@ public class CenterProfileViewController: BaseViewController { } .disposed(by: disposeBag) - output - .editingValidation + viewModel + .editingValidation? .drive { _ in // do something when editing success } diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/CenterRecruitmentPostBoardVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/CenterRecruitmentPostBoardVC.swift index 9876b252..edaec428 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/CenterRecruitmentPostBoardVC.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/CenterRecruitmentPostBoardVC.swift @@ -156,8 +156,8 @@ public class CenterRecruitmentPostBoardVC: BaseViewController { public func bind(viewModel: CenterRecruitmentPostBoardViewModelable) { - self.viewModel = viewModel - + super.bind(viewModel: viewModel) + (viewControllerDict[.onGoingPost] as? OnGoingPostVC)?.bind(viewModel: viewModel) (viewControllerDict[.closedPost] as? ClosedPostVC)?.bind(viewModel: viewModel) } diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/ClosedPostVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/ClosedPostVC.swift index 7f82047a..38a3cdfb 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/ClosedPostVC.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/ClosedPostVC.swift @@ -89,7 +89,8 @@ public class ClosedPostVC: BaseViewController { public func bind(viewModel: ClosedPostViewModelable) { - super.bind(viewModel: viewModel) + // 다수의 화면이 하나의 ViewModel을 공유하는 특수한 경우 + self.viewModel = viewModel // Output viewModel diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/OnGoingPostVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/OnGoingPostVC.swift index 0a32a6bd..6fdea532 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/OnGoingPostVC.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/OnGoingPostVC.swift @@ -102,7 +102,8 @@ public class OnGoingPostVC: BaseViewController { public func bind(viewModel: OnGoingPostViewModelable) { - super.bind(viewModel: viewModel) + // 다수의 화면이 하나의 ViewModel을 공유하는 특수한 경우 + self.viewModel = viewModel // Output viewModel diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Overview/PostOverviewVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Overview/PostOverviewVC.swift index cfa2e350..8d5e3185 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Overview/PostOverviewVC.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Overview/PostOverviewVC.swift @@ -290,12 +290,7 @@ public class PostOverviewVC: BaseViewController { } private func setObservable() { - executeRegisterButton - .rx.tap - .subscribe { [weak self] _ in - self?.view.isUserInteractionEnabled = false - } - .disposed(by: disposeBag) + } func bind(viewModel: PostOverviewViewModelable) { diff --git a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/Profile/CenterProfileViewModel.swift b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/Profile/CenterProfileViewModel.swift index dcc753e5..ab205aed 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/Profile/CenterProfileViewModel.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/Profile/CenterProfileViewModel.swift @@ -19,12 +19,7 @@ struct ChangeCenterInformation { let image: UIImage? } -public protocol CenterProfileViewModelable: BaseViewModel where Input: CenterProfileInputable, Output: CenterProfileOutputable { - associatedtype Input - associatedtype Output - var input: Input { get } - var output: Output? { get } - +public protocol CenterProfileViewModelable: BaseViewModel, CenterProfileInputable & CenterProfileOutputable { var profileMode: ProfileMode { get } } @@ -41,13 +36,13 @@ public protocol CenterProfileInputable { public protocol CenterProfileOutputable { var navigationBarTitle: String { get } - var centerName: Driver { get } - var centerLocation: Driver { get } - var centerPhoneNumber: Driver { get } - var centerIntroduction: Driver { get } - var displayingImage: Driver { get } - var isEditingMode: Driver { get } - var editingValidation: Driver { get } + var centerName: Driver? { get } + var centerLocation: Driver? { get } + var centerPhoneNumber: Driver? { get } + var centerIntroduction: Driver? { get } + var displayingImage: Driver? { get } + var isEditingMode: Driver? { get } + var editingValidation: Driver? { get } } @@ -56,9 +51,6 @@ public class CenterProfileViewModel: BaseViewModel, CenterProfileViewModelable { let profileUseCase: CenterProfileUseCase weak var coordinator: CenterProfileCoordinator? - public var input: Input - public var output: Output? = nil - public let profileMode: ProfileMode private var fetchedPhoneNumber: String? @@ -67,10 +59,33 @@ public class CenterProfileViewModel: BaseViewModel, CenterProfileViewModelable { private var editingImageInfo: ImageUploadInfo? + + public var readyToFetch: PublishRelay = .init() + public var editingButtonPressed: PublishRelay = .init() + public var editingFinishButtonPressed: PublishRelay = .init() + public var editingPhoneNumber: BehaviorRelay = .init(value: "") + public var editingInstruction: BehaviorRelay = .init(value: "") + public var selectedImage: PublishRelay = .init() + public var exitButtonClicked: RxRelay.PublishRelay = .init() + + // 기본 데이터 + public let navigationBarTitle: String + public var centerName: Driver? + public var centerLocation: Driver? + public var centerPhoneNumber: Driver? + public var centerIntroduction: Driver? + public var displayingImage: Driver? + + // 수정 상태 여부 + public var isEditingMode: Driver? + + // 요구사항 X + public var editingValidation: Driver? + func checkModification() -> (String?, String?, ImageUploadInfo?) { - let phoneNumber = input.editingPhoneNumber.value - let instruction = input.editingInstruction.value + let phoneNumber = editingPhoneNumber.value + let instruction = editingInstruction.value return ( phoneNumber == fetchedPhoneNumber ? nil : phoneNumber, @@ -89,13 +104,13 @@ public class CenterProfileViewModel: BaseViewModel, CenterProfileViewModelable { self.coordinator = coordinator self.profileUseCase = useCase - self.input = Input() + let navigationBarTitle = (mode == .myProfile ? "내 센터 정보" : "센터 정보") + self.navigationBarTitle = navigationBarTitle super.init() // MARK: fetch from server - let profileRequestResult = input - .readyToFetch + let profileRequestResult = readyToFetch .flatMap { [profileMode, profileUseCase] _ in profileUseCase.getProfile(mode: profileMode) } @@ -153,8 +168,7 @@ public class CenterProfileViewModel: BaseViewModel, CenterProfileViewModelable { }) // MARK: image validation - let imageValidationResult = input - .selectedImage + let imageValidationResult = selectedImage .map { [unowned self] image -> UIImage? in guard let imageInfo = self.validateSelectedImage(image: image) else { return nil } printIfDebug("✅ 업로드 가능한 이미지 타입 \(imageInfo.ext)") @@ -181,12 +195,11 @@ public class CenterProfileViewModel: BaseViewModel, CenterProfileViewModelable { // 최신 값들 + 버튼이 눌릴 경우 변경 로직이 실행된다. - let editingRequestResult = input - .editingFinishButtonPressed + let editingRequestResult = editingFinishButtonPressed .map({ [unowned self] _ in checkModification() }) - .flatMap { [useCase, input] (inputs) in + .flatMap { [useCase, editingPhoneNumber] (inputs) in let (phoneNumber, introduction, imageInfo) = inputs @@ -197,7 +210,7 @@ public class CenterProfileViewModel: BaseViewModel, CenterProfileViewModelable { // 전화번호는 무조건 포함시켜야 함으로 아래와 같이 포함합니다. return useCase.updateProfile( - phoneNumber: phoneNumber ?? input.editingPhoneNumber.value, + phoneNumber: phoneNumber ?? editingPhoneNumber.value, introduction: introduction, imageInfo: imageInfo ) @@ -206,12 +219,12 @@ public class CenterProfileViewModel: BaseViewModel, CenterProfileViewModelable { let editingValidation = editingRequestResult .compactMap { $0.value } - .map { [input] info in + .map { [readyToFetch] info in printIfDebug("✅ 정보가 성공적으로 업데이트됨") // 업데이트된 정보 요청 - input.readyToFetch.accept(()) + readyToFetch.accept(()) return () } @@ -235,8 +248,8 @@ public class CenterProfileViewModel: BaseViewModel, CenterProfileViewModelable { let buttonPress = Observable .merge( - input.editingButtonPressed.map { Mode.editing }, - input.editingFinishButtonPressed.map { Mode.display } + editingButtonPressed.map { Mode.editing }, + editingFinishButtonPressed.map { Mode.display } ) .map { mode in switch mode { @@ -254,34 +267,29 @@ public class CenterProfileViewModel: BaseViewModel, CenterProfileViewModelable { ) .asDriver(onErrorJustReturn: false) - - let alertDriver = Observable + Observable .merge( profileRequestFailure, editingRequestFailure, imageValidationFailure ) - .asDriver(onErrorJustReturn: .default) + .subscribe(self.alert) + .disposed(by: disposeBag) // MARK: Exit Button - input.exitButtonClicked + exitButtonClicked .subscribe(onNext: { [weak self] _ in self?.coordinator?.coordinatorDidFinish() }) .disposed(by: disposeBag) - let navigationBarTitle = (mode == .myProfile ? "내 센터 정보" : "센터 정보") - - self.output = .init( - navigationBarTitle: navigationBarTitle, - centerName: centerNameDriver, - centerLocation: centerAddressDriver, - centerPhoneNumber: centerPhoneNumberDriver, - centerIntroduction: centerIntroductionDriver, - displayingImage: displayingImageDriver, - isEditingMode: isEditingMode, - editingValidation: editingValidation - ) + self.centerName = centerNameDriver + self.centerLocation = centerAddressDriver + self.centerPhoneNumber = centerPhoneNumberDriver + self.centerIntroduction = centerIntroductionDriver + self.displayingImage = displayingImageDriver + self.isEditingMode = isEditingMode + self.editingValidation = editingValidation } func validateSelectedImage(image: UIImage) -> ImageUploadInfo? { @@ -293,54 +301,3 @@ public class CenterProfileViewModel: BaseViewModel, CenterProfileViewModelable { return nil } } - - -public extension CenterProfileViewModel { - - class Input: CenterProfileInputable { - // ViewController에서 받아오는 데이터 - public var readyToFetch: PublishRelay = .init() - public var editingButtonPressed: PublishRelay = .init() - public var editingFinishButtonPressed: PublishRelay = .init() - public var editingPhoneNumber: BehaviorRelay = .init(value: "") - public var editingInstruction: BehaviorRelay = .init(value: "") - public var selectedImage: PublishRelay = .init() - public var exitButtonClicked: RxRelay.PublishRelay = .init() - } - - class Output: CenterProfileOutputable { - // 기본 데이터 - public let navigationBarTitle: String - public var centerName: Driver - public var centerLocation: Driver - public var centerPhoneNumber: Driver - public var centerIntroduction: Driver - public var displayingImage: Driver - - // 수정 상태 여부 - public var isEditingMode: Driver - - // 요구사항 X - public var editingValidation: Driver - - init( - navigationBarTitle: String, - centerName: Driver, - centerLocation: Driver, - centerPhoneNumber: Driver, - centerIntroduction: Driver, - displayingImage: Driver, - isEditingMode: Driver, - editingValidation: Driver - ) { - self.navigationBarTitle = navigationBarTitle - self.centerName = centerName - self.centerLocation = centerLocation - self.centerPhoneNumber = centerPhoneNumber - self.centerIntroduction = centerIntroduction - self.displayingImage = displayingImage - self.isEditingMode = isEditingMode - self.editingValidation = editingValidation - } - } -} diff --git a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/CenterRecruitmentPostBoardVM.swift b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/CenterRecruitmentPostBoardVM.swift index 3ca8de30..bb14b3e0 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/CenterRecruitmentPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/CenterRecruitmentPostBoardVM.swift @@ -40,19 +40,21 @@ public class CenterRecruitmentPostBoardVM: BaseViewModel, CenterRecruitmentPostB super.init() let requestOngoingPostResult = requestOngoingPost - .map({ [weak self]_ in + .flatMap { [weak self, recruitmentPostUseCase] _ in + // 로딩 시작 self?.showLoading.onNext(()) - }) - .flatMap { [recruitmentPostUseCase] _ in - recruitmentPostUseCase + + return recruitmentPostUseCase .getOngoingPosts() } .share() requestOngoingPostResult + .delay(.milliseconds(300), scheduler: MainScheduler.instance) .subscribe (onNext: { [weak self] _ in - self?.dismissLoading.onNext(()) + self?.dismissLoading + .onNext(()) }) .disposed(by: disposeBag) @@ -64,12 +66,24 @@ public class CenterRecruitmentPostBoardVM: BaseViewModel, CenterRecruitmentPostB let requestClosedPostResult = requestClosedPost - .flatMap { [recruitmentPostUseCase] _ in - recruitmentPostUseCase + .flatMap { [weak self, recruitmentPostUseCase] _ in + + // 로딩 시작 + self?.showLoading.onNext(()) + + return recruitmentPostUseCase .getClosedPosts() } .share() + requestClosedPostResult + .delay(.milliseconds(300), scheduler: MainScheduler.instance) + .subscribe (onNext: { [weak self] _ in + self?.dismissLoading + .onNext(()) + }) + .disposed(by: disposeBag) + let requestClosedPostSuccess = requestClosedPostResult.compactMap { $0.value } let requestClosedPostFailure = requestClosedPostResult.compactMap { $0.error } diff --git a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/RegisterRecruitmentPostVM.swift b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/RegisterRecruitmentPostVM.swift index 49ec3acb..6969c460 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/RegisterRecruitmentPostVM.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/RegisterRecruitmentPostVM.swift @@ -460,6 +460,8 @@ public class RegisterRecruitmentPostVM: BaseViewModel, RegisterRecruitmentPostVi .flatMap { [weak self] _ -> Single> in guard let self else { return .never() } + self.showLoading.onNext(()) + // 공고를 등록합니다. let inputs = RegisterRecruitmentPostBundle( workTimeAndPay: state_workTimeAndPay, @@ -474,6 +476,13 @@ public class RegisterRecruitmentPostVM: BaseViewModel, RegisterRecruitmentPostVi } .share() + registerPostResult + .subscribe(onNext: { [weak self] _ in + + self?.dismissLoading.onNext(()) + }) + .disposed(by: disposeBag) + // 공고 등록 성공 registerPostResult .compactMap { $0.value } @@ -483,18 +492,18 @@ public class RegisterRecruitmentPostVM: BaseViewModel, RegisterRecruitmentPostVi .disposed(by: disposeBag) - let requestRegistrationFailure = registerPostResult + registerPostResult .compactMap { $0.error } - - requestRegistrationFailure - .map { error in - DefaultAlertContentVO( + .subscribe(onNext: { [weak self] error in + let alertVO = DefaultAlertContentVO( title: "공고등록 오류", message: error.message ) - } - .subscribe(alert) + + self?.alert.onNext(alertVO) + }) .disposed(by: disposeBag) + Observable .merge( diff --git a/project/Projects/Presentation/Feature/Root/Project.swift b/project/Projects/Presentation/Feature/Root/Project.swift index 1672ffdf..b2cc6fda 100644 --- a/project/Projects/Presentation/Feature/Root/Project.swift +++ b/project/Projects/Presentation/Feature/Root/Project.swift @@ -20,7 +20,7 @@ let project = Project( /// FeatureConcrete .target( name: "RootFeature", - destinations: DeploymentSettings.platform, + destinations: DeploymentSettings.platforms, product: .staticFramework, bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", deploymentTargets: DeploymentSettings.deployment_version, @@ -41,7 +41,7 @@ let project = Project( /// FeatureConcrete ExampleApp .target( name: "Root_ExampleApp", - destinations: DeploymentSettings.platform, + destinations: DeploymentSettings.platforms, product: .app, bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", deploymentTargets: DeploymentSettings.deployment_version, diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVC.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVC.swift index 1e6a0aba..9f1d62de 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVC.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVC.swift @@ -33,7 +33,7 @@ public class InitialScreenVC: BaseViewController { } private func setAppearance() { - view.backgroundColor = DSColor.gray0.color + view.backgroundColor = DSColor.orange500.color } private func setLayout() { } diff --git a/project/Projects/Presentation/Feature/Worker/ExampleApp/Sources/SceneDelegate.swift b/project/Projects/Presentation/Feature/Worker/ExampleApp/Sources/SceneDelegate.swift index 4178d693..942068e6 100644 --- a/project/Projects/Presentation/Feature/Worker/ExampleApp/Sources/SceneDelegate.swift +++ b/project/Projects/Presentation/Feature/Worker/ExampleApp/Sources/SceneDelegate.swift @@ -6,10 +6,7 @@ // import UIKit -import ConcreteUseCase -import ConcreteRepository import WorkerFeature -import DataSource class SceneDelegate: UIResponder, UIWindowSceneDelegate { @@ -25,23 +22,3 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window?.makeKeyAndVisible() } } - - -class TestStore: KeyValueStore { - func save(key: String, value: String) throws { - UserDefaults.standard.setValue(value, forKey: key) - } - - func get(key: String) -> String? { - UserDefaults.standard.string(forKey: key) - } - - func delete(key: String) throws { - - } - - func removeAll() throws { - - } - -} diff --git a/project/Projects/Presentation/Feature/Worker/Project.swift b/project/Projects/Presentation/Feature/Worker/Project.swift index 4463fb9e..ad4cbad1 100644 --- a/project/Projects/Presentation/Feature/Worker/Project.swift +++ b/project/Projects/Presentation/Feature/Worker/Project.swift @@ -20,7 +20,7 @@ let project = Project( /// FeatureConcrete .target( name: "WorkerFeature", - destinations: DeploymentSettings.platform, + destinations: DeploymentSettings.platforms, product: .staticFramework, bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", deploymentTargets: DeploymentSettings.deployment_version, @@ -37,7 +37,7 @@ let project = Project( /// FeatureConcrete ExampleApp .target( name: "Worker_ExampleApp", - destinations: DeploymentSettings.platform, + destinations: DeploymentSettings.platforms, product: .app, bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", deploymentTargets: DeploymentSettings.deployment_version, diff --git a/project/Projects/Presentation/PresentationCore/Project.swift b/project/Projects/Presentation/PresentationCore/Project.swift index 3d92ba14..bf831ec1 100644 --- a/project/Projects/Presentation/PresentationCore/Project.swift +++ b/project/Projects/Presentation/PresentationCore/Project.swift @@ -18,7 +18,7 @@ let proejct = Project( targets: [ .target( name: "PresentationCore", - destinations: DeploymentSettings.platform, + destinations: DeploymentSettings.platforms, product: .framework, bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", deploymentTargets: DeploymentSettings.deployment_version, diff --git a/project/Projects/Presentation/PresentationCore/Sources/Extensions/Rx+UIViewController.swift b/project/Projects/Presentation/PresentationCore/Sources/Extensions/Rx+UIViewController.swift index 97659519..a4861667 100644 --- a/project/Projects/Presentation/PresentationCore/Sources/Extensions/Rx+UIViewController.swift +++ b/project/Projects/Presentation/PresentationCore/Sources/Extensions/Rx+UIViewController.swift @@ -19,4 +19,9 @@ public extension Reactive where Base: UIViewController { let source = self.methodInvoked(#selector(Base.viewWillAppear)).map { $0.first as? Bool ?? false } return ControlEvent(events: source) } + + var viewDidAppear: ControlEvent { + let source = self.methodInvoked(#selector(Base.viewDidAppear)).map { $0.first as? Bool ?? false } + return ControlEvent(events: source) + } } diff --git a/project/graph.png b/project/graph.png index 5eae39aa..1a43fec2 100644 Binary files a/project/graph.png and b/project/graph.png differ