From 1334ebd3cc978db933f5ba6672f07c41844eef85 Mon Sep 17 00:00:00 2001 From: YeongHoon Song Date: Sun, 16 Mar 2025 17:18:46 +0900 Subject: [PATCH 01/95] add: Add issue templates --- ...4\354\212\210-\354\235\264\353\246\204.md" | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 ".github/ISSUE_TEMPLATE/\354\235\264\354\212\210-\354\235\264\353\246\204.md" diff --git "a/.github/ISSUE_TEMPLATE/\354\235\264\354\212\210-\354\235\264\353\246\204.md" "b/.github/ISSUE_TEMPLATE/\354\235\264\354\212\210-\354\235\264\353\246\204.md" new file mode 100644 index 00000000..b685dd47 --- /dev/null +++ "b/.github/ISSUE_TEMPLATE/\354\235\264\354\212\210-\354\235\264\353\246\204.md" @@ -0,0 +1,20 @@ +--- +name: 이슈 이름 +about: 팝풀 기본 템플릿 +title: '' +labels: '' +assignees: '' + +--- + +## 🤔 작업 배경 + +작업 배경을 적어주세요 + +## 📝 작업 내용 + +- 작업 내용을 적어주세요 + +## 👀 ETC (추후 개발해야 할 것, 참고자료 등) + + From 84e71dbca296f95f6cb419410afd8a402fbe4b24 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Sun, 16 Mar 2025 21:33:56 +0900 Subject: [PATCH 02/95] =?UTF-8?q?[ADD]:=20PR=20=ED=85=9C=ED=94=8C=EB=A6=BF?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/pull_request_template.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..321522df --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,15 @@ +## 📌 이슈 + +- #이슈번호 + +## ✅ 작업 사항 + +- [ ] 작업 사항을 정리해주세요 + +## 🚀 테스트 방식 + + + +## 👀 ETC (추후 개발해야 할 것, 참고자료 등) -> + + \ No newline at end of file From 3fea2e1d42e4be645df9f1864c2df7d3a00d61a7 Mon Sep 17 00:00:00 2001 From: JunYoung Date: Mon, 17 Mar 2025 21:48:47 +0900 Subject: [PATCH 03/95] =?UTF-8?q?[REFACTOR]:=20RxKakao=20SDK=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C,=20Kakao=20User=20SDK=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Poppool/Poppool.xcodeproj/project.pbxproj | 52 ++--- .../xcshareddata/swiftpm/Package.resolved | 33 +--- .../xcshareddata/xcschemes/Poppool.xcscheme | 2 +- Poppool/Poppool/Application/AppDelegate.swift | 4 +- .../Poppool/Application/SceneDelegate.swift | 92 +-------- .../Infrastructure/KakaoLoginService.swift | 177 +++++++++--------- 6 files changed, 116 insertions(+), 244 deletions(-) diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index a38282f7..d05337a5 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -343,9 +343,6 @@ 08B191B42CF609260057BC04 /* KakaoLoginService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08B191B32CF609260057BC04 /* KakaoLoginService.swift */; }; 08B191B62CF6092B0057BC04 /* AppleLoginService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08B191B52CF6092B0057BC04 /* AppleLoginService.swift */; }; 08B191B82CF6092F0057BC04 /* AuthServiceable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08B191B72CF6092F0057BC04 /* AuthServiceable.swift */; }; - 08B191BA2CF609AE0057BC04 /* RxKakaoSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 08B191B92CF609AE0057BC04 /* RxKakaoSDK */; }; - 08B191BC2CF609AE0057BC04 /* RxKakaoSDKAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 08B191BB2CF609AE0057BC04 /* RxKakaoSDKAuth */; }; - 08B191BE2CF609AE0057BC04 /* RxKakaoSDKUser in Frameworks */ = {isa = PBXBuildFile; productRef = 08B191BD2CF609AE0057BC04 /* RxKakaoSDKUser */; }; 08B191C22CF615CA0057BC04 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08B191C12CF615CA0057BC04 /* Secrets.swift */; }; 08CBEA032D38989E00248007 /* PostTokenReissueResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBEA022D38989E00248007 /* PostTokenReissueResponseDTO.swift */; }; 08CBEA062D38991600248007 /* PostTokenReissueResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBEA052D38991600248007 /* PostTokenReissueResponse.swift */; }; @@ -374,6 +371,7 @@ 08DE8A1D2D5261E70049BCAC /* MyPageTermsReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DE8A1C2D5261E70049BCAC /* MyPageTermsReactor.swift */; }; 08DE8A3F2D54DCC40049BCAC /* MyCommentedPopUpGridSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DE8A3E2D54DCC40049BCAC /* MyCommentedPopUpGridSection.swift */; }; 08DE8A412D54DCCA0049BCAC /* MyCommentedPopUpGridSectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DE8A402D54DCCA0049BCAC /* MyCommentedPopUpGridSectionCell.swift */; }; + 08F403332D884F4D00BFA61A /* KakaoSDKUser in Frameworks */ = {isa = PBXBuildFile; productRef = 08F403322D884F4D00BFA61A /* KakaoSDKUser */; }; 4E5825672D1951DF00EE83EF /* FloatingPanel in Frameworks */ = {isa = PBXBuildFile; productRef = 4E5825662D1951DF00EE83EF /* FloatingPanel */; }; 4E643FC12D738D7F0046AF29 /* PopUpStoreRegisterReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E643FC02D738D7F0046AF29 /* PopUpStoreRegisterReactor.swift */; }; 4E643FC32D738D930046AF29 /* PopUpStoreRegisterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E643FC22D738D930046AF29 /* PopUpStoreRegisterView.swift */; }; @@ -975,15 +973,13 @@ BDCA42072CF35FA6005EECF6 /* Tabman in Frameworks */, BDCA42042CF35F76005EECF6 /* PanModal in Frameworks */, 082197A12D426DCB0054094A /* Then in Frameworks */, - 08B191BA2CF609AE0057BC04 /* RxKakaoSDK in Frameworks */, - 08B191BC2CF609AE0057BC04 /* RxKakaoSDKAuth in Frameworks */, 083A25D02CF364B70099B58E /* Alamofire in Frameworks */, 088DE2472D12DB5C0030FA9E /* GoogleMaps in Frameworks */, + 08F403332D884F4D00BFA61A /* KakaoSDKUser in Frameworks */, BDCA42102CF35FF5005EECF6 /* Lottie in Frameworks */, BDCA41FE2CF35EE7005EECF6 /* ReactorKit in Frameworks */, BDCA41F22CF35D0D005EECF6 /* SnapKit in Frameworks */, BDCA420A2CF35FB1005EECF6 /* Pageboy in Frameworks */, - 08B191BE2CF609AE0057BC04 /* RxKakaoSDKUser in Frameworks */, 4E5825672D1951DF00EE83EF /* FloatingPanel in Frameworks */, 4EA9989D2D21C404009DC30B /* RxDataSources in Frameworks */, BDCA42012CF35EFE005EECF6 /* RxKeyboard in Frameworks */, @@ -3085,14 +3081,12 @@ BDCA420C2CF35FD2005EECF6 /* RxGesture */, BDCA420F2CF35FF5005EECF6 /* Lottie */, 083A25CF2CF364B70099B58E /* Alamofire */, - 08B191B92CF609AE0057BC04 /* RxKakaoSDK */, - 08B191BB2CF609AE0057BC04 /* RxKakaoSDKAuth */, - 08B191BD2CF609AE0057BC04 /* RxKakaoSDKUser */, 088DE2432D104EE70030FA9E /* SwiftSoup */, 088DE2462D12DB5C0030FA9E /* GoogleMaps */, 4E5825662D1951DF00EE83EF /* FloatingPanel */, 4EA9989C2D21C404009DC30B /* RxDataSources */, 082197A02D426DCB0054094A /* Then */, + 08F403322D884F4D00BFA61A /* KakaoSDKUser */, ); productName = Poppool; productReference = BDCA41BD2CF35AC0005EECF6 /* Poppool.app */; @@ -3142,7 +3136,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1540; - LastUpgradeCheck = 1540; + LastUpgradeCheck = 1620; TargetAttributes = { BDCA41BC2CF35AC0005EECF6 = { CreatedOnToolsVersion = 15.4; @@ -3170,7 +3164,6 @@ BDCA41F02CF35D0D005EECF6 /* XCRemoteSwiftPackageReference "SnapKit" */, BDCA41F32CF35D33005EECF6 /* XCRemoteSwiftPackageReference "Kingfisher" */, BDCA41F62CF35D9A005EECF6 /* XCRemoteSwiftPackageReference "RxSwift" */, - BDCA41F92CF35EC0005EECF6 /* XCRemoteSwiftPackageReference "kakao-ios-sdk-rx" */, BDCA41FC2CF35EE7005EECF6 /* XCRemoteSwiftPackageReference "ReactorKit" */, BDCA41FF2CF35EFE005EECF6 /* XCRemoteSwiftPackageReference "RxKeyboard" */, BDCA42022CF35F76005EECF6 /* XCRemoteSwiftPackageReference "PanModal" */, @@ -3184,6 +3177,7 @@ 4E5825652D1951DF00EE83EF /* XCRemoteSwiftPackageReference "FloatingPanel" */, 4EA9989B2D21C404009DC30B /* XCRemoteSwiftPackageReference "RxDataSources" */, 0821979F2D426DCB0054094A /* XCRemoteSwiftPackageReference "Then" */, + 08F403312D884F4D00BFA61A /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */, ); productRefGroup = BDCA41BE2CF35AC0005EECF6 /* Products */; projectDirPath = ""; @@ -3934,7 +3928,6 @@ BDCA41EB2CF35AC2005EECF6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -3954,7 +3947,6 @@ BDCA41EC2CF35AC2005EECF6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -3974,7 +3966,6 @@ BDCA41EE2CF35AC2005EECF6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 2U86LHQK8Q; @@ -3992,7 +3983,6 @@ BDCA41EF2CF35AC2005EECF6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 2U86LHQK8Q; @@ -4081,6 +4071,14 @@ minimumVersion = 9.2.0; }; }; + 08F403312D884F4D00BFA61A /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/kakao/kakao-ios-sdk.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.24.0; + }; + }; 4E5825652D1951DF00EE83EF /* XCRemoteSwiftPackageReference "FloatingPanel" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/scenee/FloatingPanel.git"; @@ -4121,14 +4119,6 @@ minimumVersion = 6.8.0; }; }; - BDCA41F92CF35EC0005EECF6 /* XCRemoteSwiftPackageReference "kakao-ios-sdk-rx" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/kakao/kakao-ios-sdk-rx"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 2.23.0; - }; - }; BDCA41FC2CF35EE7005EECF6 /* XCRemoteSwiftPackageReference "ReactorKit" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/ReactorKit/ReactorKit.git"; @@ -4208,20 +4198,10 @@ package = 088DE2452D12DB5C0030FA9E /* XCRemoteSwiftPackageReference "ios-maps-sdk" */; productName = GoogleMaps; }; - 08B191B92CF609AE0057BC04 /* RxKakaoSDK */ = { - isa = XCSwiftPackageProductDependency; - package = BDCA41F92CF35EC0005EECF6 /* XCRemoteSwiftPackageReference "kakao-ios-sdk-rx" */; - productName = RxKakaoSDK; - }; - 08B191BB2CF609AE0057BC04 /* RxKakaoSDKAuth */ = { - isa = XCSwiftPackageProductDependency; - package = BDCA41F92CF35EC0005EECF6 /* XCRemoteSwiftPackageReference "kakao-ios-sdk-rx" */; - productName = RxKakaoSDKAuth; - }; - 08B191BD2CF609AE0057BC04 /* RxKakaoSDKUser */ = { + 08F403322D884F4D00BFA61A /* KakaoSDKUser */ = { isa = XCSwiftPackageProductDependency; - package = BDCA41F92CF35EC0005EECF6 /* XCRemoteSwiftPackageReference "kakao-ios-sdk-rx" */; - productName = RxKakaoSDKUser; + package = 08F403312D884F4D00BFA61A /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDKUser; }; 4E5825662D1951DF00EE83EF /* FloatingPanel */ = { isa = XCSwiftPackageProductDependency; diff --git a/Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 717ac91b..06d33cce 100644 --- a/Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "9287fcb2d41c7fcf69aee03103ff9eac9ec5b01fe72ca36a57109537b58b6a8d", + "originHash" : "460c30523d69559c359ade610d8fe09aab6a0d403cc71804de63aa23b3649fe0", "pins" : [ { "identity" : "alamofire", @@ -33,17 +33,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/kakao/kakao-ios-sdk.git", "state" : { - "revision" : "ab4309c1950550add307046ad1e08024c7514603", - "version" : "2.23.0" - } - }, - { - "identity" : "kakao-ios-sdk-rx", - "kind" : "remoteSourceControl", - "location" : "https://github.com/kakao/kakao-ios-sdk-rx", - "state" : { - "revision" : "fa5ce05d610c4b026df8d42e891a32f31a239d58", - "version" : "2.23.0" + "revision" : "bfe2fe42f730ccfe59e85f6e9eda2f4578e9a307", + "version" : "2.24.0" } }, { @@ -64,15 +55,6 @@ "version" : "4.5.1" } }, - { - "identity" : "ohhttpstubs", - "kind" : "remoteSourceControl", - "location" : "https://github.com/AliSoftware/OHHTTPStubs.git", - "state" : { - "revision" : "12f19662426d0434d6c330c6974d53e2eb10ecd9", - "version" : "9.1.0" - } - }, { "identity" : "pageboy", "kind" : "remoteSourceControl", @@ -100,15 +82,6 @@ "version" : "3.2.0" } }, - { - "identity" : "rxalamofire", - "kind" : "remoteSourceControl", - "location" : "https://github.com/RxSwiftCommunity/RxAlamofire.git", - "state" : { - "revision" : "9535b58695b91fb67f56d58d6fd0c76462d7743a", - "version" : "6.1.2" - } - }, { "identity" : "rxdatasources", "kind" : "remoteSourceControl", diff --git a/Poppool/Poppool.xcodeproj/xcshareddata/xcschemes/Poppool.xcscheme b/Poppool/Poppool.xcodeproj/xcshareddata/xcschemes/Poppool.xcscheme index 8971a232..858b58f7 100644 --- a/Poppool/Poppool.xcodeproj/xcshareddata/xcschemes/Poppool.xcscheme +++ b/Poppool/Poppool.xcodeproj/xcshareddata/xcschemes/Poppool.xcscheme @@ -1,6 +1,6 @@ Bool { - RxKakaoSDK.initSDK(appKey: Secrets.kakaoAuthAppkey.rawValue, loggingEnable: false) +// RxKakaoSDK.initSDK(appKey: Secrets.kakaoAuthAppkey.rawValue, loggingEnable: false) GMSServices.provideAPIKey(Secrets.popPoolApiKey.rawValue) let locationManager = CLLocationManager() locationManager.requestWhenInUseAuthorization() // 권한 요청 초기화 diff --git a/Poppool/Poppool/Application/SceneDelegate.swift b/Poppool/Poppool/Application/SceneDelegate.swift index 8f18f6ac..df6fe39f 100644 --- a/Poppool/Poppool/Application/SceneDelegate.swift +++ b/Poppool/Poppool/Application/SceneDelegate.swift @@ -1,86 +1,6 @@ -//// -//// SceneDelegate.swift -//// Poppool -//// -//// Created by Porori on 11/24/24. -//// -// -//import UIKit -//import RxKakaoSDKAuth -//import KakaoSDKAuth -//import RxSwift -// -//class SceneDelegate: UIResponder, UIWindowSceneDelegate { -// -// var window: UIWindow? -// static let appDidBecomeActive = PublishSubject() -// -// func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { -// guard let windowScene = (scene as? UIWindowScene) else { return } -// window = UIWindow(windowScene: windowScene) -// -// // Debug: Admin Page Test -// let provider = ProviderImpl() -// let repository = DefaultAdminRepository(provider: provider) -// let useCase = DefaultAdminUseCase(repository: repository) -// let reactor = AdminReactor(useCase: useCase) -// let adminVC = AdminViewController() -// adminVC.reactor = reactor -// -// let navigationController = UINavigationController(rootViewController: adminVC) -// -// let rootViewController = LoginController() -// rootViewController.reactor = LoginReactor() -// -// let rootVC = WaveTabBarController() -// -// let rootViewController = DetailController() -// rootViewController.reactor = DetailReactor(popUpID: 8) -// -// let rootViewController = SearchMainController() -// rootViewController.reactor = SearchMainReactor() -// -// let navigationController = UINavigationController(rootViewController: rootVC) -// let navigationController = WaveTabBarController() -// -// window?.rootViewController = navigationController -// window?.makeKeyAndVisible() -// } -// -// func sceneDidDisconnect(_ scene: UIScene) { -// } -// -// func sceneDidBecomeActive(_ scene: UIScene) { -// SceneDelegate.appDidBecomeActive.onNext(()) -// } -// -// func sceneWillResignActive(_ scene: UIScene) { -// } -// -// func sceneWillEnterForeground(_ scene: UIScene) { -// } -// -// func sceneDidEnterBackground(_ scene: UIScene) { -// } -// -// func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { -// if let url = URLContexts.first?.url { -// if AuthApi.isKakaoTalkLoginUrl(url) { -// _ = AuthController.rx.handleOpenUrl(url: url) -// } -// } -// } -//} -// -// SceneDelegate.swift -// Poppool -// -// Created by Porori on 11/24/24. -// - import UIKit -import RxKakaoSDKAuth +//import RxKakaoSDKAuth import KakaoSDKAuth import RxSwift @@ -117,11 +37,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { - if let url = URLContexts.first?.url { - if AuthApi.isKakaoTalkLoginUrl(url) { - _ = AuthController.rx.handleOpenUrl(url: url) - } - } +// if let url = URLContexts.first?.url { +// if AuthApi.isKakaoTalkLoginUrl(url) { +// _ = AuthController.rx.handleOpenUrl(url: url) +// } +// } } } diff --git a/Poppool/Poppool/Infrastructure/KakaoLoginService.swift b/Poppool/Poppool/Infrastructure/KakaoLoginService.swift index 951c56b5..57b5a362 100644 --- a/Poppool/Poppool/Infrastructure/KakaoLoginService.swift +++ b/Poppool/Poppool/Infrastructure/KakaoLoginService.swift @@ -7,7 +7,7 @@ import RxSwift import KakaoSDKUser -import RxKakaoSDKUser +//import RxKakaoSDKUser import KakaoSDKAuth final class KakaoLoginService: AuthServiceable { @@ -21,58 +21,58 @@ final class KakaoLoginService: AuthServiceable { func unlink() -> Observable { return Observable.create { observer in - UserApi.shared.unlink { error in - if let error = error { - observer.onNext(()) - Logger.log(message: error.localizedDescription, category: .error) - } else { - observer.onNext(()) - observer.onCompleted() - } - } +// UserApi.shared.unlink { error in +// if let error = error { +// observer.onNext(()) +// Logger.log(message: error.localizedDescription, category: .error) +// } else { +// observer.onNext(()) +// observer.onCompleted() +// } +// } return Disposables.create() } } func fetchUserCredential() -> Observable { return Observable.create { [weak self] observer in - guard let self = self else { - Logger.log( - message: "KakaoTalk login Error", - category: .error, - fileName: #file, - line: #line - ) - return Disposables.create() - } - // 카카오톡 설치 유무 - guard UserApi.isKakaoTalkLoginAvailable() else { - Logger.log( - message: "KakaoTalk is not install", - category: .error, - fileName: #file, - line: #line - ) - UserApi.shared.loginWithKakaoAccount { [weak self] (oauthToken, error) in - if let error = error { - observer.onError(error) - } else { - if let self = self, let accessToken = oauthToken?.accessToken { - self.fetchUserId(observer: observer, accessToken: accessToken) - } - } - } - return Disposables.create() - } - // token을 획득하기 위한 로그인 - loginWithKakaoTalk() - .withUnretained(self) - .subscribe { (owner, loginResponse) in - owner.fetchUserId(observer: observer, accessToken: loginResponse.accessToken) - } onError: { _ in - observer.onError(AuthError.unknownError) - } - .disposed(by: disposeBag) +// guard let self = self else { +// Logger.log( +// message: "KakaoTalk login Error", +// category: .error, +// fileName: #file, +// line: #line +// ) +// return Disposables.create() +// } +// // 카카오톡 설치 유무 +// guard UserApi.isKakaoTalkLoginAvailable() else { +// Logger.log( +// message: "KakaoTalk is not install", +// category: .error, +// fileName: #file, +// line: #line +// ) +// UserApi.shared.loginWithKakaoAccount { [weak self] (oauthToken, error) in +// if let error = error { +// observer.onError(error) +// } else { +// if let self = self, let accessToken = oauthToken?.accessToken { +// self.fetchUserId(observer: observer, accessToken: accessToken) +// } +// } +// } +// return Disposables.create() +// } +// // token을 획득하기 위한 로그인 +// loginWithKakaoTalk() +// .withUnretained(self) +// .subscribe { (owner, loginResponse) in +// owner.fetchUserId(observer: observer, accessToken: loginResponse.accessToken) +// } onError: { _ in +// observer.onError(AuthError.unknownError) +// } +// .disposed(by: disposeBag) return Disposables.create() } @@ -82,50 +82,51 @@ final class KakaoLoginService: AuthServiceable { private extension KakaoLoginService { func fetchUserId(observer: AnyObserver, accessToken: String) { - UserApi.shared.rx.me() - .subscribe(onSuccess: { user in - observer.onNext(.init(kakaoUserId: user.id,kakaoAccessToken: accessToken)) - }, onFailure: { _ in - observer.onError(AuthError.unknownError) - }) - .disposed(by: self.disposeBag) +// UserApi.shared.rx.me() +// .subscribe(onSuccess: { user in +// observer.onNext(.init(kakaoUserId: user.id,kakaoAccessToken: accessToken)) +// }, onFailure: { _ in +// observer.onError(AuthError.unknownError) +// }) +// .disposed(by: self.disposeBag) } func loginWithKakaoTalk() -> Observable { - return UserApi.shared.rx.loginWithKakaoTalk() - .do { token in - Logger.log( - message: "KakaoTalk Login Response - \(token)", - category: .info, - fileName: #file, - line: #line - ) - } onError: { _ in - Logger.log( - message: "KakaoTalk Login Fail", - category: .error, - fileName: #file, - line: #line - ) - } + return Observable.just(.init(accessToken: "", tokenType: "", refreshToken: "", scope: "", scopes: [])) +// return UserApi.shared.rx.loginWithKakaoTalk() +// .do { token in +// Logger.log( +// message: "KakaoTalk Login Response - \(token)", +// category: .info, +// fileName: #file, +// line: #line +// ) +// } onError: { _ in +// Logger.log( +// message: "KakaoTalk Login Fail", +// category: .error, +// fileName: #file, +// line: #line +// ) +// } } - func fetchUserProfile() -> Single { - return UserApi.shared.rx.me() - .do { user in - Logger.log( - message: "KakaoTalk Profile Response - \(user)", - category: .info, - fileName: #file, - line: #line - ) - } onError: { _ in - Logger.log( - message: "KakaoTalk Profile Fetch Fail", - category: .error, - fileName: #file, - line: #line - ) - } - } +// func fetchUserProfile() -> Single { +// return UserApi.shared.rx.me() +// .do { user in +// Logger.log( +// message: "KakaoTalk Profile Response - \(user)", +// category: .info, +// fileName: #file, +// line: #line +// ) +// } onError: { _ in +// Logger.log( +// message: "KakaoTalk Profile Fetch Fail", +// category: .error, +// fileName: #file, +// line: #line +// ) +// } +// } } From 5499aca3c3b32aa254e537a070504fee0212a9da Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Mon, 17 Mar 2025 22:14:53 +0900 Subject: [PATCH 04/95] =?UTF-8?q?[REFACTOR]:=20KakaoSDK=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Poppool/Poppool/Application/AppDelegate.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Poppool/Poppool/Application/AppDelegate.swift b/Poppool/Poppool/Application/AppDelegate.swift index 909fce62..aebe135a 100644 --- a/Poppool/Poppool/Application/AppDelegate.swift +++ b/Poppool/Poppool/Application/AppDelegate.swift @@ -6,7 +6,8 @@ // import UIKit -import KakaoSDKAuth + +import KakaoSDKCommon import GoogleMaps import CoreLocation @@ -14,8 +15,8 @@ import CoreLocation class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { -// RxKakaoSDK.initSDK(appKey: Secrets.kakaoAuthAppkey.rawValue, loggingEnable: false) - GMSServices.provideAPIKey(Secrets.popPoolApiKey.rawValue) + KakaoSDK.initSDK(appKey: Secrets.kakaoAuthAppkey.rawValue) + GMSServices.provideAPIKey(Secrets.popPoolApiKey.rawValue) let locationManager = CLLocationManager() locationManager.requestWhenInUseAuthorization() // 권한 요청 초기화 return true From 726ecaa0b9ad023de57228183021cd89353f144c Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Mon, 17 Mar 2025 22:16:37 +0900 Subject: [PATCH 05/95] =?UTF-8?q?[STYLE]:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EB=B0=8F=20=EC=A3=BC=EC=84=9D?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Poppool/Poppool/Application/AppDelegate.swift | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/Poppool/Poppool/Application/AppDelegate.swift b/Poppool/Poppool/Application/AppDelegate.swift index aebe135a..40284a66 100644 --- a/Poppool/Poppool/Application/AppDelegate.swift +++ b/Poppool/Poppool/Application/AppDelegate.swift @@ -1,10 +1,3 @@ -// -// AppDelegate.swift -// Poppool -// -// Created by Porori on 11/24/24. -// - import UIKit import KakaoSDKCommon @@ -17,26 +10,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { KakaoSDK.initSDK(appKey: Secrets.kakaoAuthAppkey.rawValue) GMSServices.provideAPIKey(Secrets.popPoolApiKey.rawValue) + let locationManager = CLLocationManager() locationManager.requestWhenInUseAuthorization() // 권한 요청 초기화 + return true - } // MARK: UISceneSession Lifecycle - func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { - // Called when a new scene session is being created. - // Use this method to select a configuration to create the new scene with. return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } - - func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { - // Called when the user discards a scene session. - // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. - // Use this method to release any resources that were specific to the discarded scenes, as they will not return. - } - - } From 4616ac5d0ce7325950e62640a2a508a127d58417 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Mon, 17 Mar 2025 22:20:18 +0900 Subject: [PATCH 06/95] =?UTF-8?q?[REFACTOR]:=20SceneDelegate=EC=97=90?= =?UTF-8?q?=EC=84=9C=20KakaoSDK=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Poppool/Poppool/Application/SceneDelegate.swift | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Poppool/Poppool/Application/SceneDelegate.swift b/Poppool/Poppool/Application/SceneDelegate.swift index df6fe39f..a23213ca 100644 --- a/Poppool/Poppool/Application/SceneDelegate.swift +++ b/Poppool/Poppool/Application/SceneDelegate.swift @@ -1,6 +1,5 @@ import UIKit -//import RxKakaoSDKAuth import KakaoSDKAuth import RxSwift @@ -37,11 +36,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { -// if let url = URLContexts.first?.url { -// if AuthApi.isKakaoTalkLoginUrl(url) { -// _ = AuthController.rx.handleOpenUrl(url: url) -// } -// } + if let url = URLContexts.first?.url { + if AuthApi.isKakaoTalkLoginUrl(url) { + _ = AuthController.handleOpenUrl(url: url) + } + } } } From 510535f36179427a6b39caf72c50c59f51af8343 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 19 Mar 2025 00:07:11 +0900 Subject: [PATCH 07/95] =?UTF-8?q?[REFACTOR]:=20rx=20=EA=B1=B7=EC=96=B4?= =?UTF-8?q?=EB=82=B4=EB=8A=94=20=EC=9E=91=EC=97=85=20=EC=9D=BC=EB=B6=80=20?= =?UTF-8?q?=EC=A7=84=ED=96=89=20=EB=B0=8F=20=EC=9E=91=EC=97=85=20=EC=96=91?= =?UTF-8?q?=EB=8F=84=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Infrastructure/KakaoLoginService.swift | 197 +++++++++--------- 1 file changed, 100 insertions(+), 97 deletions(-) diff --git a/Poppool/Poppool/Infrastructure/KakaoLoginService.swift b/Poppool/Poppool/Infrastructure/KakaoLoginService.swift index 57b5a362..cff5897d 100644 --- a/Poppool/Poppool/Infrastructure/KakaoLoginService.swift +++ b/Poppool/Poppool/Infrastructure/KakaoLoginService.swift @@ -1,13 +1,5 @@ -// -// KakaoLoginService.swift -// MomsVillage -// -// Created by SeoJunYoung on 8/13/24. -// - import RxSwift import KakaoSDKUser -//import RxKakaoSDKUser import KakaoSDKAuth final class KakaoLoginService: AuthServiceable { @@ -21,58 +13,57 @@ final class KakaoLoginService: AuthServiceable { func unlink() -> Observable { return Observable.create { observer in -// UserApi.shared.unlink { error in -// if let error = error { -// observer.onNext(()) -// Logger.log(message: error.localizedDescription, category: .error) -// } else { -// observer.onNext(()) -// observer.onCompleted() -// } -// } + UserApi.shared.unlink { error in + if let error = error { + observer.onNext(()) + Logger.log(message: error.localizedDescription, category: .error) + } else { + observer.onNext(()) + observer.onCompleted() + } + } + return Disposables.create() } } func fetchUserCredential() -> Observable { return Observable.create { [weak self] observer in -// guard let self = self else { -// Logger.log( -// message: "KakaoTalk login Error", -// category: .error, -// fileName: #file, -// line: #line -// ) -// return Disposables.create() -// } -// // 카카오톡 설치 유무 -// guard UserApi.isKakaoTalkLoginAvailable() else { -// Logger.log( -// message: "KakaoTalk is not install", -// category: .error, -// fileName: #file, -// line: #line -// ) -// UserApi.shared.loginWithKakaoAccount { [weak self] (oauthToken, error) in -// if let error = error { -// observer.onError(error) -// } else { -// if let self = self, let accessToken = oauthToken?.accessToken { -// self.fetchUserId(observer: observer, accessToken: accessToken) -// } -// } -// } -// return Disposables.create() -// } -// // token을 획득하기 위한 로그인 -// loginWithKakaoTalk() -// .withUnretained(self) -// .subscribe { (owner, loginResponse) in -// owner.fetchUserId(observer: observer, accessToken: loginResponse.accessToken) -// } onError: { _ in -// observer.onError(AuthError.unknownError) -// } -// .disposed(by: disposeBag) + guard let self else { + Logger.log( + message: "KakaoTalk login Error", + category: .error, + fileName: #file, + line: #line + ) + return Disposables.create() + } + + // 카카오톡 설치 유무 확인 + guard UserApi.isKakaoTalkLoginAvailable() else { + Logger.log( + message: "KakaoTalk is not install", + category: .error, + fileName: #file, + line: #line + ) + + // 카카오톡 미설치시 계정으로 접속 시도 + UserApi.shared.loginWithKakaoAccount { [weak self] (oauthToken, error) in + if let error = error { + observer.onError(error) + } else { + if let self = self, let accessToken = oauthToken?.accessToken { + self.fetchUserId(observer: observer, accessToken: accessToken) + } + } + } + + return Disposables.create() + } + + // token을 획득하기 위한 로그인 + loginWithKakaoTalk(observer: observer) return Disposables.create() } @@ -82,51 +73,63 @@ final class KakaoLoginService: AuthServiceable { private extension KakaoLoginService { func fetchUserId(observer: AnyObserver, accessToken: String) { -// UserApi.shared.rx.me() -// .subscribe(onSuccess: { user in -// observer.onNext(.init(kakaoUserId: user.id,kakaoAccessToken: accessToken)) -// }, onFailure: { _ in -// observer.onError(AuthError.unknownError) -// }) -// .disposed(by: self.disposeBag) + UserApi.shared.me() { user, error in + if let error = error { + observer.onError(AuthError.unknownError) + } else { + // ???: 여기 onComplete로 observer를 종료하지 않아도 되는지? + // ???: disposeBag으로 수거를 하지 않게 되는데 observer 수거에 대한 문제 발생 가능 여부 확인이 필요해보임 + observer.onNext(.init(kakaoUserId: user?.id, kakaoAccessToken: accessToken)) + } + } } - func loginWithKakaoTalk() -> Observable { - return Observable.just(.init(accessToken: "", tokenType: "", refreshToken: "", scope: "", scopes: [])) -// return UserApi.shared.rx.loginWithKakaoTalk() -// .do { token in -// Logger.log( -// message: "KakaoTalk Login Response - \(token)", -// category: .info, -// fileName: #file, -// line: #line -// ) -// } onError: { _ in -// Logger.log( -// message: "KakaoTalk Login Fail", -// category: .error, -// fileName: #file, -// line: #line -// ) -// } + func loginWithKakaoTalk(observer: AnyObserver) { + UserApi.shared.loginWithKakaoTalk { oauthToken, error in + if let error = error { + Logger.log( + message: "KakaoTalk Login Fail", + category: .error, + fileName: #file, + line: #line + ) + observer.onError(AuthError.unknownError) + } else { + if let oauthToken = oauthToken { + Logger.log( + message: "KakaoTalk Login Response - \(oauthToken)", + category: .info, + fileName: #file, + line: #line + ) + self.fetchUserId(observer: observer, accessToken: oauthToken.accessToken) + } + + + } + } } -// func fetchUserProfile() -> Single { -// return UserApi.shared.rx.me() -// .do { user in -// Logger.log( -// message: "KakaoTalk Profile Response - \(user)", -// category: .info, -// fileName: #file, -// line: #line -// ) -// } onError: { _ in -// Logger.log( -// message: "KakaoTalk Profile Fetch Fail", -// category: .error, -// fileName: #file, -// line: #line -// ) -// } -// } + func fetchUserProfile() -> Single { + // MARK: 이런 식으로 구현하면 될거 같다 생각만 하고 여기서 멈췄습니다... ㅠㅠ +// return Observable +// .just(<#T##element: _##_#>) +// .asSingle() + return UserApi.shared.rx.me() + .do { user in + Logger.log( + message: "KakaoTalk Profile Response - \(user)", + category: .info, + fileName: #file, + line: #line + ) + } onError: { _ in + Logger.log( + message: "KakaoTalk Profile Fetch Fail", + category: .error, + fileName: #file, + line: #line + ) + } + } } From 8de2ea12f9df2c87f8bf95ac2e12a40ad29b203b Mon Sep 17 00:00:00 2001 From: JunYoung Date: Fri, 21 Mar 2025 15:16:01 +0900 Subject: [PATCH 08/95] =?UTF-8?q?[REFACTOR]:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC,=20=EC=A3=BC=EC=84=9D=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80,=20error=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EC=9D=BC?= =?UTF-8?q?=EB=B6=80=20=EB=82=B4=EC=9A=A9=20=EC=88=98=EC=A0=95=EC=9D=98=20?= =?UTF-8?q?=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Infrastructure/AppleLoginService.swift | 17 +--- .../Infrastructure/AuthServiceable.swift | 2 +- .../Infrastructure/KakaoLoginService.swift | 92 ++++++------------- 3 files changed, 34 insertions(+), 77 deletions(-) diff --git a/Poppool/Poppool/Infrastructure/AppleLoginService.swift b/Poppool/Poppool/Infrastructure/AppleLoginService.swift index e58e20b5..a26f2e0c 100644 --- a/Poppool/Poppool/Infrastructure/AppleLoginService.swift +++ b/Poppool/Poppool/Infrastructure/AppleLoginService.swift @@ -58,23 +58,12 @@ extension AppleLoginService: ASAuthorizationControllerPresentationContextProvidi case let appleIDCredential as ASAuthorizationAppleIDCredential: guard let idToken = appleIDCredential.identityToken else { // 토큰이 없는 경우 오류 방출 - Logger.log( - message: "AppleLogin Token is Not Found", - category: .error, - fileName: #file, - line: #line - ) - authServiceResponse.onError(AuthError.unknownError) + authServiceResponse.onError(AuthError.unknownError(description: "AppleLogin Token is Not Found")) return } guard let idToken = String(data: idToken, encoding: .utf8) else { - Logger.log( - message: "AppleLogin Token Convert Fail", - category: .error, - fileName: #file, - line: #line - ) - authServiceResponse.onError(AuthError.unknownError) + // 토큰 convert가 실패할 경우 오류 방출 + authServiceResponse.onError(AuthError.unknownError(description: "AppleLogin Token Convert Fail")) return } guard let authorizationCode = appleIDCredential.authorizationCode else { diff --git a/Poppool/Poppool/Infrastructure/AuthServiceable.swift b/Poppool/Poppool/Infrastructure/AuthServiceable.swift index 79c2330e..cd20f3ee 100644 --- a/Poppool/Poppool/Infrastructure/AuthServiceable.swift +++ b/Poppool/Poppool/Infrastructure/AuthServiceable.swift @@ -24,5 +24,5 @@ struct AuthServiceResponse: Encodable { enum AuthError: Error { case notInstalled - case unknownError + case unknownError(description: String?) } diff --git a/Poppool/Poppool/Infrastructure/KakaoLoginService.swift b/Poppool/Poppool/Infrastructure/KakaoLoginService.swift index cff5897d..4d234418 100644 --- a/Poppool/Poppool/Infrastructure/KakaoLoginService.swift +++ b/Poppool/Poppool/Infrastructure/KakaoLoginService.swift @@ -4,11 +4,6 @@ import KakaoSDKAuth final class KakaoLoginService: AuthServiceable { - struct Credential: Encodable { - var id: String - var token: String - } - var disposeBag = DisposeBag() func unlink() -> Observable { @@ -48,22 +43,13 @@ final class KakaoLoginService: AuthServiceable { line: #line ) - // 카카오톡 미설치시 계정으로 접속 시도 - UserApi.shared.loginWithKakaoAccount { [weak self] (oauthToken, error) in - if let error = error { - observer.onError(error) - } else { - if let self = self, let accessToken = oauthToken?.accessToken { - self.fetchUserId(observer: observer, accessToken: accessToken) - } - } - } - + // 카카오톡 미설치시 웹으로 인증 시도 + loginWithKakaoTalkWeb(observer: observer) return Disposables.create() } - - // token을 획득하기 위한 로그인 - loginWithKakaoTalk(observer: observer) + + // 카카오톡 설치시 앱으로 인증 시도 + loginWithKakaoTalkApp(observer: observer) return Disposables.create() } @@ -72,64 +58,46 @@ final class KakaoLoginService: AuthServiceable { private extension KakaoLoginService { + /// 제공된 액세스 토큰을 사용하여 사용자의 카카오 ID를 가져옵니다. + /// - Parameters: + /// - observer: 인증 응답을 처리할 옵저버. + /// - accessToken: 카카오 로그인 과정에서 얻은 액세스 토큰. func fetchUserId(observer: AnyObserver, accessToken: String) { UserApi.shared.me() { user, error in if let error = error { - observer.onError(AuthError.unknownError) + observer.onError(AuthError.unknownError(description: error.localizedDescription)) } else { - // ???: 여기 onComplete로 observer를 종료하지 않아도 되는지? - // ???: disposeBag으로 수거를 하지 않게 되는데 observer 수거에 대한 문제 발생 가능 여부 확인이 필요해보임 observer.onNext(.init(kakaoUserId: user?.id, kakaoAccessToken: accessToken)) + observer.onCompleted() } } } - - func loginWithKakaoTalk(observer: AnyObserver) { - UserApi.shared.loginWithKakaoTalk { oauthToken, error in + + /// 카카오톡 앱을 사용하여 로그인하고 액세스 토큰을 가져옵니다. + /// - Parameter observer: 인증 응답을 처리할 옵저버. + func loginWithKakaoTalkApp(observer: AnyObserver) { + UserApi.shared.loginWithKakaoTalk { [weak self] oauthToken, error in if let error = error { - Logger.log( - message: "KakaoTalk Login Fail", - category: .error, - fileName: #file, - line: #line - ) - observer.onError(AuthError.unknownError) + observer.onError(AuthError.unknownError(description: error.localizedDescription)) } else { - if let oauthToken = oauthToken { - Logger.log( - message: "KakaoTalk Login Response - \(oauthToken)", - category: .info, - fileName: #file, - line: #line - ) - self.fetchUserId(observer: observer, accessToken: oauthToken.accessToken) + if let accessToken = oauthToken?.accessToken { + self?.fetchUserId(observer: observer, accessToken: accessToken) } - - } } } - func fetchUserProfile() -> Single { - // MARK: 이런 식으로 구현하면 될거 같다 생각만 하고 여기서 멈췄습니다... ㅠㅠ -// return Observable -// .just(<#T##element: _##_#>) -// .asSingle() - return UserApi.shared.rx.me() - .do { user in - Logger.log( - message: "KakaoTalk Profile Response - \(user)", - category: .info, - fileName: #file, - line: #line - ) - } onError: { _ in - Logger.log( - message: "KakaoTalk Profile Fetch Fail", - category: .error, - fileName: #file, - line: #line - ) + /// 카카오톡 웹을 사용하여 로그인하고 액세스 토큰을 가져옵니다. + /// - Parameter observer: 인증 응답을 처리할 옵저버. + func loginWithKakaoTalkWeb(observer: AnyObserver) { + UserApi.shared.loginWithKakaoAccount { [weak self] (oauthToken, error) in + if let error = error { + observer.onError(AuthError.unknownError(description: error.localizedDescription)) + } else { + if let accessToken = oauthToken?.accessToken { + self?.fetchUserId(observer: observer, accessToken: accessToken) + } } + } } } From 1a3197d2e1f4130520372f8571dc3b86d115144d Mon Sep 17 00:00:00 2001 From: JunYoung Date: Sun, 23 Mar 2025 23:12:48 +0900 Subject: [PATCH 09/95] =?UTF-8?q?[FIX]:=20kakao=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=8B=9C=20=EC=95=B1=20=EC=82=AC=EC=9A=A9=EC=9D=84?= =?UTF-8?q?=20=EC=9C=84=ED=95=9C=20Schemes=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Poppool/Poppool/Resource/Info.plist | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Poppool/Poppool/Resource/Info.plist b/Poppool/Poppool/Resource/Info.plist index 9b1010ac..9b8b88b1 100644 --- a/Poppool/Poppool/Resource/Info.plist +++ b/Poppool/Poppool/Resource/Info.plist @@ -22,6 +22,8 @@ kakaomap tmap maps + kakaokompassauth + kakaotalk UIAppFonts From a941d63c1f2c97b94a301f22429a645913457215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=80=E1=85=B5=E1=84=92?= =?UTF-8?q?=E1=85=A7=E1=86=AB?= Date: Thu, 20 Mar 2025 01:38:25 +0900 Subject: [PATCH 10/95] =?UTF-8?q?[REFACTOR]=20:=20NMaps=20SPM=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20=EC=A7=80=EB=8F=84=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?NaverMap=20=EB=8C=80=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Poppool/Poppool.xcodeproj/project.pbxproj | 23 +- .../xcshareddata/swiftpm/Package.resolved | 168 -- Poppool/Poppool/Application/AppDelegate.swift | 11 + .../Admin/Data/MapDomain/MapPopUpStore.swift | 36 +- .../Data/MapDomain/MapPopUpStoreDTO.swift | 1 - .../MapDomain/Repository/MapRepository.swift | 4 +- .../Map/Common/ClusteringManager.swift | 187 +- .../Map/Common/ClusteringModels.swift | 27 +- .../Map/Common/GMSMapViewDelegateProxy.swift | 39 - .../Map/Common/MapUtilities.swift | 14 +- .../Map/Common/NMFMapViewDelegateProxy.swift | 56 + .../Map/Common/RegionDefinitions.swift | 500 ++-- .../FullScreenMapViewController.swift | 261 +- .../MapGuideView/MapGuideAppService.swift | 71 + .../MapGuideView/MapGuideViewController.swift | 103 +- .../Presentation/Map/MapView/MapMarker.swift | 19 +- .../Presentation/Map/MapView/MapReactor.swift | 38 + .../Presentation/Map/MapView/MapView.swift | 33 +- .../Map/MapView/MapViewController.swift | 2204 ++++++++--------- .../Map/StoreListView/StoreListReactor.swift | 3 +- 20 files changed, 1780 insertions(+), 2018 deletions(-) delete mode 100644 Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved delete mode 100644 Poppool/Poppool/Presentation/Map/Common/GMSMapViewDelegateProxy.swift create mode 100644 Poppool/Poppool/Presentation/Map/Common/NMFMapViewDelegateProxy.swift create mode 100644 Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideAppService.swift diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index d05337a5..026dc060 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -373,6 +373,7 @@ 08DE8A412D54DCCA0049BCAC /* MyCommentedPopUpGridSectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DE8A402D54DCCA0049BCAC /* MyCommentedPopUpGridSectionCell.swift */; }; 08F403332D884F4D00BFA61A /* KakaoSDKUser in Frameworks */ = {isa = PBXBuildFile; productRef = 08F403322D884F4D00BFA61A /* KakaoSDKUser */; }; 4E5825672D1951DF00EE83EF /* FloatingPanel in Frameworks */ = {isa = PBXBuildFile; productRef = 4E5825662D1951DF00EE83EF /* FloatingPanel */; }; + 4E60C2262D8AABBF003A5A37 /* NMap in Frameworks */ = {isa = PBXBuildFile; productRef = 4E60C2252D8AABBF003A5A37 /* NMap */; }; 4E643FC12D738D7F0046AF29 /* PopUpStoreRegisterReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E643FC02D738D7F0046AF29 /* PopUpStoreRegisterReactor.swift */; }; 4E643FC32D738D930046AF29 /* PopUpStoreRegisterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E643FC22D738D930046AF29 /* PopUpStoreRegisterView.swift */; }; 4E685ECE2D12CEB6001EF91C /* BalloonBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E685EAA2D12CEB6001EF91C /* BalloonBackgroundView.swift */; }; @@ -427,7 +428,7 @@ 4EA2C9432D424DF900F4D97C /* FindDirectionEndPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EA2C9422D424DF900F4D97C /* FindDirectionEndPoint.swift */; }; 4EA9989A2D21C2FC009DC30B /* StoreListSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EA998992D21C2FC009DC30B /* StoreListSection.swift */; }; 4EA9989D2D21C404009DC30B /* RxDataSources in Frameworks */ = {isa = PBXBuildFile; productRef = 4EA9989C2D21C404009DC30B /* RxDataSources */; }; - 4EAB809D2D3F78AA0041AF30 /* GMSMapViewDelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAB809C2D3F78AA0041AF30 /* GMSMapViewDelegateProxy.swift */; }; + 4EAB809D2D3F78AA0041AF30 /* NMFMapViewDelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAB809C2D3F78AA0041AF30 /* NMFMapViewDelegateProxy.swift */; }; 4EAB809F2D3F8EF50041AF30 /* ViewportBounds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAB809E2D3F8EF50041AF30 /* ViewportBounds.swift */; }; 4EDDEFB42D2D285900CFAFA5 /* DateTimePickerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EDDEFB32D2D285900CFAFA5 /* DateTimePickerManager.swift */; }; 4EDE57012D5E6A5F0014D924 /* MapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E685ECC2D12CEB6001EF91C /* MapViewController.swift */; }; @@ -914,7 +915,7 @@ 4EA2C9402D424D8400F4D97C /* GetPopUpDirectionResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetPopUpDirectionResponseDTO.swift; sourceTree = ""; }; 4EA2C9422D424DF900F4D97C /* FindDirectionEndPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindDirectionEndPoint.swift; sourceTree = ""; }; 4EA998992D21C2FC009DC30B /* StoreListSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreListSection.swift; sourceTree = ""; }; - 4EAB809C2D3F78AA0041AF30 /* GMSMapViewDelegateProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GMSMapViewDelegateProxy.swift; sourceTree = ""; }; + 4EAB809C2D3F78AA0041AF30 /* NMFMapViewDelegateProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NMFMapViewDelegateProxy.swift; sourceTree = ""; }; 4EAB809E2D3F8EF50041AF30 /* ViewportBounds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewportBounds.swift; sourceTree = ""; }; 4EDDEFB32D2D285900CFAFA5 /* DateTimePickerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimePickerManager.swift; sourceTree = ""; }; 4EDE57022D5E70650014D924 /* LocationPermissionBottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationPermissionBottomSheet.swift; sourceTree = ""; }; @@ -981,6 +982,7 @@ BDCA41F22CF35D0D005EECF6 /* SnapKit in Frameworks */, BDCA420A2CF35FB1005EECF6 /* Pageboy in Frameworks */, 4E5825672D1951DF00EE83EF /* FloatingPanel in Frameworks */, + 4E60C2262D8AABBF003A5A37 /* NMap in Frameworks */, 4EA9989D2D21C404009DC30B /* RxDataSources in Frameworks */, BDCA42012CF35EFE005EECF6 /* RxKeyboard in Frameworks */, 088DE2442D104EE70030FA9E /* SwiftSoup in Frameworks */, @@ -2880,7 +2882,7 @@ 4EED9BAA2D2272F500B288E7 /* Common */ = { isa = PBXGroup; children = ( - 4EAB809C2D3F78AA0041AF30 /* GMSMapViewDelegateProxy.swift */, + 4EAB809C2D3F78AA0041AF30 /* NMFMapViewDelegateProxy.swift */, 4EAB809E2D3F8EF50041AF30 /* ViewportBounds.swift */, 4EED9BAB2D22730400B288E7 /* FilterType.swift */, 4E6C07052D4B6E56008A962A /* RegionDefinitions.swift */, @@ -3647,7 +3649,7 @@ 086F89CA2D1E42A700CA4FC9 /* CommentUserBlockView.swift in Sources */, 086DD8D02CFDFEB900B97D3B /* HomeListReactor.swift in Sources */, 081899202D34DF880067BF01 /* MyPageNoticeDetailController.swift in Sources */, - 4EAB809D2D3F78AA0041AF30 /* GMSMapViewDelegateProxy.swift in Sources */, + 4EAB809D2D3F78AA0041AF30 /* NMFMapViewDelegateProxy.swift in Sources */, 083C86472D0DCDFB003F441C /* AddCommentTitleSection.swift in Sources */, 0841BAB62CFABEDC00049E31 /* HomePopularCardSectionCell.swift in Sources */, 082197A92D4E3EE90054094A /* CommentMyMenuController.swift in Sources */, @@ -4087,6 +4089,14 @@ minimumVersion = 2.8.6; }; }; + 4E60C2242D8AABBF003A5A37 /* XCRemoteSwiftPackageReference "NaverMap" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/zzangzzangguy/NaverMap.git"; + requirement = { + branch = main; + kind = branch; + }; + }; 4EA9989B2D21C404009DC30B /* XCRemoteSwiftPackageReference "RxDataSources" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/RxSwiftCommunity/RxDataSources.git"; @@ -4208,6 +4218,11 @@ package = 4E5825652D1951DF00EE83EF /* XCRemoteSwiftPackageReference "FloatingPanel" */; productName = FloatingPanel; }; + 4E60C2252D8AABBF003A5A37 /* NMap */ = { + isa = XCSwiftPackageProductDependency; + package = 4E60C2242D8AABBF003A5A37 /* XCRemoteSwiftPackageReference "NaverMap" */; + productName = NMap; + }; 4EA9989C2D21C404009DC30B /* RxDataSources */ = { isa = XCSwiftPackageProductDependency; package = 4EA9989B2D21C404009DC30B /* XCRemoteSwiftPackageReference "RxDataSources" */; diff --git a/Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index 06d33cce..00000000 --- a/Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,168 +0,0 @@ -{ - "originHash" : "460c30523d69559c359ade610d8fe09aab6a0d403cc71804de63aa23b3649fe0", - "pins" : [ - { - "identity" : "alamofire", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Alamofire/Alamofire.git", - "state" : { - "revision" : "513364f870f6bfc468f9d2ff0a95caccc10044c5", - "version" : "5.10.2" - } - }, - { - "identity" : "floatingpanel", - "kind" : "remoteSourceControl", - "location" : "https://github.com/scenee/FloatingPanel.git", - "state" : { - "revision" : "b6e8928b1a3ad909e6db6a0278d286c33cfd0dc3", - "version" : "2.8.6" - } - }, - { - "identity" : "ios-maps-sdk", - "kind" : "remoteSourceControl", - "location" : "https://github.com/googlemaps/ios-maps-sdk", - "state" : { - "revision" : "9fa352d6eca4a731949efcdb27ed851f1fcd4447", - "version" : "9.3.0" - } - }, - { - "identity" : "kakao-ios-sdk", - "kind" : "remoteSourceControl", - "location" : "https://github.com/kakao/kakao-ios-sdk.git", - "state" : { - "revision" : "bfe2fe42f730ccfe59e85f6e9eda2f4578e9a307", - "version" : "2.24.0" - } - }, - { - "identity" : "kingfisher", - "kind" : "remoteSourceControl", - "location" : "https://github.com/onevcat/Kingfisher.git", - "state" : { - "revision" : "3db26ab625d194c38e68c1a40e43d1bc12743fe0", - "version" : "8.2.0" - } - }, - { - "identity" : "lottie-spm", - "kind" : "remoteSourceControl", - "location" : "https://github.com/airbnb/lottie-spm.git", - "state" : { - "revision" : "8c6edf4f0fa84fe9c058600a4295eb0c01661c69", - "version" : "4.5.1" - } - }, - { - "identity" : "pageboy", - "kind" : "remoteSourceControl", - "location" : "https://github.com/uias/Pageboy.git", - "state" : { - "revision" : "be0c1f6f1964cfb07f9d819b0863f2c3f255f612", - "version" : "4.2.0" - } - }, - { - "identity" : "panmodal", - "kind" : "remoteSourceControl", - "location" : "https://github.com/slackhq/PanModal.git", - "state" : { - "revision" : "b012aecb6b67a8e46369227f893c12544846613f", - "version" : "1.2.7" - } - }, - { - "identity" : "reactorkit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/ReactorKit/ReactorKit.git", - "state" : { - "revision" : "8fa33f09c6f6621a2aa536d739956d53b84dd139", - "version" : "3.2.0" - } - }, - { - "identity" : "rxdatasources", - "kind" : "remoteSourceControl", - "location" : "https://github.com/RxSwiftCommunity/RxDataSources.git", - "state" : { - "revision" : "90c29b48b628479097fe775ed1966d75ac374518", - "version" : "5.0.2" - } - }, - { - "identity" : "rxgesture", - "kind" : "remoteSourceControl", - "location" : "https://github.com/RxSwiftCommunity/RxGesture.git", - "state" : { - "revision" : "1b137c576b4aaaab949235752278956697c9e4a0", - "version" : "4.0.4" - } - }, - { - "identity" : "rxkeyboard", - "kind" : "remoteSourceControl", - "location" : "https://github.com/RxSwiftCommunity/RxKeyboard.git", - "state" : { - "revision" : "63f6377975c962a1d89f012a6f1e5bebb2c502b7", - "version" : "2.0.1" - } - }, - { - "identity" : "rxswift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/ReactiveX/RxSwift.git", - "state" : { - "revision" : "5dd1907d64f0d36f158f61a466bab75067224893", - "version" : "6.9.0" - } - }, - { - "identity" : "snapkit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/SnapKit/SnapKit.git", - "state" : { - "revision" : "2842e6e84e82eb9a8dac0100ca90d9444b0307f4", - "version" : "5.7.1" - } - }, - { - "identity" : "swiftsoup", - "kind" : "remoteSourceControl", - "location" : "https://github.com/scinfu/SwiftSoup", - "state" : { - "revision" : "18ad8b8ff0f03f3c0a5544ffccfa2ea1c051ae6e", - "version" : "2.8.0" - } - }, - { - "identity" : "tabman", - "kind" : "remoteSourceControl", - "location" : "https://github.com/uias/Tabman.git", - "state" : { - "revision" : "3b2213290eb93e55bb50b49d1a179033005c11ab", - "version" : "3.2.0" - } - }, - { - "identity" : "then", - "kind" : "remoteSourceControl", - "location" : "https://github.com/devxoul/Then", - "state" : { - "revision" : "d41ef523faef0f911369f79c0b96815d9dbb6d7a", - "version" : "3.0.0" - } - }, - { - "identity" : "weakmaptable", - "kind" : "remoteSourceControl", - "location" : "https://github.com/ReactorKit/WeakMapTable.git", - "state" : { - "revision" : "cb05d64cef2bbf51e85c53adee937df46540a74e", - "version" : "1.2.1" - } - } - ], - "version" : 3 -} diff --git a/Poppool/Poppool/Application/AppDelegate.swift b/Poppool/Poppool/Application/AppDelegate.swift index 40284a66..8d2ed331 100644 --- a/Poppool/Poppool/Application/AppDelegate.swift +++ b/Poppool/Poppool/Application/AppDelegate.swift @@ -1,3 +1,14 @@ +// +// AppDelegate.swift +// Poppoolasdasdasdasda +// +// Created by Porori on 11/24/24. +// +import UIKit +import RxKakaoSDKAuth +import KakaoSDKAuth +import RxKakaoSDKCommon +import NMapsMap import UIKit import KakaoSDKCommon diff --git a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapPopUpStore.swift b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapPopUpStore.swift index bcc448f4..ad5875c8 100644 --- a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapPopUpStore.swift +++ b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapPopUpStore.swift @@ -1,11 +1,6 @@ -// -// MapPopUpStore.swift -// Poppool -// -// Created by 김기현 on 12/3/24. -// import Foundation import CoreLocation +import NMapsMap struct MapPopUpStore: Equatable { let id: Int64 @@ -19,35 +14,34 @@ struct MapPopUpStore: Equatable { let markerId: Int64 let markerTitle: String let markerSnippet: String - let mainImageUrl: String? // 이미지 URL 추가 - + let mainImageUrl: String? var coordinate: CLLocationCoordinate2D { CLLocationCoordinate2D(latitude: latitude, longitude: longitude) } -} - extension MapPopUpStore { - func toMarkerInput() -> MapMarker.Input { - return MapMarker.Input( - isSelected: false, - isCluster: false, - regionName: self.markerTitle, // 또는 name이나 다른 적절한 필드 - count: 0 - ) - } - + var nmgCoordinate: NMGLatLng { + NMGLatLng(lat: latitude, lng: longitude) + } + func toMarkerInput() -> MapMarker.Input { + return MapMarker.Input( + isSelected: false, + isCluster: false, + regionName: self.markerTitle, + count: 0 + ) + } func toStoreItem() -> StoreItem { return StoreItem( id: id, - thumbnailURL: mainImageUrl ?? "", // 이미지 URL 매핑 + thumbnailURL: mainImageUrl ?? "", category: category, title: name, location: address, dateRange: "\(startDate) ~ \(endDate)", - isBookmarked: false // 기본값 + isBookmarked: false ) } } diff --git a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapPopUpStoreDTO.swift b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapPopUpStoreDTO.swift index 1db431dd..cd7bb5de 100644 --- a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapPopUpStoreDTO.swift +++ b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapPopUpStoreDTO.swift @@ -15,7 +15,6 @@ struct MapPopUpStoreDTO: Codable { let mainImageUrl: String? let bookmarkYn: Bool? - // toDomain() 메서드 추가 func toDomain() -> MapPopUpStore { return MapPopUpStore( id: id, diff --git a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/Repository/MapRepository.swift b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/Repository/MapRepository.swift index 53164882..2c44294a 100644 --- a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/Repository/MapRepository.swift +++ b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/Repository/MapRepository.swift @@ -49,7 +49,7 @@ class DefaultMapRepository: MapRepository { southWestLon: southWestLon, categories: categories ), - interceptor: TokenInterceptor() // ← 토큰 누락 해결 + interceptor: TokenInterceptor() ) .map { $0.popUpStoreList } } @@ -63,7 +63,7 @@ class DefaultMapRepository: MapRepository { query: query, categories: categories ), - interceptor: TokenInterceptor() // ← 토큰 누락 해결 + interceptor: TokenInterceptor() ) .map { $0.popUpStoreList } } diff --git a/Poppool/Poppool/Presentation/Map/Common/ClusteringManager.swift b/Poppool/Poppool/Presentation/Map/Common/ClusteringManager.swift index 306392bb..8b8600f6 100644 --- a/Poppool/Poppool/Presentation/Map/Common/ClusteringManager.swift +++ b/Poppool/Poppool/Presentation/Map/Common/ClusteringManager.swift @@ -1,23 +1,22 @@ -import CoreLocation -import UIKit +import NMapsMap class ClusteringManager { - private let regions = RegionDefinitions.allClusters + private let regions = RegionType.RegionDefinitions.allClusters private class MutableCluster { let base: RegionCluster var stores: [MapPopUpStore] var storeCount: Int - var fixedCenter: CLLocationCoordinate2D? + var fixedCenter: NMGLatLng? - init(base: RegionCluster, fixedCenter: CLLocationCoordinate2D? = nil) { + init(base: RegionCluster, fixedCenter: NMGLatLng? = nil) { self.base = base self.stores = [] self.storeCount = 0 self.fixedCenter = fixedCenter } - func centerCoordinate() -> CLLocationCoordinate2D { + func centerCoordinate() -> NMGLatLng { return fixedCenter ?? base.coordinate } @@ -35,87 +34,65 @@ class ClusteringManager { } } - // 수정: 항상 구 단위 클러스터링만 사용하도록 변경 func clusterStores(_ stores: [MapPopUpStore], at zoomLevel: Float) -> [ClusterMarkerData] { - // 줌 레벨 무시하고 항상 구 단위 클러스터링 실행 - return clusterByDistrictOnly(stores) - } - - // 새로운 함수: 모든 스토어를 구 단위로만 클러스터링 - private func clusterByDistrictOnly(_ stores: [MapPopUpStore]) -> [ClusterMarkerData] { - var clusters: [String: MutableCluster] = [:] + let level = MapZoomLevel.getLevel(from: zoomLevel) - // 서울 구 클러스터 초기화 - for cluster in RegionDefinitions.seoulClusters { - clusters[cluster.name] = MutableCluster(base: cluster, fixedCenter: cluster.coordinate) + // partition() 호출 결과를 별도의 변수에 할당 + let partitionedStores = stores.partition { store in + let city = extractCity(from: store.address) + return city == "서울" || city == "경기" } - - // 경기 시/군 클러스터 초기화 - for cluster in RegionDefinitions.gyeonggiClusters { - clusters[cluster.name] = MutableCluster(base: cluster, fixedCenter: cluster.coordinate) + let seoulGyeonggiStores = partitionedStores.0 + let otherStores = partitionedStores.1 + + switch level { + case .country: + return clusterByProvince(stores) + case .city: + let seoulGyeonggiClusters = clusterByCityLevel(seoulGyeonggiStores) + let otherClusters = clusterByMetropolitan(otherStores) + return seoulGyeonggiClusters + otherClusters + case .district: + let seoulGyeonggiClusters = clusterByDistrict(seoulGyeonggiStores) + let otherClusters = clusterByMetropolitan(otherStores) + return seoulGyeonggiClusters + otherClusters + case .detailed: + return [] } + } - // 기타 광역시/도 초기화 - for cluster in RegionDefinitions.metropolitanClusters { - clusters[cluster.name] = MutableCluster(base: cluster, fixedCenter: cluster.coordinate) - } + private func clusterByMetropolitan(_ stores: [MapPopUpStore]) -> [ClusterMarkerData] { + var clusters: [String: MutableCluster] = [:] - // 도 단위 초기화 - for cluster in RegionDefinitions.provinceClusters { + // 광역시/도 클러스터 초기화 + let allClusters = RegionType.RegionDefinitions.metropolitanClusters + RegionType.RegionDefinitions.provinceClusters + for cluster in allClusters { clusters[cluster.name] = MutableCluster(base: cluster, fixedCenter: cluster.coordinate) } - // 각 스토어를 적절한 클러스터에 할당 + // 스토어 할당 for store in stores { let city = extractCity(from: store.address) - - switch city { - case "서울": - if let clusterName = findMatchingSeoulDistrictName(in: store.address), - let cluster = clusters[clusterName] { - cluster.stores.append(store) - cluster.storeCount += 1 - } - case "경기": - if let clusterName = findMatchingGyeonggiCityName(in: store.address), - let cluster = clusters[clusterName] { - cluster.stores.append(store) - cluster.storeCount += 1 - } - default: - // 서울/경기 외 지역은 광역시/도 단위로 클러스터링 - if let cluster = clusters[city] { - cluster.stores.append(store) - cluster.storeCount += 1 - } + if let cluster = clusters[city] { + cluster.stores.append(store) + cluster.storeCount += 1 } } - // 스토어가 있는 클러스터만 필터링 let validClusters = clusters.values.filter { $0.storeCount > 0 } return validClusters.map { $0.toMarkerData() } } - // 기존 함수들 유지 (다만 더 이상 사용되지 않음) - private func clusterByCityName(_ stores: [MapPopUpStore]) -> [ClusterMarkerData] { + private func clusterByCityLevel(_ stores: [MapPopUpStore]) -> [ClusterMarkerData] { var clusters: [String: MutableCluster] = [:] + // 서울/경기 클러스터 초기화 + initializeSeoulGyeonggiClusters(&clusters) + for store in stores { let city = extractCity(from: store.address) - - // 아직 해당 city 이름으로 된 MutableCluster가 없다면 생성 - if clusters[city] == nil { - let baseRegion = RegionCluster( - name: city, - subRegions: [city], - coordinate: getFixedCenterForCity(city) ?? CLLocationCoordinate2D(latitude: 37.5665, longitude: 126.9780), // 기본값(서울 좌표) - type: .metropolitan - ) - clusters[city] = MutableCluster(base: baseRegion, fixedCenter: baseRegion.coordinate) - } - - // 스토어 할당 - if let cluster = clusters[city] { + let clusterKey = determineClusterKey(for: store, city: city, clusters: &clusters) + if let cluster = clusters[clusterKey] { cluster.stores.append(store) cluster.storeCount += 1 } @@ -125,26 +102,44 @@ class ClusteringManager { return validClusters.map { $0.toMarkerData() } } - private func clusterByMetropolitan(_ stores: [MapPopUpStore]) -> [ClusterMarkerData] { - var clusters: [String: MutableCluster] = [:] - - // 광역시/도 클러스터 초기화 - let allClusters = RegionDefinitions.metropolitanClusters + RegionDefinitions.provinceClusters - for cluster in allClusters { - clusters[cluster.name] = MutableCluster(base: cluster, fixedCenter: cluster.coordinate) + private func initializeSeoulGyeonggiClusters(_ clusters: inout [String: MutableCluster]) { + let predefinedClusters = [ + ("서울 북부", RepresentativeScope.seoulNorth.center), + ("서울 남부", RepresentativeScope.seoulSouth.center), + ("경기 북부", RepresentativeScope.gyeonggiNorth.center), + ("경기 남부", RepresentativeScope.gyeonggiSouth.center) + ] + + for (name, coordinate) in predefinedClusters { + let baseRegion = RegionCluster( + name: name, + subRegions: [name], + coordinate: coordinate, + type: .metropolitan + ) + clusters[name] = MutableCluster(base: baseRegion, fixedCenter: coordinate) } + } - // 스토어 할당 - for store in stores { - let city = extractCity(from: store.address) - if let cluster = clusters[city] { - cluster.stores.append(store) - cluster.storeCount += 1 + private func determineClusterKey(for store: MapPopUpStore, city: String, clusters: inout [String: MutableCluster]) -> String { + if city == "서울" { + return seoulNorthRegions.contains(where: { store.address.contains($0) }) ? "서울 북부" : "서울 남부" + } else if city == "경기" { + return gyeonggiNorthRegions.contains(where: { store.address.contains($0) }) ? "경기 북부" : "경기 남부" + } else { + if clusters[city] == nil { + if let coordinate = getFixedCenterForCity(city) { + let baseRegion = RegionCluster( + name: city, + subRegions: [city], + coordinate: coordinate, + type: .metropolitan + ) + clusters[city] = MutableCluster(base: baseRegion, fixedCenter: coordinate) + } } + return city } - - let validClusters = clusters.values.filter { $0.storeCount > 0 } - return validClusters.map { $0.toMarkerData() } } private func clusterByDistrict(_ stores: [MapPopUpStore]) -> [ClusterMarkerData] { @@ -152,19 +147,18 @@ class ClusteringManager { var gyeonggiClusters: [String: MutableCluster] = [:] var otherClusters: [String: MutableCluster] = [:] - // 서울/경기 각 구/시 초기화 - for cluster in RegionDefinitions.seoulClusters { + // 서울/경기 클러스터 초기화 + for cluster in RegionType.RegionDefinitions.seoulClusters { seoulClusters[cluster.name] = MutableCluster(base: cluster, fixedCenter: cluster.coordinate) } - for cluster in RegionDefinitions.gyeonggiClusters { + for cluster in RegionType.RegionDefinitions.gyeonggiClusters { gyeonggiClusters[cluster.name] = MutableCluster(base: cluster, fixedCenter: cluster.coordinate) } - - // 그 외 지역 - for cluster in RegionDefinitions.metropolitanClusters { + // 다른 지역 클러스터 초기화 + for cluster in RegionType.RegionDefinitions.metropolitanClusters { otherClusters[cluster.name] = MutableCluster(base: cluster, fixedCenter: cluster.coordinate) } - for cluster in RegionDefinitions.provinceClusters { + for cluster in RegionType.RegionDefinitions.provinceClusters { otherClusters[cluster.name] = MutableCluster(base: cluster, fixedCenter: cluster.coordinate) } @@ -193,12 +187,18 @@ class ClusteringManager { let combined = Array(seoulClusters.values) + Array(gyeonggiClusters.values) + Array(otherClusters.values) let filtered = combined.filter { $0.storeCount > 0 } + + Logger.log(message: "구 단위 클러스터 생성 결과: \(filtered.count)개", category: .debug) + for cluster in filtered { + Logger.log(message: "- \(cluster.base.name): \(cluster.storeCount)개 매장", category: .debug) + } + return filtered.map { $0.toMarkerData() } } private func clusterByProvince(_ stores: [MapPopUpStore]) -> [ClusterMarkerData] { var clusters: [String: MutableCluster] = [:] - for cluster in RegionDefinitions.provinceClusters { + for cluster in RegionType.RegionDefinitions.provinceClusters { clusters[cluster.name] = MutableCluster(base: cluster) } for store in stores { @@ -213,7 +213,7 @@ class ClusteringManager { } private func findMatchingSeoulDistrictName(in address: String) -> String? { - return RegionDefinitions.seoulClusters.first { cluster in + return RegionType.RegionDefinitions.seoulClusters.first { cluster in cluster.subRegions.contains { district in address.contains(district) } @@ -221,7 +221,7 @@ class ClusteringManager { } private func findMatchingGyeonggiCityName(in address: String) -> String? { - return RegionDefinitions.gyeonggiClusters.first { cluster in + return RegionType.RegionDefinitions.gyeonggiClusters.first { cluster in cluster.subRegions.contains { cityName in address.contains(cityName) } @@ -229,14 +229,14 @@ class ClusteringManager { } private func findMatchingProvinceName(in address: String) -> String? { - return RegionDefinitions.provinceClusters.first { cluster in + return RegionType.RegionDefinitions.provinceClusters.first { cluster in cluster.subRegions.contains { province in address.contains(province) } }?.name } - private func getFixedCenterForCity(_ city: String) -> CLLocationCoordinate2D? { + private func getFixedCenterForCity(_ city: String) -> NMGLatLng? { switch city { case "대구": return RegionCoordinate.daegu case "부산": return RegionCoordinate.busan @@ -250,7 +250,6 @@ class ClusteringManager { } } -// partition() 확장: 서울·경기 vs 그 외 지역 분류 용 extension Array { func partition(by predicate: (Element) -> Bool) -> ([Element], [Element]) { var matching: [Element] = [] diff --git a/Poppool/Poppool/Presentation/Map/Common/ClusteringModels.swift b/Poppool/Poppool/Presentation/Map/Common/ClusteringModels.swift index 3d5a1c94..0f218e3c 100644 --- a/Poppool/Poppool/Presentation/Map/Common/ClusteringModels.swift +++ b/Poppool/Poppool/Presentation/Map/Common/ClusteringModels.swift @@ -1,4 +1,4 @@ -import CoreLocation +import NMapsMap enum MapZoomLevel { case country @@ -7,24 +7,27 @@ enum MapZoomLevel { case detailed static func getLevel(from zoom: Float) -> MapZoomLevel { - switch zoom { - case ..<7: - return .country - case 7..<10: - return .city - case 10..<12: - return .district - default: - return .detailed + let level: MapZoomLevel + switch zoom { + case ..<7: + level = .country + case 7..<10: + level = .city + case 10..<11: + level = .district + default: + level = .detailed + } + Logger.log(message: "줌 레벨 계산: \(zoom) -> \(level)", category: .debug) + return level } } -} struct RegionCluster { let name: String let subRegions: [String] - let coordinate: CLLocationCoordinate2D + let coordinate: NMGLatLng let type: RegionType var storeCount: Int = 0 diff --git a/Poppool/Poppool/Presentation/Map/Common/GMSMapViewDelegateProxy.swift b/Poppool/Poppool/Presentation/Map/Common/GMSMapViewDelegateProxy.swift deleted file mode 100644 index a3e7064b..00000000 --- a/Poppool/Poppool/Presentation/Map/Common/GMSMapViewDelegateProxy.swift +++ /dev/null @@ -1,39 +0,0 @@ -import RxSwift -import RxCocoa -import GoogleMaps - -class GMSMapViewDelegateProxy: DelegateProxy, DelegateProxyType, GMSMapViewDelegate { - - public private(set) weak var mapView: GMSMapView? - let didChangePositionSubject = PublishSubject() - let idleAtPositionSubject = PublishSubject() - - init(mapView: GMSMapView) { - self.mapView = mapView - super.init(parentObject: mapView, delegateProxy: GMSMapViewDelegateProxy.self) - } - - static func registerKnownImplementations() { - self.register { mapView in - GMSMapViewDelegateProxy(mapView: mapView) - } - } - - static func currentDelegate(for object: GMSMapView) -> GMSMapViewDelegate? { - return object.delegate - } - - static func setCurrentDelegate(_ delegate: GMSMapViewDelegate?, to object: GMSMapView) { - object.delegate = delegate - } - - func mapView(_ mapView: GMSMapView, didChange position: GMSCameraPosition) { - didChangePositionSubject.onNext(()) - self.forwardToDelegate()?.mapView?(mapView, didChange: position) - } - - func mapView(_ mapView: GMSMapView, idleAt position: GMSCameraPosition) { - idleAtPositionSubject.onNext(()) - self.forwardToDelegate()?.mapView?(mapView, idleAt: position) - } -} diff --git a/Poppool/Poppool/Presentation/Map/Common/MapUtilities.swift b/Poppool/Poppool/Presentation/Map/Common/MapUtilities.swift index ff6f6557..1b20d677 100644 --- a/Poppool/Poppool/Presentation/Map/Common/MapUtilities.swift +++ b/Poppool/Poppool/Presentation/Map/Common/MapUtilities.swift @@ -1,6 +1,5 @@ - - -import CoreLocation +import NMapsMap +import UIKit public func extractCity(from address: String) -> String { let components = address.components(separatedBy: " ") @@ -33,22 +32,21 @@ public let gyeonggiSouthRegions: [String] = [ // RepresentativeScope 수정 public struct RepresentativeScope { public static let seoulNorth = ( - center: CLLocationCoordinate2D(latitude: 37.6020, longitude: 127.0350), + center: NMGLatLng(lat: 37.6020, lng: 127.0350), radius: 3000.0 ) public static let seoulSouth = ( - center: CLLocationCoordinate2D(latitude: 37.4959, longitude: 127.0664), // 강남/서초 중심 + center: NMGLatLng(lat: 37.4959, lng: 127.0664), // 강남/서초 중심 radius: 3000.0 ) // 경기 북부/남부 좌표 조정 public static let gyeonggiNorth = ( - center: CLLocationCoordinate2D(latitude: 37.7358, longitude: 127.0346), // 의정부 중심 + center: NMGLatLng(lat: 37.7358, lng: 127.0346), // 의정부 중심 radius: 4000.0 ) public static let gyeonggiSouth = ( - center: CLLocationCoordinate2D(latitude: 37.2911, longitude: 127.0876), // 용인/분당 중심 + center: NMGLatLng(lat: 37.2911, lng: 127.0876), // 용인/분당 중심 radius: 4000.0 ) } - diff --git a/Poppool/Poppool/Presentation/Map/Common/NMFMapViewDelegateProxy.swift b/Poppool/Poppool/Presentation/Map/Common/NMFMapViewDelegateProxy.swift new file mode 100644 index 00000000..d5ef360c --- /dev/null +++ b/Poppool/Poppool/Presentation/Map/Common/NMFMapViewDelegateProxy.swift @@ -0,0 +1,56 @@ +import NMapsMap +import RxSwift +import RxCocoa + +class NMFMapViewDelegateProxy: DelegateProxy, DelegateProxyType, NMFMapViewDelegate { + + public weak private(set) var mapView: NMFMapView? + + // Rx 이벤트를 위한 subject 추가 + let didChangePositionSubject = PublishSubject() + let idleAtPositionSubject = PublishSubject() + + init(mapView: NMFMapView) { + self.mapView = mapView + super.init(parentObject: mapView, delegateProxy: NMFMapViewDelegateProxy.self) + } + + static func registerKnownImplementations() { + self.register { NMFMapViewDelegateProxy(mapView: $0) } + } + + static func currentDelegate(for object: NMFMapView) -> NMFMapViewDelegate? { + return object.delegate + } + + static func setCurrentDelegate(_ delegate: NMFMapViewDelegate?, to object: NMFMapView) { + object.delegate = delegate + } + + // 네이버맵의 Delegate 메서드를 Rx로 전달 + func mapView(_ mapView: NMFMapView, cameraDidChangeByReason reason: Int, animated: Bool) { + didChangePositionSubject.onNext(()) + _forwardToDelegate?.mapView?(mapView, cameraDidChangeByReason: reason, animated: animated) + } + + func mapViewIdle(_ mapView: NMFMapView) { + idleAtPositionSubject.onNext(()) + forwardToDelegate()?.mapViewIdle?(mapView) + } +} + +extension Reactive where Base: NMFMapView { + var delegate: DelegateProxy { + return NMFMapViewDelegateProxy.proxy(for: base) + } + + var didChangePosition: Observable { + let proxy = NMFMapViewDelegateProxy.proxy(for: base) + return proxy.didChangePositionSubject.asObservable() + } + + var idleAtPosition: Observable { + let proxy = NMFMapViewDelegateProxy.proxy(for: base) + return proxy.idleAtPositionSubject.asObservable() + } +} diff --git a/Poppool/Poppool/Presentation/Map/Common/RegionDefinitions.swift b/Poppool/Poppool/Presentation/Map/Common/RegionDefinitions.swift index 4f46b43e..8de0e178 100644 --- a/Poppool/Poppool/Presentation/Map/Common/RegionDefinitions.swift +++ b/Poppool/Poppool/Presentation/Map/Common/RegionDefinitions.swift @@ -1,23 +1,23 @@ -import CoreLocation +import NMapsMap struct RegionCoordinate { - static let seoul = CLLocationCoordinate2D(latitude: 37.5665, longitude: 126.9780) - static let gyeonggi = CLLocationCoordinate2D(latitude: 37.4138, longitude: 127.5183) - static let incheon = CLLocationCoordinate2D(latitude: 37.4563, longitude: 126.7052) - static let daejeon = CLLocationCoordinate2D(latitude: 36.3504, longitude: 127.3845) - static let gwangju = CLLocationCoordinate2D(latitude: 35.1595, longitude: 126.8526) - static let daegu = CLLocationCoordinate2D(latitude: 35.8714, longitude: 128.6014) - static let busan = CLLocationCoordinate2D(latitude: 35.1796, longitude: 129.0756) - static let ulsan = CLLocationCoordinate2D(latitude: 35.5384, longitude: 129.3114) - static let chungbuk = CLLocationCoordinate2D(latitude: 36.6357, longitude: 127.4914) - static let chungnam = CLLocationCoordinate2D(latitude: 36.6588, longitude: 126.6728) - static let sejong = CLLocationCoordinate2D(latitude: 36.4801, longitude: 127.2892) - static let jeonbuk = CLLocationCoordinate2D(latitude: 35.7175, longitude: 127.1530) - static let jeonnam = CLLocationCoordinate2D(latitude: 34.8679, longitude: 126.9910) - static let gyeongbuk = CLLocationCoordinate2D(latitude: 36.4919, longitude: 128.8889) - static let gyeongnam = CLLocationCoordinate2D(latitude: 35.4606, longitude: 128.2132) - static let gangwon = CLLocationCoordinate2D(latitude: 37.8228, longitude: 128.1555) - static let jeju = CLLocationCoordinate2D(latitude: 33.4890, longitude: 126.4983) + static let seoul = NMGLatLng(lat: 37.5665, lng: 126.9780) + static let gyeonggi = NMGLatLng(lat: 37.4138, lng: 127.5183) + static let incheon = NMGLatLng(lat: 37.4563, lng: 126.7052) + static let daejeon = NMGLatLng(lat: 36.3504, lng: 127.3845) + static let gwangju = NMGLatLng(lat: 35.1595, lng: 126.8526) + static let daegu = NMGLatLng(lat: 35.8714, lng: 128.6014) + static let busan = NMGLatLng(lat: 35.1796, lng: 129.0756) + static let ulsan = NMGLatLng(lat: 35.5384, lng: 129.3114) + static let chungbuk = NMGLatLng(lat: 36.6357, lng: 127.4914) + static let chungnam = NMGLatLng(lat: 36.6588, lng: 126.6728) + static let sejong = NMGLatLng(lat: 36.4801, lng: 127.2892) + static let jeonbuk = NMGLatLng(lat: 35.7175, lng: 127.1530) + static let jeonnam = NMGLatLng(lat: 34.8679, lng: 126.9910) + static let gyeongbuk = NMGLatLng(lat: 36.4919, lng: 128.8889) + static let gyeongnam = NMGLatLng(lat: 35.4606, lng: 128.2132) + static let gangwon = NMGLatLng(lat: 37.8228, lng: 128.1555) + static let jeju = NMGLatLng(lat: 33.4890, lng: 126.4983) } enum RegionType { @@ -26,241 +26,241 @@ enum RegionType { case metropolitan case province -} - -struct RegionDefinitions { - // 서울 클러스터 - static let seoulClusters: [RegionCluster] = [ - RegionCluster( - name: "도봉/노원/강북/중랑", - subRegions: ["도봉구", "노원구", "강북구", "중랑구"], - coordinate: CLLocationCoordinate2D(latitude: 37.6494, longitude: 127.0510), - type: .seoul - ), - RegionCluster( - name: "동대문/성북", - subRegions: ["동대문구", "성북구"], - coordinate: CLLocationCoordinate2D(latitude: 37.5894, longitude: 127.0435), - type: .seoul - ), - RegionCluster( - name: "중구/종로", - subRegions: ["중구", "종로구"], - coordinate: CLLocationCoordinate2D(latitude: 37.5738, longitude: 126.9861), - type: .seoul - ), - RegionCluster( - name: "성동/광진", - subRegions: ["성동구", "광진구"], - coordinate: CLLocationCoordinate2D(latitude: 37.5509, longitude: 127.0403), - type: .seoul - ), - RegionCluster( - name: "송파/강동", - subRegions: ["송파구", "강동구"], - coordinate: CLLocationCoordinate2D(latitude: 37.5145, longitude: 127.1058), - type: .seoul - ), - RegionCluster( - name: "동작/관악", - subRegions: ["동작구", "관악구"], - coordinate: CLLocationCoordinate2D(latitude: 37.4959, longitude: 126.9410), - type: .seoul - ), - RegionCluster( - name: "서초/강남", - subRegions: ["서초구", "강남구"], - coordinate: CLLocationCoordinate2D(latitude: 37.4959, longitude: 127.0664), - type: .seoul - ), - RegionCluster( - name: "은평/서대문/마포", - subRegions: ["은평구", "서대문구", "마포구"], - coordinate: CLLocationCoordinate2D(latitude: 37.5744, longitude: 126.9185), - type: .seoul - ), - RegionCluster( - name: "영등포/구로", - subRegions: ["영등포구", "구로구"], - coordinate: CLLocationCoordinate2D(latitude: 37.5162, longitude: 126.8968), - type: .seoul - ), - RegionCluster( - name: "용산", - subRegions: ["용산구"], - coordinate: CLLocationCoordinate2D(latitude: 37.5384, longitude: 126.9654), - type: .seoul - ), - RegionCluster( - name: "양천/강서/금천", - subRegions: ["양천구", "강서구", "금천구"], - coordinate: CLLocationCoordinate2D(latitude: 37.5509, longitude: 126.8553), - type: .seoul - ) - ] + struct RegionDefinitions { + // 서울 클러스터 + static let seoulClusters: [RegionCluster] = [ + RegionCluster( + name: "도봉/노원/강북/중랑", + subRegions: ["도봉구", "노원구", "강북구", "중랑구"], + coordinate: NMGLatLng(lat: 37.6494, lng: 127.0510), + type: .seoul + ), + RegionCluster( + name: "동대문/성북", + subRegions: ["동대문구", "성북구"], + coordinate: NMGLatLng(lat: 37.5894, lng: 127.0435), + type: .seoul + ), + RegionCluster( + name: "중구/종로", + subRegions: ["중구", "종로구"], + coordinate: NMGLatLng(lat: 37.5738, lng: 126.9861), + type: .seoul + ), + RegionCluster( + name: "성동/광진", + subRegions: ["성동구", "광진구"], + coordinate: NMGLatLng(lat: 37.5509, lng: 127.0403), + type: .seoul + ), + RegionCluster( + name: "송파/강동", + subRegions: ["송파구", "강동구"], + coordinate: NMGLatLng(lat: 37.5145, lng: 127.1058), + type: .seoul + ), + RegionCluster( + name: "동작/관악", + subRegions: ["동작구", "관악구"], + coordinate: NMGLatLng(lat: 37.4959, lng: 126.9410), + type: .seoul + ), + RegionCluster( + name: "서초/강남", + subRegions: ["서초구", "강남구"], + coordinate: NMGLatLng(lat: 37.4959, lng: 127.0664), + type: .seoul + ), + RegionCluster( + name: "은평/서대문/마포", + subRegions: ["은평구", "서대문구", "마포구"], + coordinate: NMGLatLng(lat: 37.5744, lng: 126.9185), + type: .seoul + ), + RegionCluster( + name: "영등포/구로", + subRegions: ["영등포구", "구로구"], + coordinate: NMGLatLng(lat: 37.5162, lng: 126.8968), + type: .seoul + ), + RegionCluster( + name: "용산", + subRegions: ["용산구"], + coordinate: NMGLatLng(lat: 37.5384, lng: 126.9654), + type: .seoul + ), + RegionCluster( + name: "양천/강서/금천", + subRegions: ["양천구", "강서구", "금천구"], + coordinate: NMGLatLng(lat: 37.5509, lng: 126.8553), + type: .seoul + ) + ] - // 경기도 클러스터 - static let gyeonggiClusters: [RegionCluster] = [ - RegionCluster( - name: "포천/연천/동두천/양주", - subRegions: ["포천시", "연천군", "동두천시", "양주시"], - coordinate: CLLocationCoordinate2D(latitude: 37.8859, longitude: 127.0543), - type: .gyeonggi - ), - RegionCluster( - name: "의정부/구리/남양주", - subRegions: ["의정부시", "구리시", "남양주시"], - coordinate: CLLocationCoordinate2D(latitude: 37.7358, longitude: 127.1422), - type: .gyeonggi - ), - RegionCluster( - name: "파주/고양/가평", - subRegions: ["파주시", "고양시", "가평군"], - coordinate: CLLocationCoordinate2D(latitude: 37.7599, longitude: 126.7762), - type: .gyeonggi - ), - RegionCluster( - name: "용인/화성/수원", - subRegions: ["용인시", "화성시", "수원시"], - coordinate: CLLocationCoordinate2D(latitude: 37.2911, longitude: 127.0876), - type: .gyeonggi - ), - RegionCluster( - name: "군포/의왕/과천/안양", - subRegions: ["군포시", "의왕시", "과천시", "안양시"], - coordinate: CLLocationCoordinate2D(latitude: 37.3956, longitude: 126.9477), - type: .gyeonggi - ), - RegionCluster( - name: "부천/광명/시흥/안산", - subRegions: ["부천시", "광명시", "시흥시", "안산시"], - coordinate: CLLocationCoordinate2D(latitude: 37.4563, longitude: 126.8040), - type: .gyeonggi - ), - RegionCluster( - name: "안성/평택/오산", - subRegions: ["안성시", "평택시", "오산시"], - coordinate: CLLocationCoordinate2D(latitude: 37.0042, longitude: 127.2003), - type: .gyeonggi - ), - RegionCluster( - name: "여주/양평/광주/이천", - subRegions: ["여주시", "양평군", "광주시", "이천시"], - coordinate: CLLocationCoordinate2D(latitude: 37.2958, longitude: 127.5986), - type: .gyeonggi - ), - RegionCluster( - name: "김포", - subRegions: ["김포시"], - coordinate: CLLocationCoordinate2D(latitude: 37.6153, longitude: 126.7164), - type: .gyeonggi - ), - RegionCluster( - name: "성남/하남", - subRegions: ["성남시", "하남시"], - coordinate: CLLocationCoordinate2D(latitude: 37.4517, longitude: 127.1486), - type: .gyeonggi - ) - ] + // 경기도 클러스터 + static let gyeonggiClusters: [RegionCluster] = [ + RegionCluster( + name: "포천/연천/동두천/양주", + subRegions: ["포천시", "연천군", "동두천시", "양주시"], + coordinate: NMGLatLng(lat: 37.8859, lng: 127.0543), + type: .gyeonggi + ), + RegionCluster( + name: "의정부/구리/남양주", + subRegions: ["의정부시", "구리시", "남양주시"], + coordinate: NMGLatLng(lat: 37.7358, lng: 127.1422), + type: .gyeonggi + ), + RegionCluster( + name: "파주/고양/가평", + subRegions: ["파주시", "고양시", "가평군"], + coordinate: NMGLatLng(lat: 37.7599, lng: 126.7762), + type: .gyeonggi + ), + RegionCluster( + name: "용인/화성/수원", + subRegions: ["용인시", "화성시", "수원시"], + coordinate: NMGLatLng(lat: 37.2911, lng: 127.0876), + type: .gyeonggi + ), + RegionCluster( + name: "군포/의왕/과천/안양", + subRegions: ["군포시", "의왕시", "과천시", "안양시"], + coordinate: NMGLatLng(lat: 37.3956, lng: 126.9477), + type: .gyeonggi + ), + RegionCluster( + name: "부천/광명/시흥/안산", + subRegions: ["부천시", "광명시", "시흥시", "안산시"], + coordinate: NMGLatLng(lat: 37.4563, lng: 126.8040), + type: .gyeonggi + ), + RegionCluster( + name: "안성/평택/오산", + subRegions: ["안성시", "평택시", "오산시"], + coordinate: NMGLatLng(lat: 37.0042, lng: 127.2003), + type: .gyeonggi + ), + RegionCluster( + name: "여주/양평/광주/이천", + subRegions: ["여주시", "양평군", "광주시", "이천시"], + coordinate: NMGLatLng(lat: 37.2958, lng: 127.5986), + type: .gyeonggi + ), + RegionCluster( + name: "김포", + subRegions: ["김포시"], + coordinate: NMGLatLng(lat: 37.6153, lng: 126.7164), + type: .gyeonggi + ), + RegionCluster( + name: "성남/하남", + subRegions: ["성남시", "하남시"], + coordinate: NMGLatLng(lat: 37.4517, lng: 127.1486), + type: .gyeonggi + ) + ] - // 광역시 및 기타 지역 - static let metropolitanClusters: [RegionCluster] = [ - RegionCluster( - name: "인천", - subRegions: ["인천광역시"], - coordinate: CLLocationCoordinate2D(latitude: 37.4563, longitude: 126.7052), - type: .metropolitan - ), - RegionCluster( - name: "대전", - subRegions: ["대전광역시"], - coordinate: CLLocationCoordinate2D(latitude: 36.3504, longitude: 127.3845), - type: .metropolitan - ), - RegionCluster( - name: "광주", - subRegions: ["광주광역시"], - coordinate: CLLocationCoordinate2D(latitude: 35.1595, longitude: 126.8526), - type: .metropolitan - ), - RegionCluster( - name: "대구", - subRegions: ["대구광역시"], - coordinate: CLLocationCoordinate2D(latitude: 35.8714, longitude: 128.6014), - type: .metropolitan - ), - RegionCluster( - name: "부산", - subRegions: ["부산광역시"], - coordinate: CLLocationCoordinate2D(latitude: 35.1796, longitude: 129.0756), - type: .metropolitan - ), - RegionCluster( - name: "울산", - subRegions: ["울산광역시"], - coordinate: CLLocationCoordinate2D(latitude: 35.5384, longitude: 129.3114), - type: .metropolitan - ) - ] + // 광역시 클러스터 + static let metropolitanClusters: [RegionCluster] = [ + RegionCluster( + name: "인천", + subRegions: ["인천광역시"], + coordinate: NMGLatLng(lat: 37.4563, lng: 126.7052), + type: .metropolitan + ), + RegionCluster( + name: "대전", + subRegions: ["대전광역시"], + coordinate: NMGLatLng(lat: 36.3504, lng: 127.3845), + type: .metropolitan + ), + RegionCluster( + name: "광주", + subRegions: ["광주광역시"], + coordinate: NMGLatLng(lat: 35.1595, lng: 126.8526), + type: .metropolitan + ), + RegionCluster( + name: "대구", + subRegions: ["대구광역시"], + coordinate: NMGLatLng(lat: 35.8714, lng: 128.6014), + type: .metropolitan + ), + RegionCluster( + name: "부산", + subRegions: ["부산광역시"], + coordinate: NMGLatLng(lat: 35.1796, lng: 129.0756), + type: .metropolitan + ), + RegionCluster( + name: "울산", + subRegions: ["울산광역시"], + coordinate: NMGLatLng(lat: 35.5384, lng: 129.3114), + type: .metropolitan + ) + ] - static let provinceClusters: [RegionCluster] = [ - RegionCluster( - name: "충북", - subRegions: ["충청북도"], - coordinate: CLLocationCoordinate2D(latitude: 36.6357, longitude: 127.4914), - type: .province - ), - RegionCluster( - name: "충남", - subRegions: ["충청남도"], - coordinate: CLLocationCoordinate2D(latitude: 36.6588, longitude: 126.6728), - type: .province - ), - RegionCluster( - name: "세종", - subRegions: ["세종특별자치시"], - coordinate: CLLocationCoordinate2D(latitude: 36.4801, longitude: 127.2892), - type: .province - ), - RegionCluster( - name: "전북", - subRegions: ["전라북도"], - coordinate: CLLocationCoordinate2D(latitude: 35.7175, longitude: 127.1530), - type: .province - ), - RegionCluster( - name: "전남", - subRegions: ["전라남도"], - coordinate: CLLocationCoordinate2D(latitude: 34.8679, longitude: 126.9910), - type: .province - ), - RegionCluster( - name: "경북", - subRegions: ["경상북도"], - coordinate: CLLocationCoordinate2D(latitude: 36.4919, longitude: 128.8889), - type: .province - ), - RegionCluster( - name: "경남", - subRegions: ["경상남도"], - coordinate: CLLocationCoordinate2D(latitude: 35.4606, longitude: 128.2132), - type: .province - ), - RegionCluster( - name: "강원", - subRegions: ["강원도"], - coordinate: CLLocationCoordinate2D(latitude: 37.8228, longitude: 128.1555), - type: .province - ), - RegionCluster( - name: "제주", - subRegions: ["제주특별자치도"], - coordinate: CLLocationCoordinate2D(latitude: 33.4890, longitude: 126.4983), - type: .province - ) - ] + // 도 클러스터 + static let provinceClusters: [RegionCluster] = [ + RegionCluster( + name: "충북", + subRegions: ["충청북도"], + coordinate: NMGLatLng(lat: 36.6357, lng: 127.4914), + type: .province + ), + RegionCluster( + name: "충남", + subRegions: ["충청남도"], + coordinate: NMGLatLng(lat: 36.6588, lng: 126.6728), + type: .province + ), + RegionCluster( + name: "세종", + subRegions: ["세종특별자치시"], + coordinate: NMGLatLng(lat: 36.4801, lng: 127.2892), + type: .province + ), + RegionCluster( + name: "전북", + subRegions: ["전라북도"], + coordinate: NMGLatLng(lat: 35.7175, lng: 127.1530), + type: .province + ), + RegionCluster( + name: "전남", + subRegions: ["전라남도"], + coordinate: NMGLatLng(lat: 34.8679, lng: 126.9910), + type: .province + ), + RegionCluster( + name: "경북", + subRegions: ["경상북도"], + coordinate: NMGLatLng(lat: 36.4919, lng: 128.8889), + type: .province + ), + RegionCluster( + name: "경남", + subRegions: ["경상남도"], + coordinate: NMGLatLng(lat: 35.4606, lng: 128.2132), + type: .province + ), + RegionCluster( + name: "강원", + subRegions: ["강원도"], + coordinate: NMGLatLng(lat: 37.8228, lng: 128.1555), + type: .province + ), + RegionCluster( + name: "제주", + subRegions: ["제주특별자치도"], + coordinate: NMGLatLng(lat: 33.4890, lng: 126.4983), + type: .province + ) + ] - static var allClusters: [RegionCluster] { - seoulClusters + gyeonggiClusters + metropolitanClusters + provinceClusters + static var allClusters: [RegionCluster] { + seoulClusters + gyeonggiClusters + metropolitanClusters + provinceClusters + } } } diff --git a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FullScreenMapViewController.swift b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FullScreenMapViewController.swift index 690afbd7..1cf7084a 100644 --- a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FullScreenMapViewController.swift +++ b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FullScreenMapViewController.swift @@ -1,186 +1,153 @@ -import Foundation import UIKit +import SnapKit import RxSwift -import ReactorKit +import RxCocoa +import NMapsMap import CoreLocation -import GoogleMaps -final class FullScreenMapViewController: MapViewController { - var selectedStore: MapPopUpStore? - var shouldAutoSelectNearestStore = false // 일반 모드와 다르게 false로 설정 +class FullScreenMapViewController: MapViewController { + // MARK: - Properties + private var initialStore: MapPopUpStore? + // MARK: - Initialization + init(store: MapPopUpStore?) { + self.initialStore = store + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() + setupFullScreenUI() + configureInitialMapPosition() + + // 풀스크린 모드에서 별도 터치 델리게이트 설정 + mainView.mapView.touchDelegate = self + } - self.navigationController?.navigationBar.isHidden = false - setupNavigation() + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + navigationController?.setNavigationBarHidden(false, animated: false) + tabBarController?.tabBar.isHidden = true + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + navigationController?.setNavigationBarHidden(false, animated: false) + tabBarController?.tabBar.isHidden = false + navigationItem.title = "찾아가는 길" + } - mainView.searchFilterContainer.isHidden = true + // MARK: - Setup + private func setupFullScreenUI() { mainView.filterChips.isHidden = true mainView.listButton.isHidden = true + mainView.locationButton.isHidden = true + mainView.searchInput.isHidden = true carouselView.isHidden = false - // 지도 델리게이트 재설정 - mainView.mapView.delegate = self + // 닫기 버튼 추가 + let closeButton = UIButton(type: .system) + closeButton.setImage(UIImage(systemName: "xmark"), for: .normal) + closeButton.tintColor = .black + view.addSubview(closeButton) - // 선택된 스토어가 있다면 즉시 마커 탭 처리 (요구사항 1) - if let store = selectedStore { - updateUI(for: store) + closeButton.snp.makeConstraints { make in + make.top.equalTo(view.safeAreaLayoutGuide).offset(16) + make.trailing.equalToSuperview().offset(-16) + make.width.height.equalTo(44) } - } - - - // MARK: - Binding - override func bind(reactor: Reactor) { - super.bind(reactor: reactor) - - // [변경] 기존 viewportStores 관련 바인딩은 풀스크린에서 marker tap 처리와 충돌할 수 있으므로 주석처리하거나 제거 - /* - reactor.state - .map { $0.viewportStores } - .distinctUntilChanged() - .debounce(.milliseconds(300), scheduler: MainScheduler.instance) - .observe(on: MainScheduler.instance) - .subscribe(onNext: { [weak self] stores in - self?.currentStores = stores - self?.updateMapWithClustering() - }) - .disposed(by: disposeBag) - */ - - // searchResult나 selectedStore 변경시에만 UI 업데이트 (요구사항 1) - reactor.state - .map { $0.selectedStore ?? $0.searchResult } - .distinctUntilChanged { $0?.id == $1?.id } - .compactMap { $0 } - .filter { [weak self] store in - // 현재 선택된 스토어와 다른 경우에만 업데이트 - self?.selectedStore?.id != store.id - } - .observe(on: MainScheduler.instance) - .subscribe(onNext: { [weak self] store in - self?.updateUI(for: store) + closeButton.rx.tap + .subscribe(onNext: { [weak self] in + self?.dismiss(animated: true, completion: nil) }) - .disposed(by: disposeBag) - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - self.navigationController?.navigationBar.isHidden = false - } + .disposed(by: disposeBag) // 상위 클래스의 disposeBag 사용 - // MARK: - Map Delegate Methods - override func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool { - // (1) 구/시 단위 클러스터 - if let clusterData = marker.userData as? ClusterMarkerData { - return handleRegionalClusterTap(marker, clusterData: clusterData) + mainView.mapView.snp.remakeConstraints { make in + make.edges.equalToSuperview() } - // (2) 동일 좌표 마이크로 클러스터 - else if let storeArray = marker.userData as? [MapPopUpStore] { - if storeArray.count > 1 { - return handleMicroClusterTap(marker, storeArray: storeArray) - } else if let singleStore = storeArray.first { - return handleSingleStoreTap(marker, store: singleStore) - } - } - // (3) 단일 스토어 - else if let singleStore = marker.userData as? MapPopUpStore { - return handleSingleStoreTap(marker, store: singleStore) + + carouselView.snp.remakeConstraints { make in + make.leading.trailing.equalToSuperview() + make.height.equalTo(140) + make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-16) } - return false } - override func mapView(_ mapView: GMSMapView, didChange position: GMSCameraPosition) { - // 카메라 이동 중 별도 처리 없음 - } + private func configureInitialMapPosition() { + guard let store = initialStore else { return } + + let position = NMGLatLng(lat: store.latitude, lng: store.longitude) + let cameraUpdate = NMFCameraUpdate(scrollTo: position, zoomTo: 15.0) + mainView.mapView.moveCamera(cameraUpdate) + + // 여기서 마커를 선택 상태로 추가 + let marker = NMFMarker() + marker.position = position + marker.userInfo = ["storeData": store] + updateMarkerStyle(marker: marker, selected: true, isCluster: false) // 선택된 스타일 적용 + marker.mapView = mainView.mapView + currentMarker = marker - override func mapView(_ mapView: GMSMapView, didTapAt coordinate: CLLocationCoordinate2D) { - // 지도 빈 공간 탭은 무시 + // 캐러셀 설정 + currentCarouselStores = [store] + carouselView.updateCards([store]) + carouselView.isHidden = false } - private func findMarkerForStore(for store: MapPopUpStore) -> GMSMarker? { - if let marker = self.currentMarker, - let markerStore = marker.userData as? MapPopUpStore, - markerStore.id == store.id { - return marker - } - return nil + // MARK: - Override Methods + override func bind(reactor: MapReactor) { + super.bind(reactor: reactor) + // 풀스크린 모드에서 추가 바인딩 필요 시 여기에 작성 } - /// 선택된 스토어 정보를 기반으로 마커, 카메라, 캐러셀을 업데이트 (요구사항 1) - private func updateUI(for store: MapPopUpStore) { - // 기존 마커 제거 - mainView.mapView.clear() - - // 새 마커 생성 - let marker = GMSMarker() - marker.position = store.coordinate - marker.userData = store - marker.groundAnchor = CGPoint(x: 0.5, y: 1.0) - - // 마커 뷰 생성 및 선택 상태 주입 - let selectedInput = MapMarker.Input( - isSelected: true, - isCluster: false, - regionName: "", - count: 1, - isMultiMarker: false - ) - let markerView = MapMarker() - markerView.injection(with: selectedInput) - marker.iconView = markerView - - // 마커를 지도에 추가 - marker.map = mainView.mapView - currentMarker = marker + override func handleSingleStoreTap(_ marker: NMFMarker, store: MapPopUpStore) -> Bool { + isMovingToMarker = true - mainView.mapView.selectedMarker = marker + if let previousMarker = currentMarker, previousMarker != marker { + updateMarkerStyle(marker: previousMarker, selected: false, isCluster: false) + } - // 카메라 이동 - let camera = GMSCameraPosition.camera( - withLatitude: store.latitude, - longitude: store.longitude, - zoom: 16 - ) - mainView.mapView.animate(to: camera) + updateMarkerStyle(marker: marker, selected: true, isCluster: false) + currentMarker = marker - // 캐러셀 업데이트 - carouselView.updateCards([store]) currentCarouselStores = [store] + carouselView.updateCards([store]) carouselView.isHidden = false + mainView.setStoreCardHidden(false, animated: true) - // 약간의 딜레이 후 마커 뷰 재갱신 (필요 시) - DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { - markerView.injection(with: selectedInput) - markerView.setNeedsLayout() - markerView.layoutIfNeeded() - } - } + let cameraUpdate = NMFCameraUpdate(scrollTo: marker.position, zoomTo: 15.0) + cameraUpdate.animation = .easeIn + cameraUpdate.animationDuration = 0.3 + mainView.mapView.moveCamera(cameraUpdate) + isMovingToMarker = false + return true + } - @objc private func backButtonTapped() { - dismiss(animated: true) + override func handleRegionalClusterTap(_ marker: NMFMarker, clusterData: ClusterMarkerData) -> Bool { + return false // 풀스크린에서는 클러스터 비활성화 } - private func setupNavigation() { - navigationItem.title = "찾아가는 길" - let appearance = UINavigationBarAppearance() - appearance.configureWithOpaqueBackground() - appearance.shadowColor = .clear - appearance.backgroundColor = .white - appearance.titleTextAttributes = [ - .foregroundColor: UIColor.black, - .font: UIFont.systemFont(ofSize: 15, weight: .regular) - ] - navigationController?.navigationBar.standardAppearance = appearance - navigationController?.navigationBar.scrollEdgeAppearance = appearance - navigationItem.leftBarButtonItem = UIBarButtonItem( - image: UIImage(named: "bakcbutton")?.withRenderingMode(.alwaysOriginal), - style: .plain, - target: self, - action: #selector(backButtonTapped) - ) - navigationItem.leftBarButtonItem?.imageInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) + override func handleMicroClusterTap(_ marker: NMFMarker, storeArray: [MapPopUpStore]) -> Bool { + return false // 풀스크린에서는 마이크로 클러스터 비활성화 } } + +// MARK: - NMFMapViewTouchDelegate +//extension FullScreenMapViewController: NMFMapViewTouchDelegate { +// func mapView(_ mapView: NMFMapView, didTapOverlay overlay: NMFOverlay) -> Bool { +// guard let marker = overlay as? NMFMarker, +// let store = marker.userInfo["storeData"] as? MapPopUpStore else { return false } +// return handleSingleStoreTap(marker, store: store) +// } +// +// func mapView(_ mapView: NMFMapView, didTapMap latlng: NMGLatLng, point: CGPoint) { +// // 풀스크린 모드에서는 지도 탭 시 동작 없음 +// } +//} diff --git a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideAppService.swift b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideAppService.swift new file mode 100644 index 00000000..aab14ce8 --- /dev/null +++ b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideAppService.swift @@ -0,0 +1,71 @@ +import UIKit +import CoreLocation + +enum MapAppType { + case naver + case kakao + case apple + + static func from(string: String) -> MapAppType? { + switch string.lowercased() { + case "naver": + return .naver + case "kakao": + return .kakao + case "apple", "applemap": + return .apple + default: + return nil + } + } + + func urlScheme(coordinate: CLLocationCoordinate2D, name: String, address: String) -> String { + let encodedName = name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "" + let encodedAddress = address.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "" + + switch self { + case .naver: + return "nmap://place?lat=\(coordinate.latitude)&lng=\(coordinate.longitude)&name=\(encodedName)&addr=\(encodedAddress)&appname=com.poppool.app" + case .kakao: + return "kakaomap://look?p=\(coordinate.latitude),\(coordinate.longitude)" + case .apple: + return "maps://?q=\(encodedName)&ll=\(coordinate.latitude),\(coordinate.longitude)&z=16" + } + } + + var appStoreURL: String { + switch self { + case .naver: + return "https://apps.apple.com/kr/app/id311867728" + case .kakao: + return "https://apps.apple.com/kr/app/id304608425" + case .apple: + return "https://apps.apple.com/kr/app/id1108185179" + } + } +} + +class MapAppService { + static func openMapApp(_ appTypeString: String, coordinate: CLLocationCoordinate2D, name: String, address: String) -> Observable { + guard let appType = MapAppType.from(string: appTypeString) else { + return Observable.just("지원하지 않는 맵 앱입니다.") + } + + let urlScheme = appType.urlScheme(coordinate: coordinate, name: name, address: address) + + Logger.log(message: "🗺 맵 앱 열기 시도: \(urlScheme)", category: .debug) + + if let url = URL(string: urlScheme), UIApplication.shared.canOpenURL(url) { + Logger.log(message: "✅ \(appType) 앱 실행", category: .debug) + UIApplication.shared.open(url, options: [:], completionHandler: nil) + return Observable.empty() + } else { + Logger.log(message: "❌ \(appType) 앱 미설치 - 앱스토어로 이동", category: .debug) + if let appStoreURL = URL(string: appType.appStoreURL) { + UIApplication.shared.open(appStoreURL, options: [:], completionHandler: nil) + return Observable.just("\(appTypeString) 앱이 설치되어 있지 않아 앱스토어로 이동합니다.") + } + return Observable.just("앱을 열 수 없습니다.") + } + } +} diff --git a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift index c07ed2b8..12c65b08 100644 --- a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift +++ b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift @@ -1,15 +1,15 @@ import UIKit import SnapKit -import GoogleMaps import ReactorKit import RxSwift import CoreLocation +import NMapsMap final class MapGuideViewController: UIViewController, View { // MARK: - Properties var disposeBag = DisposeBag() private let popUpStoreId: Int64 - private var currentCarouselStores: [MapPopUpStore] = [] // 현재 선택된 스토어 목록 + private var currentCarouselStores: [MapPopUpStore] = [] // 현재 선택된 스토어 목록 init(popUpStoreId: Int64) { self.popUpStoreId = popUpStoreId @@ -54,16 +54,14 @@ final class MapGuideViewController: UIViewController, View { return btn }() - private let mapView: GMSMapView = { - let map = GMSMapView() - map.isMyLocationEnabled = false + private let mapView: NMFMapView = { + let map = NMFMapView() map.layer.borderWidth = 1 map.layer.borderColor = UIColor.g100.cgColor map.layer.cornerRadius = 12 return map }() - /// 지도 우상단 "Expandable" 버튼 private let expandButton: UIButton = { let btn = UIButton() btn.setImage(UIImage(named: "Expandable"), for: .normal) @@ -102,9 +100,9 @@ final class MapGuideViewController: UIViewController, View { return btn }() - private let appleButton: UIButton = { + private let tmapButton: UIButton = { let btn = UIButton() - btn.setImage(UIImage(named: "AppleMap"), for: .normal) + btn.setImage(UIImage(named: "TMap"), for: .normal) btn.layer.cornerRadius = 24 btn.layer.borderWidth = 1 btn.layer.borderColor = UIColor.g100.cgColor @@ -140,14 +138,16 @@ final class MapGuideViewController: UIViewController, View { .map { Reactor.Action.openMapApp("kakao") } .bind(to: reactor.action) .disposed(by: disposeBag) - appleButton.rx.tap - .map { Reactor.Action.openMapApp("apple") } + tmapButton.rx.tap + .map { Reactor.Action.openMapApp("tmap") } .bind(to: reactor.action) .disposed(by: disposeBag) expandButton.rx.tap + .throttle(.milliseconds(300), scheduler: MainScheduler.instance) .subscribe(onNext: { [weak self] in guard let self = self else { return } + let provider = ProviderImpl() let useCase = DefaultMapUseCase(repository: DefaultMapRepository(provider: provider)) let directionRepository = DefaultMapDirectionRepository(provider: provider) @@ -155,10 +155,9 @@ final class MapGuideViewController: UIViewController, View { if let selectedStore = self.currentCarouselStores.first { reactor.action.onNext(.didSelectItem(selectedStore)) - reactor.action.onNext(.viewDidLoad(self.popUpStoreId)) - let fullScreenMapVC = FullScreenMapViewController() - fullScreenMapVC.selectedStore = selectedStore // 직접 주입 + // store: 매개변수명 사용 + let fullScreenMapVC = FullScreenMapViewController(store: selectedStore) fullScreenMapVC.reactor = reactor let nav = UINavigationController(rootViewController: fullScreenMapVC) @@ -166,24 +165,28 @@ final class MapGuideViewController: UIViewController, View { self.present(nav, animated: true) } else { reactor.action.onNext(.viewDidLoad(self.popUpStoreId)) + reactor.state .map { $0.searchResult } .distinctUntilChanged() .compactMap { $0 } .take(1) - .subscribe(onNext: { [weak self] store in - let fullScreenMapVC = FullScreenMapViewController() + .subscribe(onNext: { store in + // store: 매개변수명 사용 + let fullScreenMapVC = FullScreenMapViewController(store: store) fullScreenMapVC.reactor = reactor let nav = UINavigationController(rootViewController: fullScreenMapVC) nav.modalPresentationStyle = .fullScreen - self?.present(nav, animated: true) + self.present(nav, animated: true) }) .disposed(by: self.disposeBag) } }) .disposed(by: disposeBag) + + // 목적지 좌표로 마커 및 카메라 설정 reactor.state .map { $0.destinationCoordinate } .compactMap { $0 } @@ -192,6 +195,17 @@ final class MapGuideViewController: UIViewController, View { }) .disposed(by: disposeBag) + // searchResult로 currentCarouselStores 업데이트 + reactor.state + .map { $0.searchResult } + .distinctUntilChanged() + .compactMap { $0 } + .subscribe(onNext: { [weak self] store in + self?.currentCarouselStores = [store] + }) + .disposed(by: disposeBag) + + // Dismiss 처리 reactor.state .map { $0.shouldDismiss } .distinctUntilChanged() @@ -204,8 +218,7 @@ final class MapGuideViewController: UIViewController, View { // MARK: - UI Setup private func setupUI() { - view.backgroundColor = .clear - + view.backgroundColor = .white view.addSubview(dimmingView) dimmingView.snp.makeConstraints { $0.edges.equalToSuperview() } @@ -252,7 +265,7 @@ final class MapGuideViewController: UIViewController, View { } let bottomContainer = UIView() - modalCardView.addSubview(bottomContainer) + modalCardView.addSubview(bottomContainer) // 오타 수정 필요: bottomContainer로 변경 bottomContainer.snp.makeConstraints { make in make.top.equalTo(mapView.snp.bottom).offset(20) make.leading.trailing.equalToSuperview().inset(20) @@ -266,18 +279,17 @@ final class MapGuideViewController: UIViewController, View { make.centerY.equalToSuperview() } - // 티맵 버튼 제거, 네이버/카카오/애플맵만 포함 - let appStack = UIStackView(arrangedSubviews: [naverButton, kakaoButton, appleButton]) + let appStack = UIStackView(arrangedSubviews: [naverButton, kakaoButton, tmapButton]) appStack.axis = .horizontal appStack.alignment = .center - appStack.spacing = 16 // 버튼이 3개로 줄어 간격 다시 늘림 + appStack.spacing = 16 appStack.distribution = .fillEqually bottomContainer.addSubview(appStack) appStack.snp.makeConstraints { make in make.trailing.equalToSuperview() make.centerY.equalToSuperview() - [naverButton, kakaoButton, appleButton].forEach { button in + [naverButton, kakaoButton, tmapButton].forEach { button in button.snp.makeConstraints { make in make.size.equalTo(CGSize(width: 48, height: 48)) } @@ -300,37 +312,38 @@ final class MapGuideViewController: UIViewController, View { } private func setupMarker(at coordinate: CLLocationCoordinate2D) { - // 새 마커 생성 및 설정 - let marker = GMSMarker() - marker.position = coordinate - marker.groundAnchor = CGPoint(x: 0.5, y: 1.0) - marker.appearAnimation = .none + // 기존 마커 제거 + mapView.subviews.forEach { if $0 is NMFMarker { $0.removeFromSuperview() } } - let markerView = MapMarker() - markerView.injection(with: .init(isSelected: true)) - marker.iconView = markerView - - // 카메라 위치 설정 - let camera = GMSCameraPosition(target: coordinate, zoom: 16) - - // 애니메이션과 마커 변경을 하나의 트랜잭션으로 처리 - CATransaction.begin() - CATransaction.setDisableActions(true) - - // 카메라 이동과 마커 설정을 동시에 처리 - mapView.animate(to: camera) - marker.map = mapView + // 새 마커 생성 및 설정 + let marker = NMFMarker() + marker.position = NMGLatLng(lat: coordinate.latitude, lng: coordinate.longitude) + marker.iconImage = NMFOverlayImage(name: "TapMarker") // MapViewController에서 사용하는 기본 마커 + marker.width = 32 + marker.height = 32 + marker.anchor = CGPoint(x: 0.5, y: 1.0) + + // 먼저 마커를 지도에 추가 + marker.mapView = mapView + + // 그 다음 카메라 위치 설정 + let cameraUpdate = NMFCameraUpdate( + scrollTo: NMGLatLng(lat: coordinate.latitude, lng: coordinate.longitude), + zoomTo: 15.0 + ) + cameraUpdate.animation = .easeIn + cameraUpdate.animationDuration = 0.3 + mapView.moveCamera(cameraUpdate) - CATransaction.commit() } private func dismissModalCard() { UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseIn) { -// self.dimmingView.alpha = 0 + self.dimmingView.alpha = 0 self.modalCardBottomConstraint?.update(offset: 408) self.view.layoutIfNeeded() } completion: { _ in - self.navigationController?.popViewController(animated: false) + self.dismiss(animated: false) } } } diff --git a/Poppool/Poppool/Presentation/Map/MapView/MapMarker.swift b/Poppool/Poppool/Presentation/Map/MapView/MapMarker.swift index a90eb377..f15cc71d 100644 --- a/Poppool/Poppool/Presentation/Map/MapView/MapMarker.swift +++ b/Poppool/Poppool/Presentation/Map/MapView/MapMarker.swift @@ -1,13 +1,12 @@ import UIKit import SnapKit -import GoogleMaps +import NMapsMap final class MapMarker: UIView { // MARK: - Components private(set) var isSelected: Bool = false var currentInput: Input? - private let markerImageView: UIImageView = { let imageView = UIImageView() imageView.image = UIImage(named: "Marker") @@ -181,8 +180,6 @@ extension MapMarker: Inputable { CATransaction.commit() } - - private func setupClusterMarker(_ input: Input) { markerImageView.isHidden = true clusterContainer.isHidden = false @@ -223,7 +220,21 @@ extension MapMarker { var imageView: UIImageView { return markerImageView } + + /// 네이버맵용으로 뷰를 UIImage로 렌더링하는 함수 + func asImage() -> UIImage? { + // 필요한 경우 프레임을 강제로 업데이트합니다. + self.layoutIfNeeded() + UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, UIScreen.main.scale) + defer { UIGraphicsEndImageContext() } + if let context = UIGraphicsGetCurrentContext() { + self.layer.render(in: context) + return UIGraphicsGetImageFromCurrentImageContext() + } + return nil + } } + extension MapMarker.Input: Equatable { static func == (lhs: MapMarker.Input, rhs: MapMarker.Input) -> Bool { return lhs.isSelected == rhs.isSelected && diff --git a/Poppool/Poppool/Presentation/Map/MapView/MapReactor.swift b/Poppool/Poppool/Presentation/Map/MapView/MapReactor.swift index 8603c691..f2f6143e 100644 --- a/Poppool/Poppool/Presentation/Map/MapView/MapReactor.swift +++ b/Poppool/Poppool/Presentation/Map/MapView/MapReactor.swift @@ -15,6 +15,7 @@ final class MapReactor: Reactor { case fetchCategories case updateBothFilters(locations: [String], categories: [String]) // 새로 추가 case didSelectItem(MapPopUpStore) + case fetchAllStores case refreshMarkers(northEastLat: Double, northEastLon: Double, southWestLat: Double, southWestLon: Double) case viewportChanged( northEastLat: Double, @@ -298,6 +299,43 @@ final class MapReactor: Reactor { ) return .setSearchResult(store) } + case .fetchAllStores: + // 한국 전체 영역에 대한 바운드 설정 + let koreaRegion = ( + northEast: (lat: 38.0, lon: 132.0), + southWest: (lat: 33.0, lon: 124.0) + ) + + let categoryIDs = currentState.selectedCategoryFilters + .compactMap { currentState.categoryMapping[$0] } + + return .concat([ + .just(.setLoading(true)), + useCase.fetchStoresInBounds( + northEastLat: koreaRegion.northEast.lat, + northEastLon: koreaRegion.northEast.lon, + southWestLat: koreaRegion.southWest.lat, + southWestLon: koreaRegion.southWest.lon, + categories: categoryIDs + ) + .map { stores -> Mutation in + var filteredStores = stores + + let locationFilters = self.currentState.selectedLocationFilters + if !locationFilters.isEmpty { + filteredStores = stores.filter { store in + return locationFilters.contains { filter in + return self.store(store, matches: filter) + } + } + } + + return .setViewportStores(filteredStores) + } + .catch { error in .just(.setError(error)) }, + .just(.setLoading(false)) + ]) + case let .didSelectItem(store): return .concat([ diff --git a/Poppool/Poppool/Presentation/Map/MapView/MapView.swift b/Poppool/Poppool/Presentation/Map/MapView/MapView.swift index 30d4f7c2..3df6ad08 100644 --- a/Poppool/Poppool/Presentation/Map/MapView/MapView.swift +++ b/Poppool/Poppool/Presentation/Map/MapView/MapView.swift @@ -1,19 +1,21 @@ import UIKit import SnapKit -import GoogleMaps +import NMapsMap final class MapView: UIView { // MARK: - Components - let mapView: GMSMapView = { - let camera = GMSCameraPosition(latitude: 37.5666, longitude: 126.9784, zoom: 14) - let view = GMSMapView(frame: .zero, camera: camera) - view.settings.myLocationButton = false - view.setMinZoom(7.5, maxZoom: 20) + let mapView: NMFMapView = { + let view = NMFMapView() + view.positionMode = .disabled + view.zoomLevel = 14 - let southWest = CLLocationCoordinate2D(latitude: 33.0, longitude: 124.0) - let northEast = CLLocationCoordinate2D(latitude: 39.0, longitude: 132.0) - let koreaBounds = GMSCoordinateBounds(coordinate: southWest, coordinate: northEast) - view.cameraTargetBounds = koreaBounds + view.extent = NMGLatLngBounds( + southWest: NMGLatLng(lat: 33.0, lng: 124.0), + northEast: NMGLatLng(lat: 39.0, lng: 132.0) + ) + + view.minZoomLevel = 7.5 + view.maxZoomLevel = 20 return view }() @@ -87,28 +89,24 @@ final class MapView: UIView { // MARK: - SetUp private extension MapView { func setUpConstraints() { - // 1. MapView 설정 addSubview(mapView) mapView.snp.makeConstraints { make in make.edges.equalToSuperview() } - // 2. Search Filter Container 설정 addSubview(searchFilterContainer) searchFilterContainer.snp.makeConstraints { make in make.top.equalToSuperview().offset(56) make.leading.trailing.equalToSuperview() } - // 3. Search Input 설정 searchFilterContainer.addSubview(searchInput) searchInput.snp.makeConstraints { make in make.top.equalToSuperview() - make.leading.trailing.equalToSuperview().inset(20) + make.leading.trailing.equalToSuperview().inset(20) make.height.equalTo(37) } - // 4. Filter Chips 설정 searchFilterContainer.addSubview(filterChips) filterChips.snp.makeConstraints { make in make.top.equalTo(searchInput.snp.bottom).offset(7) @@ -117,19 +115,16 @@ private extension MapView { make.bottom.equalToSuperview() } - // 5. Store Card 설정 addSubview(storeCard) storeCard.snp.makeConstraints { make in make.leading.trailing.equalToSuperview().inset(30) make.height.equalTo(137) - make.bottom.equalTo(safeAreaLayoutGuide).offset(-24) // 수정된 부분 + make.bottom.equalTo(safeAreaLayoutGuide).offset(-24) } - // 6. Buttons 설정 addSubview(locationButton) addSubview(listButton) - // 초기 버튼 레이아웃은 updateButtonLayout()에서 설정됨 } func configureUI() { diff --git a/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift b/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift index 52bf64b6..a2c36d29 100644 --- a/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift +++ b/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift @@ -4,15 +4,13 @@ import SnapKit import RxSwift import RxCocoa import ReactorKit -import GoogleMaps +import NMapsMap import CoreLocation import RxGesture - -class MapViewController: BaseViewController, View { +class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NMFMapViewTouchDelegate, NMFMapViewCameraDelegate, UIGestureRecognizerDelegate { typealias Reactor = MapReactor - fileprivate struct CoordinateKey: Hashable { let lat: Int let lng: Int @@ -22,20 +20,18 @@ class MapViewController: BaseViewController, View { self.lng = Int(longitude * 1_000_00) } } - // 전체 스토어 목록 저장 - var allStores: [MapPopUpStore] = [] + var currentTooltipView: UIView? var currentTooltipStores: [MapPopUpStore] = [] - var currentTooltipCoordinate: CLLocationCoordinate2D? - + var currentTooltipCoordinate: NMGLatLng? // MARK: - Properties private var storeDetailsCache: [Int64: StoreItem] = [:] - private var isMovingToMarker = false + var isMovingToMarker = false var currentCarouselStores: [MapPopUpStore] = [] - private var markerDictionary: [Int64: GMSMarker] = [:] - private var individualMarkerDictionary: [Int64: GMSMarker] = [:] - private var clusterMarkerDictionary: [String: GMSMarker] = [:] + private var markerDictionary: [Int64: NMFMarker] = [:] + private var individualMarkerDictionary: [Int64: NMFMarker] = [:] + private var clusterMarkerDictionary: [String: NMFMarker] = [:] private let popUpAPIUseCase = PopUpAPIUseCaseImpl( repository: PopUpAPIRepositoryImpl(provider: ProviderImpl())) private let clusteringManager = ClusteringManager() @@ -44,7 +40,7 @@ class MapViewController: BaseViewController, View { let mainView = MapView() let carouselView = MapPopupCarouselView() private let locationManager = CLLocationManager() - var currentMarker: GMSMarker? + var currentMarker: NMFMarker? private let storeListReactor = StoreListReactor() private let storeListViewController = StoreListViewController(reactor: StoreListReactor()) private var listViewTopConstraint: Constraint? @@ -62,6 +58,7 @@ class MapViewController: BaseViewController, View { } private var modalState: ModalState = .bottom + private let idleSubject = PublishSubject() // MARK: - Lifecycle override func viewDidAppear(_ animated: Bool) { @@ -72,6 +69,7 @@ class MapViewController: BaseViewController, View { self.filterChipsTopY = frameInView.minY } } + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.tabBarController?.tabBar.isHidden = false @@ -80,60 +78,32 @@ class MapViewController: BaseViewController, View { override func viewDidLoad() { super.viewDidLoad() setUp() - mainView.mapView.padding = UIEdgeInsets(top: 0, left: 0, bottom: 10, right: 0) - + mainView.mapView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 10, right: 0) locationManager.delegate = self locationManager.requestWhenInUseAuthorization() locationManager.desiredAccuracy = kCLLocationAccuracyBest - mainView.mapView.isMyLocationEnabled = true + mainView.mapView.positionMode = .compass checkLocationAuthorization() + if let reactor = self.reactor { reactor.action.onNext(.fetchCategories) // 한국 전체 영역에 대한 경계값 설정 let koreaRegion = ( - northEast: CLLocationCoordinate2D(latitude: 38.0, longitude: 132.0), // 한국 북동쪽 끝 - southWest: CLLocationCoordinate2D(latitude: 33.0, longitude: 124.0) // 한국 남서쪽 끝 + northEast: NMGLatLng(lat: 38.0, lng: 132.0), + southWest: NMGLatLng(lat: 33.0, lng: 124.0) ) - // 전체 스토어 가져오기 reactor.action.onNext(.viewportChanged( - northEastLat: koreaRegion.northEast.latitude, - northEastLon: koreaRegion.northEast.longitude, - southWestLat: koreaRegion.southWest.latitude, - southWestLon: koreaRegion.southWest.longitude + northEastLat: koreaRegion.northEast.lat, + northEastLon: koreaRegion.northEast.lng, + southWestLat: koreaRegion.southWest.lat, + southWestLon: koreaRegion.southWest.lng )) - - // 데이터 로드 후 모든 마커 생성 및 추가 - reactor.state - .map { $0.viewportStores } - .distinctUntilChanged() - .filter { !$0.isEmpty } - .take(1) // 초기 1회만 - .observe(on: MainScheduler.instance) - .subscribe(onNext: { [weak self] stores in - guard let self = self else { return } - - // 스토어 정보 저장 - self.allStores = stores - self.currentStores = stores - - // 모든 마커 생성 및 지도에 추가 (즉시 표시) - self.addAllMarkersToMap(stores: stores) - - // 현재 줌 레벨에 맞게 클러스터링 - self.updateMapWithClustering() - - // 가까운 스토어 표시 등 필요한 작업 - if let location = self.locationManager.location { - self.findAndShowNearestStore(from: location) - } - }) - .disposed(by: disposeBag) } - + setupMapViewRxObservables() carouselView.rx.observe(Bool.self, "hidden") .distinctUntilChanged() @@ -153,27 +123,6 @@ class MapViewController: BaseViewController, View { self?.navigationController?.pushViewController(detailController, animated: true) } - mainView.mapView.rx.idleAtPosition - .observe(on: MainScheduler.instance) - .subscribe(onNext: { [weak self] in - guard let self = self else { return } - if let marker = self.currentMarker, - let storeArray = marker.userData as? [MapPopUpStore], - storeArray.count > 1 { - // 툴팁이 없으면 생성, 있으면 위치 업데이트 - if self.currentTooltipView == nil { - self.configureTooltip(for: marker, stores: storeArray) - } else { - self.updateTooltipPosition() - } - } - self.isMovingToMarker = false - }) - .disposed(by: disposeBag) - - - - carouselView.onCardScrolled = { [weak self] pageIndex in guard let self = self, pageIndex >= 0, @@ -182,13 +131,8 @@ class MapViewController: BaseViewController, View { let store = self.currentCarouselStores[pageIndex] // 이전 선택 마커 상태 초기화 - if let previousMarker = self.currentMarker, - let previousMarkerView = previousMarker.iconView as? MapMarker { - previousMarkerView.injection(with: .init( - isSelected: false, - isCluster: false, - count: (previousMarker.userData as? [MapPopUpStore])?.count ?? 1 - )) + if let previousMarker = self.currentMarker { + self.updateMarkerStyle(marker: previousMarker, selected: false, isCluster: false, count: 1) } // 스와이프한 스토어에 해당하는 마커 찾기 @@ -196,19 +140,15 @@ class MapViewController: BaseViewController, View { if let markerToFocus = markerToFocus { // 마커 선택 상태로 업데이트 - if let markerView = markerToFocus.iconView as? MapMarker { - markerView.injection(with: .init( - isSelected: true, - isCluster: false, - count: (markerToFocus.userData as? [MapPopUpStore])?.count ?? 1 - )) - } - + self.updateMarkerStyle(marker: markerToFocus, selected: true, isCluster: false, count: 1) self.currentMarker = markerToFocus // 마이크로 클러스터인 경우 툴팁 처리 - if let storeArray = markerToFocus.userData as? [MapPopUpStore], storeArray.count > 1 { - if self.currentTooltipView == nil || self.currentTooltipCoordinate != markerToFocus.position { + let userData = markerToFocus.userInfo["storeData"] as? [MapPopUpStore] + if let storeArray = userData, storeArray.count > 1 { + if self.currentTooltipView == nil || + self.currentTooltipCoordinate?.lat != markerToFocus.position.lat || + self.currentTooltipCoordinate?.lng != markerToFocus.position.lng { self.configureTooltip(for: markerToFocus, stores: storeArray) } @@ -225,41 +165,37 @@ class MapViewController: BaseViewController, View { } if let reactor = self.reactor { - bindViewport(reactor: reactor) + bindViewport(reactor: reactor) reactor.action.onNext(.fetchCategories) - - } - + } } - private func addAllMarkersToMap(stores: [MapPopUpStore]) { - // 기존 마커 제거 - clearAllMarkers() - - // 모든 스토어에 대해 마커 생성하고 바로 지도에 추가 - for store in stores { - let marker = GMSMarker() - marker.position = store.coordinate - marker.userData = store - marker.groundAnchor = CGPoint(x: 0.5, y: 1.0) - - let markerView = MapMarker() - markerView.injection(with: .init( - isSelected: false, - isCluster: false - )) - marker.iconView = markerView - marker.map = mainView.mapView // 바로 지도에 추가 - individualMarkerDictionary[store.id] = marker - } + // NMF 이벤트 설정을 위한 새로운 메서드 + private func setupMapViewRxObservables() { + // 지도 이동 완료 감지 + mainView.mapView.addCameraDelegate(delegate: self) - Logger.log( - message: "🔍 전체 마커 생성 및 지도에 추가 완료: \(stores.count)개", - category: .debug - ) + // idleAtPosition 대체 - 지도 이동 완료 시 + idleSubject + .observe(on: MainScheduler.instance) + .subscribe(onNext: { [weak self] in + guard let self = self else { return } + if let marker = self.currentMarker, + let storeArray = marker.userInfo["storeData"] as? [MapPopUpStore], + storeArray.count > 1 { + // 툴팁이 없으면 생성, 있으면 위치 업데이트 + if self.currentTooltipView == nil { + self.configureTooltip(for: marker, stores: storeArray) + } else { + self.updateTooltipPosition() + } + } + self.isMovingToMarker = false + }) + .disposed(by: disposeBag) } - private func configureTooltip(for marker: GMSMarker, stores: [MapPopUpStore]) { + private func configureTooltip(for marker: NMFMarker, stores: [MapPopUpStore]) { Logger.log(message: """ 툴팁 설정: - 현재 캐러셀 스토어: \(currentCarouselStores.map { $0.name }) @@ -283,14 +219,9 @@ class MapViewController: BaseViewController, View { self.carouselView.scrollToCard(index: index) // 선택된 상태로 업데이트 - if let markerView = marker.iconView as? MapMarker { - markerView.injection(with: .init( - isSelected: true, - isCluster: false, - count: stores.count - )) - } + self.updateMarkerStyle(marker: marker, selected: true, isCluster: false, count: stores.count) tooltipView.selectStore(at: index) + Logger.log(message: """ 툴팁 선택: - 선택된 스토어: \(stores[index].name) @@ -298,11 +229,12 @@ class MapViewController: BaseViewController, View { """, category: .debug) } - // 툴팁 위치 설정 (예시: 마커 우측에 위치) - let markerPoint = self.mainView.mapView.projection.point(for: marker.position) - let markerHeight = (marker.iconView as? MapMarker)?.imageView.frame.height ?? 32 + // 툴팁 위치 설정 (마커 위치 기준으로 계산) + let markerPoint = self.mainView.mapView.projection.point(from: marker.position) + let markerHeight: CGFloat = 32 // 마커 이미지 높이 추정값 + tooltipView.frame = CGRect( - x: markerPoint.x , // 마커 오른쪽 10포인트 + x: markerPoint.x, y: markerPoint.y - markerHeight - tooltipView.frame.height - 14, width: tooltipView.frame.width, height: tooltipView.frame.height @@ -328,7 +260,7 @@ class MapViewController: BaseViewController, View { make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-24) } carouselView.isHidden = true - mainView.mapView.delegate = self + mainView.mapView.touchDelegate = self addChild(storeListViewController) view.addSubview(storeListViewController.view) @@ -346,36 +278,14 @@ class MapViewController: BaseViewController, View { setupPanAndSwipeGestures() let mapViewTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleMapViewTap(_:))) + mapViewTapGesture.cancelsTouchesInView = false // 중요: 다른 터치 이벤트를 방해하지 않음 + mapViewTapGesture.delaysTouchesBegan = false // 터치 지연 없음 mainView.mapView.addGestureRecognizer(mapViewTapGesture) mapViewTapGesture.delegate = self - } - private let defaultZoomLevel: Float = 15.0 - private func addMarkersForAllStores(stores: [MapPopUpStore]) { - // 기존 마커 제거 - clearAllMarkers() - - for store in stores { - let marker = GMSMarker() - marker.position = store.coordinate - marker.userData = store - marker.groundAnchor = CGPoint(x: 0.5, y: 1.0) - - let markerView = MapMarker() - markerView.injection(with: .init( - isSelected: false, - isCluster: false - )) - marker.iconView = markerView - - // 여기서는 마커를 맵에 바로 추가하지 않고 딕셔너리에만 저장 - individualMarkerDictionary[store.id] = marker - } - // allStores 도 저장 - self.currentStores = stores - } + private let defaultZoomLevel: Double = 15.0 private func setupPanAndSwipeGestures() { storeListViewController.mainView.grabberHandle.rx.swipeGesture(.up) .skip(1) @@ -412,7 +322,6 @@ class MapViewController: BaseViewController, View { // MARK: - Bind func bind(reactor: Reactor) { - // 필터 관련 바인딩 mainView.filterChips.locationChip.rx.tap .map { Reactor.Action.filterTapped(.location) } @@ -438,17 +347,16 @@ class MapViewController: BaseViewController, View { guard let self = self, let location = self.locationManager.location else { return } - let camera = GMSCameraPosition.camera( - withLatitude: location.coordinate.latitude, - longitude: location.coordinate.longitude, - zoom: 15 - ) - self.mainView.mapView.animate(to: camera) - } - .disposed(by: disposeBag) - + // 현재 위치로 카메라 이동 + let cameraUpdate = NMFCameraUpdate(scrollTo: NMGLatLng( + lat: location.coordinate.latitude, + lng: location.coordinate.longitude + ), zoomTo: 15.0) + self.mainView.mapView.moveCamera(cameraUpdate) + } + .disposed(by: disposeBag) mainView.filterChips.onRemoveLocation = { [weak self] in @@ -457,45 +365,46 @@ class MapViewController: BaseViewController, View { self.reactor?.action.onNext(.clearFilters(.location)) // 현재 뷰포트의 바운드로 마커 업데이트 요청 - let bounds = self.mainView.mapView.projection.visibleRegion() + let bounds = self.getVisibleBounds() self.reactor?.action.onNext(.viewportChanged( - northEastLat: bounds.farRight.latitude, - northEastLon: bounds.farRight.longitude, - southWestLat: bounds.nearLeft.latitude, - southWestLon: bounds.nearLeft.longitude + northEastLat: bounds.northEast.lat, + northEastLon: bounds.northEast.lng, + southWestLat: bounds.southWest.lat, + southWestLon: bounds.southWest.lng )) self.clearAllMarkers() - self.clusterMarkerDictionary.values.forEach { $0.map = nil } - self.clusterMarkerDictionary.removeAll() + self.clusterMarkerDictionary.values.forEach { $0.mapView = nil } + self.clusterMarkerDictionary.removeAll() + + // 캐러셀 숨기기 추가 + self.carouselView.isHidden = true + self.carouselView.updateCards([]) + self.currentCarouselStores = [] + self.mainView.setStoreCardHidden(true, animated: true) - // 캐러셀 숨기기 추가 - self.carouselView.isHidden = true - self.carouselView.updateCards([]) - self.currentCarouselStores = [] - self.mainView.setStoreCardHidden(true, animated: true) + self.updateMapWithClustering() + } - self.updateMapWithClustering() - } mainView.filterChips.onRemoveCategory = { [weak self] in guard let self = self else { return } // 필터 제거 액션 self.reactor?.action.onNext(.clearFilters(.category)) // 현재 뷰포트의 바운드로 마커 업데이트 요청 - let bounds = self.mainView.mapView.projection.visibleRegion() + let bounds = self.getVisibleBounds() self.reactor?.action.onNext(.viewportChanged( - northEastLat: bounds.farRight.latitude, - northEastLon: bounds.farRight.longitude, - southWestLat: bounds.nearLeft.latitude, - southWestLon: bounds.nearLeft.longitude + northEastLat: bounds.northEast.lat, + northEastLon: bounds.northEast.lng, + southWestLat: bounds.southWest.lat, + southWestLon: bounds.southWest.lng )) self.resetSelectedMarker() // 만약 지도 위 마커를 전부 제거하고 싶다면 (상황에 따라) // self.clearAllMarkers() - // self.clusterMarkerDictionary.values.forEach { $0.map = nil } + // self.clusterMarkerDictionary.values.forEach { $0.mapView = nil } // self.clusterMarkerDictionary.removeAll() self.carouselView.isHidden = true self.carouselView.updateCards([]) @@ -558,34 +467,27 @@ class MapViewController: BaseViewController, View { } }) .disposed(by: disposeBag) + reactor.state.map { $0.searchResult } .distinctUntilChanged() .compactMap { $0 } .observe(on: MainScheduler.instance) .bind { [weak self] store in guard let self = self else { return } - let camera = GMSCameraPosition.camera( - withLatitude: store.latitude, - longitude: store.longitude, - zoom: 15 - ) - self.mainView.mapView.animate(to: camera) + + // 검색 결과 위치로 카메라 이동 + let cameraUpdate = NMFCameraUpdate(scrollTo: NMGLatLng( + lat: store.latitude, + lng: store.longitude + ), zoomTo: 15.0) + cameraUpdate.animation = .easeIn + cameraUpdate.animationDuration = 0.3 + self.mainView.mapView.moveCamera(cameraUpdate) + self.addMarker(for: store) } .disposed(by: disposeBag) -// mainView.searchInput.onSearch = { [weak self] query in -// self?.reactor?.action.onNext(.searchTapped(query)) -// } -// -// reactor.state.map { $0.isLoading } -// .distinctUntilChanged() -// .observe(on: MainScheduler.instance) -// .bind { [weak self] isLoading in -// self?.mainView.searchInput.searchTextField.isEnabled = !isLoading -//// self?.mainView.searchInput.setLoading(isLoading) -// } -// .disposed(by: disposeBag) -// 보류 + mainView.searchInput.rx.tapGesture() .when(.recognized) .throttle(.milliseconds(500), scheduler: MainScheduler.instance) @@ -599,8 +501,6 @@ class MapViewController: BaseViewController, View { }) .disposed(by: disposeBag) - - reactor.state.map { $0.searchResults } .distinctUntilChanged() .observe(on: MainScheduler.instance) @@ -608,7 +508,7 @@ class MapViewController: BaseViewController, View { guard let self = self else { return } // 이전 선택된 마커, 툴팁, 캐러셀 초기화 - self.mainView.mapView.clear() + self.clearAllMarkers() self.storeListViewController.reactor?.action.onNext(.setStores([])) self.carouselView.updateCards([]) self.carouselView.isHidden = true @@ -634,45 +534,20 @@ class MapViewController: BaseViewController, View { self.carouselView.isHidden = false self.currentCarouselStores = results - // 만약 현재 선택된 마커의 스토어가 새로운 결과에 없다면, 선택 상태 초기화 - if let currentMarker = self.currentMarker, - let selectedStore = currentMarker.userData as? MapPopUpStore, - !results.contains(where: { $0.id == selectedStore.id }) { - self.resetSelectedMarker() - } - // 첫 번째 검색 결과로 지도 이동 if let firstStore = results.first { - let camera = GMSCameraPosition.camera( - withLatitude: firstStore.latitude, - longitude: firstStore.longitude, - zoom: 15 - ) - self.mainView.mapView.animate(to: camera) + let cameraUpdate = NMFCameraUpdate(scrollTo: NMGLatLng( + lat: firstStore.latitude, + lng: firstStore.longitude + ), zoomTo: 15.0) + cameraUpdate.animation = .easeIn + cameraUpdate.animationDuration = 0.3 + self.mainView.mapView.moveCamera(cameraUpdate) } } .disposed(by: disposeBag) - - - - -// reactor.state.map { $0.searchResults.isEmpty } -// .distinctUntilChanged() -// .skip(1) // 초기값 스킵 -// .observe(on: MainScheduler.instance) -// .bind { [weak self] isEmpty in -// guard let self = self else { return } -// if isEmpty { -// self.showAlert( -// title: "검색 결과 없음", -// message: "검색 결과가 없습니다. 다른 키워드로 검색해보세요." -// ) -// } -// } -// .disposed(by: disposeBag) } - // MARK: - List View Control private func toggleListView() { UIView.animate(withDuration: 0.3) { @@ -682,22 +557,55 @@ class MapViewController: BaseViewController, View { self.mainView.searchFilterContainer.backgroundColor = .clear self.view.layoutIfNeeded() } + } + + // 마커 추가 메서드 (NMFMarker로 변환) + func addMarker(for store: MapPopUpStore) { + let marker = NMFMarker() + marker.position = NMGLatLng(lat: store.latitude, lng: store.longitude) + marker.userInfo = ["storeData": store] + + // 마커 스타일 설정 + updateMarkerStyle(marker: marker, selected: false, isCluster: false, count: 1) + + // 중요: 마커에 직접 터치 핸들러 추가 + marker.touchHandler = { [weak self] (overlay) -> Bool in + guard let self = self else { return false } + + Logger.log(message: "마커 터치됨! 위치: \(marker.position), 스토어: \(store.name)", category: .debug) + + // 단일 스토어 마커 처리 + return self.handleSingleStoreTap(marker, store: store) + } - } + marker.mapView = mainView.mapView + markerDictionary[store.id] = marker + } - func addMarker(for store: MapPopUpStore) { - let marker = GMSMarker() - marker.position = store.coordinate - marker.userData = store + func updateMarkerStyle(marker: NMFMarker, selected: Bool, isCluster: Bool, count: Int = 1, regionName: String = "") { + if selected { + marker.width = 44 + marker.height = 44 + marker.iconImage = NMFOverlayImage(name: "TapMarker") + } else if isCluster { + marker.width = 36 + marker.height = 36 + marker.iconImage = NMFOverlayImage(name: "cluster_marker") + } else { + marker.width = 32 + marker.height = 32 + marker.iconImage = NMFOverlayImage(name: "Marker") + } - marker.groundAnchor = CGPoint(x: 0.5, y: 1.0) + if count > 1 { + marker.captionText = "\(count)" + } else { + marker.captionText = "" + } - let markerView = MapMarker() - markerView.injection(with: store.toMarkerInput()) - marker.iconView = markerView - marker.map = mainView.mapView - } + marker.anchor = CGPoint(x: 0.5, y: 1.0) + } @objc private func handleMapViewTap(_ gesture: UITapGestureRecognizer) { // 리스트뷰가 현재 보이는 상태(중간 또는 상단)일 때만 내림 @@ -772,11 +680,12 @@ class MapViewController: BaseViewController, View { } } - private func updateMapViewAlpha(for offset: CGFloat, minOffset: CGFloat, maxOffset: CGFloat) { let progress = (maxOffset - offset) / (maxOffset - minOffset) // 0(탑) ~ 1(바텀) mainView.mapView.alpha = max(0, min(progress, 1)) // 0(완전히 가림) ~ 1(완전히 보임) } + + private func animateToState(_ state: ModalState) { guard modalState != state else { return } self.view.layoutIfNeeded() @@ -793,8 +702,6 @@ class MapViewController: BaseViewController, View { self.listViewTopConstraint?.update(offset: filterChipsFrame.maxY) self.mainView.searchInput.setBackgroundColor(.g50) - - case .middle: self.storeListViewController.setGrabberHandleVisible(true) let offset = max(self.view.frame.height * 0.3, self.filterContainerBottomY) @@ -805,52 +712,46 @@ class MapViewController: BaseViewController, View { self.mainView.mapView.isHidden = false self.mainView.searchInput.setBackgroundColor(.white) - // 리스트뷰 표시 시, 이미 가져온 전체 스토어 데이터를 바로 사용 - if !self.allStores.isEmpty { - // 이미 데이터가 있으면 바로 표시 - self.fetchStoreDetails(for: self.allStores) - - Logger.log( - message: "✅ 기존 스토어 목록으로 리스트뷰 업데이트: \(self.allStores.count)개", - category: .debug + // 리스트뷰 표시 시, 전체 한국 영역의 스토어 가져오기 + if let reactor = self.reactor { + // 한국 전체 영역에 대한 경계값 설정 + let koreaRegion = ( + northEast: NMGLatLng(lat: 38.0, lng: 132.0), + southWest: NMGLatLng(lat: 33.0, lng: 124.0) ) - } else { - // 데이터가 없으면 전체 영역 요청 (이 부분도 필요하지만, 초기 로드 시 이미 불러왔을 가능성이 높음) - if let reactor = self.reactor { - let koreaRegion = ( - northEast: CLLocationCoordinate2D(latitude: 38.0, longitude: 132.0), - southWest: CLLocationCoordinate2D(latitude: 33.0, longitude: 124.0) - ) - - reactor.action.onNext(.viewportChanged( - northEastLat: koreaRegion.northEast.latitude, - northEastLon: koreaRegion.northEast.longitude, - southWestLat: koreaRegion.southWest.latitude, - southWestLon: koreaRegion.southWest.longitude - )) - - // 즉시 구독하지만 최대 1회만 실행 - reactor.state - .map { $0.viewportStores } - .distinctUntilChanged() - .filter { !$0.isEmpty } - .take(1) - .subscribe(onNext: { [weak self] allStores in - guard let self = self else { return } - - self.allStores = allStores - self.fetchStoreDetails(for: allStores) - }) - .disposed(by: self.disposeBag) - } + + // 전체 스토어를 가져오기 위해 한국 전체 영역 요청 + reactor.action.onNext(.viewportChanged( + northEastLat: koreaRegion.northEast.lat, + northEastLon: koreaRegion.northEast.lng, + southWestLat: koreaRegion.southWest.lat, + southWestLon: koreaRegion.southWest.lng + )) + + reactor.state + .map { $0.viewportStores } + .distinctUntilChanged() + .filter { !$0.isEmpty } + .take(1) + .observe(on: MainScheduler.instance) + .subscribe(onNext: { [weak self] stores in + guard let self = self else { return } + self.fetchStoreDetails(for: stores) + + Logger.log( + message: "✅ 전체 스토어 목록으로 리스트뷰 업데이트: \(stores.count)개", + category: .debug + ) + }) + .disposed(by: self.disposeBag) } + case .bottom: self.storeListViewController.setGrabberHandleVisible(true) self.listViewTopConstraint?.update(offset: self.view.frame.height) self.mainView.mapView.alpha = 1 self.mainView.mapView.isHidden = false self.mainView.searchInput.setBackgroundColor(.white) - } self.view.layoutIfNeeded() @@ -860,1152 +761,1049 @@ class MapViewController: BaseViewController, View { } } + func imageFromView(_ view: UIView) -> UIImage? { + UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, UIScreen.main.scale) + defer { UIGraphicsEndImageContext() } + if let context = UIGraphicsGetCurrentContext() { + view.layer.render(in: context) + return UIGraphicsGetImageFromCurrentImageContext() + } + return nil + } - // updateMapWithClustering() 메서드 전체 구현 + // MARK: - Helper: 클러스터용 커스텀 마커 이미지 생성 (MapMarker를 사용) + func createClusterMarkerImage(regionName: String, count: Int) -> UIImage? { + // MapMarker의 입력값에 클러스터 상태를 전달합니다. + let markerView = MapMarker() // 기존 커스텀 뷰, 네이버맵용으로도 사용 가능하도록 구현됨. + let input = MapMarker.Input(isSelected: false, + isCluster: true, + regionName: regionName, + count: count, + isMultiMarker: false) + markerView.injection(with: input) + // 프레임이 설정되어 있지 않다면 적당한 크기로 지정 (예: 80x32) + if markerView.frame == .zero { + markerView.frame = CGRect(x: 0, y: 0, width: 80, height: 32) + } + return imageFromView(markerView) + } + // MARK: - Clustering private func updateMapWithClustering() { - let currentZoom = mainView.mapView.camera.zoom - let level = MapZoomLevel.getLevel(from: currentZoom) - let visibleRegion = mainView.mapView.projection.visibleRegion() - let visibleBoundsRect = GMSCoordinateBounds(region: visibleRegion) - - // 현재 뷰포트에 있는 스토어만 필터링 (allStores가 빈 경우 currentStores 사용) - let visibleStores = !allStores.isEmpty ? - allStores.filter { store in visibleBoundsRect.contains(store.coordinate) } : - currentStores - - // 현재 화면에 보이는 스토어 업데이트 - currentStores = visibleStores - + let currentZoom = mainView.mapView.zoomLevel + let level = MapZoomLevel.getLevel(from: Float(currentZoom)) + // 클러스터 처리 시 현재 스토어 목록(currentStores)을 사용 + Logger.log(message: "현재 줌 레벨: \(currentZoom), 모드: \(level), 스토어 수: \(currentStores.count)", category: .debug) CATransaction.begin() CATransaction.setDisableActions(true) switch level { case .detailed: - let newStoreIds = Set(visibleStores.map { $0.id }) - let groupedDict = groupStoresByExactLocation(visibleStores) + // 상세 레벨에서는 개별 마커를 사용합니다. + let newStoreIds = Set(currentStores.map { $0.id }) + let groupedDict = groupStoresByExactLocation(currentStores) - clusterMarkerDictionary.values.forEach { $0.map = nil } + // 클러스터 마커 제거 + clusterMarkerDictionary.values.forEach { $0.mapView = nil } clusterMarkerDictionary.removeAll() + // 그룹별로 개별 마커 생성/업데이트 for (coordinate, storeGroup) in groupedDict { if storeGroup.count == 1, let store = storeGroup.first { if let existingMarker = individualMarkerDictionary[store.id] { - if existingMarker.position != store.coordinate { - existingMarker.position = store.coordinate - } - - existingMarker.map = mainView.mapView - - if let markerView = existingMarker.iconView as? MapMarker, - markerView.currentInput?.isSelected != (existingMarker == currentMarker) { - markerView.injection(with: .init( - isSelected: (existingMarker == currentMarker), - isCluster: false - )) + if existingMarker.position.lat != store.latitude || + existingMarker.position.lng != store.longitude { + existingMarker.position = NMGLatLng(lat: store.latitude, lng: store.longitude) } + let isSelected = (existingMarker == currentMarker) + updateMarkerStyle(marker: existingMarker, selected: isSelected, isCluster: false) } else { - let marker = GMSMarker(position: store.coordinate) - marker.userData = store - marker.groundAnchor = CGPoint(x: 0.5, y: 1.0) - - let markerView = MapMarker() - markerView.injection(with: .init( - isSelected: false, - isCluster: false - )) - marker.iconView = markerView - marker.map = mainView.mapView + let marker = NMFMarker() + marker.position = NMGLatLng(lat: store.latitude, lng: store.longitude) + marker.userInfo = ["storeData": store] + marker.anchor = CGPoint(x: 0.5, y: 1.0) + updateMarkerStyle(marker: marker, selected: false, isCluster: false) + + // 직접 터치 핸들러 추가 + marker.touchHandler = { [weak self] (overlay) -> Bool in + guard let self = self else { return false } + + print("개별 마커 터치됨! 스토어: \(store.name)") + return self.handleSingleStoreTap(marker, store: store) + } + marker.mapView = mainView.mapView individualMarkerDictionary[store.id] = marker } } else { + // 여러 스토어가 동일 위치에 있으면 단일 마커로 표시하면서 count 갱신 guard let firstStore = storeGroup.first else { continue } let markerKey = firstStore.id - if let existingMarker = individualMarkerDictionary[markerKey] { - existingMarker.userData = storeGroup - - existingMarker.map = mainView.mapView - - if let markerView = existingMarker.iconView as? MapMarker, - markerView.currentInput?.count != storeGroup.count || - markerView.currentInput?.isSelected != (existingMarker == currentMarker) { - markerView.injection(with: .init( - isSelected: (existingMarker == currentMarker), - isCluster: false, - count: storeGroup.count - )) - } + existingMarker.userInfo = ["storeData": storeGroup] + let isSelected = (existingMarker == currentMarker) + updateMarkerStyle(marker: existingMarker, selected: isSelected, isCluster: false, count: storeGroup.count) } else { - // 새 마커 생성 - let marker = GMSMarker(position: firstStore.coordinate) - marker.userData = storeGroup - marker.groundAnchor = CGPoint(x: 0.5, y: 1.0) - - let markerView = MapMarker() - markerView.injection(with: .init( - isSelected: false, - isCluster: false, - count: storeGroup.count - )) - marker.iconView = markerView - marker.map = mainView.mapView + let marker = NMFMarker() + marker.position = NMGLatLng(lat: firstStore.latitude, lng: firstStore.longitude) + marker.userInfo = ["storeData": storeGroup] + marker.anchor = CGPoint(x: 0.5, y: 1.0) + updateMarkerStyle(marker: marker, selected: false, isCluster: false, count: storeGroup.count) + + // 직접 터치 핸들러 추가 + marker.touchHandler = { [weak self] (overlay) -> Bool in + guard let self = self else { return false } + + print("마이크로 클러스터 마커 터치됨! 스토어 수: \(storeGroup.count)개") + return self.handleMicroClusterTap(marker, storeArray: storeGroup) + } + marker.mapView = mainView.mapView individualMarkerDictionary[markerKey] = marker } } } - for (id, marker) in individualMarkerDictionary { - if !newStoreIds.contains(id) { - marker.map = nil + // 기존에 보이지 않는 개별 마커 제거 + individualMarkerDictionary = individualMarkerDictionary.filter { id, marker in + if newStoreIds.contains(id) { + return true + } else { + marker.mapView = nil + return false } } case .district, .city, .country: // 개별 마커 숨기기 - individualMarkerDictionary.values.forEach { $0.map = nil } + individualMarkerDictionary.values.forEach { $0.mapView = nil } + individualMarkerDictionary.removeAll() - // 클러스터 생성 및 업데이트 - let clusters = clusteringManager.clusterStores(visibleStores, at: currentZoom) + // 클러스터 생성 + let clusters = clusteringManager.clusterStores(currentStores, at: Float(currentZoom)) let activeClusterKeys = Set(clusters.map { $0.cluster.name }) - // 클러스터 마커 업데이트 for cluster in clusters { let clusterKey = cluster.cluster.name - + var marker: NMFMarker if let existingMarker = clusterMarkerDictionary[clusterKey] { - // 기존 마커 재사용 - if existingMarker.position != cluster.cluster.coordinate { - existingMarker.position = cluster.cluster.coordinate - } - existingMarker.userData = cluster - existingMarker.map = mainView.mapView - - if let markerView = existingMarker.iconView as? MapMarker, - markerView.currentInput?.count != cluster.storeCount { - markerView.injection(with: .init( - isSelected: false, - isCluster: true, - regionName: cluster.cluster.name, - count: cluster.storeCount - )) + marker = existingMarker + // 위치 업데이트가 필요하면 수정 + if marker.position.lat != cluster.cluster.coordinate.lat || + marker.position.lng != cluster.cluster.coordinate.lng { + marker.position = NMGLatLng(lat: cluster.cluster.coordinate.lat, lng: cluster.cluster.coordinate.lng) } } else { - // 새 클러스터 마커 생성 - let marker = GMSMarker(position: cluster.cluster.coordinate) - marker.groundAnchor = CGPoint(x: 0.5, y: 1.0) - marker.userData = cluster - - let markerView = MapMarker() - markerView.injection(with: .init( - isSelected: false, - isCluster: true, - regionName: cluster.cluster.name, - count: cluster.storeCount - )) - marker.iconView = markerView - marker.map = mainView.mapView - + marker = NMFMarker() clusterMarkerDictionary[clusterKey] = marker } + + marker.position = NMGLatLng(lat: cluster.cluster.coordinate.lat, lng: cluster.cluster.coordinate.lng) + marker.userInfo = ["clusterData": cluster] // 중요: userInfo에 cluster 객체를 직접 저장 + + // 여기서 커스텀 클러스터 마커 뷰를 이미지로 변환하여 적용합니다. + if let clusterImage = createClusterMarkerImage(regionName: cluster.cluster.name, count: cluster.storeCount) { + marker.iconImage = NMFOverlayImage(image: clusterImage) + } else { + // 기본 에셋 fallback (원하는 경우) + marker.iconImage = NMFOverlayImage(name: "cluster_marker") + } + + // 터치 핸들러 추가 - userInfo에서 클러스터 데이터를 직접 가져오기 + marker.touchHandler = { [weak self] (overlay) -> Bool in + guard let self = self, + let tappedMarker = overlay as? NMFMarker, + let clusterData = tappedMarker.userInfo["clusterData"] as? ClusterMarkerData else { + return false + } + + return self.handleRegionalClusterTap(tappedMarker, clusterData: clusterData) + } + + marker.captionText = "" + marker.anchor = CGPoint(x: 0.5, y: 0.5) + marker.mapView = mainView.mapView } + // 활성 클러스터가 아닌 마커 제거 for (key, marker) in clusterMarkerDictionary { if !activeClusterKeys.contains(key) { - marker.map = nil + marker.mapView = nil + clusterMarkerDictionary.removeValue(forKey: key) } } } CATransaction.commit() } - private func clearAllMarkers() { - individualMarkerDictionary.values.forEach { $0.map = nil } - individualMarkerDictionary.removeAll() - clusterMarkerDictionary.values.forEach { $0.map = nil } - clusterMarkerDictionary.removeAll() - markerDictionary.values.forEach { $0.map = nil } - markerDictionary.removeAll() - } + private func clearAllMarkers() { + individualMarkerDictionary.values.forEach { $0.mapView = nil } + individualMarkerDictionary.removeAll() - private func groupStoresByExactLocation(_ stores: [MapPopUpStore]) -> [CoordinateKey: [MapPopUpStore]] { - var dict = [CoordinateKey: [MapPopUpStore]]() - for store in stores { - let key = CoordinateKey(latitude: store.latitude, longitude: store.longitude) - dict[key, default: []].append(store) + clusterMarkerDictionary.values.forEach { $0.mapView = nil } + clusterMarkerDictionary.removeAll() + + markerDictionary.values.forEach { $0.mapView = nil } + markerDictionary.removeAll() } - return dict - } + private func groupStoresByExactLocation(_ stores: [MapPopUpStore]) -> [CoordinateKey: [MapPopUpStore]] { + var dict = [CoordinateKey: [MapPopUpStore]]() + for store in stores { + let key = CoordinateKey(latitude: store.latitude, longitude: store.longitude) + dict[key, default: []].append(store) + } + return dict + } - private func updateIndividualMarkers(_ stores: [MapPopUpStore]) { - var newMarkerIDs = Set() + private func updateIndividualMarkers(_ stores: [MapPopUpStore]) { + var newMarkerIDs = Set() - for store in stores { - newMarkerIDs.insert(store.id) - if let marker = individualMarkerDictionary[store.id] { - if marker.position.latitude != store.latitude || marker.position.longitude != store.longitude { - marker.position = store.coordinate - } - } else { - // 새 마커 생성 및 추가 - let marker = GMSMarker(position: store.coordinate) - marker.userData = store + for store in stores { + newMarkerIDs.insert(store.id) + if let marker = individualMarkerDictionary[store.id] { + if marker.position.lat != store.latitude || marker.position.lng != store.longitude { + marker.position = NMGLatLng(lat: store.latitude, lng: store.longitude) + } + } else { + // 새 마커 생성 및 추가 + let marker = NMFMarker() + marker.position = NMGLatLng(lat: store.latitude, lng: store.longitude) + marker.userInfo = ["storeData": store] - let markerView = MapMarker() - markerView.injection(with: store.toMarkerInput()) - marker.iconView = markerView - marker.map = mainView.mapView + updateMarkerStyle(marker: marker, selected: false, isCluster: false) + marker.mapView = mainView.mapView - individualMarkerDictionary[store.id] = marker + individualMarkerDictionary[store.id] = marker + } } - } - for (id, marker) in individualMarkerDictionary { - if !newMarkerIDs.contains(id) { - marker.map = nil - individualMarkerDictionary.removeValue(forKey: id) + for (id, marker) in individualMarkerDictionary { + if !newMarkerIDs.contains(id) { + marker.mapView = nil + individualMarkerDictionary.removeValue(forKey: id) + } } } - } + private func updateClusterMarkers(_ clusters: [ClusterMarkerData]) { for clusterData in clusters { let clusterKey = clusterData.cluster.name let fixedCoordinate = clusterData.cluster.coordinate if let marker = clusterMarkerDictionary[clusterKey] { - if marker.position.latitude != fixedCoordinate.latitude || - marker.position.longitude != fixedCoordinate.longitude { - marker.position = fixedCoordinate + if marker.position.lat != fixedCoordinate.lat || marker.position.lng != fixedCoordinate.lng { + marker.position = NMGLatLng(lat: fixedCoordinate.lat, lng: fixedCoordinate.lng) } } else { - let marker = GMSMarker() - marker.position = fixedCoordinate - marker.userData = clusterData - - let markerView = MapMarker() - markerView.injection(with: .init( - isSelected: false, - isCluster: true, - regionName: clusterData.cluster.name, - count: clusterData.storeCount - )) - marker.iconView = markerView - marker.map = mainView.mapView + let marker = NMFMarker() + marker.position = NMGLatLng(lat: fixedCoordinate.lat, lng: fixedCoordinate.lng) + marker.userInfo = ["clusterData": clusterData] + + updateMarkerStyle(marker: marker, selected: false, isCluster: true, + count: clusterData.storeCount, regionName: clusterData.cluster.name) + marker.mapView = mainView.mapView clusterMarkerDictionary[clusterKey] = marker } } } + func presentFilterBottomSheet(for filterType: FilterType) { + guard let reactor = self.reactor else { return } + let sheetReactor = FilterBottomSheetReactor( + savedSubRegions: reactor.currentState.selectedLocationFilters, + savedCategories: reactor.currentState.selectedCategoryFilters + ) + let viewController = FilterBottomSheetViewController(reactor: sheetReactor) - func presentFilterBottomSheet(for filterType: FilterType) { - guard let reactor = self.reactor else { return } - - let sheetReactor = FilterBottomSheetReactor( - savedSubRegions: reactor.currentState.selectedLocationFilters, - savedCategories: reactor.currentState.selectedCategoryFilters - ) - let viewController = FilterBottomSheetViewController(reactor: sheetReactor) + let initialIndex = (filterType == .location) ? 0 : 1 + viewController.containerView.segmentedControl.selectedSegmentIndex = initialIndex + sheetReactor.action.onNext(.segmentChanged(initialIndex)) - let initialIndex = (filterType == .location) ? 0 : 1 - viewController.containerView.segmentedControl.selectedSegmentIndex = initialIndex - sheetReactor.action.onNext(.segmentChanged(initialIndex)) + viewController.onSave = { [weak self] filterData in + guard let self = self else { return } + self.reactor?.action.onNext(.updateBothFilters( + locations: filterData.locations, + categories: filterData.categories + )) + self.reactor?.action.onNext(.filterTapped(nil)) + + let bounds = self.getVisibleBounds() + self.reactor?.action.onNext(.viewportChanged( + northEastLat: bounds.northEast.lat, + northEastLon: bounds.northEast.lng, + southWestLat: bounds.southWest.lat, + southWestLon: bounds.southWest.lng + )) + } - viewController.onSave = { [weak self] filterData in - guard let self = self else { return } - self.reactor?.action.onNext(.updateBothFilters( - locations: filterData.locations, - categories: filterData.categories - )) - self.reactor?.action.onNext(.filterTapped(nil)) + viewController.onDismiss = { [weak self] in + self?.reactor?.action.onNext(.filterTapped(nil)) + } - let bounds = self.mainView.mapView.projection.visibleRegion() - self.reactor?.action.onNext(.viewportChanged( - northEastLat: bounds.farRight.latitude, - northEastLon: bounds.farRight.longitude, - southWestLat: bounds.nearLeft.latitude, - southWestLon: bounds.nearLeft.longitude - )) - } + viewController.modalPresentationStyle = .overFullScreen + present(viewController, animated: false) { + viewController.showBottomSheet() + } - viewController.onDismiss = { [weak self] in - self?.reactor?.action.onNext(.filterTapped(nil)) + currentFilterBottomSheet = viewController } - viewController.modalPresentationStyle = .overFullScreen - present(viewController, animated: false) { - viewController.showBottomSheet() + private func dismissFilterBottomSheet() { + if let bottomSheet = currentFilterBottomSheet { + bottomSheet.hideBottomSheet() + } + currentFilterBottomSheet = nil } - currentFilterBottomSheet = viewController - } - private func dismissFilterBottomSheet() { - if let bottomSheet = currentFilterBottomSheet { - bottomSheet.hideBottomSheet() - } - currentFilterBottomSheet = nil - } - //기본 마커 + //기본 마커 private func addMarkers(for stores: [MapPopUpStore]) { - mainView.mapView.clear() + markerDictionary.values.forEach { $0.mapView = nil } markerDictionary.removeAll() for store in stores { - let marker = GMSMarker() - marker.position = store.coordinate - marker.userData = store - - let markerView = MapMarker() - markerView.injection(with: .init( - isSelected: false, - isCluster: false - )) - marker.iconView = markerView - marker.map = mainView.mapView + let marker = NMFMarker() + marker.position = NMGLatLng(lat: store.latitude, lng: store.longitude) + marker.userInfo = ["storeData": store] + + updateMarkerStyle(marker: marker, selected: false, isCluster: false) + + // 직접 터치 핸들러 추가 + marker.touchHandler = { [weak self] (overlay) -> Bool in + guard let self = self else { return false } + + print("검색 결과 마커 터치됨! 스토어: \(store.name)") + return self.handleSingleStoreTap(marker, store: store) + } + + marker.mapView = mainView.mapView markerDictionary[store.id] = marker } } - private func updateListView(with results: [MapPopUpStore]) { - // MapPopUpStore 배열을 StoreItem 배열로 변환 - let storeItems = results.map { $0.toStoreItem() } - storeListViewController.reactor?.action.onNext(.setStores(storeItems)) - } - - private func showAlert(title: String, message: String) { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "확인", style: .default, handler: nil)) - present(alert, animated: true, completion: nil) - } - // 캐러셀 영역을 제외한 실제 가시 영역 계산 함수 - private func getEffectiveViewport() -> GMSCoordinateBounds { - // 기본 가시 영역 가져오기 - let visibleRegion = mainView.mapView.projection.visibleRegion() - - // 캐러셀이 보이지 않으면 전체 영역 반환 - if carouselView.isHidden { - return GMSCoordinateBounds(region: visibleRegion) + private func updateListView(with results: [MapPopUpStore]) { + // MapPopUpStore 배열을 StoreItem 배열로 변환 + let storeItems = results.map { $0.toStoreItem() } + storeListViewController.reactor?.action.onNext(.setStores(storeItems)) } - // 캐러셀 상단 Y 좌표를 지도 좌표로 변환 - let carouselTopY = carouselView.frame.minY + private func showAlert(title: String, message: String) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "확인", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) + } - // 화면 좌표계에서 캐러셀 상단 라인을 생성 (좌우 전체 폭) - let leftPoint = CGPoint(x: 0, y: carouselTopY) - let rightPoint = CGPoint(x: view.frame.width, y: carouselTopY) + // 캐러셀 영역을 제외한 실제 가시 영역 계산 함수 + private func getEffectiveViewport() -> NMGLatLngBounds { + // 기본 가시 영역 가져오기 + let bounds = getVisibleBounds() - // 화면 좌표를 지도 좌표로 변환 - let leftCoordinate = mainView.mapView.projection.coordinate(for: leftPoint) - let rightCoordinate = mainView.mapView.projection.coordinate(for: rightPoint) + // 캐러셀이 보이지 않으면 전체 영역 반환 + if carouselView.isHidden { + return NMGLatLngBounds(southWest: bounds.southWest, northEast: bounds.northEast) + } - // 캐러셀 영역을 제외한 북쪽 경계 결정 (원래 북쪽 경계는 그대로 유지) - let adjustedSouthWest = CLLocationCoordinate2D( - latitude: max(leftCoordinate.latitude, rightCoordinate.latitude), - longitude: visibleRegion.nearLeft.longitude - ) + // 캐러셀 상단 Y 좌표 + let carouselTopY = carouselView.frame.minY - // 조정된 경계로 새 영역 생성 - return GMSCoordinateBounds( - coordinate: visibleRegion.farLeft, - coordinate: adjustedSouthWest - ) - } + // 화면 좌표계에서 캐러셀 상단 라인을 생성 (좌우 전체 폭) + let leftPoint = CGPoint(x: 0, y: carouselTopY) + let rightPoint = CGPoint(x: view.frame.width, y: carouselTopY) + // 화면 좌표를 지도 좌표로 변환 + let leftCoordinate = mainView.mapView.projection.latlng(from: leftPoint) + let rightCoordinate = mainView.mapView.projection.latlng(from: rightPoint) + // 캐러셀 영역을 제외한 경계 계산 + let adjustedSouthWest = NMGLatLng( + lat: max(leftCoordinate.lat, rightCoordinate.lat), + lng: bounds.southWest.lng + ) - // MARK: - Location - private func checkLocationAuthorization() { - switch locationManager.authorizationStatus { - case .notDetermined: - locationManager.requestWhenInUseAuthorization() - case .authorizedWhenInUse, .authorizedAlways: - locationManager.startUpdatingLocation() - case .denied, .restricted: - Logger.log( - message: "위치 서비스가 비활성화되었습니다. 설정에서 권한을 확인해주세요.", - category: .error + // 조정된 경계로 새 영역 생성 + return NMGLatLngBounds( + southWest: adjustedSouthWest, + northEast: bounds.northEast ) - @unknown default: - break } - } -} -// MARK: - CLLocationManagerDelegate -extension MapViewController: CLLocationManagerDelegate { - func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { - guard let location = locations.last else { return } + // 현재 보이는 지도 영역의 경계를 가져오는 함수 + private func getVisibleBounds() -> (northEast: NMGLatLng, southWest: NMGLatLng) { + let mapBounds = mainView.mapView.contentBounds - currentMarker?.map = nil - currentMarker = nil - carouselView.isHidden = true - currentCarouselStores = [] + let northEast = NMGLatLng(lat: mapBounds.northEastLat, lng: mapBounds.northEastLng) + let southWest = NMGLatLng(lat: mapBounds.southWestLat, lng: mapBounds.southWestLng) - let camera = GMSCameraPosition.camera( - withLatitude: location.coordinate.latitude, - longitude: location.coordinate.longitude, - zoom: 15 - ) - mainView.mapView.animate(to: camera) + return (northEast: northEast, southWest: southWest) + } - // 카메라 이동이 완료된 후 가장 가까운 스토어 찾기 - mainView.mapView.rx.idleAtPosition - .take(1) - .subscribe(onNext: { [weak self] _ in - guard let self = self else { return } - self.findAndShowNearestStore(from: location) - }) - .disposed(by: disposeBag) + // MARK: - Location + private func checkLocationAuthorization() { + switch locationManager.authorizationStatus { + case .notDetermined: + locationManager.requestWhenInUseAuthorization() + case .authorizedWhenInUse, .authorizedAlways: + locationManager.startUpdatingLocation() + mainView.mapView.positionMode = .direction // 내 위치 트래킹 모드 활성화 + case .denied, .restricted: + Logger.log( + message: "위치 서비스가 비활성화되었습니다. 설정에서 권한을 확인해주세요.", + category: .error + ) + mainView.mapView.positionMode = .disabled // 내 위치 트래킹 모드 비활성화 + @unknown default: + break + } + } - locationManager.stopUpdatingLocation() - } + private func updateTooltipPosition() { + guard let marker = currentMarker, let tooltip = currentTooltipView else { return } + // 마커 위치를 화면 좌표로 변환 + let markerPoint = mainView.mapView.projection.point(from: marker.position) + var markerCenter = markerPoint - private func findAndShowNearestStore(from location: CLLocation) { - guard !currentStores.isEmpty else { - Logger.log(message: "현재위치 표기할 스토어가 없습니다", category: .debug) - return - } + // 마커 높이 고려 (네이버 마커는 크기가 다를 수 있음) + markerCenter.y = markerPoint.y - 20 // 마커 이미지 높이의 절반 정도 - resetSelectedMarker() + // 오프셋 값 (디자인에 맞게 조정) + let offsetX: CGFloat = -10 + let offsetY: CGFloat = -10 - let nearestStore = currentStores.min { store1, store2 in - let location1 = CLLocation(latitude: store1.latitude, longitude: store1.longitude) - let location2 = CLLocation(latitude: store2.latitude, longitude: store2.longitude) - return location.distance(from: location1) < location.distance(from: location2) + tooltip.frame.origin = CGPoint( + x: markerCenter.x + offsetX, + y: markerCenter.y - tooltip.frame.height - offsetY + ) } - if let store = nearestStore, let marker = findMarkerForStore(for: store) { - // 카메라 이동 없이 선택된 마커만 업데이트합니다. - _ = handleSingleStoreTap(marker, store: store) - } - // 만약 마커가 없다면, 기존 로직과 같이 마커를 생성하고 선택 상태를 업데이트 - else if let store = nearestStore { - let marker = GMSMarker() - marker.position = store.coordinate - marker.userData = store - marker.groundAnchor = CGPoint(x: 0.5, y: 1.0) - - let markerView = MapMarker() - markerView.injection(with: .init(isSelected: true, isCluster: false, count: 1)) - marker.iconView = markerView - marker.map = mainView.mapView - - individualMarkerDictionary[store.id] = marker - currentMarker = marker - carouselView.updateCards([store]) - currentCarouselStores = [store] - carouselView.scrollToCard(index: 0) - mainView.setStoreCardHidden(false, animated: true) - } + private func resetSelectedMarker() { + if let currentMarker = currentMarker { + // 마커 스타일 업데이트 + updateMarkerStyle(marker: currentMarker, selected: false, isCluster: false) } - } - -// MARK: - GMSMapViewDelegate -extension MapViewController: GMSMapViewDelegate { - func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool { - let hitBoxSize: CGFloat = 44 // 터치 영역 크기 - let markerPoint = mapView.projection.point(for: marker.position) - let touchPoint = mapView.projection.point(for: marker.position) - let distance = sqrt( - pow(markerPoint.x - touchPoint.x, 2) + - pow(markerPoint.y - touchPoint.y, 2) - ) - - // 터치 영역을 벗어난 경우 무시 - if distance > hitBoxSize / 2 { - return false - } - // (1) 구/시 단위 클러스터 - if let clusterData = marker.userData as? ClusterMarkerData { - return handleRegionalClusterTap(marker, clusterData: clusterData) - } - // 동일 좌표 마이크로 클러스터 - else if let storeArray = marker.userData as? [MapPopUpStore] { - if storeArray.count > 1 { - return handleMicroClusterTap(marker, storeArray: storeArray) - } else if let singleStore = storeArray.first { - return handleSingleStoreTap(marker, store: singleStore) - } - } - // 단일 스토어 - else if let singleStore = marker.userData as? MapPopUpStore { - return handleSingleStoreTap(marker, store: singleStore) - } - - return false - } - - - func mapView(_ mapView: GMSMapView, didChange position: GMSCameraPosition) { - if !isMovingToMarker { + // 툴팁 제거 currentTooltipView?.removeFromSuperview() currentTooltipView = nil currentTooltipStores = [] - updateMapWithClustering() - - // 캐러셀 초기화 + currentTooltipCoordinate = nil carouselView.isHidden = true carouselView.updateCards([]) currentCarouselStores = [] - } - } - - - func mapView(_ mapView: GMSMapView, willMove gesture: Bool) { - if gesture && !isMovingToMarker { - resetSelectedMarker() - } - } - /// 지도 빈 공간 탭 → 기존 마커/캐러셀 해제 - func mapView(_ mapView: GMSMapView, didTapAt coordinate: CLLocationCoordinate2D) { - guard !isMovingToMarker else { return } - - // 현재 선택된 마커의 상태를 완전히 초기화 - if let currentMarker = currentMarker { - if let markerView = currentMarker.iconView as? MapMarker { - markerView.injection(with: .init( - isSelected: false, - isCluster: false, - count: (currentMarker.userData as? [MapPopUpStore])?.count ?? 1 - )) - } -// currentMarker.map = nil + // 현재 마커 참조 제거 self.currentMarker = nil } - // 툴팁 제거 - currentTooltipView?.removeFromSuperview() - currentTooltipView = nil - currentTooltipStores = [] - currentTooltipCoordinate = nil - - // 캐러셀 초기화 - carouselView.isHidden = true - carouselView.updateCards([]) - self.currentCarouselStores = [] - mainView.setStoreCardHidden(true, animated: true) - - // 클러스터링 업데이트 - updateMapWithClustering() - } - - - - - // MARK: - Helper for single marker tap - func handleSingleStoreTap(_ marker: GMSMarker, store: MapPopUpStore) -> Bool { - isMovingToMarker = true - - // 이전 마커 선택 상태 해제 - if let previousMarker = currentMarker, - let previousMarkerView = previousMarker.iconView as? MapMarker { - previousMarkerView.injection(with: .init( - isSelected: false, - isCluster: false, - count: (previousMarker.userData as? [MapPopUpStore])?.count ?? 1 - )) + private func updateMarkersForCluster(stores: [MapPopUpStore]) { + // 전체 개별 및 클러스터 마커 제거 + for marker in individualMarkerDictionary.values { + marker.mapView = nil } + individualMarkerDictionary.removeAll() - // 새 마커 선택 상태로 설정 - if let markerView = marker.iconView as? MapMarker { - markerView.injection(with: .init( - isSelected: true, - isCluster: false, - count: (marker.userData as? [MapPopUpStore])?.count ?? 1 - )) + for marker in clusterMarkerDictionary.values { + marker.mapView = nil } + clusterMarkerDictionary.removeAll() - currentMarker = marker + // 클러스터에 포함된 스토어들만 새 마커 추가 + for store in stores { + let marker = NMFMarker() + marker.position = NMGLatLng(lat: store.latitude, lng: store.longitude) + marker.userInfo = ["storeData": store] + marker.anchor = CGPoint(x: 0.5, y: 1.0) - // 캐러셀에 표시할 스토어 확인 - if currentCarouselStores.isEmpty || !currentCarouselStores.contains(where: { $0.id == store.id }) { - // 현재 뷰포트의 모든 스토어를 가져오기 - let visibleRegion = mainView.mapView.projection.visibleRegion() - let bounds = GMSCoordinateBounds(region: visibleRegion) + updateMarkerStyle(marker: marker, selected: false, isCluster: false) - let visibleStores = currentStores.filter { store in - bounds.contains(CLLocationCoordinate2D( - latitude: store.latitude, - longitude: store.longitude - )) + // 직접 터치 핸들러 추가 + marker.touchHandler = { [weak self] (overlay) -> Bool in + guard let self = self else { return false } + + print("클러스터 내 마커 터치됨! 스토어: \(store.name)") + return self.handleSingleStoreTap(marker, store: store) } - if !visibleStores.isEmpty { - // 뷰포트의 모든 스토어를 캐러셀에 표시 - currentCarouselStores = visibleStores - carouselView.updateCards(visibleStores) + marker.mapView = mainView.mapView + individualMarkerDictionary[store.id] = marker + } + } + - // 선택한 스토어의 인덱스를 찾아 스크롤 - if let index = visibleStores.firstIndex(where: { $0.id == store.id }) { - carouselView.scrollToCard(index: index) + private func findMarkerForStore(for store: MapPopUpStore) -> NMFMarker? { + // individualMarkerDictionary에 저장된 모든 마커를 순회 + for marker in individualMarkerDictionary.values { + if let singleStore = marker.userInfo["storeData"] as? MapPopUpStore, singleStore.id == store.id { + return marker + } + if let storeGroup = marker.userInfo["storeData"] as? [MapPopUpStore], + storeGroup.contains(where: { $0.id == store.id }) { + return marker } - } else { - // 뷰포트에 다른 스토어가 없는 경우, 선택한 스토어만 표시 - currentCarouselStores = [store] - carouselView.updateCards([store]) } - } else { - if let index = currentCarouselStores.firstIndex(where: { $0.id == store.id }) { - carouselView.scrollToCard(index: index) + // 상세 레벨이 아닐 경우 clusterMarkerDictionary에도 동일하게 검색 + for marker in clusterMarkerDictionary.values { + if let clusterData = marker.userInfo["clusterData"] as? ClusterMarkerData, + clusterData.cluster.stores.contains(where: { $0.id == store.id }) { + return marker + } } + return nil } - carouselView.isHidden = false - mainView.setStoreCardHidden(false, animated: true) - - // 툴팁 처리 - if let storeArray = marker.userData as? [MapPopUpStore], storeArray.count > 1 { - // 마이크로 클러스터인 경우 툴팁 표시 - configureTooltip(for: marker, stores: storeArray) - // 해당 스토어의 툴팁 인덱스 선택 - if let index = storeArray.firstIndex(where: { $0.id == store.id }) { - (currentTooltipView as? MarkerTooltipView)?.selectStore(at: index) + private func fetchStoreDetails(for stores: [MapPopUpStore]) { + // 빈 목록이면 처리하지 않음 + guard !stores.isEmpty else { return } + + // 먼저 기본 정보로 StoreItem 생성하여 순서 유지 + let initialStoreItems = stores.map { store in + StoreItem( + id: store.id, + thumbnailURL: store.mainImageUrl ?? "", + category: store.category, + title: store.name, + location: store.address, + dateRange: "\(store.startDate ?? "") ~ \(store.endDate ?? "")", + isBookmarked: false + ) } - } else { - currentTooltipView?.removeFromSuperview() - currentTooltipView = nil - } - isMovingToMarker = false - return true - } + // 리스트에는 모든 스토어 정보 표시 (필터링된 모든 스토어) + self.storeListViewController.reactor?.action.onNext(.setStores(initialStoreItems)) + // 각 스토어의 상세 정보를 병렬로 가져와서 업데이트 (북마크 정보 등) + stores.forEach { store in + self.popUpAPIUseCase.getPopUpDetail( + commentType: "NORMAL", + popUpStoredId: store.id + ) + .asObservable() + .observe(on: MainScheduler.instance) + .subscribe(onNext: { [weak self] detail in + self?.storeListViewController.reactor?.action.onNext(.updateStoreBookmark( + id: store.id, + isBookmarked: detail.bookmarkYn + )) + }) + .disposed(by: disposeBag) + } + } + func bindViewport(reactor: MapReactor) { + // 카메라 이동 완료 시 이벤트 발생되는 Subject + let cameraObservable = PublishSubject() + + // NMFMapViewCameraDelegate 메서드에서 호출할 수 있도록 설정 + + // 카메라 변경 감지해서 액션 전달 + cameraObservable + .throttle(.milliseconds(200), scheduler: MainScheduler.instance) + .map { [unowned self] _ -> MapReactor.Action in + let bounds = self.getVisibleBounds() + return .viewportChanged( + northEastLat: bounds.northEast.lat, + northEastLon: bounds.northEast.lng, + southWestLat: bounds.southWest.lat, + southWestLon: bounds.southWest.lng + ) + } + .bind(to: reactor.action) + .disposed(by: disposeBag) + // 현재 뷰포트 내의 스토어 업데이트 - 초기 1회 + reactor.state + .map { $0.viewportStores } + .distinctUntilChanged() + .filter { !$0.isEmpty } + .take(1) + .observe(on: MainScheduler.instance) + .subscribe(onNext: { [weak self] stores in + guard let self = self else { return } + + // 현재 위치가 있으면 가장 가까운 스토어, 없으면 첫 번째 스토어 표시 + if let location = self.locationManager.location { + self.findAndShowNearestStore(from: location) + } else if let firstStore = stores.first, + let marker = self.findMarkerForStore(for: firstStore) { + _ = self.handleSingleStoreTap(marker, store: firstStore) + } + + // 현재 스토어 목록 업데이트 및 클러스터링 + self.currentStores = stores + self.updateMapWithClustering() + }) + .disposed(by: disposeBag) + + // 뷰포트 내 마커 업데이트 및 캐러셀 표시 + reactor.state + .map { $0.viewportStores } + .distinctUntilChanged() + .throttle(.milliseconds(200), scheduler: MainScheduler.instance) + .observe(on: MainScheduler.instance) + .subscribe(onNext: { [weak self] stores in + guard let self = self else { return } - func handleRegionalClusterTap(_ marker: GMSMarker, clusterData: ClusterMarkerData) -> Bool { - let currentZoom = mainView.mapView.camera.zoom - let currentLevel = MapZoomLevel.getLevel(from: currentZoom) + let effectiveViewport = self.getEffectiveViewport() + let bounds = self.getVisibleBounds() - switch currentLevel { - case .city: // 시 단위 클러스터 - let districtZoomLevel: Float = 10.0 - let camera = GMSCameraPosition(target: marker.position, zoom: districtZoomLevel) - mainView.mapView.animate(to: camera) - case .district: // 구 단위 클러스터 - let detailedZoomLevel: Float = 12.0 - let camera = GMSCameraPosition(target: marker.position, zoom: detailedZoomLevel) - mainView.mapView.animate(to: camera) - default: - break - } + // 화면에 보이는 스토어만 필터링 + let visibleStores = stores.filter { store in + let storePosition = NMGLatLng(lat: store.latitude, lng: store.longitude) + return NMGLatLngBounds(southWest: bounds.southWest, northEast: bounds.northEast).contains(storePosition) + } + self.currentStores = visibleStores - // 클러스터에 포함된 스토어들만 표시하도록 마커 업데이트 - updateMarkersForCluster(stores: clusterData.cluster.stores) + // 개별 마커 레벨인지 확인 + let currentZoom = self.mainView.mapView.zoomLevel + let level = MapZoomLevel.getLevel(from: Float(currentZoom)) - // 캐러셀 업데이트 - carouselView.updateCards(clusterData.cluster.stores) - carouselView.isHidden = false - self.currentCarouselStores = clusterData.cluster.stores + if level == .detailed && !visibleStores.isEmpty { + // 캐러셀에 모든 마커 정보 표시 + let effectiveStores = visibleStores.filter { store in + let storePosition = NMGLatLng(lat: store.latitude, lng: store.longitude) + return effectiveViewport.contains(storePosition) + } - return true - } + self.currentCarouselStores = visibleStores + self.carouselView.updateCards(visibleStores) + self.carouselView.isHidden = false + self.mainView.setStoreCardHidden(false, animated: true) + + // 현재 선택된 마커가 있으면 해당 위치로 스크롤 + if let currentMarker = self.currentMarker { + // 마커의 스토어 정보 체크 + if let currentStore = currentMarker.userInfo["storeData"] as? MapPopUpStore, + let index = visibleStores.firstIndex(where: { $0.id == currentStore.id }) { + self.carouselView.scrollToCard(index: index) + } else if let storeArray = currentMarker.userInfo["storeData"] as? [MapPopUpStore], + let firstStore = storeArray.first, + let index = visibleStores.firstIndex(where: { $0.id == firstStore.id }) { + self.carouselView.scrollToCard(index: index) + } else { + // 선택된 마커가 현재 뷰포트에 없는 경우 + self.updateMarkerStyle(marker: currentMarker, selected: false, isCluster: false) + self.currentMarker = nil + + // 첫 번째 스토어의 마커를 선택 상태로 설정 + if let firstStore = visibleStores.first, + let marker = self.findMarkerForStore(for: firstStore) { + self.updateMarkerStyle(marker: marker, selected: true, isCluster: false) + self.currentMarker = marker + } - func handleMicroClusterTap(_ marker: GMSMarker, storeArray: [MapPopUpStore]) -> Bool { - // 이미 선택된 마커를 다시 탭할 때 - if currentMarker == marker { - // 툴팁과 캐러셀만 숨기고, 마커의 선택 상태는 유지 - currentTooltipView?.removeFromSuperview() - currentTooltipView = nil - currentTooltipStores = [] - currentTooltipCoordinate = nil + self.carouselView.scrollToCard(index: 0) + } + } else { + // 선택된 마커가 없는 경우, 첫 번째 스토어로 설정 + if let firstStore = visibleStores.first, + let marker = self.findMarkerForStore(for: firstStore) { + self.updateMarkerStyle(marker: marker, selected: true, isCluster: false) + self.currentMarker = marker + } + self.carouselView.scrollToCard(index: 0) + } + } else { + // 클러스터 레벨이거나 마커가 없는 경우 + self.carouselView.isHidden = true + self.carouselView.updateCards([]) + self.currentCarouselStores = [] + self.mainView.setStoreCardHidden(true, animated: true) + + if level == .detailed && visibleStores.isEmpty { + // 개별 마커 레벨인데 마커가 없는 경우 토스트 표시 + self.showNoMarkersToast() + } + } - carouselView.isHidden = true - carouselView.updateCards([]) - currentCarouselStores = [] + self.updateMapWithClustering() + }) + .disposed(by: disposeBag) + } - // 마커 상태 업데이트 - if let markerView = marker.iconView as? MapMarker { - markerView.injection(with: .init( - isSelected: false, - isCluster: false, - count: storeArray.count - )) + private func findAndShowNearestStore(from location: CLLocation) { + guard !currentStores.isEmpty else { + Logger.log(message: "현재위치 표기할 스토어가 없습니다", category: .debug) + return } - currentMarker = nil - isMovingToMarker = false - return false - } + resetSelectedMarker() - isMovingToMarker = true + let nearestStore = currentStores.min { store1, store2 in + let location1 = CLLocation(latitude: store1.latitude, longitude: store1.longitude) + let location2 = CLLocation(latitude: store2.latitude, longitude: store2.longitude) + return location.distance(from: location1) < location.distance(from: location2) + } - currentTooltipView?.removeFromSuperview() - currentTooltipView = nil + if let store = nearestStore, let marker = findMarkerForStore(for: store) { + // 카메라 이동 없이 선택된 마커만 업데이트 + _ = handleSingleStoreTap(marker, store: store) + } + // 마커가 없다면 새로 생성 + else if let store = nearestStore { + let marker = NMFMarker() + marker.position = NMGLatLng(lat: store.latitude, lng: store.longitude) + marker.userInfo = ["storeData": store] + marker.anchor = CGPoint(x: 0.5, y: 1.0) - if let previousMarker = currentMarker, - let previousMarkerView = previousMarker.iconView as? MapMarker { - previousMarkerView.injection(with: .init( - isSelected: false, - isCluster: false, - count: (previousMarker.userData as? [MapPopUpStore])?.count ?? 1 - )) - } + // 마커 스타일 설정 + updateMarkerStyle(marker: marker, selected: true, isCluster: false) + marker.mapView = mainView.mapView - if let markerView = marker.iconView as? MapMarker { - markerView.injection(with: .init( - isSelected: true, - isCluster: false, - count: storeArray.count - )) + individualMarkerDictionary[store.id] = marker + currentMarker = marker + carouselView.updateCards([store]) + currentCarouselStores = [store] + carouselView.scrollToCard(index: 0) + mainView.setStoreCardHidden(false, animated: true) + } } - currentMarker = marker - currentCarouselStores = storeArray - carouselView.updateCards(storeArray) - carouselView.isHidden = false - carouselView.scrollToCard(index: 0) + // MARK: - Marker Handling + func handleSingleStoreTap(_ marker: NMFMarker, store: MapPopUpStore) -> Bool { + isMovingToMarker = true - mainView.setStoreCardHidden(false, animated: true) + // 이전 마커 선택 상태 해제 + if let previousMarker = currentMarker { + updateMarkerStyle(marker: previousMarker, selected: false, isCluster: false) + } - // 지도 이동 및 툴팁 생성 - mainView.mapView.animate(toLocation: marker.position) + // 새 마커 선택 상태로 설정 + updateMarkerStyle(marker: marker, selected: true, isCluster: false) + currentMarker = marker - // 툴팁 생성을 idleAtPosition 이벤트까지 기다리지 않고 직접 호출 - if storeArray.count > 1 { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in - guard let self = self else { return } - self.configureTooltip(for: marker, stores: storeArray) - self.isMovingToMarker = false - } - } + // 캐러셀에 표시할 스토어 확인 + if currentCarouselStores.isEmpty || !currentCarouselStores.contains(where: { $0.id == store.id }) { + // 현재 뷰포트의 모든 스토어를 가져오기 + let bounds = getVisibleBounds() - return true - } - private func showNoMarkersToast() { - // 디자인 예정이므로 임시 구현 - Logger.log(message: "현재 지도 영역에 표시할 마커가 없습니다", category: .debug) - } - private func updateTooltipPosition() { - guard let marker = currentMarker, let tooltip = currentTooltipView else { return } + let visibleStores = currentStores.filter { store in + let storePosition = NMGLatLng(lat: store.latitude, lng: store.longitude) + return NMGLatLngBounds(southWest: bounds.southWest, northEast: bounds.northEast).contains(storePosition) + } - let markerPoint = mainView.mapView.projection.point(for: marker.position) - var markerCenter = markerPoint - if let iconView = marker.iconView { - markerCenter.y = markerPoint.y - iconView.bounds.height / 1.5 - } + if !visibleStores.isEmpty { + // 뷰포트의 모든 스토어를 캐러셀에 표시 + currentCarouselStores = visibleStores + carouselView.updateCards(visibleStores) - // 오프셋 값 (디자인에 맞게 조정) - let offsetX: CGFloat = -10 - let offsetY: CGFloat = -10 + // 선택한 스토어의 인덱스를 찾아 스크롤 + if let index = visibleStores.firstIndex(where: { $0.id == store.id }) { + carouselView.scrollToCard(index: index) + } + } else { + // 뷰포트에 다른 스토어가 없는 경우, 선택한 스토어만 표시 + currentCarouselStores = [store] + carouselView.updateCards([store]) + } + } else { + // 캐러셀에 이미 해당 스토어가 있는 경우, 해당 위치로 스크롤 + if let index = currentCarouselStores.firstIndex(where: { $0.id == store.id }) { + carouselView.scrollToCard(index: index) + } + } - tooltip.frame.origin = CGPoint( - x: markerCenter.x + offsetX, - y: markerCenter.y - tooltip.frame.height - offsetY - ) - } + carouselView.isHidden = false + mainView.setStoreCardHidden(false, animated: true) - private func resetSelectedMarker() { - if let currentMarker = currentMarker, - let markerView = currentMarker.iconView as? MapMarker { - // 기존 마커뷰 재사용, 새로 생성하지 않음 - if let storeArray = currentMarker.userData as? [MapPopUpStore] { - markerView.injection(with: .init( - isSelected: false, - isCluster: false, - count: storeArray.count - )) + // 툴팁 처리 + if let storeArray = marker.userInfo["storeData"] as? [MapPopUpStore], storeArray.count > 1 { + // 마이크로 클러스터인 경우 툴팁 표시 + configureTooltip(for: marker, stores: storeArray) + // 해당 스토어의 툴팁 인덱스 선택 + if let index = storeArray.firstIndex(where: { $0.id == store.id }) { + (currentTooltipView as? MarkerTooltipView)?.selectStore(at: index) + } } else { - markerView.injection(with: .init( - isSelected: false, - isCluster: false - )) + // 단일 마커인 경우 툴팁 제거 + currentTooltipView?.removeFromSuperview() + currentTooltipView = nil } + + isMovingToMarker = false + return true } - // 툴팁 제거 - currentTooltipView?.removeFromSuperview() - currentTooltipView = nil - currentTooltipStores = [] - currentTooltipCoordinate = nil - carouselView.isHidden = true - carouselView.updateCards([]) - currentCarouselStores = [] + // 리전 클러스터 탭 처리 + func handleRegionalClusterTap(_ marker: NMFMarker, clusterData: ClusterMarkerData) -> Bool { + print("handleRegionalClusterTap 함수 호출됨") - // 현재 마커 참조 제거 - self.currentMarker = nil - } + let currentZoom = mainView.mapView.zoomLevel + let currentLevel = MapZoomLevel.getLevel(from: Float(currentZoom)) + // 디버깅 + print("현재 줌 레벨: \(currentZoom), 모드: \(currentLevel)") + print("클러스터 정보: \(clusterData.cluster.name), 스토어 수: \(clusterData.storeCount)") -} + switch currentLevel { + case .city: // 시 단위 클러스터 + print("시 단위 클러스터 처리") + let districtZoomLevel: Double = 10.0 + let cameraUpdate = NMFCameraUpdate(scrollTo: marker.position, zoomTo: districtZoomLevel) + cameraUpdate.animation = .easeIn + cameraUpdate.animationDuration = 0.3 + mainView.mapView.moveCamera(cameraUpdate) + case .district: // 구 단위 클러스터 + print("구 단위 클러스터 처리") + let detailedZoomLevel: Double = 12.0 + let cameraUpdate = NMFCameraUpdate(scrollTo: marker.position, zoomTo: detailedZoomLevel) + cameraUpdate.animation = .easeIn + cameraUpdate.animationDuration = 0.3 + mainView.mapView.moveCamera(cameraUpdate) -extension MapViewController { - func bindViewport(reactor: MapReactor) { - let cameraObservable = Observable.merge([ - mainView.mapView.rx.didChangePosition, - mainView.mapView.rx.idleAtPosition - ]) - .throttle(.milliseconds(200), scheduler: MainScheduler.instance) - .map { [unowned self] in - self.mainView.mapView.camera + default: + print("기타 레벨 클러스터 처리") + break } - let distinctCameraObservable = cameraObservable.distinctUntilChanged { (cam1, cam2) -> Bool in - let loc1 = CLLocation(latitude: cam1.target.latitude, longitude: cam1.target.longitude) - let loc2 = CLLocation(latitude: cam2.target.latitude, longitude: cam2.target.longitude) - let distance = loc1.distance(from: loc2) - return distance < 40 - } + // 클러스터에 포함된 스토어들만 표시하도록 마커 업데이트 + updateMarkersForCluster(stores: clusterData.cluster.stores) - // 뷰포트가 변경될 때마다 액션 전달 - distinctCameraObservable - .map { [unowned self] _ -> MapReactor.Action in - let visibleRegion = self.mainView.mapView.projection.visibleRegion() - return .viewportChanged( - northEastLat: visibleRegion.farRight.latitude, - northEastLon: visibleRegion.farRight.longitude, - southWestLat: visibleRegion.nearLeft.latitude, - southWestLon: visibleRegion.nearLeft.longitude - ) - } - .bind(to: reactor.action) - .disposed(by: disposeBag) + // 캐러셀 업데이트 + carouselView.updateCards(clusterData.cluster.stores) + carouselView.isHidden = false + self.currentCarouselStores = clusterData.cluster.stores - // 현재 뷰포트 내의 스토어 업데이트 - 마커만 업데이트 - reactor.state - .map { $0.viewportStores } - .distinctUntilChanged() - .filter { !$0.isEmpty } - .take(1) // 초기 1회만 실행 - .observe(on: MainScheduler.instance) - .subscribe(onNext: { [weak self] stores in - guard let self = self else { return } - - // 현재 위치가 있으면 가장 가까운 스토어, 없으면 첫 번째 스토어 표시 - if let location = self.locationManager.location { - self.findAndShowNearestStore(from: location) - } else if let firstStore = stores.first, - let marker = self.findMarkerForStore(for: firstStore) { - _ = self.handleSingleStoreTap(marker, store: firstStore) - } - - // 현재 스토어 목록 업데이트 및 클러스터링 - self.currentStores = stores - self.updateMapWithClustering() - }) - .disposed(by: disposeBag) - - - // 뷰포트 내 마커 업데이트 및 캐러셀 표시 (수정된 부분) - reactor.state - .map { $0.viewportStores } - .distinctUntilChanged() - .throttle(.milliseconds(200), scheduler: MainScheduler.instance) - .observe(on: MainScheduler.instance) - .subscribe(onNext: { [weak self] stores in - guard let self = self else { return } + return true + } - let effectiveViewport = self.getEffectiveViewport() - let visibleRegion = self.mainView.mapView.projection.visibleRegion() - let bounds = GMSCoordinateBounds(region: visibleRegion) - // 화면에 보이는 스토어만 필터링 - let visibleStores = stores.filter { store in - bounds.contains(CLLocationCoordinate2D( - latitude: store.latitude, - longitude: store.longitude - )) - } + // 마이크로 클러스터 탭 처리 + func handleMicroClusterTap(_ marker: NMFMarker, storeArray: [MapPopUpStore]) -> Bool { + // 이미 선택된 마커를 다시 탭할 때 + if currentMarker == marker { + // 툴팁과 캐러셀만 숨기고, 마커의 선택 상태는 유지 + currentTooltipView?.removeFromSuperview() + currentTooltipView = nil + currentTooltipStores = [] + currentTooltipCoordinate = nil - self.currentStores = visibleStores + carouselView.isHidden = true + carouselView.updateCards([]) + currentCarouselStores = [] - // 개별 마커 레벨인지 확인 - let currentZoom = self.mainView.mapView.camera.zoom - let level = MapZoomLevel.getLevel(from: currentZoom) + // 마커 상태 업데이트 + updateMarkerStyle(marker: marker, selected: false, isCluster: false, count: storeArray.count) - if level == .detailed && !visibleStores.isEmpty { - // 캐러셀에 모든 마커 정보 표시 - let effectiveViewport = self.getEffectiveViewport() - let effectiveStores = visibleStores.filter { store in - effectiveViewport.contains(CLLocationCoordinate2D( - latitude: store.latitude, - longitude: store.longitude - )) - } + currentMarker = nil + isMovingToMarker = false + return false + } - self.currentCarouselStores = visibleStores - self.carouselView.updateCards(visibleStores) - self.carouselView.isHidden = false - self.mainView.setStoreCardHidden(false, animated: true) + isMovingToMarker = true - // 현재 선택된 마커가 있으면 해당 위치로 스크롤 - if let currentMarker = self.currentMarker { - // 마커의 스토어 정보 체크 - if let currentStore = currentMarker.userData as? MapPopUpStore, - let index = visibleStores.firstIndex(where: { $0.id == currentStore.id }) { - self.carouselView.scrollToCard(index: index) - } else if let storeArray = currentMarker.userData as? [MapPopUpStore], - let firstStore = storeArray.first, - let index = visibleStores.firstIndex(where: { $0.id == firstStore.id }) { - self.carouselView.scrollToCard(index: index) - } else { - // 선택된 마커가 현재 뷰포트에 없는 경우 - if let markerView = currentMarker.iconView as? MapMarker { - markerView.injection(with: .init( - isSelected: false, - isCluster: false, - count: (currentMarker.userData as? [MapPopUpStore])?.count ?? 1 - )) - } - self.currentMarker = nil + currentTooltipView?.removeFromSuperview() + currentTooltipView = nil - // 첫 번째 스토어의 마커를 선택 상태로 설정 - if let firstStore = visibleStores.first, - let marker = self.findMarkerForStore(for: firstStore) { - if let markerView = marker.iconView as? MapMarker { - markerView.injection(with: .init( - isSelected: true, - isCluster: false, - count: (marker.userData as? [MapPopUpStore])?.count ?? 1 - )) - } - self.currentMarker = marker - } + if let previousMarker = currentMarker { + updateMarkerStyle(marker: previousMarker, selected: false, isCluster: false) + } - self.carouselView.scrollToCard(index: 0) - } - } else { - // 선택된 마커가 없는 경우, 첫 번째 스토어로 설정 - if let firstStore = visibleStores.first, - let marker = self.findMarkerForStore(for: firstStore) { - if let markerView = marker.iconView as? MapMarker { - markerView.injection(with: .init( - isSelected: true, - isCluster: false, - count: (marker.userData as? [MapPopUpStore])?.count ?? 1 - )) - } - self.currentMarker = marker - } - self.carouselView.scrollToCard(index: 0) - } - } else { - // 클러스터 레벨이거나 마커가 없는 경우 - self.carouselView.isHidden = true - self.carouselView.updateCards([]) - self.currentCarouselStores = [] - self.mainView.setStoreCardHidden(true, animated: true) + updateMarkerStyle(marker: marker, selected: true, isCluster: false, count: storeArray.count) + currentMarker = marker - if level == .detailed && visibleStores.isEmpty { - // 개별 마커 레벨인데 마커가 없는 경우 토스트 표시 - self.showNoMarkersToast() - } - } + currentCarouselStores = storeArray + carouselView.updateCards(storeArray) + carouselView.isHidden = false + carouselView.scrollToCard(index: 0) - self.updateMapWithClustering() - }) - .disposed(by: disposeBag) + mainView.setStoreCardHidden(false, animated: true) - } - private func fetchStoreDetails(for stores: [MapPopUpStore]) { - // 빈 목록이면 처리하지 않음 - guard !stores.isEmpty else { return } - - // 먼저 기본 정보로 StoreItem 생성하여 순서 유지 - let initialStoreItems = stores.map { store in - StoreItem( - id: store.id, - thumbnailURL: store.mainImageUrl ?? "", - category: store.category, - title: store.name, - location: store.address, - dateRange: "\(store.startDate ?? "") ~ \(store.endDate ?? "")", - isBookmarked: false - ) - } + // 지도 이동 + let cameraUpdate = NMFCameraUpdate(scrollTo: marker.position) + cameraUpdate.animation = .easeIn + cameraUpdate.animationDuration = 0.3 + mainView.mapView.moveCamera(cameraUpdate) - self.storeListViewController.reactor?.action.onNext(.setStores(initialStoreItems)) + // 툴팁 생성 + if storeArray.count > 1 { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in + guard let self = self else { return } + self.configureTooltip(for: marker, stores: storeArray) + self.isMovingToMarker = false + } + } - stores.forEach { store in - self.popUpAPIUseCase.getPopUpDetail( - commentType: "NORMAL", - popUpStoredId: store.id - ) - .asObservable() - .observe(on: MainScheduler.instance) - .subscribe(onNext: { [weak self] detail in - self?.storeListViewController.reactor?.action.onNext(.updateStoreBookmark( - id: store.id, - isBookmarked: detail.bookmarkYn - )) - }) - .disposed(by: disposeBag) + return true + } + + private func showNoMarkersToast() { + // 디자인 예정이므로 임시 구현 + Logger.log(message: "현재 지도 영역에 표시할 마커가 없습니다", category: .debug) } } + // MARK: - CLLocationManagerDelegate + extension MapViewController { + func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + guard let location = locations.last else { return } - private func findMarkerForStore(for store: MapPopUpStore) -> GMSMarker? { - // individualMarkerDictionary에 저장된 모든 마커를 순회 - for marker in individualMarkerDictionary.values { - if let singleStore = marker.userData as? MapPopUpStore, singleStore.id == store.id { - return marker - } - if let storeGroup = marker.userData as? [MapPopUpStore], - storeGroup.contains(where: { $0.id == store.id }) { - return marker - } - } - // 상세 레벨이 아닐 경우 clusterMarkerDictionary에도 동일하게 검색 - for marker in clusterMarkerDictionary.values { - if let storeGroup = marker.userData as? [MapPopUpStore], - storeGroup.contains(where: { $0.id == store.id }) { - return marker + currentMarker?.mapView = nil + currentMarker = nil + carouselView.isHidden = true + currentCarouselStores = [] + + let position = NMGLatLng(lat: location.coordinate.latitude, lng: location.coordinate.longitude) + let cameraUpdate = NMFCameraUpdate(scrollTo: position, zoomTo: 15.0) + mainView.mapView.moveCamera(cameraUpdate) { [weak self] _ in + guard let self = self else { return } + self.findAndShowNearestStore(from: location) } + + locationManager.stopUpdatingLocation() } - return nil } - private func updateMarkersForCluster(stores: [MapPopUpStore]) { - for marker in individualMarkerDictionary.values { - marker.map = nil - } - individualMarkerDictionary.removeAll() - for marker in clusterMarkerDictionary.values { - marker.map = nil - } - clusterMarkerDictionary.removeAll() + // MARK: - NMFMapViewTouchDelegate + extension MapViewController { + // 마커 탭 이벤트 처리 + // 마커 탭 이벤트 처리 + func mapView(_ mapView: NMFMapView, didTap marker: NMFMarker) -> Bool { + Logger.log(message: "didTapMarker 호출됨: \(marker.position), userInfo: \(marker.userInfo)", category: .debug) + + // 클러스터 마커 확인 + if let clusterData = marker.userInfo["clusterData"] as? ClusterMarkerData { + Logger.log(message: "클러스터 데이터 감지: \(clusterData.cluster.name), 스토어 수: \(clusterData.storeCount)", category: .debug) + return handleRegionalClusterTap(marker, clusterData: clusterData) + } + // 마이크로 클러스터 또는 단일 스토어 마커 확인 + else if let storeArray = marker.userInfo["storeData"] as? [MapPopUpStore] { + if storeArray.count > 1 { + Logger.log(message: "마이크로 클러스터 감지: \(storeArray.count)개 스토어", category: .debug) + return handleMicroClusterTap(marker, storeArray: storeArray) + } else if let singleStore = storeArray.first { + Logger.log(message: "단일 스토어 감지: \(singleStore.name)", category: .debug) + return handleSingleStoreTap(marker, store: singleStore) + } + } + // 단일 스토어 마커 (배열이 아닌 경우) 확인 + else if let singleStore = marker.userInfo["storeData"] as? MapPopUpStore { + Logger.log(message: "단일 스토어 감지: \(singleStore.name)", category: .debug) + return handleSingleStoreTap(marker, store: singleStore) + } - for store in stores { - addMarker(for: store) + Logger.log(message: "인식할 수 없는 마커 타입", category: .error) + return false } - } + // 지도 탭 이벤트 처리 + func mapView(_ mapView: NMFMapView, didTapMap latlng: NMGLatLng, point: CGPoint) { + guard !isMovingToMarker else { return } -private func handleMarkerTap(_ marker: GMSMarker) -> Bool { - isMovingToMarker = true - - if let clusterData = marker.userData as? ClusterMarkerData { - let clusterToIndividualZoom: Float = 14.0 - let currentZoom = mainView.mapView.camera.zoom - let newZoom: Float = (currentZoom < clusterToIndividualZoom) - ? clusterToIndividualZoom - : min(mainView.mapView.maxZoom, currentZoom + 1) + // 선택된 마커 초기화 + if let currentMarker = currentMarker { + updateMarkerStyle(marker: currentMarker, selected: false, isCluster: false) + self.currentMarker = nil + } - let camera = GMSCameraPosition(target: marker.position, zoom: newZoom) - mainView.mapView.animate(to: camera) + // 툴팁 제거 + currentTooltipView?.removeFromSuperview() + currentTooltipView = nil + currentTooltipStores = [] + currentTooltipCoordinate = nil - // 여러 스토어 캐러셀 업데이트 - let multiStores = clusterData.cluster.stores - carouselView.updateCards(multiStores) - carouselView.isHidden = multiStores.isEmpty - currentCarouselStores = multiStores - // 클러스터 마커 강조/해제 등 필요시 추가 + // 캐러셀 초기화 + carouselView.isHidden = true + carouselView.updateCards([]) + self.currentCarouselStores = [] + mainView.setStoreCardHidden(true, animated: true) - return true + // 클러스터링 업데이트 + updateMapWithClustering() } + } - // 2) 일반 마커일 때 - if let previousMarker = currentMarker { - let markerView = MapMarker() - markerView.injection(with: .init(isSelected: false, isCluster: false)) - previousMarker.iconView = markerView + // MARK: - NMFMapViewCameraDelegate + extension MapViewController { + // 카메라 이동 시작 시 호출 + func mapView(_ mapView: NMFMapView, cameraWillChangeByReason reason: Int, animated: Bool) { + if reason == NMFMapChangedByGesture && !isMovingToMarker { + resetSelectedMarker() + } } - // 새 마커 강조 - let markerView = MapMarker() - markerView.injection(with: .init(isSelected: true, isCluster: false)) - marker.iconView = markerView - currentMarker = marker - - if let store = marker.userData as? MapPopUpStore { - // 캐러셀에 뷰포트 내 스토어들을 모두 표시 - carouselView.updateCards(currentStores) - carouselView.isHidden = currentStores.isEmpty - currentCarouselStores = currentStores - - // 탭한 스토어가 몇 번째인지 찾아서 스크롤 - if let idx = currentStores.firstIndex(where: { $0.id == store.id }) { - carouselView.scrollToCard(index: idx) + // 카메라 이동 중 호출 + func mapView(_ mapView: NMFMapView, cameraIsChangingByReason reason: Int) { + if !isMovingToMarker { + currentTooltipView?.removeFromSuperview() + currentTooltipView = nil + currentTooltipStores = [] + updateMapWithClustering() + + // 캐러셀 초기화 + carouselView.isHidden = true + carouselView.updateCards([]) + currentCarouselStores = [] } } - return true - } - - - private func getCurrentViewportBounds() -> (northEast: CLLocationCoordinate2D, southWest: CLLocationCoordinate2D) { - let region = mainView.mapView.projection.visibleRegion() - return (northEast: region.farRight, southWest: region.nearLeft) - } - // 커스텀 마커 - func updateMarkers(with newStores: [MapPopUpStore]) { - let newStoreIDs = Set(newStores.map { $0.id }) - - for store in newStores { - if let marker = individualMarkerDictionary[store.id] { - if abs(marker.position.latitude - store.latitude) > 0.0001 || - abs(marker.position.longitude - store.longitude) > 0.0001 { - marker.position = store.coordinate + // 카메라 이동 완료 시 호출 + func mapView(_ mapView: NMFMapView, cameraDidChangeByReason reason: Int, animated: Bool) { + if let marker = self.currentMarker, + let storeArray = marker.userInfo["storeData"] as? [MapPopUpStore], + storeArray.count > 1 { + // 툴팁이 없으면 생성, 있으면 위치 업데이트 + if self.currentTooltipView == nil { + self.configureTooltip(for: marker, stores: storeArray) + } else { + self.updateTooltipPosition() } - } else { - let marker = GMSMarker(position: store.coordinate) - marker.userData = store - - let markerView = MapMarker() - markerView.injection(with: store.toMarkerInput()) - marker.iconView = markerView - marker.map = mainView.mapView - - individualMarkerDictionary[store.id] = marker } - } - - for (id, marker) in individualMarkerDictionary { - if !newStoreIDs.contains(id) { - marker.map = nil - individualMarkerDictionary.removeValue(forKey: id) + self.isMovingToMarker = false + + // 뷰포트 변경 이벤트 처리 - idleSubject 통해 알림 + idleSubject.onNext(()) + + // 뷰포트 변경 이벤트 처리 + if let reactor = self.reactor { + let bounds = self.getVisibleBounds() + reactor.action.onNext(.viewportChanged( + northEastLat: bounds.northEast.lat, + northEastLon: bounds.northEast.lng, + southWestLat: bounds.southWest.lat, + southWestLon: bounds.southWest.lng + )) } } } -} -// MARK: - Reactive Extensions -extension Reactive where Base: GMSMapView { - var delegate: DelegateProxy { - return GMSMapViewDelegateProxy.proxy(for: base) - } - - var didChangePosition: Observable { - let proxy = GMSMapViewDelegateProxy.proxy(for: base) - return proxy.didChangePositionSubject.asObservable() - } - var idleAtPosition: Observable { - let proxy = GMSMapViewDelegateProxy.proxy(for: base) - return proxy.idleAtPositionSubject.asObservable() - } -} -extension CLLocationCoordinate2D: Equatable { - public static func == (lhs: CLLocationCoordinate2D, rhs: CLLocationCoordinate2D) -> Bool { - return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude - } -} -extension MapViewController: UIGestureRecognizerDelegate { - func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { - return true - } + // MARK: - UIGestureRecognizerDelegate + extension MapViewController { + // 맵뷰의 다른 제스처와 충돌하지 않도록 함 + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + // 맵의 내장 제스처와 동시 인식 허용 + return true + } - // 리스트뷰가 보일 때만 커스텀 탭 제스처 허용 - func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { - // 터치가 리스트뷰 영역에 있으면 커스텀 제스처 트리거하지 않음 - let touchPoint = touch.location(in: view) + // 리스트뷰가 보일 때만 커스텀 탭 제스처 허용 + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { + // 터치가 리스트뷰 영역에 있으면 커스텀 제스처 트리거하지 않음 + let touchPoint = touch.location(in: view) - // 리스트뷰가 보이고 터치가 리스트뷰 위에 있으면 탭 처리하지 않음 - if modalState != .bottom { - let listViewY = storeListViewController.view.frame.minY - if touchPoint.y > listViewY { - return false + // 리스트뷰가 보이고 터치가 리스트뷰 위에 있으면 탭 처리하지 않음 + if modalState != .bottom { + let listViewY = storeListViewController.view.frame.minY + if touchPoint.y > listViewY { + return false + } } - } - return true + return true + } + } +extension NMGLatLngBounds { + func contains(_ point: NMGLatLng) -> Bool { + let southWestLat = self.southWest.lat + let southWestLng = self.southWest.lng + let northEastLat = self.northEast.lat + let northEastLng = self.northEast.lng + + return point.lat >= southWestLat && + point.lat <= northEastLat && + point.lng >= southWestLng && + point.lng <= northEastLng } } diff --git a/Poppool/Poppool/Presentation/Map/StoreListView/StoreListReactor.swift b/Poppool/Poppool/Presentation/Map/StoreListView/StoreListReactor.swift index 864d2426..6d00be65 100644 --- a/Poppool/Poppool/Presentation/Map/StoreListView/StoreListReactor.swift +++ b/Poppool/Poppool/Presentation/Map/StoreListView/StoreListReactor.swift @@ -134,7 +134,8 @@ final class StoreListReactor: Reactor { guard let self = self else { return .empty() } return self.popUpAPIUseCase.getPopUpDetail( commentType: "NORMAL", - popUpStoredId: store.id + popUpStoredId: store.id, + isViewCount: false ) .map { detail in var updatedStore = store From 2a935372044e15c999121e4f93e814224bfe0e7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=80=E1=85=B5=E1=84=92?= =?UTF-8?q?=E1=85=A7=E1=86=AB?= Date: Mon, 24 Mar 2025 16:12:51 +0900 Subject: [PATCH 11/95] =?UTF-8?q?[REAFACTOR]=20:=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=8B=9C=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=A4=91=EC=B2=A9=20?= =?UTF-8?q?=EC=88=98=EC=A0=95,=EC=8A=A4=ED=86=A0=EC=96=B4=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=EC=8B=9C=20=EC=9C=A0=ED=9A=A8=EC=84=B1=EA=B2=80?= =?UTF-8?q?=EC=82=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PopUpStoreRegisterViewController.swift | 448 +++++++++++++++--- .../Admin/AdminViewController.swift | 41 +- .../FullScreenMapViewController.swift | 205 ++++++-- .../MapGuideView/MapGuideViewController.swift | 21 +- 4 files changed, 590 insertions(+), 125 deletions(-) diff --git a/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift b/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift index a559ee54..0d7914ea 100644 --- a/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift +++ b/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift @@ -23,9 +23,14 @@ final class PopUpStoreRegisterViewController: BaseViewController { private var latField: UITextField? private var lonField: UITextField? private var descTV: UITextView? + private let popupName: String = "" + private var originalImageIds: [String: Int64] = [:] + private var deletedImageIds: [Int64] = [] + private var deletedImagePaths: [String] = [] + private let editingStore: GetAdminPopUpStoreListResponseDTO.PopUpStore? let presignedService = PreSignedService() @@ -193,13 +198,21 @@ final class PopUpStoreRegisterViewController: BaseViewController { super.viewDidLoad() view.backgroundColor = UIColor(white:0.95, alpha:1) - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap)) - tapGesture.cancelsTouchesInView = false - view.addGestureRecognizer(tapGesture) if let store = editingStore { - loadStoreDetail(for: store.id) - } + // 삭제된 이미지 ID 복원 + if let savedIds = UserDefaults.standard.array(forKey: "deletedImageIds_\(store.id)") as? [Int64] { + self.deletedImageIds = savedIds + Logger.log(message: "저장된 삭제 이미지 ID 복원: \(savedIds.count)개", category: .debug) + } + + // 삭제된 이미지 경로 복원 + if let savedPaths = UserDefaults.standard.array(forKey: "deletedImagePaths_\(store.id)") as? [String] { + self.deletedImagePaths = savedPaths + Logger.log(message: "저장된 삭제 이미지 경로 복원: \(savedPaths.count)개", category: .debug) + } + loadStoreDetail(for: store.id) + } setupNavigation() setupLayout() setupRows() @@ -207,6 +220,8 @@ final class PopUpStoreRegisterViewController: BaseViewController { setupImageCollectionActions() setupKeyboardHandling() setupAddressField() + setupAllFieldListeners() + } @@ -235,6 +250,7 @@ final class PopUpStoreRegisterViewController: BaseViewController { } } private func fillFormWithExistingData(_ storeDetail: GetAdminPopUpStoreDetailResponseDTO) { + // 기본 필드 채우기 nameField?.text = storeDetail.name categoryButton.setTitle("\(storeDetail.categoryName) ▾", for: .normal) addressField?.text = storeDetail.address @@ -242,30 +258,121 @@ final class PopUpStoreRegisterViewController: BaseViewController { lonField?.text = String(storeDetail.longitude) descTV?.text = storeDetail.desc + // 중요: ID와 URL 매핑 초기화 및 설정 + self.originalImageIds.removeAll() + // deletedImageIds와 deletedImagePaths는 초기화하지 않음 (기존 삭제 정보 유지) + + // 중요: 여기서 originalImageIds 맵을 세팅합니다 + for image in storeDetail.imageList { + self.originalImageIds[image.imageUrl] = image.id + Logger.log(message: "이미지 ID 매핑: \(image.imageUrl) -> \(image.id)", category: .debug) + } + + // 날짜 설정 let isoFormatter = ISO8601DateFormatter() - + if let startDate = isoFormatter.date(from: storeDetail.startDate), let endDate = isoFormatter.date(from: storeDetail.endDate) { self.selectedStartDate = startDate self.selectedEndDate = endDate - self.updatePeriodButtonTitle() // 버튼에 날짜 텍스트 업데이트 - } - - // 대표 이미지 로드 (생략된 부분은 기존 코드 참고) - if let mainImageURL = presignedService.fullImageURL(from: storeDetail.mainImageUrl) { - URLSession.shared.dataTask(with: mainImageURL) { [weak self] data, response, error in - guard let self = self, - let data = data, - let image = UIImage(data: data) else { return } - let extendedImage = ExtendedImage(filePath: storeDetail.mainImageUrl, image: image, isMain: true) - DispatchQueue.main.async { - self.images.append(extendedImage) - self.imagesCollectionView.reloadData() - self.updateSaveButtonState() - } - }.resume() + self.updatePeriodButtonTitle() + } + + // 중요: 기존 이미지는 먼저 모두 제거 + self.images.removeAll() + + // 이미지 목록 디버깅 - 실제 서버에서 받은 목록 확인 + Logger.log(message: "서버에서 받은 이미지 목록 (총 \(storeDetail.imageList.count)개):", category: .debug) + for (index, image) in storeDetail.imageList.enumerated() { + Logger.log(message: " \(index+1). ID: \(image.id), URL: \(image.imageUrl)", category: .debug) + } + + // 삭제된 이미지 ID 집합 생성 (빠른 검색을 위해) + let deletedIdSet = Set(self.deletedImageIds) + Logger.log(message: "삭제된 이미지 ID 목록: \(deletedIdSet)", category: .debug) + + // 중복 및 삭제된 이미지 제외를 위한 집합 + var loadedImageUrls = Set() + + let dispatchGroup = DispatchGroup() + let mainImageUrl = storeDetail.mainImageUrl + Logger.log(message: "대표 이미지 URL: \(mainImageUrl)", category: .debug) + + for imageData in storeDetail.imageList { + // 중복 이미지 건너뛰기 + if loadedImageUrls.contains(imageData.imageUrl) { + Logger.log(message: "중복 이미지 스킵: \(imageData.imageUrl)", category: .debug) + continue + } + + // 삭제된 이미지 건너뛰기 + if deletedIdSet.contains(imageData.id) { + Logger.log(message: "삭제된 이미지 스킵: ID \(imageData.id), URL: \(imageData.imageUrl)", category: .debug) + continue + } + + loadedImageUrls.insert(imageData.imageUrl) + + dispatchGroup.enter() + + if let imageURL = presignedService.fullImageURL(from: imageData.imageUrl) { + Logger.log(message: "이미지 로드 시작: \(imageData.imageUrl)", category: .debug) + + URLSession.shared.dataTask(with: imageURL) { [weak self] data, response, error in + defer { dispatchGroup.leave() } + + // 응답 상태 코드 확인 추가 + if let httpResponse = response as? HTTPURLResponse { + Logger.log(message: "이미지 로드 응답 코드: \(httpResponse.statusCode) - URL: \(imageData.imageUrl)", category: .debug) + if httpResponse.statusCode != 200 { + Logger.log(message: "이미지 로드 실패 - 상태 코드: \(httpResponse.statusCode)", category: .error) + return + } + } + + if let error = error { + Logger.log(message: "이미지 로드 오류: \(error.localizedDescription)", category: .error) + return + } + + guard let self = self, + let data = data, + let image = UIImage(data: data) else { + Logger.log(message: "이미지 변환 실패", category: .error) + return + } + + let isMain = (imageData.imageUrl == mainImageUrl) + + let extendedImage = ExtendedImage(filePath: imageData.imageUrl, image: image, isMain: isMain) + + DispatchQueue.main.async { + self.images.append(extendedImage) + Logger.log(message: "이미지 로드 완료: \(imageData.imageUrl), isMain: \(isMain)", category: .debug) + } + }.resume() + } else { + Logger.log(message: "이미지 URL 생성 실패: \(imageData.imageUrl)", category: .error) + dispatchGroup.leave() + } + } + + dispatchGroup.notify(queue: .main) { [weak self] in + guard let self = self else { return } + + if !self.images.isEmpty && !self.images.contains(where: { $0.isMain }) { + self.images[0].isMain = true + Logger.log(message: "대표 이미지가 없어 첫 번째 이미지를 대표로 설정", category: .debug) + } + + Logger.log(message: "모든 이미지 로드 완료: 총 \(self.images.count)개", category: .debug) + self.imagesCollectionView.reloadData() + self.updateSaveButtonState() } } + + + func loadStoreDetail(for storeId: Int64) { Logger.log(message: "상세 정보 요청 시작 - Store ID: \(storeId)", category: .debug) @@ -281,7 +388,6 @@ final class PopUpStoreRegisterViewController: BaseViewController { } private func setupKeyboardHandling() { - // 키보드 Notification 등록 NotificationCenter.default.addObserver( self, selector: #selector(keyboardWillShow), @@ -449,10 +555,40 @@ final class PopUpStoreRegisterViewController: BaseViewController { make.height.equalTo(44) } } + private func getCurrentFormattedTime() -> String { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy.MM.dd HH:mm" + formatter.timeZone = TimeZone(identifier: "Asia/Seoul") // 한국 시간대 명시적 설정 + return formatter.string(from: Date()) + } + private func setupCreationTimeLabel() -> UILabel { + let currentTime = getCurrentFormattedTime() + return makeSimpleLabel(currentTime) + } + + private func setupAllFieldListeners() { + // 이름 필드 + nameField?.addTarget(self, action: #selector(anyFieldDidChange(_:)), for: .editingChanged) + + // 주소, 위도, 경도 필드 + addressField?.addTarget(self, action: #selector(anyFieldDidChange(_:)), for: .editingChanged) + latField?.addTarget(self, action: #selector(anyFieldDidChange(_:)), for: .editingChanged) + lonField?.addTarget(self, action: #selector(anyFieldDidChange(_:)), for: .editingChanged) + + // 설명 필드 (UITextView는 다르게 처리해야 함) + descTV?.delegate = self + + // 로그 추가 + Logger.log(message: "모든 필드에 변경 리스너가 설정되었습니다", category: .debug) + } + @objc private func anyFieldDidChange(_ textField: UITextField) { + Logger.log(message: "\(textField.accessibilityIdentifier ?? "알 수 없는 필드") 값 변경: \(textField.text ?? "nil")", category: .debug) + updateSaveButtonState() + } // MARK: - Setup Rows private func setupRows() { addRowTextField(leftTitle: "이름", placeholder: "팝업스토어 이름을 입력해 주세요.") - addRowTextField(leftTitle: "이미지", placeholder: "팝업스토어 대표 이미지를 업로드 해주세요.") +// addRowTextField(leftTitle: "이미지", placeholder: "팝업스토어 대표 이미지를 업로드 해주세요.") categoryButton.addTarget(self, action: #selector(didTapCategoryButton), for: .touchUpInside) addRowCustom(leftTitle: "카테고리", rightView: categoryButton) @@ -509,9 +645,9 @@ final class PopUpStoreRegisterViewController: BaseViewController { // (마커) => 2줄 // 1) (마커명 Label + TF) let markerLabel = makePlainLabel("마커명") - + let markerField = makeRoundedTextField("") - + let markerStackH = UIStackView(arrangedSubviews: [markerLabel, markerField]) markerStackH.axis = .horizontal markerStackH.spacing = 8 @@ -548,7 +684,7 @@ final class PopUpStoreRegisterViewController: BaseViewController { addRowCustom(leftTitle: "작성자", rightView: writerLbl) // (13) 작성시간 - let timeLbl = makeSimpleLabel("2025.01.06 10:30") + let timeLbl = setupCreationTimeLabel() addRowCustom(leftTitle: "작성시간", rightView: timeLbl) // (14) 상태값 @@ -641,17 +777,22 @@ final class PopUpStoreRegisterViewController: BaseViewController { } .disposed(by: disposeBag) } - // 저장 버튼 활성화 여부 갱신 private func updateSaveButtonState() { - let isFormValid = validateForm() // 폼 유효성 검사 결과 + // 디버깅을 위한 로깅 추가 + Logger.log(message: "updateSaveButtonState 호출됨", category: .debug) + + let isFormValid = validateForm() + + // 이전 상태와 새 상태가 다를 때만 로그 출력 + if saveButton.isEnabled != isFormValid { + Logger.log(message: "저장 버튼 상태 변경: \(saveButton.isEnabled) -> \(isFormValid)", category: .debug) + } + saveButton.isEnabled = isFormValid saveButton.backgroundColor = isFormValid ? .systemBlue : .lightGray } - /** - rowHeight: 기본(41) - totalHeight: 2줄 필요한 경우(90~100), 3줄 등 필요 시 더 크게 - */ + private func addRowCustom(leftTitle: String, rightView: UIView, rowHeight: CGFloat? = 36, @@ -845,6 +986,7 @@ final class PopUpStoreRegisterViewController: BaseViewController { private func updateCategoryButtonTitle(with category: String) { categoryButton.setTitle("\(category) ▾", for: .normal) +     updateSaveButtonState() } // MARK: - UI Helpers @@ -965,10 +1107,46 @@ private extension PopUpStoreRegisterViewController { /// 특정 index 이미지 삭제 func deleteImage(index: Int) { + // 삭제될 이미지가 대표 이미지였는지 확인 + let wasMainImage = images[index].isMain + + // 삭제된 이미지의 URL 가져오기 + let imageUrl = images[index].filePath + + // 기존에 존재하던 이미지인 경우 (ID가 있는 경우) + if let imageId = originalImageIds[imageUrl] { + // 이미 삭제 목록에 있는지 확인 (중복 방지) + if !deletedImageIds.contains(imageId) { + deletedImageIds.append(imageId) + deletedImagePaths.append(imageUrl) + Logger.log(message: "기존 이미지 삭제 목록에 추가: ID \(imageId), URL: \(imageUrl)", category: .debug) + + // 삭제된 이미지 정보 영구 저장 (앱 재시작 간에도 유지) + if let store = editingStore { + UserDefaults.standard.set(deletedImageIds, forKey: "deletedImageIds_\(store.id)") + UserDefaults.standard.set(deletedImagePaths, forKey: "deletedImagePaths_\(store.id)") + Logger.log(message: "삭제된 이미지 정보 저장 완료", category: .debug) + } + } + } + + // S3 삭제 로직 제거 - 서버 업데이트 후로 이동 + + // 이미지 배열에서 제거 images.remove(at: index) + + // 대표 이미지가 삭제되었고, 다른 이미지가 남아있다면 첫 번째 이미지를 대표 이미지로 설정 + if wasMainImage && !images.isEmpty { + images[0].isMain = true + } + imagesCollectionView.reloadData() updateSaveButtonState() } + + + + } extension PopUpStoreRegisterViewController: PHPickerViewControllerDelegate { func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { @@ -983,6 +1161,10 @@ extension PopUpStoreRegisterViewController: PHPickerViewControllerDelegate { let itemProviders = results.map(\.itemProvider) let dispatchGroup = DispatchGroup() + // 이미 로드된 이미지 경로 목록 (중복 방지) + let existingPaths = Set(self.images.map { $0.filePath }) + Logger.log(message: "기존 이미지 경로 수: \(existingPaths.count)", category: .debug) + for (i, provider) in itemProviders.enumerated() { if provider.canLoadObject(ofClass: UIImage.self) { dispatchGroup.enter() @@ -992,6 +1174,13 @@ extension PopUpStoreRegisterViewController: PHPickerViewControllerDelegate { let image = object as? UIImage else { return } let filePath = "PopUpImage/\(name)/\(uuid)/\(i).jpg" + + // 이미 같은 경로가 있는지 확인 (거의 발생하지 않겠지만 안전장치) + if existingPaths.contains(filePath) { + Logger.log(message: "중복된 이미지 경로 발견: \(filePath)", category: .debug) + return + } + let extended = ExtendedImage( filePath: filePath, image: image, @@ -1003,16 +1192,25 @@ extension PopUpStoreRegisterViewController: PHPickerViewControllerDelegate { } dispatchGroup.notify(queue: .main) { + if newImages.isEmpty { + Logger.log(message: "추가할 새 이미지가 없음", category: .debug) + return + } + + Logger.log(message: "새 이미지 \(newImages.count)개 추가", category: .debug) self.images.append(contentsOf: newImages) + if !self.images.isEmpty && !self.images.contains(where: { $0.isMain }) { self.images[0].isMain = true // 첫 번째 이미지를 대표 이미지로 + Logger.log(message: "대표 이미지 설정: \(self.images[0].filePath)", category: .debug) } + self.imagesCollectionView.reloadData() self.updateSaveButtonState() } } - } + private extension PopUpStoreRegisterViewController { private func validateForm() -> Bool { // (1) 팝업스토어 이름 @@ -1138,14 +1336,87 @@ private extension PopUpStoreRegisterViewController { return true } private func updateStore(_ store: GetAdminPopUpStoreListResponseDTO.PopUpStore) { - // 이미지가 수정되었다면 먼저 이미지 업로드 - if !images.isEmpty { - uploadImagesForUpdate(store) + // 기존에 저장된 이미지는 재업로드하지 않고 그대로 사용 + // 새로 추가된 이미지만 업로드 + + // 새로 추가된 이미지만 필터링 + let newImages = images.filter { image in + return !originalImageIds.keys.contains(image.filePath) + } + + if !newImages.isEmpty { + // 새 이미지만 업로드 + uploadNewImagesForUpdate(store, newImages: newImages) } else { - // 이미지 수정이 없다면 바로 스토어 정보 업데이트 + // 새 이미지가 없으면 바로 스토어 정보 업데이트 updateStoreInfo(store, updatedImagePaths: nil) } } + private func uploadNewImagesForUpdate(_ store: GetAdminPopUpStoreListResponseDTO.PopUpStore, newImages: [ExtendedImage]) { + let uuid = UUID().uuidString + let updatedImages = newImages.enumerated().map { index, image in + let filePath = "PopUpImage/\(nameField?.text ?? "")/\(uuid)/\(index).jpg" + return ExtendedImage( + filePath: filePath, + image: image.image, + isMain: image.isMain) + } + + presignedService.tryUpload(datas: updatedImages.map { + PreSignedService.PresignedURLRequest(filePath: $0.filePath, image: $0.image) + }) + .subscribe( + onSuccess: { [weak self] _ in + guard let self = self else { return } + Logger.log(message: "새 이미지 업로드 성공", category: .info) + + // 모든 이미지 경로 (기존 이미지 + 새 이미지) + var allPaths: [String] = [] + + // 삭제되지 않은 기존 이미지 경로 추가 + let deletedIdSet = Set(self.deletedImageIds) + for (path, id) in self.originalImageIds { + if !deletedIdSet.contains(id) { + allPaths.append(path) + } + } + + // 새로 업로드된 이미지 경로 추가 + let newPaths = updatedImages.map { $0.filePath } + allPaths.append(contentsOf: newPaths) + + // 메인 이미지 경로 결정 + let mainImage: String + if let mainImg = self.images.first(where: { $0.isMain }) { + if self.originalImageIds.keys.contains(mainImg.filePath) { + // 기존 이미지가 메인 + mainImage = mainImg.filePath + } else { + // 새 이미지가 메인인 경우, 새 경로 찾기 + let idx = self.images.firstIndex(where: { $0.filePath == mainImg.filePath }) ?? 0 + if idx < updatedImages.count { + mainImage = updatedImages[idx].filePath + } else { + mainImage = updatedImages.first?.filePath ?? "" + } + } + } else if !allPaths.isEmpty { + mainImage = allPaths[0] + } else { + mainImage = "" + } + + self.updateStoreInfo(store, updatedImagePaths: allPaths, mainImage: mainImage) + }, + onError: { [weak self] error in + Logger.log(message: "이미지 업로드 실패: \(error.localizedDescription)", category: .error) + self?.showErrorAlert(message: "이미지 업로드 실패: \(error.localizedDescription)") + } + ) + .disposed(by: disposeBag) + } + + private func uploadImagesForUpdate(_ store: GetAdminPopUpStoreListResponseDTO.PopUpStore) { let uuid = UUID().uuidString let updatedImages = images.enumerated().map { index, image in @@ -1174,7 +1445,7 @@ private extension PopUpStoreRegisterViewController { .disposed(by: disposeBag) } - private func updateStoreInfo(_ store: GetAdminPopUpStoreListResponseDTO.PopUpStore, updatedImagePaths: [String]?) { + private func updateStoreInfo(_ store: GetAdminPopUpStoreListResponseDTO.PopUpStore, updatedImagePaths: [String]?, mainImage: String? = nil) { guard let name = nameField?.text, let address = addressField?.text, let latitude = Double(latField?.text ?? ""), @@ -1184,9 +1455,27 @@ private extension PopUpStoreRegisterViewController { return } - // 업데이트할 이미지가 있다면 첫 번째 값을 대표 이미지로, 없으면 기존 스토어의 mainImageUrl 사용 - let mainImage = updatedImagePaths?.first ?? store.mainImageUrl + // 메인 이미지 결정 + let finalMainImage: String + if let mainImagePath = mainImage { + finalMainImage = mainImagePath + } else if let firstImage = updatedImagePaths?.first { + finalMainImage = firstImage + } else { + // 이미지가 없는 경우 기존 메인 이미지 사용 + finalMainImage = store.mainImageUrl + } + // 이미지 URL 목록 결정 + let imageUrls: [String] + if let paths = updatedImagePaths, !paths.isEmpty { + imageUrls = paths + } else { + // 이미지 변동이 없을 경우 + imageUrls = [store.mainImageUrl] + } + + // 서버에 스토어 정보 업데이트 요청 let request = UpdatePopUpStoreRequestDTO( popUpStore: .init( id: store.id, @@ -1196,9 +1485,9 @@ private extension PopUpStoreRegisterViewController { address: address, startDate: getFormattedDate(from: selectedStartDate), endDate: getFormattedDate(from: selectedEndDate), - mainImageUrl: mainImage, - bannerYn: !mainImage.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty, - imageUrl: updatedImagePaths ?? [store.mainImageUrl], + mainImageUrl: finalMainImage, + bannerYn: !finalMainImage.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty, + imageUrl: imageUrls, startDateBeforeEndDate: true ), location: .init( @@ -1207,17 +1496,34 @@ private extension PopUpStoreRegisterViewController { markerTitle: "마커 제목", markerSnippet: "마커 설명" ), - imagesToAdd: updatedImagePaths ?? [], - imagesToDelete: [] // 필요한 경우 기존 이미지 삭제 로직 추가 - ) - + imagesToAdd: updatedImagePaths?.filter { !originalImageIds.keys.contains($0) } ?? [], + imagesToDelete: deletedImageIds + ) + // 요청 데이터 로깅 + Logger.log(message: "업데이트 요청 데이터: \(request)", category: .debug) adminUseCase.updateStore(request: request) .subscribe( onNext: { [weak self] _ in + guard let self = self else { return } Logger.log(message: "updateStore API 호출 성공", category: .info) - self?.showSuccessAlert(isUpdate: true) + + // 서버 응답이 성공하면 S3에서 이미지 삭제 수행 + self.deleteImagesFromS3() + + // 성공 시 저장된 삭제 이미지 정보 초기화 + if let storeId = self.editingStore?.id { + UserDefaults.standard.removeObject(forKey: "deletedImageIds_\(storeId)") + UserDefaults.standard.removeObject(forKey: "deletedImagePaths_\(storeId)") + Logger.log(message: "삭제된 이미지 정보 영구 저장소에서 제거 완료", category: .debug) + } + + // 메모리 내 삭제 목록도 초기화 + self.deletedImageIds.removeAll() + self.deletedImagePaths.removeAll() + + self.showSuccessAlert(isUpdate: true) }, onError: { [weak self] error in Logger.log(message: "updateStore API 호출 실패: \(error.localizedDescription)", category: .error) @@ -1225,6 +1531,23 @@ private extension PopUpStoreRegisterViewController { } ) .disposed(by: disposeBag) + + } + private func deleteImagesFromS3() { + // 삭제해야 할 이미지가 없으면 바로 리턴 + guard !deletedImagePaths.isEmpty else { return } + + // 모든 이미지 한 번에 삭제 + presignedService.tryDelete(targetPaths: .init(objectKeyList: deletedImagePaths)) + .subscribe( + onCompleted: { + Logger.log(message: "S3에서 모든 이미지 삭제 성공: \(self.deletedImagePaths.count)개", category: .info) + }, + onError: { error in + Logger.log(message: "S3에서 이미지 삭제 실패: \(error.localizedDescription)", category: .error) + } + ) + .disposed(by: disposeBag) } private func showSuccessAlert(isUpdate: Bool = false) { @@ -1450,6 +1773,7 @@ private extension PopUpStoreRegisterViewController { + private func showSuccessAlert() { let alert = UIAlertController( title: "등록 성공", @@ -1499,7 +1823,7 @@ extension PopUpStoreRegisterViewController: UITextFieldDelegate { return Observable.create { observer in let geocoder = CLGeocoder() let fullAddress = "\(address), Korea" - + geocoder.geocodeAddressString( fullAddress, in: nil, @@ -1511,7 +1835,7 @@ extension PopUpStoreRegisterViewController: UITextFieldDelegate { observer.onCompleted() return } - + if let location = placemarks?.first?.location { observer.onNext(location) } else { @@ -1519,7 +1843,7 @@ extension PopUpStoreRegisterViewController: UITextFieldDelegate { } observer.onCompleted() } - + return Disposables.create() } } @@ -1532,23 +1856,23 @@ extension PopUpStoreRegisterViewController: UITextFieldDelegate { }) .disposed(by: disposeBag) } - - + + @objc private func addressFieldDidChange(_ textField: UITextField) { guard let address = textField.text, !address.isEmpty else { return } - + // 한국 주소임을 명시 let geocoder = CLGeocoder() let addressWithCountry = address + ", South Korea" - + geocoder.geocodeAddressString(addressWithCountry) { [weak self] placemarks, error in if let error = error { print("Geocoding error: \(error.localizedDescription)") return } - + guard let location = placemarks?.first?.location else { return } - + DispatchQueue.main.async { self?.latField?.text = String(format: "%.6f", location.coordinate.latitude) self?.lonField?.text = String(format: "%.6f", location.coordinate.longitude) @@ -1556,5 +1880,11 @@ extension PopUpStoreRegisterViewController: UITextFieldDelegate { } } } - + +} +extension PopUpStoreRegisterViewController: UITextViewDelegate { + func textViewDidChange(_ textView: UITextView) { + Logger.log(message: "설명 필드 값 변경: \(textView.text.count) 글자", category: .debug) + updateSaveButtonState() + } } diff --git a/Poppool/Poppool/Presentation/Admin/AdminViewController.swift b/Poppool/Poppool/Presentation/Admin/AdminViewController.swift index 24e86807..f3a77fac 100644 --- a/Poppool/Poppool/Presentation/Admin/AdminViewController.swift +++ b/Poppool/Poppool/Presentation/Admin/AdminViewController.swift @@ -182,21 +182,46 @@ final class AdminViewController: BaseViewController, View { } private func deleteStore(_ store: GetAdminPopUpStoreListResponseDTO.PopUpStore) { - let imageService = PreSignedService() - - imageService.tryDelete(targetPaths: .init(objectKeyList: [store.mainImageUrl])) - .andThen(adminUseCase.deleteStore(id: store.id)) + // 먼저 스토어 상세 정보를 가져와 모든 이미지 URL을 확인 + adminUseCase.fetchStoreDetail(id: store.id) .observe(on: MainScheduler.instance) .subscribe( - onNext: { [weak self] _ in - self?.reactor?.action.onNext(.reloadData) - ToastMaker.createToast(message: "삭제되었습니다") + onNext: { [weak self] storeDetail in + guard let self = self else { return } + + var allImageUrls = [String]() + + allImageUrls.append(storeDetail.mainImageUrl) + + // 다른 모든 이미지 URL 추가 + let otherImageUrls = storeDetail.imageList.map { $0.imageUrl } + allImageUrls.append(contentsOf: otherImageUrls) + + allImageUrls = Array(Set(allImageUrls)) + + Logger.log(message: "삭제할 이미지: \(allImageUrls.count)개", category: .debug) + + let imageService = PreSignedService() + imageService.tryDelete(targetPaths: .init(objectKeyList: allImageUrls)) + .andThen(self.adminUseCase.deleteStore(id: store.id)) + .observe(on: MainScheduler.instance) + .subscribe( + onNext: { [weak self] _ in + self?.reactor?.action.onNext(.reloadData) + ToastMaker.createToast(message: "삭제되었습니다") + }, + onError: { [weak self] error in + self?.showErrorAlert(message: "삭제 실패: \(error.localizedDescription)") + } + ) + .disposed(by: self.disposeBag) }, onError: { [weak self] error in - self?.showErrorAlert(message: "삭제 실패: \(error.localizedDescription)") + self?.showErrorAlert(message: "스토어 정보 조회 실패: \(error.localizedDescription)") } ) .disposed(by: disposeBag) + } private func showErrorAlert(message: String) { let alert = UIAlertController( diff --git a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FullScreenMapViewController.swift b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FullScreenMapViewController.swift index 1cf7084a..083dfdc3 100644 --- a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FullScreenMapViewController.swift +++ b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FullScreenMapViewController.swift @@ -8,13 +8,20 @@ import CoreLocation class FullScreenMapViewController: MapViewController { // MARK: - Properties private var initialStore: MapPopUpStore? + private var isFullScreenMode = true // 풀스크린 모드 플래그 추가 + private var markerLocked = false // 마커 상태 잠금 플래그 + private var initialMarker: NMFMarker? + // MARK: - Initialization - init(store: MapPopUpStore?) { + init(store: MapPopUpStore?, existingMarker: NMFMarker? = nil) { self.initialStore = store + self.initialMarker = existingMarker super.init() } + + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -23,23 +30,55 @@ class FullScreenMapViewController: MapViewController { override func viewDidLoad() { super.viewDidLoad() setupFullScreenUI() + setupNavigation() +// configureInitialMapPosition() + self.navigationController?.navigationBar.isHidden = false + Logger.log(message: "💡 초기 위치 구성 직전: initialStore=\(String(describing: initialStore?.name))", category: .debug) configureInitialMapPosition() - // 풀스크린 모드에서 별도 터치 델리게이트 설정 + + Logger.log(message: "✅ FullScreenMapViewController - viewDidLoad 완료", category: .debug) + mainView.mapView.touchDelegate = self } + private func setupNavigation() { + navigationItem.title = "찾아가는 길" + let appearance = UINavigationBarAppearance() + appearance.configureWithOpaqueBackground() + appearance.shadowColor = .clear + appearance.backgroundColor = .white + appearance.titleTextAttributes = [ + .foregroundColor: UIColor.black, + .font: UIFont.systemFont(ofSize: 15, weight: .regular) + ] + navigationController?.navigationBar.standardAppearance = appearance + navigationController?.navigationBar.scrollEdgeAppearance = appearance + navigationItem.leftBarButtonItem = UIBarButtonItem( + image: UIImage(named: "bakcbutton")?.withRenderingMode(.alwaysOriginal), + style: .plain, + target: self, + action: #selector(backButtonTapped) + ) + navigationItem.leftBarButtonItem?.imageInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) + } + + @objc private func backButtonTapped() { + dismiss(animated: true) + } + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.setNavigationBarHidden(false, animated: false) tabBarController?.tabBar.isHidden = true + markerLocked = true // 마커 상태 잠금 } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) navigationController?.setNavigationBarHidden(false, animated: false) tabBarController?.tabBar.isHidden = false - navigationItem.title = "찾아가는 길" + navigationItem.title = "찾아가는 길" } // MARK: - Setup @@ -50,24 +89,6 @@ class FullScreenMapViewController: MapViewController { mainView.searchInput.isHidden = true carouselView.isHidden = false - // 닫기 버튼 추가 - let closeButton = UIButton(type: .system) - closeButton.setImage(UIImage(systemName: "xmark"), for: .normal) - closeButton.tintColor = .black - view.addSubview(closeButton) - - closeButton.snp.makeConstraints { make in - make.top.equalTo(view.safeAreaLayoutGuide).offset(16) - make.trailing.equalToSuperview().offset(-16) - make.width.height.equalTo(44) - } - - closeButton.rx.tap - .subscribe(onNext: { [weak self] in - self?.dismiss(animated: true, completion: nil) - }) - .disposed(by: disposeBag) // 상위 클래스의 disposeBag 사용 - mainView.mapView.snp.remakeConstraints { make in make.edges.equalToSuperview() } @@ -83,16 +104,38 @@ class FullScreenMapViewController: MapViewController { guard let store = initialStore else { return } let position = NMGLatLng(lat: store.latitude, lng: store.longitude) + + // 카메라 이동 let cameraUpdate = NMFCameraUpdate(scrollTo: position, zoomTo: 15.0) + cameraUpdate.animation = .easeIn + cameraUpdate.animationDuration = 0.3 mainView.mapView.moveCamera(cameraUpdate) - // 여기서 마커를 선택 상태로 추가 - let marker = NMFMarker() - marker.position = position - marker.userInfo = ["storeData": store] - updateMarkerStyle(marker: marker, selected: true, isCluster: false) // 선택된 스타일 적용 - marker.mapView = mainView.mapView - currentMarker = marker + // 기존 마커가 있으면 재활용 + if let existingMarker = initialMarker { + // 기존 마커가 맵뷰에 설정되어 있지 않으면 설정 + if existingMarker.mapView == nil { + existingMarker.mapView = mainView.mapView + } + + // 마커 스타일 업데이트 + updateMarkerStyle(marker: existingMarker, selected: true, isCluster: false, count: 1) + currentMarker = existingMarker + } else { + // 기존 마커가 없는 경우에만 새로 생성 + let marker = NMFMarker() + marker.position = position + marker.iconImage = NMFOverlayImage(name: "TapMarker") + marker.width = 44 + marker.height = 44 + marker.anchor = CGPoint(x: 0.5, y: 1.0) + marker.userInfo = ["storeData": store] + marker.mapView = mainView.mapView + currentMarker = marker + + // 마커 스타일 업데이트 + updateMarkerStyle(marker: marker, selected: true, isCluster: false, count: 1) + } // 캐러셀 설정 currentCarouselStores = [store] @@ -100,54 +143,120 @@ class FullScreenMapViewController: MapViewController { carouselView.isHidden = false } - // MARK: - Override Methods + override func bind(reactor: MapReactor) { super.bind(reactor: reactor) - // 풀스크린 모드에서 추가 바인딩 필요 시 여기에 작성 + + // 캐러셀 상태 관찰 + carouselView.rx.observe(Bool.self, "isHidden") + .distinctUntilChanged() + .subscribe(onNext: { [weak self] isHidden in + if let isHidden = isHidden, isHidden == true, self?.isFullScreenMode == true { + // 풀스크린 모드에서 캐러셀이 숨겨진 경우 다시 표시 + DispatchQueue.main.async { + self?.carouselView.isHidden = false + } + } + }) + .disposed(by: disposeBag) + } + + // 마커 스타일 업데이트 함수 - 항상 TapMarker로만 설정하도록 수정 + private func fullScreenUpdateMarkerStyle(marker: NMFMarker, selected: Bool) { + // 선택 여부와 상관없이 항상 TapMarker + marker.width = 44 + marker.height = 44 + marker.iconImage = NMFOverlayImage(name: "TapMarker") + marker.anchor = CGPoint(x: 0.5, y: 1.0) } + override func updateMarkerStyle(marker: NMFMarker, selected: Bool, isCluster: Bool, count: Int = 1, regionName: String = "") { + if selected { + // 선택된 경우 항상 TapMarker + marker.width = 44 + marker.height = 44 + marker.iconImage = NMFOverlayImage(name: "TapMarker") + } else { + // 선택되지 않은 경우 일반 마커 + marker.width = 32 + marker.height = 32 + marker.iconImage = NMFOverlayImage(name: "Marker") + } + marker.anchor = CGPoint(x: 0.5, y: 1.0) + + if count > 1 { + marker.captionText = "\(count)" + } else { + marker.captionText = "" + } + } override func handleSingleStoreTap(_ marker: NMFMarker, store: MapPopUpStore) -> Bool { isMovingToMarker = true + markerLocked = true // 마커 상태 잠금 + // 이전 마커 선택 상태 해제 if let previousMarker = currentMarker, previousMarker != marker { - updateMarkerStyle(marker: previousMarker, selected: false, isCluster: false) + fullScreenUpdateMarkerStyle(marker: previousMarker, selected: false) } - updateMarkerStyle(marker: marker, selected: true, isCluster: false) + // 현재 마커를 TapMarker로 설정 + marker.iconImage = NMFOverlayImage(name: "TapMarker") + marker.width = 44 + marker.height = 44 + fullScreenUpdateMarkerStyle(marker: marker, selected: true) currentMarker = marker + // 캐러셀 업데이트 및 표시 currentCarouselStores = [store] carouselView.updateCards([store]) carouselView.isHidden = false mainView.setStoreCardHidden(false, animated: true) + // 카메라 이동 let cameraUpdate = NMFCameraUpdate(scrollTo: marker.position, zoomTo: 15.0) cameraUpdate.animation = .easeIn cameraUpdate.animationDuration = 0.3 mainView.mapView.moveCamera(cameraUpdate) - isMovingToMarker = false + // 약간의 지연 후 플래그 리셋 + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in + self?.isMovingToMarker = false + } + return true } + // 맵뷰 탭 처리 오버라이드 + override func mapView(_ mapView: NMFMapView, didTapMap latlng: NMGLatLng, point: CGPoint) { + // 풀스크린 모드에서는 맵 탭 시 아무 동작도 하지 않음 (캐러셀 유지) + return + } + + // 카메라 이동 시작 시 호출 + override func mapView(_ mapView: NMFMapView, cameraWillChangeByReason reason: Int, animated: Bool) { + // 풀스크린 모드에서는 캐러셀 유지 + if isFullScreenMode && markerLocked { + // 상위 클래스의 기본 동작 방지 + return + } + super.mapView(mapView, cameraWillChangeByReason: reason, animated: animated) + } + + // 카메라 이동 중 호출 + override func mapView(_ mapView: NMFMapView, cameraIsChangingByReason reason: Int) { + // 마커가 잠겨있을 때는 캐러셀 유지 + if isFullScreenMode && markerLocked { + // 기존 동작을 방지하고 풀스크린 동작 수행 + return + } + super.mapView(mapView, cameraIsChangingByReason: reason) + } + override func handleRegionalClusterTap(_ marker: NMFMarker, clusterData: ClusterMarkerData) -> Bool { - return false // 풀스크린에서는 클러스터 비활성화 + return false } override func handleMicroClusterTap(_ marker: NMFMarker, storeArray: [MapPopUpStore]) -> Bool { - return false // 풀스크린에서는 마이크로 클러스터 비활성화 + return false } } - -// MARK: - NMFMapViewTouchDelegate -//extension FullScreenMapViewController: NMFMapViewTouchDelegate { -// func mapView(_ mapView: NMFMapView, didTapOverlay overlay: NMFOverlay) -> Bool { -// guard let marker = overlay as? NMFMarker, -// let store = marker.userInfo["storeData"] as? MapPopUpStore else { return false } -// return handleSingleStoreTap(marker, store: store) -// } -// -// func mapView(_ mapView: NMFMapView, didTapMap latlng: NMGLatLng, point: CGPoint) { -// // 풀스크린 모드에서는 지도 탭 시 동작 없음 -// } -//} diff --git a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift index 12c65b08..78a5d81a 100644 --- a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift +++ b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift @@ -10,6 +10,7 @@ final class MapGuideViewController: UIViewController, View { var disposeBag = DisposeBag() private let popUpStoreId: Int64 private var currentCarouselStores: [MapPopUpStore] = [] // 현재 선택된 스토어 목록 + init(popUpStoreId: Int64) { self.popUpStoreId = popUpStoreId @@ -156,7 +157,7 @@ final class MapGuideViewController: UIViewController, View { if let selectedStore = self.currentCarouselStores.first { reactor.action.onNext(.didSelectItem(selectedStore)) - // store: 매개변수명 사용 + // 기존 코드 그대로 사용 let fullScreenMapVC = FullScreenMapViewController(store: selectedStore) fullScreenMapVC.reactor = reactor @@ -171,8 +172,8 @@ final class MapGuideViewController: UIViewController, View { .distinctUntilChanged() .compactMap { $0 } .take(1) + .observe(on: MainScheduler.instance) .subscribe(onNext: { store in - // store: 매개변수명 사용 let fullScreenMapVC = FullScreenMapViewController(store: store) fullScreenMapVC.reactor = reactor @@ -186,6 +187,7 @@ final class MapGuideViewController: UIViewController, View { .disposed(by: disposeBag) + // 목적지 좌표로 마커 및 카메라 설정 reactor.state .map { $0.destinationCoordinate } @@ -257,15 +259,15 @@ final class MapGuideViewController: UIViewController, View { make.height.equalTo(320) } - mapView.addSubview(expandButton) + modalCardView.addSubview(expandButton) expandButton.snp.makeConstraints { make in - make.bottom.equalToSuperview().inset(10) - make.trailing.equalToSuperview().inset(10) + make.bottom.equalTo(mapView.snp.bottom).offset(-10) + make.trailing.equalTo(mapView.snp.trailing).offset(-10) make.width.height.equalTo(32) } let bottomContainer = UIView() - modalCardView.addSubview(bottomContainer) // 오타 수정 필요: bottomContainer로 변경 + modalCardView.addSubview(bottomContainer) bottomContainer.snp.makeConstraints { make in make.top.equalTo(mapView.snp.bottom).offset(20) make.leading.trailing.equalToSuperview().inset(20) @@ -312,15 +314,14 @@ final class MapGuideViewController: UIViewController, View { } private func setupMarker(at coordinate: CLLocationCoordinate2D) { - // 기존 마커 제거 mapView.subviews.forEach { if $0 is NMFMarker { $0.removeFromSuperview() } } // 새 마커 생성 및 설정 let marker = NMFMarker() marker.position = NMGLatLng(lat: coordinate.latitude, lng: coordinate.longitude) - marker.iconImage = NMFOverlayImage(name: "TapMarker") // MapViewController에서 사용하는 기본 마커 - marker.width = 32 - marker.height = 32 + marker.iconImage = NMFOverlayImage(name: "TapMarker") + marker.width = 44 + marker.height = 44 marker.anchor = CGPoint(x: 0.5, y: 1.0) // 먼저 마커를 지도에 추가 From 2bfc482869e25692b27c73dd5a25a050b19d83b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=80=E1=85=B5=E1=84=92?= =?UTF-8?q?=E1=85=A7=E1=86=AB?= Date: Mon, 24 Mar 2025 21:32:28 +0900 Subject: [PATCH 12/95] =?UTF-8?q?[REFACTOR]=20=20=F0=9F=94=84=20SPM?= =?UTF-8?q?=ED=99=94=20=ED=95=B4=EB=91=94=20=EB=84=A4=EC=9D=B4=EB=B2=84?= =?UTF-8?q?=EB=A7=B5=EC=9D=B4=EC=95=84=EB=8B=8C=20=EA=B3=B5=EC=8B=9D=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=EC=A4=91=EC=9D=B8=20SPM=20=EC=B1=84=ED=83=9D?= =?UTF-8?q?=ED=9B=84=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Poppool/Poppool.xcodeproj/project.pbxproj | 28 +++++++++---------- .../Map/MapView/MapViewController.swift | 21 +++----------- 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index 026dc060..eac04068 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -373,7 +373,6 @@ 08DE8A412D54DCCA0049BCAC /* MyCommentedPopUpGridSectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DE8A402D54DCCA0049BCAC /* MyCommentedPopUpGridSectionCell.swift */; }; 08F403332D884F4D00BFA61A /* KakaoSDKUser in Frameworks */ = {isa = PBXBuildFile; productRef = 08F403322D884F4D00BFA61A /* KakaoSDKUser */; }; 4E5825672D1951DF00EE83EF /* FloatingPanel in Frameworks */ = {isa = PBXBuildFile; productRef = 4E5825662D1951DF00EE83EF /* FloatingPanel */; }; - 4E60C2262D8AABBF003A5A37 /* NMap in Frameworks */ = {isa = PBXBuildFile; productRef = 4E60C2252D8AABBF003A5A37 /* NMap */; }; 4E643FC12D738D7F0046AF29 /* PopUpStoreRegisterReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E643FC02D738D7F0046AF29 /* PopUpStoreRegisterReactor.swift */; }; 4E643FC32D738D930046AF29 /* PopUpStoreRegisterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E643FC22D738D930046AF29 /* PopUpStoreRegisterView.swift */; }; 4E685ECE2D12CEB6001EF91C /* BalloonBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E685EAA2D12CEB6001EF91C /* BalloonBackgroundView.swift */; }; @@ -433,6 +432,7 @@ 4EDDEFB42D2D285900CFAFA5 /* DateTimePickerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EDDEFB32D2D285900CFAFA5 /* DateTimePickerManager.swift */; }; 4EDE57012D5E6A5F0014D924 /* MapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E685ECC2D12CEB6001EF91C /* MapViewController.swift */; }; 4EDE57032D5E70650014D924 /* LocationPermissionBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EDE57022D5E70650014D924 /* LocationPermissionBottomSheet.swift */; }; + 4EE360FD2D91876300D2441D /* NMapsMap in Frameworks */ = {isa = PBXBuildFile; productRef = 4EE360FC2D91876300D2441D /* NMapsMap */; }; 4EE5A3D32D40E4A600A2469A /* MapGuideReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE5A3D22D40E4A600A2469A /* MapGuideReactor.swift */; }; 4EEA1D8F2D352012003E7DE9 /* ImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EEA1D8E2D352012003E7DE9 /* ImageCell.swift */; }; 4EEA1D912D352027003E7DE9 /* ExtendedImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EEA1D902D352027003E7DE9 /* ExtendedImage.swift */; }; @@ -982,8 +982,8 @@ BDCA41F22CF35D0D005EECF6 /* SnapKit in Frameworks */, BDCA420A2CF35FB1005EECF6 /* Pageboy in Frameworks */, 4E5825672D1951DF00EE83EF /* FloatingPanel in Frameworks */, - 4E60C2262D8AABBF003A5A37 /* NMap in Frameworks */, 4EA9989D2D21C404009DC30B /* RxDataSources in Frameworks */, + 4EE360FD2D91876300D2441D /* NMapsMap in Frameworks */, BDCA42012CF35EFE005EECF6 /* RxKeyboard in Frameworks */, 088DE2442D104EE70030FA9E /* SwiftSoup in Frameworks */, ); @@ -4089,20 +4089,20 @@ minimumVersion = 2.8.6; }; }; - 4E60C2242D8AABBF003A5A37 /* XCRemoteSwiftPackageReference "NaverMap" */ = { + 4EA9989B2D21C404009DC30B /* XCRemoteSwiftPackageReference "RxDataSources" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/zzangzzangguy/NaverMap.git"; + repositoryURL = "https://github.com/RxSwiftCommunity/RxDataSources.git"; requirement = { - branch = main; - kind = branch; + kind = upToNextMajorVersion; + minimumVersion = 5.0.2; }; }; - 4EA9989B2D21C404009DC30B /* XCRemoteSwiftPackageReference "RxDataSources" */ = { + 4EE360FB2D91876300D2441D /* XCRemoteSwiftPackageReference "SPM-NMapsMap" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/RxSwiftCommunity/RxDataSources.git"; + repositoryURL = "https://github.com/navermaps/SPM-NMapsMap"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 5.0.2; + minimumVersion = 3.21.0; }; }; BDCA41F02CF35D0D005EECF6 /* XCRemoteSwiftPackageReference "SnapKit" */ = { @@ -4218,16 +4218,16 @@ package = 4E5825652D1951DF00EE83EF /* XCRemoteSwiftPackageReference "FloatingPanel" */; productName = FloatingPanel; }; - 4E60C2252D8AABBF003A5A37 /* NMap */ = { - isa = XCSwiftPackageProductDependency; - package = 4E60C2242D8AABBF003A5A37 /* XCRemoteSwiftPackageReference "NaverMap" */; - productName = NMap; - }; 4EA9989C2D21C404009DC30B /* RxDataSources */ = { isa = XCSwiftPackageProductDependency; package = 4EA9989B2D21C404009DC30B /* XCRemoteSwiftPackageReference "RxDataSources" */; productName = RxDataSources; }; + 4EE360FC2D91876300D2441D /* NMapsMap */ = { + isa = XCSwiftPackageProductDependency; + package = 4EE360FB2D91876300D2441D /* XCRemoteSwiftPackageReference "SPM-NMapsMap" */; + productName = NMapsMap; + }; BDCA41F12CF35D0D005EECF6 /* SnapKit */ = { isa = XCSwiftPackageProductDependency; package = BDCA41F02CF35D0D005EECF6 /* XCRemoteSwiftPackageReference "SnapKit" */; diff --git a/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift b/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift index a2c36d29..98aee4ca 100644 --- a/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift +++ b/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift @@ -402,7 +402,7 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM self.resetSelectedMarker() - // 만약 지도 위 마커를 전부 제거하고 싶다면 (상황에 따라) + // 만약 지도 위 마커를 전부 제거 (상황에 따라) // self.clearAllMarkers() // self.clusterMarkerDictionary.values.forEach { $0.mapView = nil } // self.clusterMarkerDictionary.removeAll() @@ -681,8 +681,8 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM } private func updateMapViewAlpha(for offset: CGFloat, minOffset: CGFloat, maxOffset: CGFloat) { - let progress = (maxOffset - offset) / (maxOffset - minOffset) // 0(탑) ~ 1(바텀) - mainView.mapView.alpha = max(0, min(progress, 1)) // 0(완전히 가림) ~ 1(완전히 보임) + let progress = (maxOffset - offset) / (maxOffset - minOffset) + mainView.mapView.alpha = max(0, min(progress, 1)) } @@ -712,21 +712,8 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM self.mainView.mapView.isHidden = false self.mainView.searchInput.setBackgroundColor(.white) - // 리스트뷰 표시 시, 전체 한국 영역의 스토어 가져오기 if let reactor = self.reactor { - // 한국 전체 영역에 대한 경계값 설정 - let koreaRegion = ( - northEast: NMGLatLng(lat: 38.0, lng: 132.0), - southWest: NMGLatLng(lat: 33.0, lng: 124.0) - ) - - // 전체 스토어를 가져오기 위해 한국 전체 영역 요청 - reactor.action.onNext(.viewportChanged( - northEastLat: koreaRegion.northEast.lat, - northEastLon: koreaRegion.northEast.lng, - southWestLat: koreaRegion.southWest.lat, - southWestLon: koreaRegion.southWest.lng - )) + reactor.action.onNext(.fetchAllStores) reactor.state .map { $0.viewportStores } From 7821d39917599bd194e6ae60e845495a5722b9f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=80=E1=85=B5=E1=84=92?= =?UTF-8?q?=E1=85=A7=E1=86=AB?= Date: Wed, 26 Mar 2025 18:57:35 +0900 Subject: [PATCH 13/95] =?UTF-8?q?[REFACTOR]=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=ED=83=AD=EC=97=90=EC=84=9C=20=EC=A7=80=EC=97=AD?= =?UTF-8?q?=ED=83=AD=EC=9D=B4=20=EA=B2=B9=EC=B3=90=EB=B3=B4=EC=9D=B4?= =?UTF-8?q?=EB=8D=98=20=EC=9D=B4=EC=8A=88=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BalloonBackgroundView.swift | 39 +- .../FilterBottomSheetView.swift | 46 +- .../FilterBottomSheetViewController.swift | 489 ++++++++++-------- .../FillterSheetView/FilterChipsView.swift | 2 +- .../Map/MapView/MapViewController.swift | 2 +- .../StoreListViewController.swift | 4 +- 6 files changed, 330 insertions(+), 252 deletions(-) diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonBackgroundView.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonBackgroundView.swift index 3c673979..28a18b39 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonBackgroundView.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonBackgroundView.swift @@ -99,9 +99,11 @@ final class BalloonBackgroundView: UIView { private func setupLayout() { addSubview(containerView) containerView.snp.makeConstraints { make in - make.left.right.bottom.equalToSuperview() - make.top.equalToSuperview().offset(arrowHeight) - } + make.left.right.equalToSuperview() + make.top.equalToSuperview().offset(arrowHeight) + make.bottom.equalToSuperview().priority(.high) + } + containerView.addSubview(collectionView) containerView.addSubview(singleRegionIcon) @@ -109,9 +111,11 @@ final class BalloonBackgroundView: UIView { containerView.addSubview(singleRegionDetailLabel) collectionView.snp.makeConstraints { make in - make.edges.equalToSuperview() + make.top.left.right.equalToSuperview() + make.bottom.equalToSuperview().priority(.high) } + singleRegionIcon.snp.makeConstraints { make in make.top.equalToSuperview().offset(24) make.centerX.equalToSuperview() @@ -140,29 +144,25 @@ final class BalloonBackgroundView: UIView { override func draw(_ rect: CGRect) { super.draw(rect) - let arrowWidth: CGFloat = 12 // 화살표 너비 조정 - let arrowHeight: CGFloat = 8 // 화살표 높이 조정 + let arrowWidth: CGFloat = 12 + let arrowHeight: CGFloat = 8 - // 화살표의 시작 x좌표 계산 let arrowX = bounds.width * arrowPosition - (arrowWidth / 2) - // 경로 그리기 let path = UIBezierPath() - // 1. 화살표 그리기 - path.move(to: CGPoint(x: arrowX, y: arrowHeight)) // 왼쪽 아래 - path.addLine(to: CGPoint(x: arrowX + (arrowWidth / 2), y: 0)) // 상단 중앙 - path.addLine(to: CGPoint(x: arrowX + arrowWidth, y: arrowHeight)) // 오른쪽 아래 + path.move(to: CGPoint(x: arrowX, y: arrowHeight)) + path.addLine(to: CGPoint(x: arrowX + (arrowWidth / 2), y: 0)) + path.addLine(to: CGPoint(x: arrowX + arrowWidth, y: arrowHeight)) - // 2. 말풍선 본체 그리기 let balloonRect = CGRect(x: 0, y: arrowHeight, width: bounds.width, height: bounds.height - arrowHeight) - path.addLine(to: CGPoint(x: balloonRect.maxX, y: balloonRect.minY)) // 오른쪽 상단 - path.addLine(to: CGPoint(x: balloonRect.maxX, y: balloonRect.maxY)) // 오른쪽 하단 - path.addLine(to: CGPoint(x: balloonRect.minX, y: balloonRect.maxY)) // 왼쪽 하단 - path.addLine(to: CGPoint(x: balloonRect.minX, y: balloonRect.minY)) // 왼쪽 상단 + path.addLine(to: CGPoint(x: balloonRect.maxX, y: balloonRect.minY)) + path.addLine(to: CGPoint(x: balloonRect.maxX, y: balloonRect.maxY)) + path.addLine(to: CGPoint(x: balloonRect.minX, y: balloonRect.maxY)) + path.addLine(to: CGPoint(x: balloonRect.minX, y: balloonRect.minY)) path.close() @@ -246,7 +246,6 @@ final class BalloonBackgroundView: UIView { collectionView.layoutIfNeeded() - print("실제 contentSize 높이: \(collectionView.collectionViewLayout.collectionViewContentSize.height)") let balloonWidth = self.bounds.width let horizontalSpacing: CGFloat = 8 @@ -254,24 +253,20 @@ final class BalloonBackgroundView: UIView { let rightPadding: CGFloat = 20 let availableWidth = balloonWidth - leftPadding - rightPadding - print("사용 가능한 너비: \(availableWidth)") var currentRowWidth: CGFloat = 0 var numberOfRows: Int = 1 for input in inputDataList { let buttonWidth = calculateButtonWidth(for: input.title ?? "", font: .systemFont(ofSize: 12), isSelected: input.isSelected ?? false) - print("버튼 너비 [\(input.title ?? "")]: \(buttonWidth)") let widthWithSpacing = currentRowWidth == 0 ? buttonWidth : buttonWidth + horizontalSpacing if currentRowWidth + widthWithSpacing > availableWidth { numberOfRows += 1 currentRowWidth = buttonWidth - print("새로운 줄 시작: \(numberOfRows)번째 줄") } else { currentRowWidth += widthWithSpacing - print("현재 줄 너비: \(currentRowWidth)") } } diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift index 3301184c..67034b7f 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift @@ -96,7 +96,7 @@ final class FilterBottomSheetView: UIView { }() - private let buttonStack: UIStackView = { + let buttonStack: UIStackView = { let stack = UIStackView() stack.axis = .horizontal stack.spacing = 12 @@ -158,18 +158,15 @@ final class FilterBottomSheetView: UIView { } private func setupConstraints() { - // 1. 먼저 self의 width 설정 self.snp.makeConstraints { make in make.width.equalTo(UIScreen.main.bounds.width) } - // 2. containerView 설정 containerView.snp.makeConstraints { make in make.left.right.bottom.equalToSuperview() make.top.equalTo(headerView.snp.top) } - // 3. headerView 및 내부 요소들 headerView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() make.height.equalTo(70) @@ -186,13 +183,11 @@ final class FilterBottomSheetView: UIView { make.size.equalTo(24) } - // 4. segmentedControl segmentedControl.snp.makeConstraints { make in make.top.equalTo(headerView.snp.bottom).offset(16) make.leading.trailing.equalToSuperview() } - // 5. locationScrollView 및 contentView locationScrollView.snp.makeConstraints { make in make.top.equalTo(segmentedControl.snp.bottom).offset(20) make.leading.trailing.equalToSuperview() @@ -204,28 +199,24 @@ final class FilterBottomSheetView: UIView { make.height.equalToSuperview() } - // 6. categoryCollectionView categoryCollectionView.snp.makeConstraints { make in make.top.equalTo(segmentedControl.snp.bottom).offset(16) make.leading.trailing.equalToSuperview() // categoryHeightConstraint = make.height.equalTo(160).constraint } - // 7. balloonBackgroundView balloonBackgroundView.snp.makeConstraints { make in make.top.equalTo(locationScrollView.snp.bottom).offset(16) make.leading.trailing.equalToSuperview().inset(16) balloonHeightConstraint = make.height.equalTo(0).constraint } - // 8. filterChipsView filterChipsView.snp.makeConstraints { make in make.top.equalTo(balloonBackgroundView.snp.bottom).offset(24) make.leading.trailing.equalToSuperview().inset(16) make.height.equalTo(80) } - // 9. buttonStack buttonStack.snp.makeConstraints { make in make.top.equalTo(filterChipsView.snp.bottom).offset(24) make.leading.trailing.equalToSuperview().inset(16) @@ -314,7 +305,7 @@ final class FilterBottomSheetView: UIView { } func updateContentVisibility(isCategorySelected: Bool) { - UIView.animate(withDuration: 0.3) { + UIView.performWithoutAnimation { self.locationScrollView.alpha = isCategorySelected ? 0 : 1 self.balloonBackgroundView.alpha = isCategorySelected ? 0 : 1 self.categoryCollectionView.alpha = isCategorySelected ? 1 : 0 @@ -323,6 +314,17 @@ final class FilterBottomSheetView: UIView { self.balloonBackgroundView.isHidden = isCategorySelected self.categoryCollectionView.isHidden = !isCategorySelected + // filterChipsView 제약조건 업데이트 + self.filterChipsView.snp.remakeConstraints { make in + if isCategorySelected { + make.top.equalTo(self.categoryCollectionView.snp.bottom).offset(16) + } else { + make.top.equalTo(self.balloonBackgroundView.snp.bottom).offset(24) + } + make.leading.trailing.equalToSuperview().inset(16) + make.height.equalTo(80) + } + let newHeight = isCategorySelected ? 170 : self.balloonBackgroundView.calculateHeight() self.balloonHeightConstraint?.update(offset: newHeight) @@ -333,6 +335,7 @@ final class FilterBottomSheetView: UIView { + private func createStyledButton(title: String, isSelected: Bool = false) -> PPButton { let button = PPButton( style: .secondary, @@ -375,14 +378,22 @@ final class FilterBottomSheetView: UIView { } func updateBalloonHeight(isHidden: Bool, dynamicHeight: CGFloat = 160) { - UIView.animate(withDuration: 0.3) { - self.balloonBackgroundView.alpha = isHidden ? 0 : 1 - self.balloonHeightConstraint?.update(offset: isHidden ? 0 : dynamicHeight) - self.layoutIfNeeded() + if isHidden { + balloonBackgroundView.alpha = 0 + balloonBackgroundView.isHidden = true + balloonHeightConstraint?.update(offset: 0) + } else { + balloonBackgroundView.alpha = 1 + balloonBackgroundView.isHidden = false + balloonHeightConstraint?.update(offset: dynamicHeight) } + + self.setNeedsLayout() + self.layoutIfNeeded() } + func updateBalloonPosition(for button: UIButton) { guard let window = button.window else { return } @@ -395,8 +406,8 @@ final class FilterBottomSheetView: UIView { let position = relativeX / balloonBackgroundView.bounds.width - let minPosition: CGFloat = 0.1 // 왼쪽 여백 - let maxPosition: CGFloat = 0.9 // 오른쪽 여백 + let minPosition: CGFloat = 0.1 + let maxPosition: CGFloat = 0.9 let clampedPosition = min(maxPosition, max(minPosition, position)) balloonBackgroundView.arrowPosition = clampedPosition @@ -433,7 +444,6 @@ extension FilterBottomSheetView: UIScrollViewDelegate { return button.backgroundColor == .blu500 }) as? PPButton else { return } - // 스크롤 중에도 실시간으로 위치 업데이트 DispatchQueue.main.async { [weak self] in self?.updateBalloonPosition(for: selectedButton) } diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetViewController.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetViewController.swift index c398fc99..67663ca4 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetViewController.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetViewController.swift @@ -1,3 +1,4 @@ + import UIKit import SnapKit import RxSwift @@ -12,21 +13,28 @@ final class FilterBottomSheetViewController: UIViewController, View { var disposeBag = DisposeBag() var onSave: ((FilterData) -> Void)? var onDismiss: (() -> Void)? + + // Container height를 업데이트할 때 SnapKit Constraint를 직접 저장해 둠 private var bottomConstraint: Constraint? + private var containerHeightConstraint: Constraint? + + // 바텀시트 실제 UI let containerView = FilterBottomSheetView() - private var containerViewBottomConstraint: NSLayoutConstraint? + + // 필요하다면 다른 속성들 private var savedLocation: String? private var savedCategory: String? private var tagSection: TagSection? - private lazy var dimmedView: UIView = { - let view = UIView() - view.backgroundColor = .black.withAlphaComponent(0.4) - view.alpha = 0 - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapDimmedView)) - view.addGestureRecognizer(tapGesture) - return view + private lazy var dimmedView: UIControl = { + let control = UIControl() + control.backgroundColor = .black.withAlphaComponent(0.4) + control.alpha = 0 + control.addTarget(self, action: #selector(hideBottomSheet), for: .touchUpInside) + return control }() + + // MARK: - Initialization init(reactor: Reactor) { super.init(nibName: nil, bundle: nil) @@ -38,28 +46,32 @@ final class FilterBottomSheetViewController: UIViewController, View { } // MARK: - Lifecycle - override func viewDidLoad() { super.viewDidLoad() - setupLayout() + + setupLayout() // 오토레이아웃 setupGestures() setupCollectionView() + + // ChipsView에서 필터 제거 로직 containerView.filterChipsView.onRemoveChip = { [weak self] removedOption in guard let self = self, let reactor = self.reactor else { return } + if reactor.currentState.selectedCategories.contains(removedOption) { reactor.action.onNext(.toggleCategory(removedOption)) } else if reactor.currentState.selectedSubRegions.contains(removedOption) { reactor.action.onNext(.toggleSubRegion(removedOption)) + + let currentSegment = self.containerView.segmentedControl.selectedSegmentIndex + if currentSegment == 0 { + self.updateUIForCurrentTab(segment: currentSegment) + } } } - -// let tapOutsideGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapOutside)) -// tapOutsideGesture.cancelsTouchesInView = false -// self.view.addGestureRecognizer(tapOutsideGesture) - } - // MARK: - Setup + + // MARK: - Setup Layout private func setupLayout() { view.backgroundColor = .clear @@ -71,13 +83,178 @@ final class FilterBottomSheetViewController: UIViewController, View { view.addSubview(containerView) containerView.snp.makeConstraints { make in make.left.right.equalToSuperview() - make.height.equalTo(UIScreen.main.bounds.height * 0.7) + containerHeightConstraint = make.height.equalTo(UIScreen.main.bounds.height * 0.7).constraint + bottomConstraint = make.bottom.equalToSuperview().offset(UIScreen.main.bounds.height).constraint } - view.sendSubviewToBack(dimmedView) - dimmedView.isUserInteractionEnabled = true + containerView.isUserInteractionEnabled = true + } + + // MARK: - Setup Gestures + private func setupGestures() { + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(hideBottomSheet)) + tapGesture.delegate = self + dimmedView.addGestureRecognizer(tapGesture) + } + + + +// @objc private func didTapDimmedView() { +// // 딤드 뷰 탭 → 시트 닫기 +// hideBottomSheet() +// } + + @objc private func handlePanGesture(_ gesture: UIPanGestureRecognizer) { + let translation = gesture.translation(in: view) + + switch gesture.state { + case .changed: + guard translation.y >= 0 else { return } + bottomConstraint?.update(offset: translation.y) + view.layoutIfNeeded() + + case .ended: + let velocity = gesture.velocity(in: view) + if translation.y > 150 || velocity.y > 1000 { + // (pan) 시트 끌어내리면 닫기 + let currentSegment = containerView.segmentedControl.selectedSegmentIndex + updateUIForCurrentTab(segment: currentSegment) + hideBottomSheet() + } else { + UIView.animate(withDuration: 0.25) { + self.bottomConstraint?.update(offset: 0) + self.view.layoutIfNeeded() + } + } + default: + break + } + } + + // MARK: - Public Show / Hide + func showBottomSheet() { + guard let reactor = reactor else { return } + + // (A) location 초기선택 + if let locations = reactor.currentState.savedSubRegions.first?.split(separator: "/").first.map(String.init), + let index = reactor.currentState.locations.firstIndex(where: { $0.main == locations }) { + reactor.action.onNext(.selectLocation(index)) + } + + // (B) 필터 칩 뷰 업데이트 + containerView.update( + locationText: reactor.currentState.savedSubRegions.joined(separator: ", "), + categoryText: reactor.currentState.savedCategories.joined(separator: ", ") + ) + + // (C) 시트 애니메이션 + UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut) { + self.dimmedView.alpha = 1 + self.bottomConstraint?.update(offset: 0) + self.view.layoutIfNeeded() + } + } + + @objc func hideBottomSheet() { + UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseIn) { + self.dimmedView.alpha = 0 + self.bottomConstraint?.update(offset: UIScreen.main.bounds.height) + self.view.layoutIfNeeded() + } completion: { _ in + self.dismiss(animated: false) + self.onDismiss?() + } + } + + // MARK: - UI Update (for tabs) + private func updateUIForCurrentTab(segment: Int) { + UIView.performWithoutAnimation { + // 탭 전환 시 모든 뷰 숨김 + containerView.categoryCollectionView.isHidden = true + containerView.locationScrollView.isHidden = true + containerView.balloonBackgroundView.isHidden = true + + if segment == 0 { + // 지역 탭 + containerView.locationScrollView.isHidden = false + if let selectedLocationIndex = reactor?.currentState.selectedLocationIndex, + let locations = reactor?.currentState.locations, + selectedLocationIndex >= 0, selectedLocationIndex < locations.count { + + let location = locations[selectedLocationIndex] + containerView.balloonBackgroundView.configure( + for: location.main, + subRegions: location.sub, + selectedRegions: reactor?.currentState.selectedSubRegions ?? [], + selectionHandler: { [weak self] subRegion in + self?.reactor?.action.onNext(.toggleSubRegion(subRegion)) + }, + allSelectionHandler: { [weak self] in + self?.reactor?.action.onNext(.toggleAllSubRegions) + } + ) + containerView.balloonBackgroundView.isHidden = false + let dynamicHeight = containerView.balloonBackgroundView.calculateHeight() + containerView.updateBalloonHeight(isHidden: false, dynamicHeight: dynamicHeight) + } + } else { + containerView.categoryCollectionView.isHidden = false + containerView.updateBalloonHeight(isHidden: true) + + // ★ 카테고리 탭에 들어오면, 한 번 더 layout 후 시트 높이 갱신 + DispatchQueue.main.async { + // 콜렉션뷰 레이아웃 강제 반영 + self.containerView.categoryCollectionView.layoutIfNeeded() + let contentHeight = self.containerView.categoryCollectionView.contentSize.height + + // 콜렉션뷰 높이 업데이트 + self.containerView.categoryCollectionView.snp.updateConstraints { make in + make.height.equalTo(contentHeight + 40) + } + self.containerView.layoutIfNeeded() + + // 시트 높이 갱신 + self.updateContainerHeight() + } + } + + containerView.layoutIfNeeded() + view.layoutIfNeeded() + } + + // 시트 높이 업데이트 + updateContainerHeight() + } + + private func updateContainerHeight() { + let segmentIndex = containerView.segmentedControl.selectedSegmentIndex + + let headerHeight = containerView.headerView.frame.height + let segmentHeight = containerView.segmentedControl.frame.height + let filterHeight = containerView.filterChipsView.frame.height + let buttonHeight: CGFloat = 52 + let padding: CGFloat = 60 + + let contentHeight: CGFloat + if segmentIndex == 0 { + let locationHeight = containerView.locationScrollView.frame.height + let balloonHeight = containerView.balloonBackgroundView.isHidden ? 0 : containerView.balloonBackgroundView.calculateHeight() + contentHeight = headerHeight + segmentHeight + locationHeight + balloonHeight + filterHeight + buttonHeight + padding + } else { + let categoryHeight = containerView.categoryCollectionView.frame.height + contentHeight = headerHeight + segmentHeight + categoryHeight + filterHeight + buttonHeight + padding + } + + let minHeight: CGFloat = 300 + let maxHeight = UIScreen.main.bounds.height * 0.7 + let newHeight = min(max(contentHeight, minHeight), maxHeight) + + UIView.animate(withDuration: 0.2) { + self.containerHeightConstraint?.update(offset: newHeight) + self.view.layoutIfNeeded() + } } private func setupCollectionView() { @@ -85,60 +262,57 @@ final class FilterBottomSheetViewController: UIViewController, View { containerView.categoryCollectionView.delegate = self } - // MARK: - Binding + // MARK: - Reactor Binding func bind(reactor: Reactor) { - // 1. 세그먼트 컨트롤 바인딩 + // (1) 세그먼트 컨트롤 containerView.segmentedControl.rx.selectedSegmentIndex + .do(onNext: { [weak self] segmentIndex in + self?.updateUIForCurrentTab(segment: segmentIndex) + self?.updateContainerHeight() + }) .map { Reactor.Action.segmentChanged($0) } .bind(to: reactor.action) .disposed(by: disposeBag) - // 2. 리셋 버튼 바인딩 + // (2) 리셋 버튼 containerView.resetButton.rx.tap - .do(onNext: { [weak self] _ in - guard let self = self, - let reactor = self.reactor, - let selectedIndex = reactor.currentState.selectedLocationIndex else { return } - - let location = reactor.currentState.locations[selectedIndex] - // 현재 location에 대한 configure 재설정 - self.containerView.balloonBackgroundView.configure( - for: location.main, - subRegions: location.sub, - selectedRegions: reactor.currentState.selectedSubRegions, - selectionHandler: { [weak self] subRegion in - self?.reactor?.action.onNext(.toggleSubRegion(subRegion)) - }, - allSelectionHandler: { [weak self] in - self?.reactor?.action.onNext(.toggleAllSubRegions) - } - ) + .do(onNext: { [weak self] _ in + guard let self = self, + let reactor = self.reactor, + let selectedIndex = reactor.currentState.selectedLocationIndex else { return } + let location = reactor.currentState.locations[selectedIndex] + self.containerView.balloonBackgroundView.configure( + for: location.main, + subRegions: location.sub, + selectedRegions: reactor.currentState.selectedSubRegions, + selectionHandler: { [weak self] subRegion in + self?.reactor?.action.onNext(.toggleSubRegion(subRegion)) + }, + allSelectionHandler: { [weak self] in + self?.reactor?.action.onNext(.toggleAllSubRegions) + } + ) + }) + .map { Reactor.Action.resetFilters } + .bind(to: reactor.action) + .disposed(by: disposeBag) - }) - .map { Reactor.Action.resetFilters } - .bind(to: reactor.action) - .disposed(by: disposeBag) - - - + // (3) 저장 containerView.saveButton.rx.tap .bind { [weak self] _ in guard let self = self, let reactor = self.reactor else { return } - let filterData: FilterData = ( locations: reactor.currentState.selectedSubRegions, categories: reactor.currentState.selectedCategories ) - self.onSave?(filterData) reactor.action.onNext(.applyFilters(filterData.locations + filterData.categories)) - self.hideBottomSheet() } .disposed(by: disposeBag) - + // (4) 닫기 containerView.closeButton.rx.tap .bind { [weak self] _ in guard let self = self, let reactor = self.reactor else { return } @@ -151,8 +325,7 @@ final class FilterBottomSheetViewController: UIViewController, View { } .disposed(by: disposeBag) - - // 5. 탭 변경 + // (5) 액티브 세그먼트 reactor.state.map { $0.activeSegment } .distinctUntilChanged() .bind { [weak self] activeSegment in @@ -160,24 +333,20 @@ final class FilterBottomSheetViewController: UIViewController, View { if activeSegment == 0 { let dynamicHeight = self.containerView.balloonBackgroundView.calculateHeight() self.containerView.updateBalloonHeight(isHidden: false, dynamicHeight: dynamicHeight) - } else if activeSegment == 1 { + } else { self.containerView.updateBalloonHeight(isHidden: true) } self.containerView.updateContentVisibility(isCategorySelected: activeSegment == 1) } .disposed(by: disposeBag) - // 6. 위치 데이터 바인딩 - let locations = reactor.state - .map { $0.locations } - .distinctUntilChanged() - .share(replay: 1) + // (6) 위치 리스트 + let locations = reactor.state.map { $0.locations }.distinctUntilChanged().share(replay: 1) locations .observe(on: MainScheduler.instance) .bind { [weak self] locations in self?.containerView.setupLocationScrollView(locations: locations) { [weak self] index, button in guard let self = self else { return } - if index == 0 { if let selectedSubRegions = self.reactor?.currentState.selectedSubRegions, !selectedSubRegions.isEmpty { @@ -187,38 +356,39 @@ final class FilterBottomSheetViewController: UIViewController, View { } } self.reactor?.action.onNext(.selectLocation(index)) - self.containerView.updateBalloonPosition(for: button) } } .disposed(by: disposeBag) - - let locationAndSubRegions = reactor.state - .map { ($0.selectedLocationIndex, $0.selectedSubRegions) } + // (7) locationAndSubRegions + reactor.state.map { ($0.selectedLocationIndex, $0.selectedSubRegions) } .distinctUntilChanged { prev, curr in let isIndexSame = prev.0 == curr.0 let isSubRegionsSame = prev.1 == curr.1 return isIndexSame && isSubRegionsSame } .share(replay: 1) - - locationAndSubRegions .observe(on: MainScheduler.instance) .bind { [weak self] data in guard let self = self, let reactor = self.reactor else { return } let (selectedIndexOptional, selectedSubRegions) = data - guard let selectedIndex = selectedIndexOptional, selectedIndex >= 0, selectedIndex < reactor.currentState.locations.count else { return } + // 현재 탭이 지역(0)인지 체크해서 풍선 뷰 노출할지 결정 + let currentSegment = self.containerView.segmentedControl.selectedSegmentIndex + if currentSegment != 0 { + return + } + let location = reactor.currentState.locations[selectedIndex] self.containerView.balloonBackgroundView.configure( - for: location.main, // 첫 번째 인자는 메인 지역(String) - subRegions: location.sub, // 두 번째 인자는 [String] - selectedRegions: selectedSubRegions, // 세 번째 인자는 [String] + for: location.main, + subRegions: location.sub, + selectedRegions: selectedSubRegions, selectionHandler: { [weak self] subRegion in self?.reactor?.action.onNext(.toggleSubRegion(subRegion)) }, @@ -227,20 +397,23 @@ final class FilterBottomSheetViewController: UIViewController, View { } ) - - if let button = self.containerView.locationContentView.subviews[selectedIndex] as? UIButton { + // 화살표 위치 + let subviews = self.containerView.locationContentView.subviews + if selectedIndex < subviews.count, + let button = subviews[selectedIndex] as? UIButton { self.containerView.updateBalloonPosition(for: button) } DispatchQueue.main.async { let dynamicHeight = self.containerView.balloonBackgroundView.calculateHeight() self.containerView.updateBalloonHeight(isHidden: false, dynamicHeight: dynamicHeight) + self.updateContainerHeight() } - self.containerView.balloonBackgroundView.isHidden = false } .disposed(by: disposeBag) + // (8) 카테고리 바인딩 Observable.combineLatest( reactor.state.map { $0.categories }.distinctUntilChanged(), reactor.state.map { $0.selectedCategories }.distinctUntilChanged() @@ -266,14 +439,14 @@ final class FilterBottomSheetViewController: UIViewController, View { } .disposed(by: disposeBag) - - + // (9) 필터칩 업데이트 reactor.state.map { $0.selectedSubRegions + $0.selectedCategories } .distinctUntilChanged() .bind { [weak self] selectedOptions in UIView.performWithoutAnimation { self?.containerView.filterChipsView.updateChips(with: selectedOptions) self?.containerView.layoutIfNeeded() + self?.updateContainerHeight() } } .disposed(by: disposeBag) @@ -288,7 +461,6 @@ final class FilterBottomSheetViewController: UIViewController, View { } .disposed(by: disposeBag) - reactor.state.map { $0.isSaveEnabled } .distinctUntilChanged() .observe(on: MainScheduler.instance) @@ -305,173 +477,74 @@ final class FilterBottomSheetViewController: UIViewController, View { } } .disposed(by: disposeBag) - Observable.just(()) - .withLatestFrom(reactor.state) - .take(1) - .subscribe(onNext: { [weak self] state in - // 저장된 지역 필터 설정 - if !state.savedSubRegions.isEmpty { - state.savedSubRegions.forEach { region in - reactor.action.onNext(.toggleSubRegion(region)) - } - } - // 저장된 카테고리 필터 설정 - if !state.savedCategories.isEmpty { - state.savedCategories.forEach { category in - reactor.action.onNext(.toggleCategory(category)) - } + Observable.just(()) + .withLatestFrom(reactor.state) + .take(1) + .subscribe(onNext: { [weak self] state in + // 저장된 지역 필터 + if !state.savedSubRegions.isEmpty { + state.savedSubRegions.forEach { region in + reactor.action.onNext(.toggleSubRegion(region)) } - - // 지역이 선택되어 있다면 해당 지역 버튼도 활성화 - if let locations = state.savedSubRegions.first?.split(separator: "/").first.map(String.init), - let index = reactor.currentState.locations.firstIndex(where: { $0.main == locations }) { - reactor.action.onNext(.selectLocation(index)) + } + // 저장된 카테고리 필터 + if !state.savedCategories.isEmpty { + state.savedCategories.forEach { category in + reactor.action.onNext(.toggleCategory(category)) } - }) - .disposed(by: disposeBag) - - Observable.combineLatest( - reactor.state.map { $0.savedSubRegions }.distinctUntilChanged(), - reactor.state.map { $0.savedCategories }.distinctUntilChanged() - ) - .take(1) - .subscribe(onNext: { [weak self] (subRegions, categories) in - guard let self = self else { return } - - subRegions.forEach { region in - reactor.action.onNext(.toggleSubRegion(region)) } - categories.forEach { category in - reactor.action.onNext(.toggleCategory(category)) + // 위치 선택 + if let locMain = state.savedSubRegions.first?.split(separator: "/").first.map(String.init), + let idx = reactor.currentState.locations.firstIndex(where: { $0.main == locMain }) { + reactor.action.onNext(.selectLocation(idx)) } - - self.containerView.categoryCollectionView.reloadData() - self.containerView.balloonBackgroundView.setNeedsDisplay() }) .disposed(by: disposeBag) + // (12) 추가 combineLatest... (생략) } +} - private func updateContentVisibility(_ isCategoryTab: Bool, subRegionCount: Int) { - UIView.animate(withDuration: 0.3) { - self.containerView.updateContentVisibility(isCategorySelected: isCategoryTab) - if !isCategoryTab { - self.containerView.updateBalloonHeight(isHidden: false, dynamicHeight: subRegionCount > 0 ? self.containerView.balloonBackgroundView.calculateHeight() : 80) - } - self.view.layoutIfNeeded() - } - } - - private func setupGestures() { - // dimmedView에만 탭 제스처를 설정하고 다른 제스처와의 충돌을 방지 - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapDimmedView)) - dimmedView.addGestureRecognizer(tapGesture) - dimmedView.isUserInteractionEnabled = true // 확실히 활성화 - - // 패닝 제스처는 유지 - let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture)) - containerView.addGestureRecognizer(panGesture) - } - - @objc private func handleDimmedViewTap() { - hideBottomSheet() - } - func showBottomSheet() { - guard let reactor = reactor else { return } - - // 1. 이전에 저장된 지역 필터가 있다면 해당 지역 버튼 활성화 - if let locations = reactor.currentState.savedSubRegions.first?.split(separator: "/").first.map(String.init), - let index = reactor.currentState.locations.firstIndex(where: { $0.main == locations }) { - reactor.action.onNext(.selectLocation(index)) - - - } - - // 4. 필터 칩 뷰 업데이트 - containerView.update( - locationText: reactor.currentState.savedSubRegions.joined(separator: ", "), - categoryText: reactor.currentState.savedCategories.joined(separator: ", ") - ) - - // 5. 애니메이션 - UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut) { - self.dimmedView.alpha = 1 - self.bottomConstraint?.update(offset: 0) - self.view.layoutIfNeeded() - } - } - - +// MARK: - UIGestureRecognizerDelegate +extension FilterBottomSheetViewController: UIGestureRecognizerDelegate { + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { + let point = touch.location(in: self.view) - func hideBottomSheet() { - UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseIn) { - self.dimmedView.alpha = 0 - self.bottomConstraint?.update(offset: UIScreen.main.bounds.height) - self.view.layoutIfNeeded() - } completion: { _ in - self.dismiss(animated: false) - self.onDismiss?() + if containerView.frame.contains(point) { + return false } - } - @objc private func handleTapDimmedView() { - hideBottomSheet() - } - - @objc private func handlePanGesture(_ gesture: UIPanGestureRecognizer) { - let translation = gesture.translation(in: view) - - switch gesture.state { - case .changed: - guard translation.y >= 0 else { return } - bottomConstraint?.update(offset: translation.y) - view.layoutIfNeeded() - - case .ended: - let velocity = gesture.velocity(in: view) - if translation.y > 150 || velocity.y > 1000 { - hideBottomSheet() - } else { - UIView.animate(withDuration: 0.25) { - self.bottomConstraint?.update(offset: 0) - self.view.layoutIfNeeded() - } - } - - default: - break - } + return true } } +// MARK: - UICollectionViewDataSource extension FilterBottomSheetViewController: UICollectionViewDataSource { - func numberOfSections(in collectionView: UICollectionView) -> Int { - return 1 - } + func numberOfSections(in collectionView: UICollectionView) -> Int { return 1 } - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + func collectionView(_ collectionView: UICollectionView, + numberOfItemsInSection section: Int) -> Int { return tagSection?.inputDataList.count ?? 0 } - func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + func collectionView(_ collectionView: UICollectionView, + cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell( withReuseIdentifier: TagSectionCell.identifiers, for: indexPath ) as? TagSectionCell else { return UICollectionViewCell() } - if let input = tagSection?.inputDataList[indexPath.item] { cell.injection(with: input) } - return cell } } -// MARK: - UICollectionViewDelegate +// MARK: - UICollectionViewDelegateFlowLayout extension FilterBottomSheetViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { guard let category = tagSection?.inputDataList[indexPath.item].title else { return } diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterChipsView.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterChipsView.swift index 68aa555a..1e108269 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterChipsView.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterChipsView.swift @@ -63,7 +63,7 @@ final class FilterChipsView: UIView { make.top.equalTo(titleLabel.snp.bottom).offset(12) make.leading.trailing.equalToSuperview() make.bottom.equalToSuperview().offset(-8) - make.height.equalTo(44) + make.height.greaterThanOrEqualTo(44).priority(.high) } emptyStateLabel.snp.makeConstraints { make in diff --git a/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift b/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift index 98aee4ca..c08ca70d 100644 --- a/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift +++ b/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift @@ -278,7 +278,7 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM setupPanAndSwipeGestures() let mapViewTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleMapViewTap(_:))) - mapViewTapGesture.cancelsTouchesInView = false // 중요: 다른 터치 이벤트를 방해하지 않음 +// mapViewTapGesture.cancelsTouchesInView = false // 중요: 다른 터치 이벤트를 방해하지 않음 mapViewTapGesture.delaysTouchesBegan = false // 터치 지연 없음 mainView.mapView.addGestureRecognizer(mapViewTapGesture) mapViewTapGesture.delegate = self diff --git a/Poppool/Poppool/Presentation/Map/StoreListView/StoreListViewController.swift b/Poppool/Poppool/Presentation/Map/StoreListView/StoreListViewController.swift index e8fb9c69..9fc339e9 100644 --- a/Poppool/Poppool/Presentation/Map/StoreListView/StoreListViewController.swift +++ b/Poppool/Poppool/Presentation/Map/StoreListView/StoreListViewController.swift @@ -180,13 +180,13 @@ final class StoreListViewController: UIViewController, View { viewController.modalPresentationStyle = .overFullScreen present(viewController, animated: false) { - viewController.showBottomSheet() +// viewController.showBottomSheet() } } private func dismissFilterBottomSheet() { if let sheet = presentedViewController as? FilterBottomSheetViewController { - sheet.hideBottomSheet() + sheet.dismiss(animated: true) } } } From 21c6036bc2fbd30a801f9c87ebc9b508bad413b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=80=E1=85=B5=E1=84=92?= =?UTF-8?q?=E1=85=A7=E1=86=AB?= Date: Thu, 27 Mar 2025 16:04:52 +0900 Subject: [PATCH 14/95] =?UTF-8?q?[FIX]=20=EB=93=B1=EB=A1=9D=EC=8B=9C=20?= =?UTF-8?q?=EC=84=A4=EB=AA=85=EB=9E=80=EC=97=90=EC=84=9C=20=EB=B9=88?= =?UTF-8?q?=EA=B3=B5=EA=B0=84=ED=83=AD=EC=8B=9C=20=ED=82=A4=EB=B3=B4?= =?UTF-8?q?=EB=93=9C=20=EB=82=B4=EB=A0=A4=EA=B0=80=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AdminRegister/PopUpStoreRegisterViewController.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift b/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift index 0d7914ea..82515490 100644 --- a/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift +++ b/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift @@ -196,6 +196,10 @@ final class PopUpStoreRegisterViewController: BaseViewController { // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap)) + tapGesture.cancelsTouchesInView = false + view.addGestureRecognizer(tapGesture) + view.backgroundColor = UIColor(white:0.95, alpha:1) if let store = editingStore { From 86f2abd783ca9336ec2d2d3dcbeb49a4137e95e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=80=E1=85=B5=E1=84=92?= =?UTF-8?q?=E1=85=A7=E1=86=AB?= Date: Thu, 27 Mar 2025 17:49:23 +0900 Subject: [PATCH 15/95] =?UTF-8?q?[REFACTOR]=20=EC=82=AC=EC=9A=A9=EB=90=98?= =?UTF-8?q?=EC=A7=80=EC=95=8A=EB=8A=94=20Google=20SDK=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Poppool/Poppool.xcodeproj/project.pbxproj | 12 +----------- .../PopUpStoreRegisterViewController.swift | 1 - 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index eac04068..9285ca81 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -236,7 +236,6 @@ 086F8A182D265C5F00CA4FC9 /* MyPageListSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086F8A172D265C5F00CA4FC9 /* MyPageListSection.swift */; }; 086F8A1A2D265C6300CA4FC9 /* MyPageListSectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086F8A192D265C6300CA4FC9 /* MyPageListSectionCell.swift */; }; 088DE2442D104EE70030FA9E /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = 088DE2432D104EE70030FA9E /* SwiftSoup */; }; - 088DE2472D12DB5C0030FA9E /* GoogleMaps in Frameworks */ = {isa = PBXBuildFile; productRef = 088DE2462D12DB5C0030FA9E /* GoogleMaps */; }; 088DE24A2D12F3360030FA9E /* DetailInfoSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 088DE2492D12F3360030FA9E /* DetailInfoSection.swift */; }; 088DE24C2D12F33B0030FA9E /* DetailInfoSectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 088DE24B2D12F33B0030FA9E /* DetailInfoSectionCell.swift */; }; 088DE24F2D13019A0030FA9E /* DetailCommentTitleSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 088DE24E2D13019A0030FA9E /* DetailCommentTitleSection.swift */; }; @@ -975,8 +974,6 @@ BDCA42042CF35F76005EECF6 /* PanModal in Frameworks */, 082197A12D426DCB0054094A /* Then in Frameworks */, 083A25D02CF364B70099B58E /* Alamofire in Frameworks */, - 088DE2472D12DB5C0030FA9E /* GoogleMaps in Frameworks */, - 08F403332D884F4D00BFA61A /* KakaoSDKUser in Frameworks */, BDCA42102CF35FF5005EECF6 /* Lottie in Frameworks */, BDCA41FE2CF35EE7005EECF6 /* ReactorKit in Frameworks */, BDCA41F22CF35D0D005EECF6 /* SnapKit in Frameworks */, @@ -3084,7 +3081,6 @@ BDCA420F2CF35FF5005EECF6 /* Lottie */, 083A25CF2CF364B70099B58E /* Alamofire */, 088DE2432D104EE70030FA9E /* SwiftSoup */, - 088DE2462D12DB5C0030FA9E /* GoogleMaps */, 4E5825662D1951DF00EE83EF /* FloatingPanel */, 4EA9989C2D21C404009DC30B /* RxDataSources */, 082197A02D426DCB0054094A /* Then */, @@ -3175,7 +3171,6 @@ BDCA420E2CF35FF5005EECF6 /* XCRemoteSwiftPackageReference "lottie-spm" */, 083A25CE2CF364B70099B58E /* XCRemoteSwiftPackageReference "Alamofire" */, 088DE2422D104EE70030FA9E /* XCRemoteSwiftPackageReference "SwiftSoup" */, - 088DE2452D12DB5C0030FA9E /* XCRemoteSwiftPackageReference "ios-maps-sdk" */, 4E5825652D1951DF00EE83EF /* XCRemoteSwiftPackageReference "FloatingPanel" */, 4EA9989B2D21C404009DC30B /* XCRemoteSwiftPackageReference "RxDataSources" */, 0821979F2D426DCB0054094A /* XCRemoteSwiftPackageReference "Then" */, @@ -4203,12 +4198,7 @@ package = 088DE2422D104EE70030FA9E /* XCRemoteSwiftPackageReference "SwiftSoup" */; productName = SwiftSoup; }; - 088DE2462D12DB5C0030FA9E /* GoogleMaps */ = { - isa = XCSwiftPackageProductDependency; - package = 088DE2452D12DB5C0030FA9E /* XCRemoteSwiftPackageReference "ios-maps-sdk" */; - productName = GoogleMaps; - }; - 08F403322D884F4D00BFA61A /* KakaoSDKUser */ = { + 08B191B92CF609AE0057BC04 /* RxKakaoSDK */ = { isa = XCSwiftPackageProductDependency; package = 08F403312D884F4D00BFA61A /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; productName = KakaoSDKUser; diff --git a/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift b/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift index 82515490..d17c9fa0 100644 --- a/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift +++ b/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift @@ -5,7 +5,6 @@ import RxSwift import RxCocoa import PhotosUI import Alamofire -import GoogleMaps import CoreLocation final class PopUpStoreRegisterViewController: BaseViewController { From f7e543999e3aa9c8df7d7fe7ad0de17ed4591aad Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Sun, 30 Mar 2025 18:44:25 +0900 Subject: [PATCH 16/95] =?UTF-8?q?remove/#93:=20=EC=BB=A8=EB=B2=A4=EC=85=98?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Convention/ControllerConvention.swift | 47 ------------------- .../ConventionCollectionViewCell.swift | 40 ---------------- .../Convention/ConventionTableViewCell.swift | 44 ----------------- .../Convention/ReactorConvention.swift | 46 ------------------ .../Convention/TestDynamicCell.swift | 44 ----------------- .../Convention/TestDynamicSection.swift | 44 ----------------- .../Convention/ViewConvention.swift | 31 ------------ 7 files changed, 296 deletions(-) delete mode 100644 Poppool/Poppool/Presentation/Convention/ControllerConvention.swift delete mode 100644 Poppool/Poppool/Presentation/Convention/ConventionCollectionViewCell.swift delete mode 100644 Poppool/Poppool/Presentation/Convention/ConventionTableViewCell.swift delete mode 100644 Poppool/Poppool/Presentation/Convention/ReactorConvention.swift delete mode 100644 Poppool/Poppool/Presentation/Convention/TestDynamicCell.swift delete mode 100644 Poppool/Poppool/Presentation/Convention/TestDynamicSection.swift delete mode 100644 Poppool/Poppool/Presentation/Convention/ViewConvention.swift diff --git a/Poppool/Poppool/Presentation/Convention/ControllerConvention.swift b/Poppool/Poppool/Presentation/Convention/ControllerConvention.swift deleted file mode 100644 index bdce512a..00000000 --- a/Poppool/Poppool/Presentation/Convention/ControllerConvention.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// ControllerConvention.swift -// MomsVillage -// -// Created by SeoJunYoung on 9/20/24. -// - -import UIKit - -import SnapKit -import RxCocoa -import RxSwift -import ReactorKit - -final class ControllerConvention: BaseViewController, View { - - typealias Reactor = ReactorConvention - - // MARK: - Properties - var disposeBag = DisposeBag() - - private var mainView = ViewConvention() -} - -// MARK: - Life Cycle -extension ControllerConvention { - override func viewDidLoad() { - super.viewDidLoad() - setUp() - } -} - -// MARK: - SetUp -private extension ControllerConvention { - func setUp() { - view.addSubview(mainView) - mainView.snp.makeConstraints { make in - make.edges.equalTo(view.safeAreaLayoutGuide) - } - } -} - -// MARK: - Methods -extension ControllerConvention { - func bind(reactor: Reactor) { - } -} diff --git a/Poppool/Poppool/Presentation/Convention/ConventionCollectionViewCell.swift b/Poppool/Poppool/Presentation/Convention/ConventionCollectionViewCell.swift deleted file mode 100644 index e5eb7c34..00000000 --- a/Poppool/Poppool/Presentation/Convention/ConventionCollectionViewCell.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// ConventionCollectionViewCell.swift -// MomsVillage -// -// Created by SeoJunYoung on 9/27/24. -// - -import UIKit - -import SnapKit - -final class ConventionCollectionViewCell: UICollectionViewCell { - - // MARK: - Components - - // MARK: - init - - override init(frame: CGRect) { - super.init(frame: frame) - setUpConstraints() - } - - required init?(coder: NSCoder) { - fatalError() - } -} - -// MARK: - SetUp -private extension ConventionCollectionViewCell { - func setUpConstraints() { - } -} - -extension ConventionCollectionViewCell: Inputable { - struct Input { - } - - func injection(with input: Input) { - } -} diff --git a/Poppool/Poppool/Presentation/Convention/ConventionTableViewCell.swift b/Poppool/Poppool/Presentation/Convention/ConventionTableViewCell.swift deleted file mode 100644 index c80f88aa..00000000 --- a/Poppool/Poppool/Presentation/Convention/ConventionTableViewCell.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// ConventionTableViewCell.swift -// MomsVillage -// -// Created by SeoJunYoung on 9/26/24. -// - -import UIKit - -import SnapKit -import RxSwift - -final class ConventionTableViewCell: UITableViewCell { - - // MARK: - Components - - let disposeBag = DisposeBag() - - // MARK: - init - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - setUpConstraints() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -// MARK: - SetUp, Methods -private extension ConventionTableViewCell { - func setUpConstraints() { - } -} - -// MARK: - Inputable -extension ConventionTableViewCell: Inputable { - struct Input { - } - - func injection(with input: Input) { - - } -} diff --git a/Poppool/Poppool/Presentation/Convention/ReactorConvention.swift b/Poppool/Poppool/Presentation/Convention/ReactorConvention.swift deleted file mode 100644 index cc9a1ed9..00000000 --- a/Poppool/Poppool/Presentation/Convention/ReactorConvention.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// ReactorConvention.swift -// MomsVillage -// -// Created by SeoJunYoung on 9/20/24. -// - -import ReactorKit -import RxSwift -import RxCocoa - -final class ReactorConvention: Reactor { - - // MARK: - Reactor - enum Action { - } - - enum Mutation { - } - - struct State { - } - - // MARK: - properties - - var initialState: State - var disposeBag = DisposeBag() - - // MARK: - init - init() { - self.initialState = State() - } - - // MARK: - Reactor Methods - func mutate(action: Action) -> Observable { - switch action { - } - } - - func reduce(state: State, mutation: Mutation) -> State { - var newState = state - switch mutation { - } - return newState - } -} diff --git a/Poppool/Poppool/Presentation/Convention/TestDynamicCell.swift b/Poppool/Poppool/Presentation/Convention/TestDynamicCell.swift deleted file mode 100644 index 2f3ae731..00000000 --- a/Poppool/Poppool/Presentation/Convention/TestDynamicCell.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// TestDynamicCell.swift -// MomsVillage -// -// Created by SeoJunYoung on 9/27/24. -// - -import UIKit - -import SnapKit -import RxSwift - -final class TestDynamicCell: UICollectionViewCell { - - // MARK: - Components - - let disposeBag = DisposeBag() - // MARK: - init - - override init(frame: CGRect) { - super.init(frame: frame) - setUpConstraints() - } - - required init?(coder: NSCoder) { - fatalError() - } -} - -// MARK: - SetUp -private extension TestDynamicCell { - func setUpConstraints() { - } -} - -extension TestDynamicCell: Inputable { - struct Input { - - } - - func injection(with input: Input) { - - } -} diff --git a/Poppool/Poppool/Presentation/Convention/TestDynamicSection.swift b/Poppool/Poppool/Presentation/Convention/TestDynamicSection.swift deleted file mode 100644 index 04b245b8..00000000 --- a/Poppool/Poppool/Presentation/Convention/TestDynamicSection.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// TestDynamicSection.swift -// MomsVillage -// -// Created by SeoJunYoung on 9/27/24. -// - -import UIKit - -import RxSwift - -struct TestDynamicSection: Sectionable { - - var currentPage: PublishSubject = .init() - - typealias CellType = TestDynamicCell - - var inputDataList: [CellType.Input] - - var supplementaryItems: [any SectionSupplementaryItemable]? - - var decorationItems: [any SectionDecorationItemable]? - - func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { - let itemSize = NSCollectionLayoutSize( - widthDimension: .fractionalWidth(1), - heightDimension: .estimated(1000) - ) - let item = NSCollectionLayoutItem(layoutSize: itemSize) - item.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10) - - let groupSize = NSCollectionLayoutSize( - widthDimension: .fractionalWidth(1.0), - heightDimension: .estimated(1000) - ) - let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) - - // 섹션 생성 - let section = NSCollectionLayoutSection(group: group) - section.contentInsets = .init(top: 20, leading: 20, bottom: 20, trailing: 20) - - return section - } -} diff --git a/Poppool/Poppool/Presentation/Convention/ViewConvention.swift b/Poppool/Poppool/Presentation/Convention/ViewConvention.swift deleted file mode 100644 index 8222a445..00000000 --- a/Poppool/Poppool/Presentation/Convention/ViewConvention.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// ViewConvention.swift -// MomsVillage -// -// Created by SeoJunYoung on 9/20/24. -// -import UIKit - -import SnapKit - -final class ViewConvention: UIView { - - // MARK: - Components - - // MARK: - init - init() { - super.init(frame: .zero) - setUpConstraints() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -// MARK: - SetUp -private extension ViewConvention { - - func setUpConstraints() { - } -} From 4795a0afbef0232ca6a7951e48c74785651ddafd Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Sun, 30 Mar 2025 18:44:47 +0900 Subject: [PATCH 17/95] =?UTF-8?q?chore/#93:=20=EB=B9=8C=EB=93=9C=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 컨벤션 파일 제거로 인한 빌드 파일 수정 --- Poppool/Poppool.xcodeproj/project.pbxproj | 36 ----------------------- 1 file changed, 36 deletions(-) diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index d05337a5..02a36d99 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -97,13 +97,6 @@ 082197B42D4E4E280054094A /* NormalCommentEditReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082197B32D4E4E280054094A /* NormalCommentEditReactor.swift */; }; 083A25822CF361EF0099B58E /* BaseTabmanController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083A257F2CF361EF0099B58E /* BaseTabmanController.swift */; }; 083A25832CF361EF0099B58E /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083A25802CF361EF0099B58E /* BaseViewController.swift */; }; - 083A258C2CF361F90099B58E /* ControllerConvention.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083A25842CF361F90099B58E /* ControllerConvention.swift */; }; - 083A258D2CF361F90099B58E /* ConventionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083A25852CF361F90099B58E /* ConventionCollectionViewCell.swift */; }; - 083A258E2CF361F90099B58E /* ConventionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083A25862CF361F90099B58E /* ConventionTableViewCell.swift */; }; - 083A258F2CF361F90099B58E /* ReactorConvention.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083A25872CF361F90099B58E /* ReactorConvention.swift */; }; - 083A25902CF361F90099B58E /* TestDynamicCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083A25882CF361F90099B58E /* TestDynamicCell.swift */; }; - 083A25912CF361F90099B58E /* TestDynamicSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083A25892CF361F90099B58E /* TestDynamicSection.swift */; }; - 083A25922CF361F90099B58E /* ViewConvention.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083A258A2CF361F90099B58E /* ViewConvention.swift */; }; 083A25992CF362090099B58E /* Sectionable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083A25932CF362090099B58E /* Sectionable.swift */; }; 083A259A2CF362090099B58E /* SectionDecorationItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083A25942CF362090099B58E /* SectionDecorationItem.swift */; }; 083A259B2CF362090099B58E /* SectionSupplementaryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083A25952CF362090099B58E /* SectionSupplementaryItem.swift */; }; @@ -590,13 +583,6 @@ 082197B32D4E4E280054094A /* NormalCommentEditReactor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NormalCommentEditReactor.swift; sourceTree = ""; }; 083A257F2CF361EF0099B58E /* BaseTabmanController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTabmanController.swift; sourceTree = ""; }; 083A25802CF361EF0099B58E /* BaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseViewController.swift; sourceTree = ""; }; - 083A25842CF361F90099B58E /* ControllerConvention.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControllerConvention.swift; sourceTree = ""; }; - 083A25852CF361F90099B58E /* ConventionCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConventionCollectionViewCell.swift; sourceTree = ""; }; - 083A25862CF361F90099B58E /* ConventionTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConventionTableViewCell.swift; sourceTree = ""; }; - 083A25872CF361F90099B58E /* ReactorConvention.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactorConvention.swift; sourceTree = ""; }; - 083A25882CF361F90099B58E /* TestDynamicCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestDynamicCell.swift; sourceTree = ""; }; - 083A25892CF361F90099B58E /* TestDynamicSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestDynamicSection.swift; sourceTree = ""; }; - 083A258A2CF361F90099B58E /* ViewConvention.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewConvention.swift; sourceTree = ""; }; 083A25932CF362090099B58E /* Sectionable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sectionable.swift; sourceTree = ""; }; 083A25942CF362090099B58E /* SectionDecorationItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionDecorationItem.swift; sourceTree = ""; }; 083A25952CF362090099B58E /* SectionSupplementaryItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionSupplementaryItem.swift; sourceTree = ""; }; @@ -1407,7 +1393,6 @@ children = ( 4E755B1B2D2B9ABF00ADFB21 /* Admin */, 4E685ECD2D12CEB6001EF91C /* Map */, - 083A258B2CF361F90099B58E /* Convention */, 08B1915F2CF430D40057BC04 /* Components */, 083A25C22CF3635B0099B58E /* Scene */, 083A259D2CF3620B0099B58E /* Utills */, @@ -1425,20 +1410,6 @@ path = Controllers; sourceTree = ""; }; - 083A258B2CF361F90099B58E /* Convention */ = { - isa = PBXGroup; - children = ( - 083A25842CF361F90099B58E /* ControllerConvention.swift */, - 083A25852CF361F90099B58E /* ConventionCollectionViewCell.swift */, - 083A25862CF361F90099B58E /* ConventionTableViewCell.swift */, - 083A25872CF361F90099B58E /* ReactorConvention.swift */, - 083A25882CF361F90099B58E /* TestDynamicCell.swift */, - 083A25892CF361F90099B58E /* TestDynamicSection.swift */, - 083A258A2CF361F90099B58E /* ViewConvention.swift */, - ); - path = Convention; - sourceTree = ""; - }; 083A25962CF362090099B58E /* Sectionable */ = { isa = PBXGroup; children = ( @@ -3269,7 +3240,6 @@ 4EECA3942D56770B00A07CCA /* MapPopUpStore.swift in Sources */, 4E9C12782D2BC7A0006744D6 /* AdminBottomSheetView.swift in Sources */, 086DD9362D00963900B97D3B /* SearchTitleSection.swift in Sources */, - 083A25922CF361F90099B58E /* ViewConvention.swift in Sources */, 086F89D92D1E79E200CA4FC9 /* GetOtherUserCommentListRequestDTO.swift in Sources */, 083C864F2D0DD3A6003F441C /* AddCommentImageSection.swift in Sources */, 08B191372CF366680057BC04 /* UICollectionViewCell+.swift in Sources */, @@ -3327,7 +3297,6 @@ 083C86622D0EC49E003F441C /* InstaCommentAddController.swift in Sources */, 08B1919C2CF4A77C0057BC04 /* TagSection.swift in Sources */, BD91038B2CF614A900BBCCAE /* AuthRepository.swift in Sources */, - 083A25902CF361F90099B58E /* TestDynamicCell.swift in Sources */, 08DC61F32CF75037002A2F44 /* KeyChainService.swift in Sources */, 086DD93B2D009A1C00B97D3B /* CancelableTagSection.swift in Sources */, 4E755B232D2B9C5D00ADFB21 /* AdminViewController.swift in Sources */, @@ -3345,7 +3314,6 @@ 083C864C2D0DCF9B003F441C /* AddCommentDescriptionSectionCell.swift in Sources */, 086DD8CE2CFDFEB000B97D3B /* HomeListView.swift in Sources */, 08B191C22CF615CA0057BC04 /* Secrets.swift in Sources */, - 083A258C2CF361F90099B58E /* ControllerConvention.swift in Sources */, 083C86242D087A44003F441C /* DetailTitleSection.swift in Sources */, 08A2E4822D1BCDEA00102313 /* CommentDetailController.swift in Sources */, 0841BAA32CFA31A300049E31 /* SpacingSection.swift in Sources */, @@ -3390,7 +3358,6 @@ 081899452D35FEA10067BF01 /* RecentPopUpSection.swift in Sources */, 083A259A2CF362090099B58E /* SectionDecorationItem.swift in Sources */, BD9103892CF614A900BBCCAE /* PopUpStoreResponse.swift in Sources */, - 083A258F2CF361F90099B58E /* ReactorConvention.swift in Sources */, 086F89C72D1E348400CA4FC9 /* CommentUserInfoReactor.swift in Sources */, 0818990E2D34B68C0067BF01 /* GetNoticeListResponseDTO.swift in Sources */, 086DD8C82CFDEA9200B97D3B /* UIView+.swift in Sources */, @@ -3468,7 +3435,6 @@ BD9103652CF6149D00BBCCAE /* HomeAPIEndpoint.swift in Sources */, 086DD8E32CFF356300B97D3B /* HomeCardGridSection.swift in Sources */, 0841BABE2CFB5AA600049E31 /* Date?+.swift in Sources */, - 083A258D2CF361F90099B58E /* ConventionCollectionViewCell.swift in Sources */, 4E685EE12D12CEB6001EF91C /* StoreListReactor.swift in Sources */, 4E685EE52D12CEB6001EF91C /* MapMarker.swift in Sources */, 081898FB2D33D9320067BF01 /* GetBlockUserListResponseDTO.swift in Sources */, @@ -3581,7 +3547,6 @@ 081898EE2D33A39D0067BF01 /* MyCommentSortedModalController.swift in Sources */, 081898AA2D2CEA2F0067BF01 /* WithdrawlCheckSectionCell.swift in Sources */, 086DD8CC2CFDFEA800B97D3B /* HomeListController.swift in Sources */, - 083A258E2CF361F90099B58E /* ConventionTableViewCell.swift in Sources */, 086DD9342D00962500B97D3B /* SearchTitleSectionCell.swift in Sources */, 08B191592CF41E610057BC04 /* SignUpMainController.swift in Sources */, 0899526C2D0473EC0022AEF9 /* GetSearchPopUpListResponseDTO.swift in Sources */, @@ -3658,7 +3623,6 @@ 08A2E49D2D1C416800102313 /* CommentListTitleSection.swift in Sources */, 0818989C2D2BAA570067BF01 /* WithdrawlCheckModalView.swift in Sources */, 081899122D34CA9E0067BF01 /* GetNoticeDetailResponseDTO.swift in Sources */, - 083A25912CF361F90099B58E /* TestDynamicSection.swift in Sources */, 086DD8D82CFF185200B97D3B /* PostBookmarkPopUpRequestDTO.swift in Sources */, 089952422D031E650022AEF9 /* SearchSortedController.swift in Sources */, 089952532D033C940022AEF9 /* PopUpAPIRepositoryImpl.swift in Sources */, From 847a7256f7dc3a1ccf1f2ff22718bd09a6fed676 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Sun, 30 Mar 2025 19:22:20 +0900 Subject: [PATCH 18/95] =?UTF-8?q?chore/#93:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EC=84=B8=ED=8C=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Run SwiftLint 스크립트 추가 - SwiftLint rule 파일 추가 --- Poppool/Poppool.xcodeproj/project.pbxproj | 27 +++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index 02a36d99..0026d3f0 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -493,6 +493,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 05229DD02D99519200D88E73 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; 0818988D2D295DC30067BF01 /* MyPageLogoutSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageLogoutSection.swift; sourceTree = ""; }; 0818988F2D295DC80067BF01 /* MyPageLogoutSectionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageLogoutSectionCell.swift; sourceTree = ""; }; 081898932D2965C20067BF01 /* ProfileEditController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileEditController.swift; sourceTree = ""; }; @@ -2976,6 +2977,7 @@ BDCA41B42CF35AC0005EECF6 = { isa = PBXGroup; children = ( + 05229DD02D99519200D88E73 /* .swiftlint.yml */, BDCA41BF2CF35AC0005EECF6 /* Poppool */, BDCA41D62CF35AC1005EECF6 /* PoppoolTests */, BDCA41E02CF35AC1005EECF6 /* PoppoolUITests */, @@ -3031,6 +3033,7 @@ isa = PBXNativeTarget; buildConfigurationList = BDCA41E72CF35AC2005EECF6 /* Build configuration list for PBXNativeTarget "Poppool" */; buildPhases = ( + 05229DCF2D99507C00D88E73 /* Run SwiftLint */, BDCA41B92CF35AC0005EECF6 /* Sources */, BDCA41BA2CF35AC0005EECF6 /* Frameworks */, BDCA41BB2CF35AC0005EECF6 /* Resources */, @@ -3198,6 +3201,28 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 05229DCF2D99507C00D88E73 /* Run SwiftLint */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run SwiftLint"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [[ \"$(uname -m)\" == arm64 ]]; then\n export PATH=\"/opt/homebrew/bin:$PATH\"\nfi\n\nif which swiftlint > /dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ BDCA41B92CF35AC0005EECF6 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -3816,6 +3841,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5QTRMS954; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Poppool/Resource/Info.plist; INFOPLIST_KEY_NSCameraUsageDescription = "사용자가 프로필 사진을 업로드하거나 댓글에 사진을 추가할 수 있도록 카메라를 사용합니다."; @@ -3858,6 +3884,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5QTRMS954; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Poppool/Resource/Info.plist; INFOPLIST_KEY_NSCameraUsageDescription = "사용자가 프로필 사진을 업로드하거나 댓글에 사진을 추가할 수 있도록 카메라를 사용합니다."; From 6cec4f8f188edbe12efcafca581e899a1dceb8c9 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Sun, 30 Mar 2025 19:22:38 +0900 Subject: [PATCH 19/95] =?UTF-8?q?add/#93:=20swiftlint=20rule=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Poppool/.swiftlint.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 Poppool/.swiftlint.yml diff --git a/Poppool/.swiftlint.yml b/Poppool/.swiftlint.yml new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/Poppool/.swiftlint.yml @@ -0,0 +1 @@ + From 578230532227336081fe9dce28467bcf82856035 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Mon, 31 Mar 2025 09:07:18 +0900 Subject: [PATCH 20/95] =?UTF-8?q?chore/#93:=20SwiftLint=20rule=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EA=B8=B0=EB=B3=B8=20=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Poppool/.swiftlint.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Poppool/.swiftlint.yml b/Poppool/.swiftlint.yml index 8b137891..b1f8439d 100644 --- a/Poppool/.swiftlint.yml +++ b/Poppool/.swiftlint.yml @@ -1 +1,15 @@ +# 기본 활성화된 룰 중에 비활성화할 룰을 지정 +disabled_rules: + + +# 기본(default) 룰이 아닌 룰들을 활성화 +opt_in_rules: + + +# swiftlint 과정에 포함할 파일 경로 +included: + + +# swiftlint를 적용하지 않게 설정할 파일 경로 +excluded: From 8f392db9a11d480bbe2617e425782549221de92b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=80=E1=85=B5=E1=84=92?= =?UTF-8?q?=E1=85=A7=E1=86=AB?= Date: Sun, 30 Mar 2025 18:23:05 +0900 Subject: [PATCH 21/95] =?UTF-8?q?[FIX]=20=ED=95=84=ED=84=B0=EB=B0=94?= =?UTF-8?q?=ED=85=80=EC=8B=9C=ED=8A=B8=EB=B7=B0=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=94=A4=EB=93=9C=EC=98=81=EC=97=AD=20=ED=83=AD=EC=8B=9C=20?= =?UTF-8?q?=EC=A0=9C=EB=8C=80=EB=A1=9C=20=EB=8B=AB=ED=9E=88=EC=A7=80?= =?UTF-8?q?=EC=95=8A=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Poppool/Poppool.xcodeproj/project.pbxproj | 45 +- .../xcshareddata/swiftpm/Package.resolved | 177 ++++++ Poppool/Poppool/Application/AppDelegate.swift | 25 +- .../Map/Common/ClusteringManager.swift | 66 +-- .../Map/Common/MapUtilities.swift | 21 - .../BalloonBackgroundView.swift | 22 +- .../FillterSheetView/BalloonChipCell.swift | 1 + .../FilterBottomSheetView.swift | 155 ++--- .../FilterBottomSheetViewController.swift | 549 +++++++++--------- 9 files changed, 556 insertions(+), 505 deletions(-) create mode 100644 Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index 9285ca81..ada98d42 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -370,7 +370,10 @@ 08DE8A1D2D5261E70049BCAC /* MyPageTermsReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DE8A1C2D5261E70049BCAC /* MyPageTermsReactor.swift */; }; 08DE8A3F2D54DCC40049BCAC /* MyCommentedPopUpGridSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DE8A3E2D54DCC40049BCAC /* MyCommentedPopUpGridSection.swift */; }; 08DE8A412D54DCCA0049BCAC /* MyCommentedPopUpGridSectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DE8A402D54DCCA0049BCAC /* MyCommentedPopUpGridSectionCell.swift */; }; - 08F403332D884F4D00BFA61A /* KakaoSDKUser in Frameworks */ = {isa = PBXBuildFile; productRef = 08F403322D884F4D00BFA61A /* KakaoSDKUser */; }; + 4E15142A2D99480200DFD08F /* NMapsMap in Frameworks */ = {isa = PBXBuildFile; productRef = 4E1514292D99480200DFD08F /* NMapsMap */; }; + 4E15142C2D994A3A00DFD08F /* KakaoSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 4E15142B2D994A3A00DFD08F /* KakaoSDK */; }; + 4E15142E2D994A3A00DFD08F /* KakaoSDKAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 4E15142D2D994A3A00DFD08F /* KakaoSDKAuth */; }; + 4E1514302D994A3A00DFD08F /* KakaoSDKCommon in Frameworks */ = {isa = PBXBuildFile; productRef = 4E15142F2D994A3A00DFD08F /* KakaoSDKCommon */; }; 4E5825672D1951DF00EE83EF /* FloatingPanel in Frameworks */ = {isa = PBXBuildFile; productRef = 4E5825662D1951DF00EE83EF /* FloatingPanel */; }; 4E643FC12D738D7F0046AF29 /* PopUpStoreRegisterReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E643FC02D738D7F0046AF29 /* PopUpStoreRegisterReactor.swift */; }; 4E643FC32D738D930046AF29 /* PopUpStoreRegisterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E643FC22D738D930046AF29 /* PopUpStoreRegisterView.swift */; }; @@ -973,7 +976,9 @@ BDCA42072CF35FA6005EECF6 /* Tabman in Frameworks */, BDCA42042CF35F76005EECF6 /* PanModal in Frameworks */, 082197A12D426DCB0054094A /* Then in Frameworks */, + 4E15142E2D994A3A00DFD08F /* KakaoSDKAuth in Frameworks */, 083A25D02CF364B70099B58E /* Alamofire in Frameworks */, + 4E1514302D994A3A00DFD08F /* KakaoSDKCommon in Frameworks */, BDCA42102CF35FF5005EECF6 /* Lottie in Frameworks */, BDCA41FE2CF35EE7005EECF6 /* ReactorKit in Frameworks */, BDCA41F22CF35D0D005EECF6 /* SnapKit in Frameworks */, @@ -982,7 +987,9 @@ 4EA9989D2D21C404009DC30B /* RxDataSources in Frameworks */, 4EE360FD2D91876300D2441D /* NMapsMap in Frameworks */, BDCA42012CF35EFE005EECF6 /* RxKeyboard in Frameworks */, + 4E15142A2D99480200DFD08F /* NMapsMap in Frameworks */, 088DE2442D104EE70030FA9E /* SwiftSoup in Frameworks */, + 4E15142C2D994A3A00DFD08F /* KakaoSDK in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3084,7 +3091,10 @@ 4E5825662D1951DF00EE83EF /* FloatingPanel */, 4EA9989C2D21C404009DC30B /* RxDataSources */, 082197A02D426DCB0054094A /* Then */, - 08F403322D884F4D00BFA61A /* KakaoSDKUser */, + 4E1514292D99480200DFD08F /* NMapsMap */, + 4E15142B2D994A3A00DFD08F /* KakaoSDK */, + 4E15142D2D994A3A00DFD08F /* KakaoSDKAuth */, + 4E15142F2D994A3A00DFD08F /* KakaoSDKCommon */, ); productName = Poppool; productReference = BDCA41BD2CF35AC0005EECF6 /* Poppool.app */; @@ -3175,6 +3185,7 @@ 4EA9989B2D21C404009DC30B /* XCRemoteSwiftPackageReference "RxDataSources" */, 0821979F2D426DCB0054094A /* XCRemoteSwiftPackageReference "Then" */, 08F403312D884F4D00BFA61A /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */, + 4E1514282D99480200DFD08F /* XCRemoteSwiftPackageReference "SPM-NMapsMap" */, ); productRefGroup = BDCA41BE2CF35AC0005EECF6 /* Products */; projectDirPath = ""; @@ -4060,20 +4071,20 @@ minimumVersion = 2.7.6; }; }; - 088DE2452D12DB5C0030FA9E /* XCRemoteSwiftPackageReference "ios-maps-sdk" */ = { + 08F403312D884F4D00BFA61A /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/googlemaps/ios-maps-sdk"; + repositoryURL = "https://github.com/kakao/kakao-ios-sdk.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 9.2.0; + minimumVersion = 2.24.0; }; }; - 08F403312D884F4D00BFA61A /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */ = { + 4E1514282D99480200DFD08F /* XCRemoteSwiftPackageReference "SPM-NMapsMap" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/kakao/kakao-ios-sdk.git"; + repositoryURL = "https://github.com/navermaps/SPM-NMapsMap"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 2.24.0; + minimumVersion = 3.21.0; }; }; 4E5825652D1951DF00EE83EF /* XCRemoteSwiftPackageReference "FloatingPanel" */ = { @@ -4198,10 +4209,22 @@ package = 088DE2422D104EE70030FA9E /* XCRemoteSwiftPackageReference "SwiftSoup" */; productName = SwiftSoup; }; - 08B191B92CF609AE0057BC04 /* RxKakaoSDK */ = { + 4E1514292D99480200DFD08F /* NMapsMap */ = { + isa = XCSwiftPackageProductDependency; + package = 4E1514282D99480200DFD08F /* XCRemoteSwiftPackageReference "SPM-NMapsMap" */; + productName = NMapsMap; + }; + 4E15142B2D994A3A00DFD08F /* KakaoSDK */ = { + isa = XCSwiftPackageProductDependency; + productName = KakaoSDK; + }; + 4E15142D2D994A3A00DFD08F /* KakaoSDKAuth */ = { + isa = XCSwiftPackageProductDependency; + productName = KakaoSDKAuth; + }; + 4E15142F2D994A3A00DFD08F /* KakaoSDKCommon */ = { isa = XCSwiftPackageProductDependency; - package = 08F403312D884F4D00BFA61A /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; - productName = KakaoSDKUser; + productName = KakaoSDKCommon; }; 4E5825662D1951DF00EE83EF /* FloatingPanel */ = { isa = XCSwiftPackageProductDependency; diff --git a/Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..172ad40b --- /dev/null +++ b/Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,177 @@ +{ + "originHash" : "a264a064b245047631c2da3a5af0624dcc8a9814c5033d1880880c8dbbdc6a20", + "pins" : [ + { + "identity" : "alamofire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/Alamofire.git", + "state" : { + "revision" : "513364f870f6bfc468f9d2ff0a95caccc10044c5", + "version" : "5.10.2" + } + }, + { + "identity" : "floatingpanel", + "kind" : "remoteSourceControl", + "location" : "https://github.com/scenee/FloatingPanel.git", + "state" : { + "revision" : "b6e8928b1a3ad909e6db6a0278d286c33cfd0dc3", + "version" : "2.8.6" + } + }, + { + "identity" : "kakao-ios-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kakao/kakao-ios-sdk.git", + "state" : { + "revision" : "bfe2fe42f730ccfe59e85f6e9eda2f4578e9a307", + "version" : "2.24.0" + } + }, + { + "identity" : "kingfisher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/onevcat/Kingfisher.git", + "state" : { + "revision" : "3db26ab625d194c38e68c1a40e43d1bc12743fe0", + "version" : "8.2.0" + } + }, + { + "identity" : "lottie-spm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/airbnb/lottie-spm.git", + "state" : { + "revision" : "8c6edf4f0fa84fe9c058600a4295eb0c01661c69", + "version" : "4.5.1" + } + }, + { + "identity" : "pageboy", + "kind" : "remoteSourceControl", + "location" : "https://github.com/uias/Pageboy.git", + "state" : { + "revision" : "be0c1f6f1964cfb07f9d819b0863f2c3f255f612", + "version" : "4.2.0" + } + }, + { + "identity" : "panmodal", + "kind" : "remoteSourceControl", + "location" : "https://github.com/slackhq/PanModal.git", + "state" : { + "revision" : "b012aecb6b67a8e46369227f893c12544846613f", + "version" : "1.2.7" + } + }, + { + "identity" : "reactorkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ReactorKit/ReactorKit.git", + "state" : { + "revision" : "8fa33f09c6f6621a2aa536d739956d53b84dd139", + "version" : "3.2.0" + } + }, + { + "identity" : "rxdatasources", + "kind" : "remoteSourceControl", + "location" : "https://github.com/RxSwiftCommunity/RxDataSources.git", + "state" : { + "revision" : "90c29b48b628479097fe775ed1966d75ac374518", + "version" : "5.0.2" + } + }, + { + "identity" : "rxgesture", + "kind" : "remoteSourceControl", + "location" : "https://github.com/RxSwiftCommunity/RxGesture.git", + "state" : { + "revision" : "1b137c576b4aaaab949235752278956697c9e4a0", + "version" : "4.0.4" + } + }, + { + "identity" : "rxkeyboard", + "kind" : "remoteSourceControl", + "location" : "https://github.com/RxSwiftCommunity/RxKeyboard.git", + "state" : { + "revision" : "63f6377975c962a1d89f012a6f1e5bebb2c502b7", + "version" : "2.0.1" + } + }, + { + "identity" : "rxswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ReactiveX/RxSwift.git", + "state" : { + "revision" : "5dd1907d64f0d36f158f61a466bab75067224893", + "version" : "6.9.0" + } + }, + { + "identity" : "snapkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SnapKit/SnapKit.git", + "state" : { + "revision" : "2842e6e84e82eb9a8dac0100ca90d9444b0307f4", + "version" : "5.7.1" + } + }, + { + "identity" : "spm-nmapsgeometry", + "kind" : "remoteSourceControl", + "location" : "https://github.com/navermaps/SPM-NMapsGeometry.git", + "state" : { + "revision" : "436d5e2e684f557faf5ef5862fd6633a42d7af11", + "version" : "1.0.2" + } + }, + { + "identity" : "spm-nmapsmap", + "kind" : "remoteSourceControl", + "location" : "https://github.com/navermaps/SPM-NMapsMap", + "state" : { + "revision" : "ad89e53fdfec3b8d8994280fb0414b5a7b1c3e8e", + "version" : "3.21.0" + } + }, + { + "identity" : "swiftsoup", + "kind" : "remoteSourceControl", + "location" : "https://github.com/scinfu/SwiftSoup", + "state" : { + "revision" : "18ad8b8ff0f03f3c0a5544ffccfa2ea1c051ae6e", + "version" : "2.8.0" + } + }, + { + "identity" : "tabman", + "kind" : "remoteSourceControl", + "location" : "https://github.com/uias/Tabman.git", + "state" : { + "revision" : "3b2213290eb93e55bb50b49d1a179033005c11ab", + "version" : "3.2.0" + } + }, + { + "identity" : "then", + "kind" : "remoteSourceControl", + "location" : "https://github.com/devxoul/Then", + "state" : { + "revision" : "d41ef523faef0f911369f79c0b96815d9dbb6d7a", + "version" : "3.0.0" + } + }, + { + "identity" : "weakmaptable", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ReactorKit/WeakMapTable.git", + "state" : { + "revision" : "cb05d64cef2bbf51e85c53adee937df46540a74e", + "version" : "1.2.1" + } + } + ], + "version" : 3 +} diff --git a/Poppool/Poppool/Application/AppDelegate.swift b/Poppool/Poppool/Application/AppDelegate.swift index 8d2ed331..bf1ed285 100644 --- a/Poppool/Poppool/Application/AppDelegate.swift +++ b/Poppool/Poppool/Application/AppDelegate.swift @@ -1,36 +1,23 @@ -// -// AppDelegate.swift -// Poppoolasdasdasdasda -// -// Created by Porori on 11/24/24. -// import UIKit -import RxKakaoSDKAuth -import KakaoSDKAuth -import RxKakaoSDKCommon -import NMapsMap -import UIKit - import KakaoSDKCommon -import GoogleMaps import CoreLocation +import NMapsMap @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { KakaoSDK.initSDK(appKey: Secrets.kakaoAuthAppkey.rawValue) - GMSServices.provideAPIKey(Secrets.popPoolApiKey.rawValue) - + NMFAuthManager.shared().clientId = Secrets.naverMapClientId.rawValue + let locationManager = CLLocationManager() - locationManager.requestWhenInUseAuthorization() // 권한 요청 초기화 - + locationManager.requestWhenInUseAuthorization() + return true - } + } // MARK: UISceneSession Lifecycle func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } } - diff --git a/Poppool/Poppool/Presentation/Map/Common/ClusteringManager.swift b/Poppool/Poppool/Presentation/Map/Common/ClusteringManager.swift index 8b8600f6..ad3b265f 100644 --- a/Poppool/Poppool/Presentation/Map/Common/ClusteringManager.swift +++ b/Poppool/Poppool/Presentation/Map/Common/ClusteringManager.swift @@ -37,7 +37,6 @@ class ClusteringManager { func clusterStores(_ stores: [MapPopUpStore], at zoomLevel: Float) -> [ClusterMarkerData] { let level = MapZoomLevel.getLevel(from: zoomLevel) - // partition() 호출 결과를 별도의 변수에 할당 let partitionedStores = stores.partition { store in let city = extractCity(from: store.address) return city == "서울" || city == "경기" @@ -49,7 +48,7 @@ class ClusteringManager { case .country: return clusterByProvince(stores) case .city: - let seoulGyeonggiClusters = clusterByCityLevel(seoulGyeonggiStores) + let seoulGyeonggiClusters = clusterByDistrict(seoulGyeonggiStores) let otherClusters = clusterByMetropolitan(otherStores) return seoulGyeonggiClusters + otherClusters case .district: @@ -64,13 +63,11 @@ class ClusteringManager { private func clusterByMetropolitan(_ stores: [MapPopUpStore]) -> [ClusterMarkerData] { var clusters: [String: MutableCluster] = [:] - // 광역시/도 클러스터 초기화 let allClusters = RegionType.RegionDefinitions.metropolitanClusters + RegionType.RegionDefinitions.provinceClusters for cluster in allClusters { clusters[cluster.name] = MutableCluster(base: cluster, fixedCenter: cluster.coordinate) } - // 스토어 할당 for store in stores { let city = extractCity(from: store.address) if let cluster = clusters[city] { @@ -83,78 +80,17 @@ class ClusteringManager { return validClusters.map { $0.toMarkerData() } } - private func clusterByCityLevel(_ stores: [MapPopUpStore]) -> [ClusterMarkerData] { - var clusters: [String: MutableCluster] = [:] - - // 서울/경기 클러스터 초기화 - initializeSeoulGyeonggiClusters(&clusters) - - for store in stores { - let city = extractCity(from: store.address) - let clusterKey = determineClusterKey(for: store, city: city, clusters: &clusters) - if let cluster = clusters[clusterKey] { - cluster.stores.append(store) - cluster.storeCount += 1 - } - } - - let validClusters = clusters.values.filter { $0.storeCount > 0 } - return validClusters.map { $0.toMarkerData() } - } - - private func initializeSeoulGyeonggiClusters(_ clusters: inout [String: MutableCluster]) { - let predefinedClusters = [ - ("서울 북부", RepresentativeScope.seoulNorth.center), - ("서울 남부", RepresentativeScope.seoulSouth.center), - ("경기 북부", RepresentativeScope.gyeonggiNorth.center), - ("경기 남부", RepresentativeScope.gyeonggiSouth.center) - ] - - for (name, coordinate) in predefinedClusters { - let baseRegion = RegionCluster( - name: name, - subRegions: [name], - coordinate: coordinate, - type: .metropolitan - ) - clusters[name] = MutableCluster(base: baseRegion, fixedCenter: coordinate) - } - } - - private func determineClusterKey(for store: MapPopUpStore, city: String, clusters: inout [String: MutableCluster]) -> String { - if city == "서울" { - return seoulNorthRegions.contains(where: { store.address.contains($0) }) ? "서울 북부" : "서울 남부" - } else if city == "경기" { - return gyeonggiNorthRegions.contains(where: { store.address.contains($0) }) ? "경기 북부" : "경기 남부" - } else { - if clusters[city] == nil { - if let coordinate = getFixedCenterForCity(city) { - let baseRegion = RegionCluster( - name: city, - subRegions: [city], - coordinate: coordinate, - type: .metropolitan - ) - clusters[city] = MutableCluster(base: baseRegion, fixedCenter: coordinate) - } - } - return city - } - } - private func clusterByDistrict(_ stores: [MapPopUpStore]) -> [ClusterMarkerData] { var seoulClusters: [String: MutableCluster] = [:] var gyeonggiClusters: [String: MutableCluster] = [:] var otherClusters: [String: MutableCluster] = [:] - // 서울/경기 클러스터 초기화 for cluster in RegionType.RegionDefinitions.seoulClusters { seoulClusters[cluster.name] = MutableCluster(base: cluster, fixedCenter: cluster.coordinate) } for cluster in RegionType.RegionDefinitions.gyeonggiClusters { gyeonggiClusters[cluster.name] = MutableCluster(base: cluster, fixedCenter: cluster.coordinate) } - // 다른 지역 클러스터 초기화 for cluster in RegionType.RegionDefinitions.metropolitanClusters { otherClusters[cluster.name] = MutableCluster(base: cluster, fixedCenter: cluster.coordinate) } diff --git a/Poppool/Poppool/Presentation/Map/Common/MapUtilities.swift b/Poppool/Poppool/Presentation/Map/Common/MapUtilities.swift index 1b20d677..3b4cd3bf 100644 --- a/Poppool/Poppool/Presentation/Map/Common/MapUtilities.swift +++ b/Poppool/Poppool/Presentation/Map/Common/MapUtilities.swift @@ -29,24 +29,3 @@ public let gyeonggiSouthRegions: [String] = [ "여주시", "양평군", "광주시", "이천시" ] -// RepresentativeScope 수정 -public struct RepresentativeScope { - public static let seoulNorth = ( - center: NMGLatLng(lat: 37.6020, lng: 127.0350), - radius: 3000.0 - ) - public static let seoulSouth = ( - center: NMGLatLng(lat: 37.4959, lng: 127.0664), // 강남/서초 중심 - radius: 3000.0 - ) - - // 경기 북부/남부 좌표 조정 - public static let gyeonggiNorth = ( - center: NMGLatLng(lat: 37.7358, lng: 127.0346), // 의정부 중심 - radius: 4000.0 - ) - public static let gyeonggiSouth = ( - center: NMGLatLng(lat: 37.2911, lng: 127.0876), // 용인/분당 중심 - radius: 4000.0 - ) -} diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonBackgroundView.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonBackgroundView.swift index 28a18b39..4b9afe75 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonBackgroundView.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonBackgroundView.swift @@ -13,7 +13,6 @@ final class BalloonBackgroundView: UIView { return view }() - // 기존 말풍선 UI: 서브 지역을 나열하는 CollectionView (서울/경기/부산용) private let collectionView: UICollectionView = { let layout = UICollectionViewCompositionalLayout { section, env in let itemSize = NSCollectionLayoutSize( @@ -44,8 +43,8 @@ final class BalloonBackgroundView: UIView { let iv = UIImageView() iv.contentMode = .scaleAspectFit iv.tintColor = .blu500 - iv.image = UIImage(named: "Marker") // 에셋에 추가된 Marker 이미지 - iv.isHidden = true // 기본은 숨김 + iv.image = UIImage(named: "Marker") + iv.isHidden = true return iv }() @@ -62,7 +61,7 @@ final class BalloonBackgroundView: UIView { label.font = UIFont.systemFont(ofSize: 14, weight: .medium) label.textColor = UIColor.g400 label.textAlignment = .center - label.numberOfLines = 2 // 두 줄 표시 + label.numberOfLines = 2 return label }() @@ -88,15 +87,21 @@ final class BalloonBackgroundView: UIView { override init(frame: CGRect) { super.init(frame: frame) backgroundColor = .clear + self.isUserInteractionEnabled = true + containerView.isUserInteractionEnabled = true + collectionView.isUserInteractionEnabled = true setupLayout() setupCollectionView() } + + required init?(coder: NSCoder) { fatalError() } // MARK: - Setup private func setupLayout() { + addSubview(containerView) containerView.snp.makeConstraints { make in make.left.right.equalToSuperview() @@ -169,12 +174,6 @@ final class BalloonBackgroundView: UIView { UIColor.g50.setFill() path.fill() - // 그림자 설정 -// layer.shadowPath = path.cgPath -// layer.shadowColor = UIColor.black.cgColor -// layer.shadowOpacity = 0.1 -// layer.shadowOffset = CGSize(width: 0, height: 2) -// layer.shadowRadius = 4 } @@ -272,7 +271,7 @@ final class BalloonBackgroundView: UIView { let itemHeight: CGFloat = 36 let interGroupSpacing: CGFloat = 8 - let verticalInset: CGFloat = 20 + 20 // top: 20, bottom: 20 + let verticalInset: CGFloat = 20 + 20 let totalHeight = max( (itemHeight * CGFloat(numberOfRows)) + (interGroupSpacing * CGFloat(numberOfRows - 1)) + @@ -280,7 +279,6 @@ final class BalloonBackgroundView: UIView { 36 ) - print("계산된 최종 높이: \(totalHeight)") return totalHeight } private func calculateButtonWidth(for text: String, font: UIFont, isSelected: Bool) -> CGFloat { diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonChipCell.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonChipCell.swift index 3919e1bf..afea2a71 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonChipCell.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonChipCell.swift @@ -11,6 +11,7 @@ final class BalloonChipCell: UICollectionViewCell { font: .KorFont(style: .medium, size: 11), cornerRadius: 15 ) + button.titleLabel?.lineBreakMode = .byTruncatingTail button.titleLabel?.adjustsFontSizeToFitWidth = false return button diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift index 67034b7f..c233ea4b 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift @@ -9,15 +9,9 @@ final class FilterBottomSheetView: UIView { view.layer.cornerRadius = 20 view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] view.layer.masksToBounds = true - return view }() - let headerView: UIView = { - let view = UIView() - view.backgroundColor = .white - return view - }() let titleLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 18, text: "보기 옵션을 선택해주세요") label.textColor = .black @@ -41,6 +35,7 @@ final class FilterBottomSheetView: UIView { scrollView.showsHorizontalScrollIndicator = false return scrollView }() + let locationContentView = UIView() var categoryHeightConstraint: Constraint? @@ -81,7 +76,6 @@ final class FilterBottomSheetView: UIView { return collectionView }() - let balloonBackgroundView = BalloonBackgroundView() let resetButton: PPButton = { @@ -89,14 +83,12 @@ final class FilterBottomSheetView: UIView { return button }() - let saveButton: PPButton = { let button = PPButton(style: .primary, text: "옵션저장", disabledText: "옵션저장") return button }() - - let buttonStack: UIStackView = { + private let buttonStack: UIStackView = { let stack = UIStackView() stack.axis = .horizontal stack.spacing = 12 @@ -112,7 +104,7 @@ final class FilterBottomSheetView: UIView { private var balloonHeightConstraint: Constraint? // MARK: - Initialization - + override init(frame: CGRect) { super.init(frame: frame) setupLayout() @@ -127,33 +119,19 @@ final class FilterBottomSheetView: UIView { backgroundColor = .clear addSubview(containerView) - containerView.addSubview(headerView) - headerView.addSubview(titleLabel) - headerView.addSubview(closeButton) - + containerView.addSubview(titleLabel) + containerView.addSubview(closeButton) containerView.addSubview(segmentedControl) containerView.addSubview(locationScrollView) locationScrollView.addSubview(locationContentView) - containerView.addSubview(balloonBackgroundView) containerView.addSubview(categoryCollectionView) - categoryCollectionView.snp.makeConstraints { make in - make.top.equalTo(segmentedControl.snp.bottom).offset(16) - make.leading.trailing.equalToSuperview() - categoryHeightConstraint = make.height.equalTo(160).constraint - } - containerView.addSubview(filterChipsView) + buttonStack.addArrangedSubview(resetButton) buttonStack.addArrangedSubview(saveButton) containerView.addSubview(buttonStack) - filterChipsView.snp.makeConstraints { make in - make.top.equalTo(balloonBackgroundView.snp.bottom).offset(24) - make.leading.trailing.equalToSuperview().inset(16) - make.height.equalTo(80) - } - setupConstraints() } @@ -164,12 +142,7 @@ final class FilterBottomSheetView: UIView { containerView.snp.makeConstraints { make in make.left.right.bottom.equalToSuperview() - make.top.equalTo(headerView.snp.top) - } - - headerView.snp.makeConstraints { make in - make.top.leading.trailing.equalToSuperview() - make.height.equalTo(70) + make.top.equalTo(self.snp.top) } titleLabel.snp.makeConstraints { make in @@ -184,7 +157,7 @@ final class FilterBottomSheetView: UIView { } segmentedControl.snp.makeConstraints { make in - make.top.equalTo(headerView.snp.bottom).offset(16) + make.top.equalTo(titleLabel.snp.bottom).offset(16) make.leading.trailing.equalToSuperview() } @@ -202,7 +175,7 @@ final class FilterBottomSheetView: UIView { categoryCollectionView.snp.makeConstraints { make in make.top.equalTo(segmentedControl.snp.bottom).offset(16) make.leading.trailing.equalToSuperview() -// categoryHeightConstraint = make.height.equalTo(160).constraint + categoryHeightConstraint = make.height.equalTo(160).constraint } balloonBackgroundView.snp.makeConstraints { make in @@ -260,6 +233,7 @@ final class FilterBottomSheetView: UIView { } } } + func updateCategoryButtonSelection(_ category: String) { categoryCollectionView.subviews.forEach { subview in if let stackView = subview as? UIStackView { @@ -305,36 +279,19 @@ final class FilterBottomSheetView: UIView { } func updateContentVisibility(isCategorySelected: Bool) { - UIView.performWithoutAnimation { - self.locationScrollView.alpha = isCategorySelected ? 0 : 1 - self.balloonBackgroundView.alpha = isCategorySelected ? 0 : 1 - self.categoryCollectionView.alpha = isCategorySelected ? 1 : 0 - - self.locationScrollView.isHidden = isCategorySelected - self.balloonBackgroundView.isHidden = isCategorySelected - self.categoryCollectionView.isHidden = !isCategorySelected - - // filterChipsView 제약조건 업데이트 - self.filterChipsView.snp.remakeConstraints { make in - if isCategorySelected { - make.top.equalTo(self.categoryCollectionView.snp.bottom).offset(16) - } else { - make.top.equalTo(self.balloonBackgroundView.snp.bottom).offset(24) - } - make.leading.trailing.equalToSuperview().inset(16) - make.height.equalTo(80) - } - - let newHeight = isCategorySelected ? 170 : self.balloonBackgroundView.calculateHeight() - self.balloonHeightConstraint?.update(offset: newHeight) - - self.layoutIfNeeded() - } - } - + locationScrollView.isHidden = isCategorySelected + balloonBackgroundView.isHidden = isCategorySelected + categoryCollectionView.isHidden = !isCategorySelected + locationScrollView.alpha = isCategorySelected ? 0 : 1 + balloonBackgroundView.alpha = isCategorySelected ? 0 : 1 + categoryCollectionView.alpha = isCategorySelected ? 1 : 0 + let newHeight = isCategorySelected ? 170 : self.balloonBackgroundView.calculateHeight() + balloonHeightConstraint?.update(offset: newHeight) + self.layoutIfNeeded() + } private func createStyledButton(title: String, isSelected: Bool = false) -> PPButton { let button = PPButton( @@ -371,49 +328,37 @@ final class FilterBottomSheetView: UIView { button.setBackgroundColor(.w100, for: .normal) button.setTitleColor(.g400, for: .normal) button.layer.borderColor = UIColor.g200.cgColor - button.titleLabel?.font = .KorFont(style: .medium, size: 13) + button.titleLabel?.font = .KorFont(style: .medium, size: 13) button.layer.borderWidth = 1 } } } func updateBalloonHeight(isHidden: Bool, dynamicHeight: CGFloat = 160) { - if isHidden { - balloonBackgroundView.alpha = 0 - balloonBackgroundView.isHidden = true - balloonHeightConstraint?.update(offset: 0) - } else { - balloonBackgroundView.alpha = 1 - balloonBackgroundView.isHidden = false - balloonHeightConstraint?.update(offset: dynamicHeight) - } - - self.setNeedsLayout() + balloonBackgroundView.alpha = isHidden ? 0 : 1 + balloonBackgroundView.isHidden = isHidden + balloonHeightConstraint?.update(offset: isHidden ? 0 : dynamicHeight) self.layoutIfNeeded() } - - - + func updateBalloonPosition(for button: UIButton) { - guard let window = button.window else { return } - - let buttonFrameInWindow = button.convert(button.bounds, to: window) - let balloonFrameInWindow = balloonBackgroundView.convert(balloonBackgroundView.bounds, to: window) + DispatchQueue.main.async { + guard let window = button.window else { return } - let buttonCenterX = buttonFrameInWindow.midX + let buttonFrameInWindow = button.convert(button.bounds, to: window) + let balloonFrameInWindow = self.balloonBackgroundView.convert(self.balloonBackgroundView.bounds, to: window) - let relativeX = buttonCenterX - balloonFrameInWindow.minX + let buttonCenterX = buttonFrameInWindow.midX + let relativeX = buttonCenterX - balloonFrameInWindow.minX + let position = relativeX / self.balloonBackgroundView.bounds.width + let minPosition: CGFloat = 0.1 + let maxPosition: CGFloat = 0.9 + let clampedPosition = min(maxPosition, max(minPosition, position)) - let position = relativeX / balloonBackgroundView.bounds.width - - let minPosition: CGFloat = 0.1 - let maxPosition: CGFloat = 0.9 - let clampedPosition = min(maxPosition, max(minPosition, position)) - - balloonBackgroundView.arrowPosition = clampedPosition - balloonBackgroundView.setNeedsDisplay() + self.balloonBackgroundView.arrowPosition = clampedPosition + self.balloonBackgroundView.setNeedsDisplay() + } } - private func updateBalloonPositionAccurately(for button: PPButton) { let buttonFrameInBalloon = button.convert(button.bounds, to: balloonBackgroundView) let arrowPosition = buttonFrameInBalloon.midX / balloonBackgroundView.bounds.width @@ -435,17 +380,29 @@ extension FilterBottomSheetView { filterChipsView.updateChips(with: filters) } -} +} + extension FilterBottomSheetView: UIScrollViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { - // 선택된 버튼 찾기 + updateSelectedButtonPosition() + } + + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + updateSelectedButtonPosition() + } + + func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + if !decelerate { + updateSelectedButtonPosition() + } + } + + private func updateSelectedButtonPosition() { guard let selectedButton = locationContentView.subviews.first(where: { view in guard let button = view as? PPButton else { return false } return button.backgroundColor == .blu500 }) as? PPButton else { return } - - DispatchQueue.main.async { [weak self] in - self?.updateBalloonPosition(for: selectedButton) - } + + updateBalloonPosition(for: selectedButton) } } diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetViewController.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetViewController.swift index 67663ca4..828141f5 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetViewController.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetViewController.swift @@ -1,4 +1,3 @@ - import UIKit import SnapKit import RxSwift @@ -13,28 +12,21 @@ final class FilterBottomSheetViewController: UIViewController, View { var disposeBag = DisposeBag() var onSave: ((FilterData) -> Void)? var onDismiss: (() -> Void)? - - // Container height를 업데이트할 때 SnapKit Constraint를 직접 저장해 둠 private var bottomConstraint: Constraint? private var containerHeightConstraint: Constraint? - // 바텀시트 실제 UI let containerView = FilterBottomSheetView() - - // 필요하다면 다른 속성들 + private var containerViewBottomConstraint: NSLayoutConstraint? private var savedLocation: String? private var savedCategory: String? private var tagSection: TagSection? - private lazy var dimmedView: UIControl = { - let control = UIControl() - control.backgroundColor = .black.withAlphaComponent(0.4) - control.alpha = 0 - control.addTarget(self, action: #selector(hideBottomSheet), for: .touchUpInside) - return control + private lazy var dimmedView: UIView = { + let view = UIView() + view.backgroundColor = .black.withAlphaComponent(0.4) + view.alpha = 0 + return view }() - - // MARK: - Initialization init(reactor: Reactor) { super.init(nibName: nil, bundle: nil) @@ -46,32 +38,55 @@ final class FilterBottomSheetViewController: UIViewController, View { } // MARK: - Lifecycle + override func viewDidLoad() { super.viewDidLoad() - - setupLayout() // 오토레이아웃 + setupLayout() setupGestures() setupCollectionView() - - // ChipsView에서 필터 제거 로직 + containerView.isUserInteractionEnabled = true containerView.filterChipsView.onRemoveChip = { [weak self] removedOption in guard let self = self, let reactor = self.reactor else { return } - if reactor.currentState.selectedCategories.contains(removedOption) { + let isCategory = reactor.currentState.selectedCategories.contains(removedOption) + let isSubRegion = reactor.currentState.selectedSubRegions.contains(removedOption) + + if isCategory { reactor.action.onNext(.toggleCategory(removedOption)) - } else if reactor.currentState.selectedSubRegions.contains(removedOption) { + } else if isSubRegion { reactor.action.onNext(.toggleSubRegion(removedOption)) + } - let currentSegment = self.containerView.segmentedControl.selectedSegmentIndex - if currentSegment == 0 { - self.updateUIForCurrentTab(segment: currentSegment) + DispatchQueue.main.async { + let activeSegment = reactor.currentState.activeSegment + + if isCategory && activeSegment == 1 { + self.containerView.categoryCollectionView.reloadData() + } else if isSubRegion && activeSegment == 0 { + if let selectedIndex = reactor.currentState.selectedLocationIndex { + let location = reactor.currentState.locations[selectedIndex] + self.containerView.balloonBackgroundView.configure( + for: location.main, + subRegions: location.sub, + selectedRegions: reactor.currentState.selectedSubRegions, + selectionHandler: { [weak self] subRegion in + self?.reactor?.action.onNext(.toggleSubRegion(subRegion)) + }, + allSelectionHandler: { [weak self] in + self?.reactor?.action.onNext(.toggleAllSubRegions) + } + ) + } } + + self.updateContainerHeight() + self.containerView.updateContentVisibility(isCategorySelected: activeSegment == 1) } } - } + } - // MARK: - Setup Layout + // MARK: - Setup private func setupLayout() { view.backgroundColor = .clear @@ -83,178 +98,13 @@ final class FilterBottomSheetViewController: UIViewController, View { view.addSubview(containerView) containerView.snp.makeConstraints { make in make.left.right.equalToSuperview() - containerHeightConstraint = make.height.equalTo(UIScreen.main.bounds.height * 0.7).constraint - + containerHeightConstraint = make.height.greaterThanOrEqualTo(400).constraint bottomConstraint = make.bottom.equalToSuperview().offset(UIScreen.main.bounds.height).constraint } - containerView.isUserInteractionEnabled = true - } - - // MARK: - Setup Gestures - private func setupGestures() { - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(hideBottomSheet)) - tapGesture.delegate = self - dimmedView.addGestureRecognizer(tapGesture) - } - - - -// @objc private func didTapDimmedView() { -// // 딤드 뷰 탭 → 시트 닫기 -// hideBottomSheet() -// } - - @objc private func handlePanGesture(_ gesture: UIPanGestureRecognizer) { - let translation = gesture.translation(in: view) - - switch gesture.state { - case .changed: - guard translation.y >= 0 else { return } - bottomConstraint?.update(offset: translation.y) - view.layoutIfNeeded() - - case .ended: - let velocity = gesture.velocity(in: view) - if translation.y > 150 || velocity.y > 1000 { - // (pan) 시트 끌어내리면 닫기 - let currentSegment = containerView.segmentedControl.selectedSegmentIndex - updateUIForCurrentTab(segment: currentSegment) - hideBottomSheet() - } else { - UIView.animate(withDuration: 0.25) { - self.bottomConstraint?.update(offset: 0) - self.view.layoutIfNeeded() - } - } - default: - break - } - } - - // MARK: - Public Show / Hide - func showBottomSheet() { - guard let reactor = reactor else { return } - - // (A) location 초기선택 - if let locations = reactor.currentState.savedSubRegions.first?.split(separator: "/").first.map(String.init), - let index = reactor.currentState.locations.firstIndex(where: { $0.main == locations }) { - reactor.action.onNext(.selectLocation(index)) - } - - // (B) 필터 칩 뷰 업데이트 - containerView.update( - locationText: reactor.currentState.savedSubRegions.joined(separator: ", "), - categoryText: reactor.currentState.savedCategories.joined(separator: ", ") - ) - - // (C) 시트 애니메이션 - UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut) { - self.dimmedView.alpha = 1 - self.bottomConstraint?.update(offset: 0) - self.view.layoutIfNeeded() - } - } - - @objc func hideBottomSheet() { - UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseIn) { - self.dimmedView.alpha = 0 - self.bottomConstraint?.update(offset: UIScreen.main.bounds.height) - self.view.layoutIfNeeded() - } completion: { _ in - self.dismiss(animated: false) - self.onDismiss?() - } - } - - // MARK: - UI Update (for tabs) - private func updateUIForCurrentTab(segment: Int) { - UIView.performWithoutAnimation { - // 탭 전환 시 모든 뷰 숨김 - containerView.categoryCollectionView.isHidden = true - containerView.locationScrollView.isHidden = true - containerView.balloonBackgroundView.isHidden = true - - if segment == 0 { - // 지역 탭 - containerView.locationScrollView.isHidden = false - if let selectedLocationIndex = reactor?.currentState.selectedLocationIndex, - let locations = reactor?.currentState.locations, - selectedLocationIndex >= 0, selectedLocationIndex < locations.count { - - let location = locations[selectedLocationIndex] - containerView.balloonBackgroundView.configure( - for: location.main, - subRegions: location.sub, - selectedRegions: reactor?.currentState.selectedSubRegions ?? [], - selectionHandler: { [weak self] subRegion in - self?.reactor?.action.onNext(.toggleSubRegion(subRegion)) - }, - allSelectionHandler: { [weak self] in - self?.reactor?.action.onNext(.toggleAllSubRegions) - } - ) - - containerView.balloonBackgroundView.isHidden = false - let dynamicHeight = containerView.balloonBackgroundView.calculateHeight() - containerView.updateBalloonHeight(isHidden: false, dynamicHeight: dynamicHeight) - } - } else { - containerView.categoryCollectionView.isHidden = false - containerView.updateBalloonHeight(isHidden: true) - - // ★ 카테고리 탭에 들어오면, 한 번 더 layout 후 시트 높이 갱신 - DispatchQueue.main.async { - // 콜렉션뷰 레이아웃 강제 반영 - self.containerView.categoryCollectionView.layoutIfNeeded() - let contentHeight = self.containerView.categoryCollectionView.contentSize.height - - // 콜렉션뷰 높이 업데이트 - self.containerView.categoryCollectionView.snp.updateConstraints { make in - make.height.equalTo(contentHeight + 40) - } - self.containerView.layoutIfNeeded() - - // 시트 높이 갱신 - self.updateContainerHeight() - } - } - - containerView.layoutIfNeeded() - view.layoutIfNeeded() - } - - // 시트 높이 업데이트 - updateContainerHeight() - } - - private func updateContainerHeight() { - let segmentIndex = containerView.segmentedControl.selectedSegmentIndex - - let headerHeight = containerView.headerView.frame.height - let segmentHeight = containerView.segmentedControl.frame.height - let filterHeight = containerView.filterChipsView.frame.height - let buttonHeight: CGFloat = 52 - let padding: CGFloat = 60 - - let contentHeight: CGFloat - if segmentIndex == 0 { - let locationHeight = containerView.locationScrollView.frame.height - let balloonHeight = containerView.balloonBackgroundView.isHidden ? 0 : containerView.balloonBackgroundView.calculateHeight() - contentHeight = headerHeight + segmentHeight + locationHeight + balloonHeight + filterHeight + buttonHeight + padding - } else { - let categoryHeight = containerView.categoryCollectionView.frame.height - contentHeight = headerHeight + segmentHeight + categoryHeight + filterHeight + buttonHeight + padding - } - - let minHeight: CGFloat = 300 - let maxHeight = UIScreen.main.bounds.height * 0.7 - let newHeight = min(max(contentHeight, minHeight), maxHeight) + view.sendSubviewToBack(dimmedView) + dimmedView.isUserInteractionEnabled = true - UIView.animate(withDuration: 0.2) { - self.containerHeightConstraint?.update(offset: newHeight) - self.view.layoutIfNeeded() - } } private func setupCollectionView() { @@ -262,57 +112,60 @@ final class FilterBottomSheetViewController: UIViewController, View { containerView.categoryCollectionView.delegate = self } - // MARK: - Reactor Binding + // MARK: - Binding func bind(reactor: Reactor) { - // (1) 세그먼트 컨트롤 + // 1. 세그먼트 컨트롤 바인딩 containerView.segmentedControl.rx.selectedSegmentIndex - .do(onNext: { [weak self] segmentIndex in - self?.updateUIForCurrentTab(segment: segmentIndex) - self?.updateContainerHeight() - }) .map { Reactor.Action.segmentChanged($0) } .bind(to: reactor.action) .disposed(by: disposeBag) - // (2) 리셋 버튼 + // 2. 리셋 버튼 바인딩 containerView.resetButton.rx.tap - .do(onNext: { [weak self] _ in - guard let self = self, - let reactor = self.reactor, - let selectedIndex = reactor.currentState.selectedLocationIndex else { return } + .do(onNext: { [weak self] _ in + guard let self = self, + let reactor = self.reactor, + let selectedIndex = reactor.currentState.selectedLocationIndex else { return } + + let location = reactor.currentState.locations[selectedIndex] + // 현재 location에 대한 configure 재설정 + self.containerView.balloonBackgroundView.configure( + for: location.main, + subRegions: location.sub, + selectedRegions: reactor.currentState.selectedSubRegions, + selectionHandler: { [weak self] subRegion in + self?.reactor?.action.onNext(.toggleSubRegion(subRegion)) + }, + allSelectionHandler: { [weak self] in + self?.reactor?.action.onNext(.toggleAllSubRegions) + } + ) + + + }) + .map { Reactor.Action.resetFilters } + .bind(to: reactor.action) + .disposed(by: disposeBag) + - let location = reactor.currentState.locations[selectedIndex] - self.containerView.balloonBackgroundView.configure( - for: location.main, - subRegions: location.sub, - selectedRegions: reactor.currentState.selectedSubRegions, - selectionHandler: { [weak self] subRegion in - self?.reactor?.action.onNext(.toggleSubRegion(subRegion)) - }, - allSelectionHandler: { [weak self] in - self?.reactor?.action.onNext(.toggleAllSubRegions) - } - ) - }) - .map { Reactor.Action.resetFilters } - .bind(to: reactor.action) - .disposed(by: disposeBag) - // (3) 저장 containerView.saveButton.rx.tap .bind { [weak self] _ in guard let self = self, let reactor = self.reactor else { return } + let filterData: FilterData = ( locations: reactor.currentState.selectedSubRegions, categories: reactor.currentState.selectedCategories ) + self.onSave?(filterData) reactor.action.onNext(.applyFilters(filterData.locations + filterData.categories)) + self.hideBottomSheet() } .disposed(by: disposeBag) - // (4) 닫기 + containerView.closeButton.rx.tap .bind { [weak self] _ in guard let self = self, let reactor = self.reactor else { return } @@ -325,7 +178,8 @@ final class FilterBottomSheetViewController: UIViewController, View { } .disposed(by: disposeBag) - // (5) 액티브 세그먼트 + + // 5. 탭 변경 reactor.state.map { $0.activeSegment } .distinctUntilChanged() .bind { [weak self] activeSegment in @@ -333,20 +187,28 @@ final class FilterBottomSheetViewController: UIViewController, View { if activeSegment == 0 { let dynamicHeight = self.containerView.balloonBackgroundView.calculateHeight() self.containerView.updateBalloonHeight(isHidden: false, dynamicHeight: dynamicHeight) - } else { + } else if activeSegment == 1 { self.containerView.updateBalloonHeight(isHidden: true) } self.containerView.updateContentVisibility(isCategorySelected: activeSegment == 1) + + // 여기에 컨테이너 높이 업데이트 추가 + self.updateContainerHeight() } .disposed(by: disposeBag) - // (6) 위치 리스트 - let locations = reactor.state.map { $0.locations }.distinctUntilChanged().share(replay: 1) + + // 6. 위치 데이터 바인딩 + let locations = reactor.state + .map { $0.locations } + .distinctUntilChanged() + .share(replay: 1) locations .observe(on: MainScheduler.instance) .bind { [weak self] locations in self?.containerView.setupLocationScrollView(locations: locations) { [weak self] index, button in guard let self = self else { return } + if index == 0 { if let selectedSubRegions = self.reactor?.currentState.selectedSubRegions, !selectedSubRegions.isEmpty { @@ -356,39 +218,38 @@ final class FilterBottomSheetViewController: UIViewController, View { } } self.reactor?.action.onNext(.selectLocation(index)) + self.containerView.updateBalloonPosition(for: button) } } .disposed(by: disposeBag) - // (7) locationAndSubRegions - reactor.state.map { ($0.selectedLocationIndex, $0.selectedSubRegions) } + + let locationAndSubRegions = reactor.state + .map { ($0.selectedLocationIndex, $0.selectedSubRegions) } .distinctUntilChanged { prev, curr in let isIndexSame = prev.0 == curr.0 let isSubRegionsSame = prev.1 == curr.1 return isIndexSame && isSubRegionsSame } .share(replay: 1) + + locationAndSubRegions .observe(on: MainScheduler.instance) .bind { [weak self] data in guard let self = self, let reactor = self.reactor else { return } let (selectedIndexOptional, selectedSubRegions) = data + guard let selectedIndex = selectedIndexOptional, selectedIndex >= 0, selectedIndex < reactor.currentState.locations.count else { return } - // 현재 탭이 지역(0)인지 체크해서 풍선 뷰 노출할지 결정 - let currentSegment = self.containerView.segmentedControl.selectedSegmentIndex - if currentSegment != 0 { - return - } - let location = reactor.currentState.locations[selectedIndex] self.containerView.balloonBackgroundView.configure( - for: location.main, - subRegions: location.sub, - selectedRegions: selectedSubRegions, + for: location.main, // 첫 번째 인자는 메인 지역(String) + subRegions: location.sub, // 두 번째 인자는 [String] + selectedRegions: selectedSubRegions, // 세 번째 인자는 [String] selectionHandler: { [weak self] subRegion in self?.reactor?.action.onNext(.toggleSubRegion(subRegion)) }, @@ -397,23 +258,20 @@ final class FilterBottomSheetViewController: UIViewController, View { } ) - // 화살표 위치 - let subviews = self.containerView.locationContentView.subviews - if selectedIndex < subviews.count, - let button = subviews[selectedIndex] as? UIButton { + + if let button = self.containerView.locationContentView.subviews[selectedIndex] as? UIButton { self.containerView.updateBalloonPosition(for: button) } DispatchQueue.main.async { let dynamicHeight = self.containerView.balloonBackgroundView.calculateHeight() self.containerView.updateBalloonHeight(isHidden: false, dynamicHeight: dynamicHeight) - self.updateContainerHeight() } + self.containerView.balloonBackgroundView.isHidden = false } .disposed(by: disposeBag) - // (8) 카테고리 바인딩 Observable.combineLatest( reactor.state.map { $0.categories }.distinctUntilChanged(), reactor.state.map { $0.selectedCategories }.distinctUntilChanged() @@ -439,14 +297,14 @@ final class FilterBottomSheetViewController: UIViewController, View { } .disposed(by: disposeBag) - // (9) 필터칩 업데이트 + + reactor.state.map { $0.selectedSubRegions + $0.selectedCategories } .distinctUntilChanged() .bind { [weak self] selectedOptions in UIView.performWithoutAnimation { self?.containerView.filterChipsView.updateChips(with: selectedOptions) self?.containerView.layoutIfNeeded() - self?.updateContainerHeight() } } .disposed(by: disposeBag) @@ -461,6 +319,7 @@ final class FilterBottomSheetViewController: UIViewController, View { } .disposed(by: disposeBag) + reactor.state.map { $0.isSaveEnabled } .distinctUntilChanged() .observe(on: MainScheduler.instance) @@ -477,77 +336,211 @@ final class FilterBottomSheetViewController: UIViewController, View { } } .disposed(by: disposeBag) - Observable.just(()) - .withLatestFrom(reactor.state) - .take(1) - .subscribe(onNext: { [weak self] state in - // 저장된 지역 필터 - if !state.savedSubRegions.isEmpty { - state.savedSubRegions.forEach { region in - reactor.action.onNext(.toggleSubRegion(region)) + .withLatestFrom(reactor.state) + .take(1) + .subscribe(onNext: { [weak self] state in + // 저장된 지역 필터 설정 + if !state.savedSubRegions.isEmpty { + state.savedSubRegions.forEach { region in + reactor.action.onNext(.toggleSubRegion(region)) + } } - } - // 저장된 카테고리 필터 - if !state.savedCategories.isEmpty { - state.savedCategories.forEach { category in - reactor.action.onNext(.toggleCategory(category)) + + // 저장된 카테고리 필터 설정 + if !state.savedCategories.isEmpty { + state.savedCategories.forEach { category in + reactor.action.onNext(.toggleCategory(category)) + } + } + + // 지역이 선택되어 있다면 해당 지역 버튼도 활성화 + if let locations = state.savedSubRegions.first?.split(separator: "/").first.map(String.init), + let index = reactor.currentState.locations.firstIndex(where: { $0.main == locations }) { + reactor.action.onNext(.selectLocation(index)) } + }) + .disposed(by: disposeBag) + + Observable.combineLatest( + reactor.state.map { $0.savedSubRegions }.distinctUntilChanged(), + reactor.state.map { $0.savedCategories }.distinctUntilChanged() + ) + .take(1) + .subscribe(onNext: { [weak self] (subRegions, categories) in + guard let self = self else { return } + + subRegions.forEach { region in + reactor.action.onNext(.toggleSubRegion(region)) } - // 위치 선택 - if let locMain = state.savedSubRegions.first?.split(separator: "/").first.map(String.init), - let idx = reactor.currentState.locations.firstIndex(where: { $0.main == locMain }) { - reactor.action.onNext(.selectLocation(idx)) + categories.forEach { category in + reactor.action.onNext(.toggleCategory(category)) } + + self.containerView.categoryCollectionView.reloadData() + self.containerView.balloonBackgroundView.setNeedsDisplay() }) .disposed(by: disposeBag) - // (12) 추가 combineLatest... (생략) } -} -// MARK: - UIGestureRecognizerDelegate -extension FilterBottomSheetViewController: UIGestureRecognizerDelegate { - func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { - let point = touch.location(in: self.view) + private func updateContentVisibility(_ isCategoryTab: Bool, subRegionCount: Int) { + UIView.animate(withDuration: 0.3) { + self.containerView.updateContentVisibility(isCategorySelected: isCategoryTab) + if !isCategoryTab { + self.containerView.updateBalloonHeight(isHidden: false, dynamicHeight: subRegionCount > 0 ? self.containerView.balloonBackgroundView.calculateHeight() : 80) + } + self.view.layoutIfNeeded() + } + } + func updateContainerHeight() { + let contentHeight: CGFloat - if containerView.frame.contains(point) { - return false + if containerView.segmentedControl.selectedSegmentIndex == 0 { + // 지역탭일 때 + contentHeight = containerView.balloonBackgroundView.calculateHeight() + + containerView.filterChipsView.frame.height + + containerView.segmentedControl.frame.height + + containerView.saveButton.frame.height + 100 // 패딩 및 여유 높이 + } else { + // 카테고리탭일 때 + contentHeight = containerView.categoryCollectionView.contentSize.height + + containerView.filterChipsView.frame.height + + containerView.segmentedControl.frame.height + + containerView.saveButton.frame.height + 100 } - return true + // 최소 400, 최대는 화면 높이의 80%로 제한 + let finalHeight = min(max(contentHeight, 400), UIScreen.main.bounds.height * 0.8) + containerHeightConstraint?.update(offset: finalHeight) + + // 컨테이너 크기 변경 후 레이아웃 업데이트 + view.layoutIfNeeded() + } + + + private func setupGestures() { + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapDimmedView)) + tapGesture.delegate = self + dimmedView.addGestureRecognizer(tapGesture) + dimmedView.isUserInteractionEnabled = true + + // 패닝 제스처는 유지 + let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture)) + containerView.addGestureRecognizer(panGesture) + } + + @objc private func handleDimmedViewTap() { + hideBottomSheet() + } + func showBottomSheet() { + guard let reactor = reactor else { return } + + // 1. 이전에 저장된 지역 필터가 있다면 해당 지역 버튼 활성화 + if let locations = reactor.currentState.savedSubRegions.first?.split(separator: "/").first.map(String.init), + let index = reactor.currentState.locations.firstIndex(where: { $0.main == locations }) { + reactor.action.onNext(.selectLocation(index)) + + + } + + // 4. 필터 칩 뷰 업데이트 + containerView.update( + locationText: reactor.currentState.savedSubRegions.joined(separator: ", "), + categoryText: reactor.currentState.savedCategories.joined(separator: ", ") + ) + + // 5. 애니메이션 + UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut) { + self.dimmedView.alpha = 1 + self.bottomConstraint?.update(offset: 0) + self.view.layoutIfNeeded() + } + } + + + + func hideBottomSheet() { + UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseIn) { + self.dimmedView.alpha = 0 + self.bottomConstraint?.update(offset: UIScreen.main.bounds.height) + self.view.layoutIfNeeded() + } completion: { _ in + self.dismiss(animated: false) + self.onDismiss?() + } + } + + @objc private func handleTapDimmedView() { + hideBottomSheet() + } + + @objc private func handlePanGesture(_ gesture: UIPanGestureRecognizer) { + let translation = gesture.translation(in: view) + + switch gesture.state { + case .changed: + guard translation.y >= 0 else { return } + bottomConstraint?.update(offset: translation.y) + view.layoutIfNeeded() + + case .ended: + let velocity = gesture.velocity(in: view) + if translation.y > 150 || velocity.y > 1000 { + hideBottomSheet() + } else { + UIView.animate(withDuration: 0.25) { + self.bottomConstraint?.update(offset: 0) + self.view.layoutIfNeeded() + } + } + + default: + break + } } } -// MARK: - UICollectionViewDataSource extension FilterBottomSheetViewController: UICollectionViewDataSource { - func numberOfSections(in collectionView: UICollectionView) -> Int { return 1 } + func numberOfSections(in collectionView: UICollectionView) -> Int { + return 1 + } - func collectionView(_ collectionView: UICollectionView, - numberOfItemsInSection section: Int) -> Int { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return tagSection?.inputDataList.count ?? 0 } - func collectionView(_ collectionView: UICollectionView, - cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell( withReuseIdentifier: TagSectionCell.identifiers, for: indexPath ) as? TagSectionCell else { return UICollectionViewCell() } + if let input = tagSection?.inputDataList[indexPath.item] { cell.injection(with: input) } + return cell } } -// MARK: - UICollectionViewDelegateFlowLayout +// MARK: - UICollectionViewDelegate extension FilterBottomSheetViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { guard let category = tagSection?.inputDataList[indexPath.item].title else { return } reactor?.action.onNext(.toggleCategory(category)) } } +extension FilterBottomSheetViewController: UIGestureRecognizerDelegate { + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { + if gestureRecognizer.view == dimmedView { + // 딤드 영역에서만 터치 인식 + let touchPoint = touch.location(in: view) + return !containerView.frame.contains(touchPoint) + } + return true + } +} From 3e5021321d75046fb69d9676ff7b160edb2ccdad Mon Sep 17 00:00:00 2001 From: JunYoung Date: Mon, 31 Mar 2025 14:33:51 +0900 Subject: [PATCH 22/95] =?UTF-8?q?feat/#92:=20URL=EC=9D=84=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=9C=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EB=8B=A4?= =?UTF-8?q?=EC=9A=B4=EB=A1=9C=EB=93=9C=20=EA=B8=B0=EB=8A=A5=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Poppool/Poppool.xcodeproj/project.pbxproj | 12 +++++ .../ImageLoader/ImageLoader.swift | 50 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index a38282f7..10e9c12c 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -270,6 +270,7 @@ 0899526E2D0474340022AEF9 /* GetSearchPopUpListResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0899526D2D0474340022AEF9 /* GetSearchPopUpListResponse.swift */; }; 089952732D0475E90022AEF9 /* SearchResultCountSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089952722D0475E90022AEF9 /* SearchResultCountSection.swift */; }; 089952752D0475F20022AEF9 /* SearchResultCountSectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089952742D0475F20022AEF9 /* SearchResultCountSectionCell.swift */; }; + 089B4FD82D9A57AE00FC0CC3 /* ImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089B4FD72D9A57AE00FC0CC3 /* ImageLoader.swift */; }; 08A2E46C2D15BC5000102313 /* CommentLikeRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08A2E46B2D15BC5000102313 /* CommentLikeRequestDTO.swift */; }; 08A2E4792D1B06A300102313 /* ImageDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08A2E4782D1B06A300102313 /* ImageDetailView.swift */; }; 08A2E47B2D1B06AA00102313 /* ImageDetailController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08A2E47A2D1B06AA00102313 /* ImageDetailController.swift */; }; @@ -762,6 +763,7 @@ 0899526D2D0474340022AEF9 /* GetSearchPopUpListResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetSearchPopUpListResponse.swift; sourceTree = ""; }; 089952722D0475E90022AEF9 /* SearchResultCountSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultCountSection.swift; sourceTree = ""; }; 089952742D0475F20022AEF9 /* SearchResultCountSectionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultCountSectionCell.swift; sourceTree = ""; }; + 089B4FD72D9A57AE00FC0CC3 /* ImageLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageLoader.swift; sourceTree = ""; }; 08A2E46B2D15BC5000102313 /* CommentLikeRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentLikeRequestDTO.swift; sourceTree = ""; }; 08A2E4782D1B06A300102313 /* ImageDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDetailView.swift; sourceTree = ""; }; 08A2E47A2D1B06AA00102313 /* ImageDetailController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDetailController.swift; sourceTree = ""; }; @@ -1494,6 +1496,7 @@ 083A25A02CF3623C0099B58E /* Infrastructure */ = { isa = PBXGroup; children = ( + 089B4FD62D9A576F00FC0CC3 /* ImageLoader */, 0841BA832CF9F61500049E31 /* PreSignedService */, 083A25B12CF362670099B58E /* NetworkLayer */, 08DC61F42CF765B5002A2F44 /* UserDefaultService.swift */, @@ -2311,6 +2314,14 @@ path = View; sourceTree = ""; }; + 089B4FD62D9A576F00FC0CC3 /* ImageLoader */ = { + isa = PBXGroup; + children = ( + 089B4FD72D9A57AE00FC0CC3 /* ImageLoader.swift */, + ); + path = ImageLoader; + sourceTree = ""; + }; 08A2E4772D1B069300102313 /* ImageDetail */ = { isa = PBXGroup; children = ( @@ -3316,6 +3327,7 @@ 4E685EDB2D12CEB6001EF91C /* MapAPIEndpoint.swift in Sources */, 086F89CC2D1E42B000CA4FC9 /* CommentUserBlockController.swift in Sources */, 08DC62032CF8AC06002A2F44 /* HomeView.swift in Sources */, + 089B4FD82D9A57AE00FC0CC3 /* ImageLoader.swift in Sources */, BD9103622CF6149D00BBCCAE /* LoginResponseDTO.swift in Sources */, 083C86642D0EC4A5003F441C /* InstaCommentAddReactor.swift in Sources */, 08DE8A3F2D54DCC40049BCAC /* MyCommentedPopUpGridSection.swift in Sources */, diff --git a/Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift b/Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift new file mode 100644 index 00000000..798074e6 --- /dev/null +++ b/Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift @@ -0,0 +1,50 @@ +import UIKit + +//URL을 사용한 이미지 로드 +//메모리 캐싱 +//디스크 캐싱 +//일정 시간 후 캐싱 데이터를 제거 +//이미지 리사이징 모듈 + +enum ImageLoaderError: Error { + case invalidURL + case networkError(description: String?) +} + +class ImageLoader { + + static let shared = ImageLoader() + + private init() {} + + func loadImage(with stringURL: String?, completion: @escaping (Result) -> Void) { + guard let stringURL = stringURL, + let url = URL(string: stringURL) else { + completion(.failure(ImageLoaderError.invalidURL)) + return + } + + fetchImageFrom(url: url) { result in + completion(result) + } + } +} + +private extension ImageLoader { + func fetchImageFrom(url: URL, completion: @escaping (Result) -> Void) { + let task = URLSession.shared.dataTask(with: url) { data, response, error in + if let error = error { + completion(.failure(ImageLoaderError.networkError(description: "Network Error: \(error.localizedDescription)"))) + return + } + + guard let data = data, let image = UIImage(data: data) else { + completion(.failure(ImageLoaderError.networkError(description: "Network Error: Invalid image data"))) + return + } + + completion(.success(image)) + } + task.resume() + } +} From 1541948e05f46dec9ef7832f4094053af5e6c350 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Mon, 31 Mar 2025 16:49:46 +0900 Subject: [PATCH 23/95] =?UTF-8?q?remove#93:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "Poppool/AdminRepository\\.swift" | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 "Poppool/AdminRepository\\.swift" diff --git "a/Poppool/AdminRepository\\.swift" "b/Poppool/AdminRepository\\.swift" deleted file mode 100644 index b5f7eebc..00000000 --- "a/Poppool/AdminRepository\\.swift" +++ /dev/null @@ -1,8 +0,0 @@ -// -// AdminRepository\.swift -// Poppool -// -// Created by 김기현 on 1/6/25. -// - -import Foundation From c3ad17a0e1a1dd96ce0110b5f7477de92d163f59 Mon Sep 17 00:00:00 2001 From: JunYoung Date: Mon, 31 Mar 2025 17:47:30 +0900 Subject: [PATCH 24/95] =?UTF-8?q?feat/#92:=20NSCache=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ImageLoader/ImageLoader.swift | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift b/Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift index 798074e6..bed25c13 100644 --- a/Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift +++ b/Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift @@ -14,6 +14,7 @@ enum ImageLoaderError: Error { class ImageLoader { static let shared = ImageLoader() + private static let memoryCache = NSCache() private init() {} @@ -24,8 +25,24 @@ class ImageLoader { return } + let cacheKey = url.absoluteString as NSString + + if let cachedImage = fetchImageFromMemory(forKey: cacheKey) { + completion(.success(cachedImage)) + return + } + fetchImageFrom(url: url) { result in - completion(result) + switch result { + case .success(let image): + if let image = image { + self.storeInMemoryCache(image: image, forKey: cacheKey) + } + completion(.success(image)) + + case .failure(let error): + completion(.failure(error)) + } } } } @@ -47,4 +64,12 @@ private extension ImageLoader { } task.resume() } + + func storeInMemoryCache(image: UIImage, forKey key: NSString) { + ImageLoader.memoryCache.setObject(image, forKey: key) + } + + func fetchImageFromMemory(forKey key: NSString) -> UIImage? { + return ImageLoader.memoryCache.object(forKey: key) + } } From 42d4a9541feee8ed37a7dedbc3d414bca1f97952 Mon Sep 17 00:00:00 2001 From: JunYoung Date: Mon, 31 Mar 2025 19:43:18 +0900 Subject: [PATCH 25/95] =?UTF-8?q?feat/#92:=20=EB=A9=94=EB=AA=A8=EB=A6=AC?= =?UTF-8?q?=20=EC=BA=90=EC=8B=9C=20=EC=8A=A4=ED=86=A0=EB=A6=AC=EC=A7=80=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=EC=9E=90=EB=8F=99=20=EC=BA=90?= =?UTF-8?q?=EC=8B=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A0=9C=EA=B1=B0=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=A0=81=EC=9A=A9=20=EB=B0=8F=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Poppool/Poppool.xcodeproj/project.pbxproj | 4 + .../ImageLoader/ImageLoader.swift | 91 ++++++++++-------- .../ImageLoader/MemoryStorage.swift | 92 +++++++++++++++++++ .../Presentation/Extension/UIImageView+.swift | 19 ++-- 4 files changed, 161 insertions(+), 45 deletions(-) create mode 100644 Poppool/Poppool/Infrastructure/ImageLoader/MemoryStorage.swift diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index 87a218d9..c447b668 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -271,6 +271,7 @@ 089952732D0475E90022AEF9 /* SearchResultCountSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089952722D0475E90022AEF9 /* SearchResultCountSection.swift */; }; 089952752D0475F20022AEF9 /* SearchResultCountSectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089952742D0475F20022AEF9 /* SearchResultCountSectionCell.swift */; }; 089B4FD82D9A57AE00FC0CC3 /* ImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089B4FD72D9A57AE00FC0CC3 /* ImageLoader.swift */; }; + 089B4FDF2D9A8F9A00FC0CC3 /* MemoryStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089B4FDE2D9A8F9A00FC0CC3 /* MemoryStorage.swift */; }; 08A2E46C2D15BC5000102313 /* CommentLikeRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08A2E46B2D15BC5000102313 /* CommentLikeRequestDTO.swift */; }; 08A2E4792D1B06A300102313 /* ImageDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08A2E4782D1B06A300102313 /* ImageDetailView.swift */; }; 08A2E47B2D1B06AA00102313 /* ImageDetailController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08A2E47A2D1B06AA00102313 /* ImageDetailController.swift */; }; @@ -762,6 +763,7 @@ 089952722D0475E90022AEF9 /* SearchResultCountSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultCountSection.swift; sourceTree = ""; }; 089952742D0475F20022AEF9 /* SearchResultCountSectionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultCountSectionCell.swift; sourceTree = ""; }; 089B4FD72D9A57AE00FC0CC3 /* ImageLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageLoader.swift; sourceTree = ""; }; + 089B4FDE2D9A8F9A00FC0CC3 /* MemoryStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryStorage.swift; sourceTree = ""; }; 08A2E46B2D15BC5000102313 /* CommentLikeRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentLikeRequestDTO.swift; sourceTree = ""; }; 08A2E4782D1B06A300102313 /* ImageDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDetailView.swift; sourceTree = ""; }; 08A2E47A2D1B06AA00102313 /* ImageDetailController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDetailController.swift; sourceTree = ""; }; @@ -2314,6 +2316,7 @@ isa = PBXGroup; children = ( 089B4FD72D9A57AE00FC0CC3 /* ImageLoader.swift */, + 089B4FDE2D9A8F9A00FC0CC3 /* MemoryStorage.swift */, ); path = ImageLoader; sourceTree = ""; @@ -3327,6 +3330,7 @@ 08DE8A3F2D54DCC40049BCAC /* MyCommentedPopUpGridSection.swift in Sources */, 081898C52D30AEF40067BF01 /* GetMyProfileResponse.swift in Sources */, BD9103922CF6166800BBCCAE /* SplashView.swift in Sources */, + 089B4FDF2D9A8F9A00FC0CC3 /* MemoryStorage.swift in Sources */, 0899526E2D0474340022AEF9 /* GetSearchPopUpListResponse.swift in Sources */, 08B191392CF366680057BC04 /* UITableViewCell+.swift in Sources */, 08A2E48F2D1BF6E500102313 /* CommentListView.swift in Sources */, diff --git a/Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift b/Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift index bed25c13..512beafd 100644 --- a/Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift +++ b/Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift @@ -1,75 +1,90 @@ import UIKit -//URL을 사용한 이미지 로드 -//메모리 캐싱 -//디스크 캐싱 -//일정 시간 후 캐싱 데이터를 제거 -//이미지 리사이징 모듈 - enum ImageLoaderError: Error { case invalidURL case networkError(description: String?) + case convertError(description: String?) +} + +/// 이미지 로더 설정 클래스 +/// - `memoryCacheExpiration`: 메모리 캐시 만료 시간 (기본값 300초) +class ImageLoaderConfigure { + var memoryCacheExpiration: TimeInterval = 300 } +/// URL을 통해 이미지를 비동기적으로 로드하는 클래스 class ImageLoader { static let shared = ImageLoader() - private static let memoryCache = NSCache() + + /// 이미지 로더 설정 객체 + let configure = ImageLoaderConfigure() private init() {} + /// URL을 통해 이미지를 로드하고, 실패 시 기본 이미지를 반환하는 메서드 + /// - Parameters: + /// - stringURL: 이미지 URL 문자열 + /// - defaultImage: 로드 실패 시 반환할 기본 이미지 + /// - completion: 로드 완료 후 호출되는 클로저 + func loadImage(with stringURL: String?, defaultImage: UIImage?, completion: @escaping (UIImage?) -> Void) { + loadImage(with: stringURL) { result in + switch result { + case .success(let image): + completion(image) + case .failure: + completion(defaultImage) + } + } + } +} + +private extension ImageLoader { + + /// URL을 통해 이미지를 로드하는 내부 메서드 + /// - Parameters: + /// - stringURL: 이미지 URL 문자열 + /// - completion: 로드 완료 후 호출되는 클로저 func loadImage(with stringURL: String?, completion: @escaping (Result) -> Void) { - guard let stringURL = stringURL, - let url = URL(string: stringURL) else { + guard let stringURL = stringURL, let url = URL(string: stringURL) else { completion(.failure(ImageLoaderError.invalidURL)) return } - - let cacheKey = url.absoluteString as NSString - - if let cachedImage = fetchImageFromMemory(forKey: cacheKey) { + + // 메모리 캐시에서 이미지 조회 + if let cachedImage = MemoryStorage.shared.fetchImage(url: stringURL) { completion(.success(cachedImage)) return } - fetchImageFrom(url: url) { result in + // 네트워크에서 데이터 요청 + fetchDataFrom(url: url) { result in switch result { - case .success(let image): - if let image = image { - self.storeInMemoryCache(image: image, forKey: cacheKey) + case .success(let data): + if let data = data, let image = UIImage(data: data) { + MemoryStorage.shared.store(image: image, url: stringURL) + completion(.success(image)) + } else { + completion(.failure(ImageLoaderError.convertError(description: "Failed to convert data to UIImage"))) } - completion(.success(image)) - case .failure(let error): completion(.failure(error)) } } } -} - -private extension ImageLoader { - func fetchImageFrom(url: URL, completion: @escaping (Result) -> Void) { + + /// URL을 통해 데이터를 요청하는 메서드 + /// - Parameters: + /// - url: 요청할 URL 객체 + /// - completion: 요청 완료 후 호출되는 클로저 + func fetchDataFrom(url: URL, completion: @escaping (Result) -> Void) { let task = URLSession.shared.dataTask(with: url) { data, response, error in if let error = error { completion(.failure(ImageLoaderError.networkError(description: "Network Error: \(error.localizedDescription)"))) return } - - guard let data = data, let image = UIImage(data: data) else { - completion(.failure(ImageLoaderError.networkError(description: "Network Error: Invalid image data"))) - return - } - - completion(.success(image)) + completion(.success(data)) } task.resume() } - - func storeInMemoryCache(image: UIImage, forKey key: NSString) { - ImageLoader.memoryCache.setObject(image, forKey: key) - } - - func fetchImageFromMemory(forKey key: NSString) -> UIImage? { - return ImageLoader.memoryCache.object(forKey: key) - } } diff --git a/Poppool/Poppool/Infrastructure/ImageLoader/MemoryStorage.swift b/Poppool/Poppool/Infrastructure/ImageLoader/MemoryStorage.swift new file mode 100644 index 00000000..b2203678 --- /dev/null +++ b/Poppool/Poppool/Infrastructure/ImageLoader/MemoryStorage.swift @@ -0,0 +1,92 @@ +import UIKit + +/// 캐시할 이미지와 만료 시간을 저장하는 클래스 +class StorageData: NSObject { + let image: UIImage? /// 캐시된 이미지 + let expirationDate: Date /// 캐시 만료 시간 + + /// 초기화 메서드 + /// - Parameters: + /// - image: 저장할 이미지 + /// - expiration: 만료 시간 (초 단위) + init(image: UIImage?, expiration: TimeInterval) { + self.image = image + self.expirationDate = Date().addingTimeInterval(expiration) + } + + /// 캐시가 만료되었는지 확인하는 메서드 + /// - Returns: 만료 여부 (true: 만료됨, false: 유효함) + func isExpired() -> Bool { + return Date() > expirationDate + } +} + +/// 메모리 캐시를 관리하는 클래스 +class MemoryStorage { + + /// 싱글톤 인스턴스 + static let shared = MemoryStorage() + + /// 이미지 캐시 저장소 + private let cache = NSCache() + + /// 현재 캐시에 저장된 키 목록 + private var cachedKeys: Set = [] + + /// 초기화 (자동 캐시 정리 시작) + private init() { + startCacheCleanup() + } + + /// 이미지를 캐시에 저장하는 메서드 + /// - Parameters: + /// - image: 저장할 이미지 + /// - url: 이미지 URL 문자열 + func store(image: UIImage?, url: String) { + let cachedData = StorageData(image: image, expiration: ImageLoader.shared.configure.memoryCacheExpiration) + cache.setObject(cachedData, forKey: url as NSString) + cachedKeys.insert(url) + } + + /// 캐시에서 이미지를 가져오는 메서드 + /// - Parameter url: 이미지 URL 문자열 + /// - Returns: 캐시된 UIImage (없으면 nil) + func fetchImage(url: String) -> UIImage? { + if let cachedData = cache.object(forKey: url as NSString), !cachedData.isExpired() { + return cachedData.image + } else { + removeData(url: url) + return nil + } + } + + /// 특정 URL의 캐시 데이터를 제거하는 메서드 + /// - Parameter url: 제거할 이미지의 URL 문자열 + func removeData(url: String) { + cache.removeObject(forKey: url as NSString) + cachedKeys.remove(url) + } + + /// 모든 캐시 데이터를 삭제하는 메서드 + func clearCache() { + cache.removeAllObjects() + cachedKeys.removeAll() + } + + /// 주기적으로 만료된 캐시를 정리하는 메서드 + private func startCacheCleanup() { + DispatchQueue.global(qos: .background).async { [weak self] in + guard let self = self else { return } + + Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { _ in + for key in self.cachedKeys { + let nsKey = key as NSString + if let cachedData = self.cache.object(forKey: nsKey), cachedData.isExpired() { + self.cache.removeObject(forKey: nsKey) + self.cachedKeys.remove(key) + } + } + } + } + } +} diff --git a/Poppool/Poppool/Presentation/Extension/UIImageView+.swift b/Poppool/Poppool/Presentation/Extension/UIImageView+.swift index 8040bddb..8b7b398c 100644 --- a/Poppool/Poppool/Presentation/Extension/UIImageView+.swift +++ b/Poppool/Poppool/Presentation/Extension/UIImageView+.swift @@ -17,15 +17,20 @@ extension UIImageView { } let imageURLString = Secrets.popPoolS3BaseURL.rawValue + path if let cenvertimageURL = imageURLString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) { - let imageURL = URL(string: cenvertimageURL) - self.kf.setImage(with: imageURL) { result in - switch result { - case .failure(let error): - Logger.log(message: "\(path) image Load Fail: \(error.localizedDescription)", category: .error) - default: - break + ImageLoader.shared.loadImage(with: cenvertimageURL, defaultImage: UIImage(named: "image_default")) { [weak self] image in + DispatchQueue.main.async { + self?.image = image } } +// let imageURL = URL(string: cenvertimageURL) +// self.kf.setImage(with: imageURL) { result in +// switch result { +// case .failure(let error): +// Logger.log(message: "\(path) image Load Fail: \(error.localizedDescription)", category: .error) +// default: +// break +// } +// } } } From 8e0e591eeb732c0a7b7b1e714db112828af2a468 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Tue, 1 Apr 2025 00:50:38 +0900 Subject: [PATCH 26/95] =?UTF-8?q?docs/#93:=20SwiftLint=20=EB=AC=B8?= =?UTF-8?q?=EB=B2=95=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Poppool/.swiftlint.yml | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Poppool/.swiftlint.yml b/Poppool/.swiftlint.yml index b1f8439d..0686d111 100644 --- a/Poppool/.swiftlint.yml +++ b/Poppool/.swiftlint.yml @@ -1,15 +1,21 @@ # 기본 활성화된 룰 중에 비활성화할 룰을 지정 disabled_rules: - + - redundant_optional_initialization # 기본(default) 룰이 아닌 룰들을 활성화 opt_in_rules: - - -# swiftlint 과정에 포함할 파일 경로 -included: + - sorted_imports + - direct_return + - file_header + - weak_delegate +# 기본 활성화된 룰 중에 조건을 변경할 룰 +file_length: 400 -# swiftlint를 적용하지 않게 설정할 파일 경로 -excluded: +function_body_length: + warning: 100 + error: 150 +cyclomatic_complexity: + warning: 15 + error: 30 From 395729f400b6f60af1083a09ebd9fbf8a10e702a Mon Sep 17 00:00:00 2001 From: JunYoung Date: Tue, 1 Apr 2025 19:27:24 +0900 Subject: [PATCH 27/95] =?UTF-8?q?feat/#92:=20DiskStorage=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Poppool/Poppool.xcodeproj/project.pbxproj | 4 + .../ImageLoader/DiskStorage.swift | 155 ++++++++++++++++++ .../ImageLoader/ImageLoader.swift | 14 +- .../ImageLoader/MemoryStorage.swift | 7 +- 4 files changed, 176 insertions(+), 4 deletions(-) create mode 100644 Poppool/Poppool/Infrastructure/ImageLoader/DiskStorage.swift diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index c447b668..d4a99af2 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -353,6 +353,7 @@ 08CBEA3A2D3FABE100248007 /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBEA392D3FABE100248007 /* ToastView.swift */; }; 08CBEA3C2D3FABED00248007 /* BookMarkToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBEA3B2D3FABED00248007 /* BookMarkToastView.swift */; }; 08CBEA3E2D3FF6A100248007 /* PopUpCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBEA3D2D3FF6A100248007 /* PopUpCardView.swift */; }; + 08CFD3922D9BDE99004CDD50 /* DiskStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CFD3912D9BDE99004CDD50 /* DiskStorage.swift */; }; 08DC61F32CF75037002A2F44 /* KeyChainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DC61F22CF75037002A2F44 /* KeyChainService.swift */; }; 08DC61F52CF765B5002A2F44 /* UserDefaultService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DC61F42CF765B5002A2F44 /* UserDefaultService.swift */; }; 08DC61F82CF76843002A2F44 /* SignUpCompleteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DC61F72CF76843002A2F44 /* SignUpCompleteView.swift */; }; @@ -845,6 +846,7 @@ 08CBEA392D3FABE100248007 /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = ""; }; 08CBEA3B2D3FABED00248007 /* BookMarkToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookMarkToastView.swift; sourceTree = ""; }; 08CBEA3D2D3FF6A100248007 /* PopUpCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopUpCardView.swift; sourceTree = ""; }; + 08CFD3912D9BDE99004CDD50 /* DiskStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiskStorage.swift; sourceTree = ""; }; 08DC61F22CF75037002A2F44 /* KeyChainService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyChainService.swift; sourceTree = ""; }; 08DC61F42CF765B5002A2F44 /* UserDefaultService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDefaultService.swift; sourceTree = ""; }; 08DC61F72CF76843002A2F44 /* SignUpCompleteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpCompleteView.swift; sourceTree = ""; }; @@ -2317,6 +2319,7 @@ children = ( 089B4FD72D9A57AE00FC0CC3 /* ImageLoader.swift */, 089B4FDE2D9A8F9A00FC0CC3 /* MemoryStorage.swift */, + 08CFD3912D9BDE99004CDD50 /* DiskStorage.swift */, ); path = ImageLoader; sourceTree = ""; @@ -3485,6 +3488,7 @@ 086DD8E32CFF356300B97D3B /* HomeCardGridSection.swift in Sources */, 0841BABE2CFB5AA600049E31 /* Date?+.swift in Sources */, 083A258D2CF361F90099B58E /* ConventionCollectionViewCell.swift in Sources */, + 08CFD3922D9BDE99004CDD50 /* DiskStorage.swift in Sources */, 4E685EE12D12CEB6001EF91C /* StoreListReactor.swift in Sources */, 4E685EE52D12CEB6001EF91C /* MapMarker.swift in Sources */, 081898FB2D33D9320067BF01 /* GetBlockUserListResponseDTO.swift in Sources */, diff --git a/Poppool/Poppool/Infrastructure/ImageLoader/DiskStorage.swift b/Poppool/Poppool/Infrastructure/ImageLoader/DiskStorage.swift new file mode 100644 index 00000000..92c0f05c --- /dev/null +++ b/Poppool/Poppool/Infrastructure/ImageLoader/DiskStorage.swift @@ -0,0 +1,155 @@ +import UIKit +import CryptoKit + +/// 디스크에 이미지를 캐싱하는 클래스 +final class DiskStorage { + + /// 싱글톤 인스턴스 + static let shared = DiskStorage() + + /// 파일 관리 객체 + private let fileManager = FileManager.default + + /// 이미지 캐시 디렉터리 경로 + private let cacheDirectory: URL + + /// 초기화 메서드 (캐시 디렉터리 생성 및 자동 삭제 스케줄 시작) + private init() { + let urls = fileManager.urls(for: .cachesDirectory, in: .userDomainMask) + cacheDirectory = urls[0].appendingPathComponent("ImageCache") + + // 디렉터리가 존재하지 않으면 생성 + if !fileManager.fileExists(atPath: cacheDirectory.path) { + try? fileManager.createDirectory(at: cacheDirectory, withIntermediateDirectories: true, attributes: nil) + } + startCacheCleanup() + } + + /// URL을 안전한 파일명으로 변환하는 메서드 + /// - Parameter url: 원본 URL 문자열 + /// - Returns: 파일명으로 변환된 문자열 + private func cacheFileName(for url: String) -> String { + let data = Data(url.utf8) + let hashed = SHA256.hash(data: data) + return hashed.compactMap { String(format: "%02x", $0) }.joined() + } + + /// 이미지를 디스크에 저장하는 메서드 + /// - Parameters: + /// - image: 저장할 UIImage 객체 + /// - url: 해당 이미지의 원본 URL 문자열 + func store(image: UIImage, url: String) { + let fileName = cacheFileName(for: url) + let fileURL = cacheDirectory.appendingPathComponent(fileName) + let metadataURL = cacheDirectory.appendingPathComponent("\(fileName).metadata") + + // 이미지 데이터를 JPEG 형식으로 변환하여 저장 + if let data = image.jpegData(compressionQuality: 0.8) { + do { + try data.write(to: fileURL) + } catch { + print("Error writing image data to disk: \(error)") + } + } + + // 만료 시간 기록 + let expirationDate = Date().addingTimeInterval(ImageLoader.shared.configure.diskCacheExpiration) + let metadata = ["expiration": expirationDate.timeIntervalSince1970] + + // 만료 정보를 JSON 형태로 저장 + if let metadataData = try? JSONSerialization.data(withJSONObject: metadata) { + do { + try metadataData.write(to: metadataURL) + } catch { + print("Error writing metadata: \(error)") + } + } + } + + /// 디스크에서 이미지를 불러오는 메서드 (만료된 경우 자동 삭제) + /// - Parameter url: 이미지의 원본 URL 문자열 + /// - Returns: UIImage 객체 (없거나 만료된 경우 nil) + func fetchImage(url: String) -> UIImage? { + let fileName = cacheFileName(for: url) + let fileURL = cacheDirectory.appendingPathComponent(fileName) + let metadataURL = cacheDirectory.appendingPathComponent("\(fileName).metadata") + + // 만료 시간 확인 + if let metadataData = try? Data(contentsOf: metadataURL), + let metadata = try? JSONSerialization.jsonObject(with: metadataData) as? [String: TimeInterval], + let expirationTime = metadata["expiration"] { + + // 만료 시간이 현재 시각을 초과하면 삭제 후 nil 반환 + if Date().timeIntervalSince1970 > expirationTime { + removeImage(url: url) + return nil + } + } + + // 이미지 파일이 존재하면 로드하여 반환 + if let data = try? Data(contentsOf: fileURL) { + return UIImage(data: data) + } + + return nil + } + + /// 특정 URL에 해당하는 이미지를 디스크에서 삭제하는 메서드 + /// - Parameter url: 삭제할 이미지의 원본 URL 문자열 + func removeImage(url: String) { + let fileName = cacheFileName(for: url) + let fileURL = cacheDirectory.appendingPathComponent(fileName) + let metadataURL = cacheDirectory.appendingPathComponent("\(fileName).metadata") + + do { + try fileManager.removeItem(at: fileURL) // 이미지 파일 삭제 + try fileManager.removeItem(at: metadataURL) // 메타데이터 파일 삭제 + } catch { + print("Failed to remove image: \(error)") + } + } + + /// 모든 캐시 데이터를 삭제하는 메서드 + func clearCache() { + do { + try fileManager.removeItem(at: cacheDirectory) + try fileManager.createDirectory(at: cacheDirectory, withIntermediateDirectories: true, attributes: nil) + } catch { + print("Failed to clear cache: \(error)") + } + } + + /// 주기적으로 만료된 캐시를 삭제하는 메서드 + /// - 5분(300초)마다 실행되며, 만료된 이미지와 메타데이터를 정리함. + private func startCacheCleanup() { + DispatchQueue.global(qos: .background).async { [weak self] in + guard let self = self else { return } + + let cleanTimer = Timer.scheduledTimer(withTimeInterval: 300, repeats: true) { _ in + let files = (try? self.fileManager.contentsOfDirectory(at: self.cacheDirectory, includingPropertiesForKeys: nil)) ?? [] + + for file in files { + if file.pathExtension == "metadata", + let metadataData = try? Data(contentsOf: file), + let metadata = try? JSONSerialization.jsonObject(with: metadataData) as? [String: TimeInterval], + let expirationTime = metadata["expiration"] { + + // 만료 시간이 지나면 이미지와 메타데이터 삭제 + if Date().timeIntervalSince1970 > expirationTime { + let imageFileURL = file.deletingPathExtension() // 메타데이터와 동일한 이름의 이미지 파일 + do { + try self.fileManager.removeItem(at: imageFileURL) + try self.fileManager.removeItem(at: file) // 메타데이터 삭제 + } catch { + print("Failed to delete expired cache: \(error)") + } + } + } + } + } + // 백그라운드에서 실행되는 타이머를 메인 루프에 추가 + RunLoop.current.add(cleanTimer, forMode: .common) + RunLoop.current.run() // 백그라운드 스레드에서 타이머를 계속 실행하기 위해 RunLoop를 유지 + } + } +} diff --git a/Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift b/Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift index 512beafd..90df18cc 100644 --- a/Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift +++ b/Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift @@ -10,10 +10,11 @@ enum ImageLoaderError: Error { /// - `memoryCacheExpiration`: 메모리 캐시 만료 시간 (기본값 300초) class ImageLoaderConfigure { var memoryCacheExpiration: TimeInterval = 300 + var diskCacheExpiration: TimeInterval = 86_400 } /// URL을 통해 이미지를 비동기적으로 로드하는 클래스 -class ImageLoader { +final class ImageLoader { static let shared = ImageLoader() @@ -50,19 +51,28 @@ private extension ImageLoader { completion(.failure(ImageLoaderError.invalidURL)) return } - + // 메모리 캐시에서 이미지 조회 if let cachedImage = MemoryStorage.shared.fetchImage(url: stringURL) { completion(.success(cachedImage)) return } + // 디스크 캐시 확인 + if let diskImage = DiskStorage.shared.fetchImage(url: stringURL) { + // 메모리 캐시에 저장 후 반환 + MemoryStorage.shared.store(image: diskImage, url: stringURL) + completion(.success(diskImage)) + return + } + // 네트워크에서 데이터 요청 fetchDataFrom(url: url) { result in switch result { case .success(let data): if let data = data, let image = UIImage(data: data) { MemoryStorage.shared.store(image: image, url: stringURL) + DiskStorage.shared.store(image: image, url: stringURL) completion(.success(image)) } else { completion(.failure(ImageLoaderError.convertError(description: "Failed to convert data to UIImage"))) diff --git a/Poppool/Poppool/Infrastructure/ImageLoader/MemoryStorage.swift b/Poppool/Poppool/Infrastructure/ImageLoader/MemoryStorage.swift index b2203678..517bc9a7 100644 --- a/Poppool/Poppool/Infrastructure/ImageLoader/MemoryStorage.swift +++ b/Poppool/Poppool/Infrastructure/ImageLoader/MemoryStorage.swift @@ -22,7 +22,7 @@ class StorageData: NSObject { } /// 메모리 캐시를 관리하는 클래스 -class MemoryStorage { +final class MemoryStorage { /// 싱글톤 인스턴스 static let shared = MemoryStorage() @@ -78,7 +78,7 @@ class MemoryStorage { DispatchQueue.global(qos: .background).async { [weak self] in guard let self = self else { return } - Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { _ in + let cleanTimer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { _ in for key in self.cachedKeys { let nsKey = key as NSString if let cachedData = self.cache.object(forKey: nsKey), cachedData.isExpired() { @@ -87,6 +87,9 @@ class MemoryStorage { } } } + // 백그라운드에서 실행되는 타이머를 메인 루프에 추가 + RunLoop.current.add(cleanTimer, forMode: .common) + RunLoop.current.run() // 백그라운드 스레드에서 타이머를 계속 실행하기 위해 RunLoop를 유지 } } } From b529e2424633912701c8311d809729cee3cedd28 Mon Sep 17 00:00:00 2001 From: JunYoung Date: Tue, 1 Apr 2025 20:16:48 +0900 Subject: [PATCH 28/95] =?UTF-8?q?feat/#92:=20=EC=9D=B4=EB=AF=B8=EC=A7=80?= =?UTF-8?q?=20=EB=A6=AC=EC=82=AC=EC=9D=B4=EC=A7=95=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ImageLoader/ImageLoader.swift | 55 ++++++++++++++++++- .../Presentation/Extension/UIImageView+.swift | 2 +- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift b/Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift index 90df18cc..9e254f47 100644 --- a/Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift +++ b/Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift @@ -6,6 +6,26 @@ enum ImageLoaderError: Error { case convertError(description: String?) } +enum ImageSizeOption { + case low + case middle + case high + case origin + + var size: CGSize { + switch self { + case .low: + return CGSize(width: 100, height: 100) + case .middle: + return CGSize(width: 200, height: 200) + case .high: + return CGSize(width: 400, height: 400) + case .origin: + return CGSize(width: 1000, height: 1000) + } + } +} + /// 이미지 로더 설정 클래스 /// - `memoryCacheExpiration`: 메모리 캐시 만료 시간 (기본값 300초) class ImageLoaderConfigure { @@ -28,11 +48,16 @@ final class ImageLoader { /// - stringURL: 이미지 URL 문자열 /// - defaultImage: 로드 실패 시 반환할 기본 이미지 /// - completion: 로드 완료 후 호출되는 클로저 - func loadImage(with stringURL: String?, defaultImage: UIImage?, completion: @escaping (UIImage?) -> Void) { - loadImage(with: stringURL) { result in + func loadImage( + with stringURL: String?, + defaultImage: UIImage?, + imageQuality: ImageSizeOption = .origin, + completion: @escaping (UIImage?) -> Void + ) { + loadImage(with: stringURL) { [weak self] result in switch result { case .success(let image): - completion(image) + completion(self?.resizeImage(image, defaultImage: defaultImage, with: imageQuality)) case .failure: completion(defaultImage) } @@ -97,4 +122,28 @@ private extension ImageLoader { } task.resume() } + + func resizeImage(_ image: UIImage?, defaultImage: UIImage?, with sizeOption: ImageSizeOption) -> UIImage? { + guard let image else { return defaultImage } + + if sizeOption == .origin { return image } + + let targetSize = sizeOption.size + + // 비율 유지 리사이징 + let aspectRatio = image.size.width / image.size.height + var newSize = targetSize + + if aspectRatio > 1 { // 가로 이미지 + newSize.height = targetSize.width / aspectRatio + } else { // 세로 이미지 + newSize.width = targetSize.height * aspectRatio + } + + let renderer = UIGraphicsImageRenderer(size: newSize) + + return renderer.image { _ in + image.draw(in: CGRect(origin: .zero, size: newSize)) + } + } } diff --git a/Poppool/Poppool/Presentation/Extension/UIImageView+.swift b/Poppool/Poppool/Presentation/Extension/UIImageView+.swift index 8b7b398c..f2670759 100644 --- a/Poppool/Poppool/Presentation/Extension/UIImageView+.swift +++ b/Poppool/Poppool/Presentation/Extension/UIImageView+.swift @@ -17,7 +17,7 @@ extension UIImageView { } let imageURLString = Secrets.popPoolS3BaseURL.rawValue + path if let cenvertimageURL = imageURLString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) { - ImageLoader.shared.loadImage(with: cenvertimageURL, defaultImage: UIImage(named: "image_default")) { [weak self] image in + ImageLoader.shared.loadImage(with: cenvertimageURL, defaultImage: UIImage(named: "image_default"), imageQuality: .origin) { [weak self] image in DispatchQueue.main.async { self?.image = image } From ed0e2355aa5b3729e2e8e04a1d3b6afc56fd2804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=80=E1=85=B5=E1=84=92?= =?UTF-8?q?=E1=85=A7=E1=86=AB?= Date: Sun, 30 Mar 2025 18:23:05 +0900 Subject: [PATCH 29/95] =?UTF-8?q?[FIX]=20=ED=95=84=ED=84=B0=EB=B0=94?= =?UTF-8?q?=ED=85=80=EC=8B=9C=ED=8A=B8=EB=B7=B0=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=94=A4=EB=93=9C=EC=98=81=EC=97=AD=20=ED=83=AD=EC=8B=9C=20?= =?UTF-8?q?=EC=A0=9C=EB=8C=80=EB=A1=9C=20=EB=8B=AB=ED=9E=88=EC=A7=80?= =?UTF-8?q?=EC=95=8A=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Map/Common/RegionDefinitions.swift | 2 - .../FullScreenMapViewController.swift | 56 ++++++------- .../MapGuideView/MapGuideViewController.swift | 82 ++++++++++++++----- .../Map/MapView/MapViewController.swift | 17 +--- 4 files changed, 86 insertions(+), 71 deletions(-) diff --git a/Poppool/Poppool/Presentation/Map/Common/RegionDefinitions.swift b/Poppool/Poppool/Presentation/Map/Common/RegionDefinitions.swift index 8de0e178..92e35ab1 100644 --- a/Poppool/Poppool/Presentation/Map/Common/RegionDefinitions.swift +++ b/Poppool/Poppool/Presentation/Map/Common/RegionDefinitions.swift @@ -27,7 +27,6 @@ enum RegionType { case province struct RegionDefinitions { - // 서울 클러스터 static let seoulClusters: [RegionCluster] = [ RegionCluster( name: "도봉/노원/강북/중랑", @@ -201,7 +200,6 @@ enum RegionType { ) ] - // 도 클러스터 static let provinceClusters: [RegionCluster] = [ RegionCluster( name: "충북", diff --git a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FullScreenMapViewController.swift b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FullScreenMapViewController.swift index 083dfdc3..8eeaa084 100644 --- a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FullScreenMapViewController.swift +++ b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FullScreenMapViewController.swift @@ -105,24 +105,26 @@ class FullScreenMapViewController: MapViewController { let position = NMGLatLng(lat: store.latitude, lng: store.longitude) - // 카메라 이동 let cameraUpdate = NMFCameraUpdate(scrollTo: position, zoomTo: 15.0) cameraUpdate.animation = .easeIn cameraUpdate.animationDuration = 0.3 mainView.mapView.moveCamera(cameraUpdate) - // 기존 마커가 있으면 재활용 if let existingMarker = initialMarker { // 기존 마커가 맵뷰에 설정되어 있지 않으면 설정 if existingMarker.mapView == nil { existingMarker.mapView = mainView.mapView } - // 마커 스타일 업데이트 - updateMarkerStyle(marker: existingMarker, selected: true, isCluster: false, count: 1) + // 명시적으로 TapMarker 스타일 적용 (selected 매개변수는 무시됨) + existingMarker.iconImage = NMFOverlayImage(name: "TapMarker") + existingMarker.width = 44 + existingMarker.height = 44 + existingMarker.anchor = CGPoint(x: 0.5, y: 1.0) + currentMarker = existingMarker } else { - // 기존 마커가 없는 경우에만 새로 생성 + // 새 마커 생성 시에도 TapMarker 적용 let marker = NMFMarker() marker.position = position marker.iconImage = NMFOverlayImage(name: "TapMarker") @@ -132,11 +134,11 @@ class FullScreenMapViewController: MapViewController { marker.userInfo = ["storeData": store] marker.mapView = mainView.mapView currentMarker = marker - - // 마커 스타일 업데이트 - updateMarkerStyle(marker: marker, selected: true, isCluster: false, count: 1) } + // 마커 잠금 설정 + markerLocked = true + // 캐러셀 설정 currentCarouselStores = [store] carouselView.updateCards([store]) @@ -144,6 +146,7 @@ class FullScreenMapViewController: MapViewController { } + override func bind(reactor: MapReactor) { super.bind(reactor: reactor) @@ -171,54 +174,49 @@ class FullScreenMapViewController: MapViewController { } override func updateMarkerStyle(marker: NMFMarker, selected: Bool, isCluster: Bool, count: Int = 1, regionName: String = "") { - if selected { - // 선택된 경우 항상 TapMarker + // 풀스크린 모드에서는 항상 TapMarker 스타일 적용 + if isFullScreenMode && markerLocked { marker.width = 44 marker.height = 44 marker.iconImage = NMFOverlayImage(name: "TapMarker") - } else { - // 선택되지 않은 경우 일반 마커 - marker.width = 32 - marker.height = 32 - marker.iconImage = NMFOverlayImage(name: "Marker") - } - marker.anchor = CGPoint(x: 0.5, y: 1.0) + marker.anchor = CGPoint(x: 0.5, y: 1.0) - if count > 1 { - marker.captionText = "\(count)" - } else { - marker.captionText = "" + if count > 1 { + marker.captionText = "\(count)" + } else { + marker.captionText = "" + } + return } + + super.updateMarkerStyle(marker: marker, selected: selected, isCluster: isCluster, count: count, regionName: regionName) } + + override func handleSingleStoreTap(_ marker: NMFMarker, store: MapPopUpStore) -> Bool { isMovingToMarker = true - markerLocked = true // 마커 상태 잠금 + markerLocked = true - // 이전 마커 선택 상태 해제 if let previousMarker = currentMarker, previousMarker != marker { fullScreenUpdateMarkerStyle(marker: previousMarker, selected: false) } - // 현재 마커를 TapMarker로 설정 marker.iconImage = NMFOverlayImage(name: "TapMarker") marker.width = 44 marker.height = 44 fullScreenUpdateMarkerStyle(marker: marker, selected: true) currentMarker = marker - // 캐러셀 업데이트 및 표시 currentCarouselStores = [store] carouselView.updateCards([store]) carouselView.isHidden = false mainView.setStoreCardHidden(false, animated: true) - // 카메라 이동 let cameraUpdate = NMFCameraUpdate(scrollTo: marker.position, zoomTo: 15.0) cameraUpdate.animation = .easeIn cameraUpdate.animationDuration = 0.3 mainView.mapView.moveCamera(cameraUpdate) - // 약간의 지연 후 플래그 리셋 DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in self?.isMovingToMarker = false } @@ -228,15 +226,12 @@ class FullScreenMapViewController: MapViewController { // 맵뷰 탭 처리 오버라이드 override func mapView(_ mapView: NMFMapView, didTapMap latlng: NMGLatLng, point: CGPoint) { - // 풀스크린 모드에서는 맵 탭 시 아무 동작도 하지 않음 (캐러셀 유지) return } // 카메라 이동 시작 시 호출 override func mapView(_ mapView: NMFMapView, cameraWillChangeByReason reason: Int, animated: Bool) { - // 풀스크린 모드에서는 캐러셀 유지 if isFullScreenMode && markerLocked { - // 상위 클래스의 기본 동작 방지 return } super.mapView(mapView, cameraWillChangeByReason: reason, animated: animated) @@ -244,7 +239,6 @@ class FullScreenMapViewController: MapViewController { // 카메라 이동 중 호출 override func mapView(_ mapView: NMFMapView, cameraIsChangingByReason reason: Int) { - // 마커가 잠겨있을 때는 캐러셀 유지 if isFullScreenMode && markerLocked { // 기존 동작을 방지하고 풀스크린 동작 수행 return diff --git a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift index 78a5d81a..3776cebe 100644 --- a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift +++ b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift @@ -10,11 +10,12 @@ final class MapGuideViewController: UIViewController, View { var disposeBag = DisposeBag() private let popUpStoreId: Int64 private var currentCarouselStores: [MapPopUpStore] = [] // 현재 선택된 스토어 목록 - + init(popUpStoreId: Int64) { self.popUpStoreId = popUpStoreId super.init(nibName: nil, bundle: nil) + modalPresentationStyle = .overFullScreen // 모달 스타일 설정 } required init?(coder: NSCoder) { @@ -23,7 +24,7 @@ final class MapGuideViewController: UIViewController, View { private let dimmingView: UIView = { let v = UIView() - v.backgroundColor = UIColor.gray.withAlphaComponent(0.3) + v.backgroundColor = UIColor.gray.withAlphaComponent(0.7) // 더 진한 딤드 효과 v.alpha = 0 return v }() @@ -101,9 +102,9 @@ final class MapGuideViewController: UIViewController, View { return btn }() - private let tmapButton: UIButton = { + private let appleButton: UIButton = { let btn = UIButton() - btn.setImage(UIImage(named: "TMap"), for: .normal) + btn.setImage(UIImage(named: "AppleMap"), for: .normal) btn.layer.cornerRadius = 24 btn.layer.borderWidth = 1 btn.layer.borderColor = UIColor.g100.cgColor @@ -117,16 +118,33 @@ final class MapGuideViewController: UIViewController, View { override func viewDidLoad() { super.viewDidLoad() setupUI() + setupTapGesture() // 탭 제스처 설정 추가 presentModalCard() } + // 딤드 영역 탭 제스처 설정 + private func setupTapGesture() { + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapOnDimmingView)) + dimmingView.addGestureRecognizer(tapGesture) + dimmingView.isUserInteractionEnabled = true // 중요: 상호작용 활성화 + } + + // 딤드 영역 탭 처리 + @objc private func handleTapOnDimmingView(_ sender: UITapGestureRecognizer) { + // 탭 위치가 modalCardView 영역이 아닌 경우에만 닫기 + let location = sender.location(in: view) + if !modalCardView.frame.contains(location) { + dismissModalCard() + } + } + func bind(reactor: MapGuideReactor) { reactor.action.onNext(.viewDidLoad(self.popUpStoreId)) // 닫기 버튼 closeButton.rx.tap .subscribe(onNext: { [weak self] in - self?.dismiss(animated: true, completion: nil) + self?.dismissModalCard() }) .disposed(by: disposeBag) @@ -139,8 +157,8 @@ final class MapGuideViewController: UIViewController, View { .map { Reactor.Action.openMapApp("kakao") } .bind(to: reactor.action) .disposed(by: disposeBag) - tmapButton.rx.tap - .map { Reactor.Action.openMapApp("tmap") } + appleButton.rx.tap + .map { Reactor.Action.openMapApp("apple") } .bind(to: reactor.action) .disposed(by: disposeBag) @@ -157,11 +175,21 @@ final class MapGuideViewController: UIViewController, View { if let selectedStore = self.currentCarouselStores.first { reactor.action.onNext(.didSelectItem(selectedStore)) - // 기존 코드 그대로 사용 - let fullScreenMapVC = FullScreenMapViewController(store: selectedStore) - fullScreenMapVC.reactor = reactor - - let nav = UINavigationController(rootViewController: fullScreenMapVC) + // 현재 맵에 표시된 마커 생성 또는 가져오기 + let marker = NMFMarker() + marker.position = NMGLatLng(lat: selectedStore.latitude, lng: selectedStore.longitude) + // 중요: 명시적으로 TapMarker 설정 + marker.iconImage = NMFOverlayImage(name: "TapMarker") + marker.width = 44 + marker.height = 44 + marker.anchor = CGPoint(x: 0.5, y: 1.0) + marker.userInfo = ["storeData": selectedStore] + + // 풀스크린 맵 뷰 컨트롤러에 선택된 마커 정보 전달 + let fullScreenMapViewController = FullScreenMapViewController(store: selectedStore, existingMarker: marker) + fullScreenMapViewController.reactor = reactor + + let nav = UINavigationController(rootViewController: fullScreenMapViewController) nav.modalPresentationStyle = .fullScreen self.present(nav, animated: true) } else { @@ -174,10 +202,19 @@ final class MapGuideViewController: UIViewController, View { .take(1) .observe(on: MainScheduler.instance) .subscribe(onNext: { store in - let fullScreenMapVC = FullScreenMapViewController(store: store) - fullScreenMapVC.reactor = reactor - - let nav = UINavigationController(rootViewController: fullScreenMapVC) + // 여기서도 동일하게 마커 생성 및 설정 + let marker = NMFMarker() + marker.position = NMGLatLng(lat: store.latitude, lng: store.longitude) + marker.iconImage = NMFOverlayImage(name: "TapMarker") + marker.width = 44 + marker.height = 44 + marker.anchor = CGPoint(x: 0.5, y: 1.0) + marker.userInfo = ["storeData": store] + + let fullScreenMapViewController = FullScreenMapViewController(store: store, existingMarker: marker) + fullScreenMapViewController.reactor = reactor + + let nav = UINavigationController(rootViewController: fullScreenMapViewController) nav.modalPresentationStyle = .fullScreen self.present(nav, animated: true) }) @@ -220,7 +257,7 @@ final class MapGuideViewController: UIViewController, View { // MARK: - UI Setup private func setupUI() { - view.backgroundColor = .white + view.backgroundColor = .clear // 배경색을 clear로 변경하여 항상 딤드 뷰가 보이도록 함 view.addSubview(dimmingView) dimmingView.snp.makeConstraints { $0.edges.equalToSuperview() } @@ -256,7 +293,7 @@ final class MapGuideViewController: UIViewController, View { mapView.snp.makeConstraints { make in make.top.equalTo(topContainer.snp.bottom).offset(20) make.leading.trailing.equalToSuperview().inset(20) - make.height.equalTo(320) + make.height.equalTo(240) // 약간 줄임 } modalCardView.addSubview(expandButton) @@ -267,7 +304,7 @@ final class MapGuideViewController: UIViewController, View { } let bottomContainer = UIView() - modalCardView.addSubview(bottomContainer) + modalCardView.addSubview(bottomContainer) bottomContainer.snp.makeConstraints { make in make.top.equalTo(mapView.snp.bottom).offset(20) make.leading.trailing.equalToSuperview().inset(20) @@ -281,7 +318,7 @@ final class MapGuideViewController: UIViewController, View { make.centerY.equalToSuperview() } - let appStack = UIStackView(arrangedSubviews: [naverButton, kakaoButton, tmapButton]) + let appStack = UIStackView(arrangedSubviews: [naverButton, kakaoButton, appleButton]) appStack.axis = .horizontal appStack.alignment = .center appStack.spacing = 16 @@ -291,7 +328,7 @@ final class MapGuideViewController: UIViewController, View { appStack.snp.makeConstraints { make in make.trailing.equalToSuperview() make.centerY.equalToSuperview() - [naverButton, kakaoButton, tmapButton].forEach { button in + [naverButton, kakaoButton, appleButton].forEach { button in button.snp.makeConstraints { make in make.size.equalTo(CGSize(width: 48, height: 48)) } @@ -300,7 +337,9 @@ final class MapGuideViewController: UIViewController, View { } private func presentModalCard() { + // 백그라운드가 보이도록 처음부터 alpha를 1로 설정 self.dimmingView.alpha = 1 + UIView.animate( withDuration: 0.3, delay: 0, @@ -335,7 +374,6 @@ final class MapGuideViewController: UIViewController, View { cameraUpdate.animation = .easeIn cameraUpdate.animationDuration = 0.3 mapView.moveCamera(cameraUpdate) - } private func dismissModalCard() { diff --git a/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift b/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift index c08ca70d..ac292485 100644 --- a/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift +++ b/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift @@ -175,7 +175,6 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM // 지도 이동 완료 감지 mainView.mapView.addCameraDelegate(delegate: self) - // idleAtPosition 대체 - 지도 이동 완료 시 idleSubject .observe(on: MainScheduler.instance) .subscribe(onNext: { [weak self] in @@ -183,7 +182,6 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM if let marker = self.currentMarker, let storeArray = marker.userInfo["storeData"] as? [MapPopUpStore], storeArray.count > 1 { - // 툴팁이 없으면 생성, 있으면 위치 업데이트 if self.currentTooltipView == nil { self.configureTooltip(for: marker, stores: storeArray) } else { @@ -208,17 +206,14 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM let tooltipView = MarkerTooltipView() tooltipView.configure(with: stores) - // 선택된 상태로 표시 - 첫 번째 정보를 기본 선택 상태로 만듦 tooltipView.selectStore(at: 0) - // onStoreSelected 클로저 설정 tooltipView.onStoreSelected = { [weak self] index in guard let self = self, index < stores.count else { return } self.currentCarouselStores = stores self.carouselView.updateCards(stores) self.carouselView.scrollToCard(index: index) - // 선택된 상태로 업데이트 self.updateMarkerStyle(marker: marker, selected: true, isCluster: false, count: stores.count) tooltipView.selectStore(at: index) @@ -229,9 +224,8 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM """, category: .debug) } - // 툴팁 위치 설정 (마커 위치 기준으로 계산) let markerPoint = self.mainView.mapView.projection.point(from: marker.position) - let markerHeight: CGFloat = 32 // 마커 이미지 높이 추정값 + let markerHeight: CGFloat = 32 tooltipView.frame = CGRect( x: markerPoint.x, @@ -1082,34 +1076,25 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM present(alert, animated: true, completion: nil) } - // 캐러셀 영역을 제외한 실제 가시 영역 계산 함수 private func getEffectiveViewport() -> NMGLatLngBounds { - // 기본 가시 영역 가져오기 let bounds = getVisibleBounds() - // 캐러셀이 보이지 않으면 전체 영역 반환 if carouselView.isHidden { return NMGLatLngBounds(southWest: bounds.southWest, northEast: bounds.northEast) } - // 캐러셀 상단 Y 좌표 let carouselTopY = carouselView.frame.minY - - // 화면 좌표계에서 캐러셀 상단 라인을 생성 (좌우 전체 폭) let leftPoint = CGPoint(x: 0, y: carouselTopY) let rightPoint = CGPoint(x: view.frame.width, y: carouselTopY) - // 화면 좌표를 지도 좌표로 변환 let leftCoordinate = mainView.mapView.projection.latlng(from: leftPoint) let rightCoordinate = mainView.mapView.projection.latlng(from: rightPoint) - // 캐러셀 영역을 제외한 경계 계산 let adjustedSouthWest = NMGLatLng( lat: max(leftCoordinate.lat, rightCoordinate.lat), lng: bounds.southWest.lng ) - // 조정된 경계로 새 영역 생성 return NMGLatLngBounds( southWest: adjustedSouthWest, northEast: bounds.northEast From 6498cf0fb66cffadf37067993adf759fa3e5b3b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=80=E1=85=B5=E1=84=92?= =?UTF-8?q?=E1=85=A7=E1=86=AB?= Date: Wed, 2 Apr 2025 02:40:41 +0900 Subject: [PATCH 30/95] =?UTF-8?q?fix=20#95:=20=EC=9E=94=EC=A1=B4=ED=95=B4?= =?UTF-8?q?=EC=9E=88=EB=8D=98=20CoreLocation=20=EC=B0=B8=EC=A1=B0=EB=AC=B8?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Admin/Data/MapDomain/MapPopUpStore.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapPopUpStore.swift b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapPopUpStore.swift index ad5875c8..02384864 100644 --- a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapPopUpStore.swift +++ b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapPopUpStore.swift @@ -1,5 +1,4 @@ import Foundation -import CoreLocation import NMapsMap struct MapPopUpStore: Equatable { @@ -16,9 +15,6 @@ struct MapPopUpStore: Equatable { let markerSnippet: String let mainImageUrl: String? - var coordinate: CLLocationCoordinate2D { - CLLocationCoordinate2D(latitude: latitude, longitude: longitude) - } var nmgCoordinate: NMGLatLng { NMGLatLng(lat: latitude, lng: longitude) From c7929cc4b310ae41e67a1cec842114379e6ca9d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=80=E1=85=B5=E1=84=92?= =?UTF-8?q?=E1=85=A7=E1=86=AB?= Date: Wed, 2 Apr 2025 15:53:32 +0900 Subject: [PATCH 31/95] =?UTF-8?q?fix/#95:=20=EB=A7=B5=EA=B0=80=EC=9D=B4?= =?UTF-8?q?=EB=93=9C=EB=B7=B0=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20?= =?UTF-8?q?=EB=B3=B5=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FilterBottomSheetView.swift | 72 ++- .../FilterBottomSheetViewController.swift | 2 +- .../MapGuideView/MapGuideViewController.swift | 512 ++++++++++-------- .../Presentation/Map/MapView/MapMarker.swift | 2 - 4 files changed, 317 insertions(+), 271 deletions(-) diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift index c233ea4b..d465e412 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift @@ -200,7 +200,7 @@ final class FilterBottomSheetView: UIView { func setupLocationScrollView(locations: [Location], buttonAction: @escaping (Int, UIButton) -> Void) { locationContentView.subviews.forEach { $0.removeFromSuperview() } - locationScrollView.delegate = self + locationScrollView.delegate = self as? UIScrollViewDelegate var lastButton: UIButton? @@ -208,10 +208,14 @@ final class FilterBottomSheetView: UIView { let button = createStyledButton(title: location.main) button.tag = index - button.addAction(UIAction { _ in + button.addTarget(self, action: #selector(locationButtonTapped(_:)), for: .touchUpInside) + + // actionHandler 클로저 저장 + button.layer.setValue(index, forKey: "buttonIndex") + objc_setAssociatedObject(button, &AssociatedKeys.actionHandler, { [weak self] in buttonAction(index, button) - self.updateMainLocationSelection(index) - }, for: .touchUpInside) + self?.updateMainLocationSelection(index) + }, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) locationContentView.addSubview(button) @@ -234,6 +238,16 @@ final class FilterBottomSheetView: UIView { } } + private struct AssociatedKeys { + static var actionHandler = "actionHandler" + } + + @objc private func locationButtonTapped(_ sender: UIButton) { + if let actionHandler = objc_getAssociatedObject(sender, &AssociatedKeys.actionHandler) as? () -> Void { + actionHandler() + } + } + func updateCategoryButtonSelection(_ category: String) { categoryCollectionView.subviews.forEach { subview in if let stackView = subview as? UIStackView { @@ -256,6 +270,19 @@ final class FilterBottomSheetView: UIView { } } + func updateContentVisibility(isCategorySelected: Bool) { + self.locationScrollView.isHidden = isCategorySelected + self.balloonBackgroundView.isHidden = isCategorySelected + self.categoryCollectionView.isHidden = !isCategorySelected + + self.locationScrollView.alpha = isCategorySelected ? 0 : 1 + self.balloonBackgroundView.alpha = isCategorySelected ? 0 : 1 + self.categoryCollectionView.alpha = isCategorySelected ? 1 : 0 + + let newHeight = isCategorySelected ? 170 : self.balloonBackgroundView.calculateHeight() + self.balloonHeightConstraint?.update(offset: newHeight) + } + private func createCategoryButton(title: String, isSelected: Bool) -> UIButton { let button = UIButton(type: .system) button.setTitle(title, for: .normal) @@ -278,21 +305,6 @@ final class FilterBottomSheetView: UIView { return button } - func updateContentVisibility(isCategorySelected: Bool) { - locationScrollView.isHidden = isCategorySelected - balloonBackgroundView.isHidden = isCategorySelected - categoryCollectionView.isHidden = !isCategorySelected - - locationScrollView.alpha = isCategorySelected ? 0 : 1 - balloonBackgroundView.alpha = isCategorySelected ? 0 : 1 - categoryCollectionView.alpha = isCategorySelected ? 1 : 0 - - let newHeight = isCategorySelected ? 170 : self.balloonBackgroundView.calculateHeight() - balloonHeightConstraint?.update(offset: newHeight) - - self.layoutIfNeeded() - } - private func createStyledButton(title: String, isSelected: Bool = false) -> PPButton { let button = PPButton( style: .secondary, @@ -340,7 +352,7 @@ final class FilterBottomSheetView: UIView { balloonHeightConstraint?.update(offset: isHidden ? 0 : dynamicHeight) self.layoutIfNeeded() } - + func updateBalloonPosition(for button: UIButton) { DispatchQueue.main.async { guard let window = button.window else { return } @@ -359,14 +371,25 @@ final class FilterBottomSheetView: UIView { self.balloonBackgroundView.setNeedsDisplay() } } + private func updateBalloonPositionAccurately(for button: PPButton) { let buttonFrameInBalloon = button.convert(button.bounds, to: balloonBackgroundView) let arrowPosition = buttonFrameInBalloon.midX / balloonBackgroundView.bounds.width balloonBackgroundView.arrowPosition = arrowPosition balloonBackgroundView.setNeedsDisplay() } + + private func updateSelectedButtonPosition() { + guard let selectedButton = locationContentView.subviews.first(where: { view in + guard let button = view as? PPButton else { return false } + return button.backgroundColor == .blu500 + }) as? PPButton else { return } + + updateBalloonPosition(for: selectedButton) + } } +// MARK: - Extensions extension FilterBottomSheetView { func update(locationText: String?, categoryText: String?) { var filters: [String] = [] @@ -396,13 +419,4 @@ extension FilterBottomSheetView: UIScrollViewDelegate { updateSelectedButtonPosition() } } - - private func updateSelectedButtonPosition() { - guard let selectedButton = locationContentView.subviews.first(where: { view in - guard let button = view as? PPButton else { return false } - return button.backgroundColor == .blu500 - }) as? PPButton else { return } - - updateBalloonPosition(for: selectedButton) - } } diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetViewController.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetViewController.swift index 828141f5..d0c196d4 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetViewController.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetViewController.swift @@ -44,7 +44,7 @@ final class FilterBottomSheetViewController: UIViewController, View { setupLayout() setupGestures() setupCollectionView() - containerView.isUserInteractionEnabled = true + containerView.filterChipsView.onRemoveChip = { [weak self] removedOption in guard let self = self, let reactor = self.reactor else { return } diff --git a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift index 3776cebe..c7b71cb4 100644 --- a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift +++ b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift @@ -6,310 +6,222 @@ import CoreLocation import NMapsMap final class MapGuideViewController: UIViewController, View { + + // MARK: - Constants + private enum Constant { + // Modal Card + static let modalCardHeight: CGFloat = 408 + static let modalCardInitialBottomOffset: CGFloat = 408 + static let modalCardAnimationDuration: TimeInterval = 0.3 + static let modalCardAnimationDamping: CGFloat = 0.8 + static let modalCardAnimationInitialVelocity: CGFloat = 0.5 + + // Top Container + static let topContainerTopOffset: CGFloat = 20 + static let topContainerHeight: CGFloat = 44 + + // Title Label + static let titleLabelLeadingOffset: CGFloat = 20 + + // Close Button + static let closeButtonTrailingInset: CGFloat = 16 + static let closeButtonSize: CGFloat = 24 + + // MapView + static let mapViewTopOffset: CGFloat = 20 + static let mapViewHorizontalInset: CGFloat = 20 + static let mapViewHeight: CGFloat = 240 + static let mapZoomLevel: Double = 15.0 + + // Expand Button + static let expandButtonBottomOffset: CGFloat = -10 + static let expandButtonTrailingOffset: CGFloat = -10 + static let expandButtonSize: CGFloat = 32 + + // Bottom Container + static let bottomContainerTopOffset: CGFloat = 20 + static let bottomContainerHorizontalInset: CGFloat = 20 + static let bottomContainerHeight: CGFloat = 44 + static let bottomContainerBottomInset: CGFloat = 60 + + // Throttle + static let expandButtonThrottleMilliseconds: Int = 300 + } + // MARK: - Properties var disposeBag = DisposeBag() private let popUpStoreId: Int64 private var currentCarouselStores: [MapPopUpStore] = [] // 현재 선택된 스토어 목록 - - init(popUpStoreId: Int64) { - self.popUpStoreId = popUpStoreId - super.init(nibName: nil, bundle: nil) - modalPresentationStyle = .overFullScreen // 모달 스타일 설정 - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - + // MARK: - UI Components private let dimmingView: UIView = { - let v = UIView() - v.backgroundColor = UIColor.gray.withAlphaComponent(0.7) // 더 진한 딤드 효과 - v.alpha = 0 - return v + let viewInstance = UIView() + viewInstance.backgroundColor = UIColor.gray.withAlphaComponent(0.7) + viewInstance.alpha = 0 + return viewInstance }() private let modalCardView: UIView = { - let v = UIView() - v.backgroundColor = .white - v.layer.cornerRadius = 16 - v.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] - v.layer.shadowColor = UIColor.black.cgColor - v.layer.shadowOpacity = 0.1 - v.layer.shadowOffset = .zero - v.layer.shadowRadius = 8 - return v + let viewInstance = UIView() + viewInstance.backgroundColor = .white + viewInstance.layer.cornerRadius = 16 + viewInstance.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] + viewInstance.layer.shadowColor = UIColor.black.cgColor + viewInstance.layer.shadowOpacity = 0.1 + viewInstance.layer.shadowOffset = .zero + viewInstance.layer.shadowRadius = 8 + return viewInstance }() private let titleLabel: UILabel = { - let lb = UILabel() - lb.text = "찾아가는 길" - lb.font = UIFont.boldSystemFont(ofSize: 17) - lb.textColor = .black - return lb + let label = UILabel() + label.text = "찾아가는 길" + label.font = UIFont.boldSystemFont(ofSize: 17) + label.textColor = .black + return label }() private let closeButton: UIButton = { - let btn = UIButton(type: .system) + let button = UIButton(type: .system) let image = UIImage(named: "icon_xmark")?.withRenderingMode(.alwaysOriginal) - btn.setImage(image, for: .normal) - return btn + button.setImage(image, for: .normal) + return button }() private let mapView: NMFMapView = { - let map = NMFMapView() - map.layer.borderWidth = 1 - map.layer.borderColor = UIColor.g100.cgColor - map.layer.cornerRadius = 12 - return map + let mapViewInstance = NMFMapView() + mapViewInstance.layer.borderWidth = 1 + mapViewInstance.layer.borderColor = UIColor.g100.cgColor + mapViewInstance.layer.cornerRadius = 12 + return mapViewInstance }() private let expandButton: UIButton = { - let btn = UIButton() - btn.setImage(UIImage(named: "Expandable"), for: .normal) - btn.backgroundColor = UIColor.white - btn.layer.cornerRadius = 16 - btn.clipsToBounds = true - return btn + let button = UIButton() + button.setImage(UIImage(named: "Expandable"), for: .normal) + button.backgroundColor = UIColor.white + button.layer.cornerRadius = 16 + button.clipsToBounds = true + return button }() private let promptLabel: UILabel = { - let lb = UILabel() - lb.text = "지도 앱으로\n바로 찾아볼까요?" - lb.font = UIFont.systemFont(ofSize: 15, weight: .medium) - lb.textColor = .darkGray - lb.numberOfLines = 2 - return lb + let label = UILabel() + label.text = "지도 앱으로\n바로 찾아볼까요?" + label.font = UIFont.systemFont(ofSize: 15, weight: .medium) + label.textColor = .darkGray + label.numberOfLines = 2 + return label }() private let naverButton: UIButton = { - let btn = UIButton() - btn.setImage(UIImage(named: "naver"), for: .normal) - btn.layer.cornerRadius = 24 - btn.layer.borderWidth = 1 - btn.layer.borderColor = UIColor.g100.cgColor - btn.clipsToBounds = true - return btn + let button = UIButton() + button.setImage(UIImage(named: "naver"), for: .normal) + button.layer.cornerRadius = 24 + button.layer.borderWidth = 1 + button.layer.borderColor = UIColor.g100.cgColor + button.clipsToBounds = true + return button }() private let kakaoButton: UIButton = { - let btn = UIButton() - btn.setImage(UIImage(named: "kakao"), for: .normal) - btn.layer.cornerRadius = 24 - btn.layer.borderWidth = 1 - btn.layer.borderColor = UIColor.g100.cgColor - btn.clipsToBounds = true - return btn + let button = UIButton() + button.setImage(UIImage(named: "kakao"), for: .normal) + button.layer.cornerRadius = 24 + button.layer.borderWidth = 1 + button.layer.borderColor = UIColor.g100.cgColor + button.clipsToBounds = true + return button }() private let appleButton: UIButton = { - let btn = UIButton() - btn.setImage(UIImage(named: "AppleMap"), for: .normal) - btn.layer.cornerRadius = 24 - btn.layer.borderWidth = 1 - btn.layer.borderColor = UIColor.g100.cgColor - btn.clipsToBounds = true - return btn + let button = UIButton() + button.setImage(UIImage(named: "AppleMap"), for: .normal) + button.layer.cornerRadius = 24 + button.layer.borderWidth = 1 + button.layer.borderColor = UIColor.g100.cgColor + button.clipsToBounds = true + return button }() private var modalCardBottomConstraint: Constraint? + // MARK: - Initializer + init(popUpStoreId: Int64) { + self.popUpStoreId = popUpStoreId + super.init(nibName: nil, bundle: nil) + modalPresentationStyle = .overFullScreen + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() setupUI() - setupTapGesture() // 탭 제스처 설정 추가 + setupTapGesture() presentModalCard() } - // 딤드 영역 탭 제스처 설정 - private func setupTapGesture() { - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapOnDimmingView)) - dimmingView.addGestureRecognizer(tapGesture) - dimmingView.isUserInteractionEnabled = true // 중요: 상호작용 활성화 - } - - // 딤드 영역 탭 처리 - @objc private func handleTapOnDimmingView(_ sender: UITapGestureRecognizer) { - // 탭 위치가 modalCardView 영역이 아닌 경우에만 닫기 - let location = sender.location(in: view) - if !modalCardView.frame.contains(location) { - dismissModalCard() - } - } - - func bind(reactor: MapGuideReactor) { - reactor.action.onNext(.viewDidLoad(self.popUpStoreId)) - - // 닫기 버튼 - closeButton.rx.tap - .subscribe(onNext: { [weak self] in - self?.dismissModalCard() - }) - .disposed(by: disposeBag) - - // 지도 앱 열기 - naverButton.rx.tap - .map { Reactor.Action.openMapApp("naver") } - .bind(to: reactor.action) - .disposed(by: disposeBag) - kakaoButton.rx.tap - .map { Reactor.Action.openMapApp("kakao") } - .bind(to: reactor.action) - .disposed(by: disposeBag) - appleButton.rx.tap - .map { Reactor.Action.openMapApp("apple") } - .bind(to: reactor.action) - .disposed(by: disposeBag) - - expandButton.rx.tap - .throttle(.milliseconds(300), scheduler: MainScheduler.instance) - .subscribe(onNext: { [weak self] in - guard let self = self else { return } - - let provider = ProviderImpl() - let useCase = DefaultMapUseCase(repository: DefaultMapRepository(provider: provider)) - let directionRepository = DefaultMapDirectionRepository(provider: provider) - let reactor = MapReactor(useCase: useCase, directionRepository: directionRepository) - - if let selectedStore = self.currentCarouselStores.first { - reactor.action.onNext(.didSelectItem(selectedStore)) - - // 현재 맵에 표시된 마커 생성 또는 가져오기 - let marker = NMFMarker() - marker.position = NMGLatLng(lat: selectedStore.latitude, lng: selectedStore.longitude) - // 중요: 명시적으로 TapMarker 설정 - marker.iconImage = NMFOverlayImage(name: "TapMarker") - marker.width = 44 - marker.height = 44 - marker.anchor = CGPoint(x: 0.5, y: 1.0) - marker.userInfo = ["storeData": selectedStore] - - // 풀스크린 맵 뷰 컨트롤러에 선택된 마커 정보 전달 - let fullScreenMapViewController = FullScreenMapViewController(store: selectedStore, existingMarker: marker) - fullScreenMapViewController.reactor = reactor - - let nav = UINavigationController(rootViewController: fullScreenMapViewController) - nav.modalPresentationStyle = .fullScreen - self.present(nav, animated: true) - } else { - reactor.action.onNext(.viewDidLoad(self.popUpStoreId)) - - reactor.state - .map { $0.searchResult } - .distinctUntilChanged() - .compactMap { $0 } - .take(1) - .observe(on: MainScheduler.instance) - .subscribe(onNext: { store in - // 여기서도 동일하게 마커 생성 및 설정 - let marker = NMFMarker() - marker.position = NMGLatLng(lat: store.latitude, lng: store.longitude) - marker.iconImage = NMFOverlayImage(name: "TapMarker") - marker.width = 44 - marker.height = 44 - marker.anchor = CGPoint(x: 0.5, y: 1.0) - marker.userInfo = ["storeData": store] - - let fullScreenMapViewController = FullScreenMapViewController(store: store, existingMarker: marker) - fullScreenMapViewController.reactor = reactor - - let nav = UINavigationController(rootViewController: fullScreenMapViewController) - nav.modalPresentationStyle = .fullScreen - self.present(nav, animated: true) - }) - .disposed(by: self.disposeBag) - } - }) - .disposed(by: disposeBag) - - - - // 목적지 좌표로 마커 및 카메라 설정 - reactor.state - .map { $0.destinationCoordinate } - .compactMap { $0 } - .subscribe(onNext: { [weak self] coordinate in - self?.setupMarker(at: coordinate) - }) - .disposed(by: disposeBag) - - // searchResult로 currentCarouselStores 업데이트 - reactor.state - .map { $0.searchResult } - .distinctUntilChanged() - .compactMap { $0 } - .subscribe(onNext: { [weak self] store in - self?.currentCarouselStores = [store] - }) - .disposed(by: disposeBag) - - // Dismiss 처리 - reactor.state - .map { $0.shouldDismiss } - .distinctUntilChanged() - .filter { $0 } - .subscribe(onNext: { [weak self] _ in - self?.dismissModalCard() - }) - .disposed(by: disposeBag) - } - // MARK: - UI Setup private func setupUI() { - view.backgroundColor = .clear // 배경색을 clear로 변경하여 항상 딤드 뷰가 보이도록 함 + view.backgroundColor = .clear view.addSubview(dimmingView) - dimmingView.snp.makeConstraints { $0.edges.equalToSuperview() } + dimmingView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } view.addSubview(modalCardView) modalCardView.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() - make.height.equalTo(408) - self.modalCardBottomConstraint = make.bottom.equalToSuperview().offset(408).constraint + make.height.equalTo(Constant.modalCardHeight) + self.modalCardBottomConstraint = make.bottom.equalToSuperview().offset(Constant.modalCardInitialBottomOffset).constraint } let topContainer = UIView() modalCardView.addSubview(topContainer) topContainer.snp.makeConstraints { make in - make.top.equalToSuperview().offset(20) + make.top.equalToSuperview().offset(Constant.topContainerTopOffset) make.leading.trailing.equalToSuperview() - make.height.equalTo(44) + make.height.equalTo(Constant.topContainerHeight) } topContainer.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.centerY.equalToSuperview() - make.leading.equalToSuperview().offset(20) + make.leading.equalToSuperview().offset(Constant.titleLabelLeadingOffset) } topContainer.addSubview(closeButton) closeButton.snp.makeConstraints { make in make.centerY.equalToSuperview() - make.trailing.equalToSuperview().inset(16) - make.width.height.equalTo(24) + make.trailing.equalToSuperview().inset(Constant.closeButtonTrailingInset) + make.width.height.equalTo(Constant.closeButtonSize) } modalCardView.addSubview(mapView) mapView.snp.makeConstraints { make in - make.top.equalTo(topContainer.snp.bottom).offset(20) - make.leading.trailing.equalToSuperview().inset(20) - make.height.equalTo(240) // 약간 줄임 + make.top.equalTo(topContainer.snp.bottom).offset(Constant.mapViewTopOffset) + make.leading.trailing.equalToSuperview().inset(Constant.mapViewHorizontalInset) + make.height.equalTo(Constant.mapViewHeight) } modalCardView.addSubview(expandButton) expandButton.snp.makeConstraints { make in - make.bottom.equalTo(mapView.snp.bottom).offset(-10) - make.trailing.equalTo(mapView.snp.trailing).offset(-10) - make.width.height.equalTo(32) + make.bottom.equalTo(mapView.snp.bottom).offset(Constant.expandButtonBottomOffset) + make.trailing.equalTo(mapView.snp.trailing).offset(Constant.expandButtonTrailingOffset) + make.width.height.equalTo(Constant.expandButtonSize) } let bottomContainer = UIView() modalCardView.addSubview(bottomContainer) bottomContainer.snp.makeConstraints { make in - make.top.equalTo(mapView.snp.bottom).offset(20) - make.leading.trailing.equalToSuperview().inset(20) - make.height.equalTo(44) - make.bottom.equalTo(modalCardView.snp.bottom).inset(60) + make.top.equalTo(mapView.snp.bottom).offset(Constant.bottomContainerTopOffset) + make.leading.trailing.equalToSuperview().inset(Constant.bottomContainerHorizontalInset) + make.height.equalTo(Constant.bottomContainerHeight) + make.bottom.equalTo(modalCardView.snp.bottom).inset(Constant.bottomContainerBottomInset) } bottomContainer.addSubview(promptLabel) @@ -336,15 +248,19 @@ final class MapGuideViewController: UIViewController, View { } } + private func setupTapGesture() { + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapOnDimmingView)) + dimmingView.addGestureRecognizer(tapGesture) + dimmingView.isUserInteractionEnabled = true + } + private func presentModalCard() { - // 백그라운드가 보이도록 처음부터 alpha를 1로 설정 self.dimmingView.alpha = 1 - UIView.animate( - withDuration: 0.3, + withDuration: Constant.modalCardAnimationDuration, delay: 0, - usingSpringWithDamping: 0.8, - initialSpringVelocity: 0.5, + usingSpringWithDamping: Constant.modalCardAnimationDamping, + initialSpringVelocity: Constant.modalCardAnimationInitialVelocity, options: .curveEaseOut ) { self.modalCardBottomConstraint?.update(offset: 0) @@ -353,36 +269,154 @@ final class MapGuideViewController: UIViewController, View { } private func setupMarker(at coordinate: CLLocationCoordinate2D) { - mapView.subviews.forEach { if $0 is NMFMarker { $0.removeFromSuperview() } } + // 기존 마커 제거 + self.mapView.subviews.forEach { subview in + if subview is NMFMarker { + subview.removeFromSuperview() + } + } - // 새 마커 생성 및 설정 let marker = NMFMarker() marker.position = NMGLatLng(lat: coordinate.latitude, lng: coordinate.longitude) marker.iconImage = NMFOverlayImage(name: "TapMarker") marker.width = 44 marker.height = 44 marker.anchor = CGPoint(x: 0.5, y: 1.0) + marker.mapView = self.mapView - // 먼저 마커를 지도에 추가 - marker.mapView = mapView - - // 그 다음 카메라 위치 설정 let cameraUpdate = NMFCameraUpdate( scrollTo: NMGLatLng(lat: coordinate.latitude, lng: coordinate.longitude), - zoomTo: 15.0 + zoomTo: Constant.mapZoomLevel ) cameraUpdate.animation = .easeIn - cameraUpdate.animationDuration = 0.3 - mapView.moveCamera(cameraUpdate) + cameraUpdate.animationDuration = Constant.modalCardAnimationDuration + self.mapView.moveCamera(cameraUpdate) } private func dismissModalCard() { - UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseIn) { + UIView.animate(withDuration: Constant.modalCardAnimationDuration, delay: 0, options: .curveEaseIn) { self.dimmingView.alpha = 0 - self.modalCardBottomConstraint?.update(offset: 408) + self.modalCardBottomConstraint?.update(offset: Constant.modalCardInitialBottomOffset) self.view.layoutIfNeeded() } completion: { _ in self.dismiss(animated: false) } } + + @objc private func handleTapOnDimmingView(_ sender: UITapGestureRecognizer) { + let location = sender.location(in: self.view) + if !modalCardView.frame.contains(location) { + dismissModalCard() + } + } +} + +// MARK: - ReactorKit Binding +extension MapGuideViewController { + func bind(reactor: MapGuideReactor) { + reactor.action.onNext(.viewDidLoad(self.popUpStoreId)) + + closeButton.rx.tap + .subscribe(onNext: { [weak self] in + self?.dismissModalCard() + }) + .disposed(by: self.disposeBag) + + naverButton.rx.tap + .map { Reactor.Action.openMapApp("naver") } + .bind(to: reactor.action) + .disposed(by: self.disposeBag) + + kakaoButton.rx.tap + .map { Reactor.Action.openMapApp("kakao") } + .bind(to: reactor.action) + .disposed(by: self.disposeBag) + + appleButton.rx.tap + .map { Reactor.Action.openMapApp("apple") } + .bind(to: reactor.action) + .disposed(by: self.disposeBag) + + expandButton.rx.tap + .throttle(.milliseconds(Constant.expandButtonThrottleMilliseconds), scheduler: MainScheduler.instance) + .subscribe(onNext: { [weak self] in + guard let self = self else { return } + let provider = ProviderImpl() + let useCase = DefaultMapUseCase(repository: DefaultMapRepository(provider: provider)) + let directionRepository = DefaultMapDirectionRepository(provider: provider) + let reactorInstance = MapReactor(useCase: useCase, directionRepository: directionRepository) + + if let selectedStore = self.currentCarouselStores.first { + reactorInstance.action.onNext(.didSelectItem(selectedStore)) + + let marker = NMFMarker() + marker.position = NMGLatLng(lat: selectedStore.latitude, lng: selectedStore.longitude) + marker.iconImage = NMFOverlayImage(name: "TapMarker") + marker.width = 44 + marker.height = 44 + marker.anchor = CGPoint(x: 0.5, y: 1.0) + marker.userInfo = ["storeData": selectedStore] + + let fullScreenMapViewController = FullScreenMapViewController(store: selectedStore, existingMarker: marker) + fullScreenMapViewController.reactor = reactorInstance + + let navigationController = UINavigationController(rootViewController: fullScreenMapViewController) + navigationController.modalPresentationStyle = .fullScreen + self.present(navigationController, animated: true) + } else { + reactorInstance.action.onNext(.viewDidLoad(self.popUpStoreId)) + + reactorInstance.state + .map { $0.searchResult } + .distinctUntilChanged() + .compactMap { $0 } + .take(1) + .observe(on: MainScheduler.instance) + .subscribe(onNext: { store in + let marker = NMFMarker() + marker.position = NMGLatLng(lat: store.latitude, lng: store.longitude) + marker.iconImage = NMFOverlayImage(name: "TapMarker") + marker.width = 44 + marker.height = 44 + marker.anchor = CGPoint(x: 0.5, y: 1.0) + marker.userInfo = ["storeData": store] + + let fullScreenMapViewController = FullScreenMapViewController(store: store, existingMarker: marker) + fullScreenMapViewController.reactor = reactorInstance + + let navigationController = UINavigationController(rootViewController: fullScreenMapViewController) + navigationController.modalPresentationStyle = .fullScreen + self.present(navigationController, animated: true) + }) + .disposed(by: self.disposeBag) + } + }) + .disposed(by: self.disposeBag) + + reactor.state + .map { $0.destinationCoordinate } + .compactMap { $0 } + .subscribe(onNext: { [weak self] coordinate in + self?.setupMarker(at: coordinate) + }) + .disposed(by: self.disposeBag) + + reactor.state + .map { $0.searchResult } + .distinctUntilChanged() + .compactMap { $0 } + .subscribe(onNext: { [weak self] store in + self?.currentCarouselStores = [store] + }) + .disposed(by: self.disposeBag) + + reactor.state + .map { $0.shouldDismiss } + .distinctUntilChanged() + .filter { $0 } + .subscribe(onNext: { [weak self] _ in + self?.dismissModalCard() + }) + .disposed(by: self.disposeBag) + } } diff --git a/Poppool/Poppool/Presentation/Map/MapView/MapMarker.swift b/Poppool/Poppool/Presentation/Map/MapView/MapMarker.swift index f15cc71d..9ddfb0e1 100644 --- a/Poppool/Poppool/Presentation/Map/MapView/MapMarker.swift +++ b/Poppool/Poppool/Presentation/Map/MapView/MapMarker.swift @@ -221,9 +221,7 @@ extension MapMarker { return markerImageView } - /// 네이버맵용으로 뷰를 UIImage로 렌더링하는 함수 func asImage() -> UIImage? { - // 필요한 경우 프레임을 강제로 업데이트합니다. self.layoutIfNeeded() UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, UIScreen.main.scale) defer { UIGraphicsEndImageContext() } From 03eb7102e05b489fc13fc2f4be8d958521272697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=80=E1=85=B5=E1=84=92?= =?UTF-8?q?=E1=85=A7=E1=86=AB?= Date: Wed, 2 Apr 2025 15:53:53 +0900 Subject: [PATCH 32/95] =?UTF-8?q?feat/#95=20NMFMapViewDelegateProxy=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Map/Common/NMFMapViewDelegateProxy.swift | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/Poppool/Poppool/Presentation/Map/Common/NMFMapViewDelegateProxy.swift b/Poppool/Poppool/Presentation/Map/Common/NMFMapViewDelegateProxy.swift index d5ef360c..dadff963 100644 --- a/Poppool/Poppool/Presentation/Map/Common/NMFMapViewDelegateProxy.swift +++ b/Poppool/Poppool/Presentation/Map/Common/NMFMapViewDelegateProxy.swift @@ -2,53 +2,89 @@ import NMapsMap import RxSwift import RxCocoa +/// NMFMapViewDelegateProxy는 NMFMapView의 delegate 이벤트를 RxSwift Observable로 변환하는 역할 class NMFMapViewDelegateProxy: DelegateProxy, DelegateProxyType, NMFMapViewDelegate { + // MARK: - Properties + + /// 연결된 NMFMapView 인스턴스 (약한 참조) public weak private(set) var mapView: NMFMapView? - // Rx 이벤트를 위한 subject 추가 + /// 카메라 위치 변경 이벤트를 전달하기 위한 Rx Subject let didChangePositionSubject = PublishSubject() + + /// 맵이 idle 상태가 되었을 때 이벤트를 전달하기 위한 Rx Subject let idleAtPositionSubject = PublishSubject() + // MARK: - Initializer + + /// NMFMapViewDelegateProxy 초기화 메서드 + /// - Parameter mapView: 이벤트를 받아올 NMFMapView 인스턴스 init(mapView: NMFMapView) { self.mapView = mapView super.init(parentObject: mapView, delegateProxy: NMFMapViewDelegateProxy.self) } + // MARK: - DelegateProxyType Implementation + + /// Rx에서 사용하기 위한 구현 등록 static func registerKnownImplementations() { self.register { NMFMapViewDelegateProxy(mapView: $0) } } + /// 지정된 NMFMapView의 현재 delegate를 반환 + /// - Parameter object: NMFMapView 인스턴스 + /// - Returns: 해당 mapView의 delegate static func currentDelegate(for object: NMFMapView) -> NMFMapViewDelegate? { return object.delegate } + /// 지정된 NMFMapView에 delegate를 설정 + /// - Parameters: + /// - delegate: 설정할 delegate + /// - object: NMFMapView 인스턴스 static func setCurrentDelegate(_ delegate: NMFMapViewDelegate?, to object: NMFMapView) { object.delegate = delegate } - // 네이버맵의 Delegate 메서드를 Rx로 전달 + // MARK: - NMFMapViewDelegate Methods + + /// 카메라 위치가 변경될 때 호출되는 메서드. + /// - Parameters: + /// - mapView: 이벤트가 발생한 NMFMapView + /// - reason: 카메라 변경 사유 + /// - animated: 애니메이션 여부 func mapView(_ mapView: NMFMapView, cameraDidChangeByReason reason: Int, animated: Bool) { didChangePositionSubject.onNext(()) + // 기존 delegate로 이벤트 전달 (옵셔널 체이닝) _forwardToDelegate?.mapView?(mapView, cameraDidChangeByReason: reason, animated: animated) } + /// 맵뷰가 idle 상태가 되었을 때 호출되는 메서드. + /// Rx Subject를 통해 idle 이벤트를 전달하고, 기존 delegate에게 까지 + /// - Parameter mapView: idle 상태가 된 NMFMapView func mapViewIdle(_ mapView: NMFMapView) { idleAtPositionSubject.onNext(()) + // 기존 delegate로 idle 이벤트 전달 forwardToDelegate()?.mapViewIdle?(mapView) } } +/// NMFMapView의 Reactive 확장 extension Reactive where Base: NMFMapView { + + /// NMFMapViewDelegateProxy를 반환하여 delegate 이벤트를 처리할 수 있도록 var delegate: DelegateProxy { return NMFMapViewDelegateProxy.proxy(for: base) } + /// mapView의 카메라 위치 변경 이벤트를 Observable로 var didChangePosition: Observable { let proxy = NMFMapViewDelegateProxy.proxy(for: base) return proxy.didChangePositionSubject.asObservable() } + /// mapView가 idle 상태가 되었을 때의 이벤트를 Observable로 var idleAtPosition: Observable { let proxy = NMFMapViewDelegateProxy.proxy(for: base) return proxy.idleAtPositionSubject.asObservable() From a4d4d941b9d87960f1234bf96d7a4c70bf5a449a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=80=E1=85=B5=E1=84=92?= =?UTF-8?q?=E1=85=A7=E1=86=AB?= Date: Wed, 2 Apr 2025 15:54:23 +0900 Subject: [PATCH 33/95] =?UTF-8?q?fix/#95=20:=20=EB=A7=B5=EA=B0=80=EC=9D=B4?= =?UTF-8?q?=EB=93=9C=EB=B7=B0=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20?= =?UTF-8?q?=EC=B6=95=EC=95=BD=EC=96=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Map/Common/ClusteringModels.swift | 24 +- .../MapGuideView/MapGuideViewController.swift | 502 +++++++++--------- 2 files changed, 247 insertions(+), 279 deletions(-) diff --git a/Poppool/Poppool/Presentation/Map/Common/ClusteringModels.swift b/Poppool/Poppool/Presentation/Map/Common/ClusteringModels.swift index 0f218e3c..b0f80b7d 100644 --- a/Poppool/Poppool/Presentation/Map/Common/ClusteringModels.swift +++ b/Poppool/Poppool/Presentation/Map/Common/ClusteringModels.swift @@ -7,22 +7,18 @@ enum MapZoomLevel { case detailed static func getLevel(from zoom: Float) -> MapZoomLevel { - let level: MapZoomLevel - switch zoom { - case ..<7: - level = .country - case 7..<10: - level = .city - case 10..<11: - level = .district - default: - level = .detailed - } - Logger.log(message: "줌 레벨 계산: \(zoom) -> \(level)", category: .debug) - return level + switch zoom { + case ..<7: + return .country + case 7..<10: + return .city + case 10..<11: + return .district + default: + return .detailed } } - +} struct RegionCluster { let name: String diff --git a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift index c7b71cb4..4810452c 100644 --- a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift +++ b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift @@ -6,57 +6,16 @@ import CoreLocation import NMapsMap final class MapGuideViewController: UIViewController, View { - - // MARK: - Constants - private enum Constant { - // Modal Card - static let modalCardHeight: CGFloat = 408 - static let modalCardInitialBottomOffset: CGFloat = 408 - static let modalCardAnimationDuration: TimeInterval = 0.3 - static let modalCardAnimationDamping: CGFloat = 0.8 - static let modalCardAnimationInitialVelocity: CGFloat = 0.5 - - // Top Container - static let topContainerTopOffset: CGFloat = 20 - static let topContainerHeight: CGFloat = 44 - - // Title Label - static let titleLabelLeadingOffset: CGFloat = 20 - - // Close Button - static let closeButtonTrailingInset: CGFloat = 16 - static let closeButtonSize: CGFloat = 24 - - // MapView - static let mapViewTopOffset: CGFloat = 20 - static let mapViewHorizontalInset: CGFloat = 20 - static let mapViewHeight: CGFloat = 240 - static let mapZoomLevel: Double = 15.0 - - // Expand Button - static let expandButtonBottomOffset: CGFloat = -10 - static let expandButtonTrailingOffset: CGFloat = -10 - static let expandButtonSize: CGFloat = 32 - - // Bottom Container - static let bottomContainerTopOffset: CGFloat = 20 - static let bottomContainerHorizontalInset: CGFloat = 20 - static let bottomContainerHeight: CGFloat = 44 - static let bottomContainerBottomInset: CGFloat = 60 - - // Throttle - static let expandButtonThrottleMilliseconds: Int = 300 - } - // MARK: - Properties var disposeBag = DisposeBag() - private let popUpStoreId: Int64 - private var currentCarouselStores: [MapPopUpStore] = [] // 현재 선택된 스토어 목록 + private let popupStoreIdentifier: Int64 + private var currentCarouselStoreList: [MapPopUpStore] = [] // 현재 선택된 스토어 목록 // MARK: - UI Components + private let dimmingView: UIView = { let viewInstance = UIView() - viewInstance.backgroundColor = UIColor.gray.withAlphaComponent(0.7) + viewInstance.backgroundColor = UIColor.gray.withAlphaComponent(0.7) viewInstance.alpha = 0 return viewInstance }() @@ -74,18 +33,18 @@ final class MapGuideViewController: UIViewController, View { }() private let titleLabel: UILabel = { - let label = UILabel() - label.text = "찾아가는 길" - label.font = UIFont.boldSystemFont(ofSize: 17) - label.textColor = .black - return label + let labelInstance = UILabel() + labelInstance.text = "찾아가는 길" + labelInstance.font = UIFont.boldSystemFont(ofSize: 17) + labelInstance.textColor = .black + return labelInstance }() private let closeButton: UIButton = { - let button = UIButton(type: .system) + let buttonInstance = UIButton(type: .system) let image = UIImage(named: "icon_xmark")?.withRenderingMode(.alwaysOriginal) - button.setImage(image, for: .normal) - return button + buttonInstance.setImage(image, for: .normal) + return buttonInstance }() private let mapView: NMFMapView = { @@ -97,60 +56,60 @@ final class MapGuideViewController: UIViewController, View { }() private let expandButton: UIButton = { - let button = UIButton() - button.setImage(UIImage(named: "Expandable"), for: .normal) - button.backgroundColor = UIColor.white - button.layer.cornerRadius = 16 - button.clipsToBounds = true - return button + let buttonInstance = UIButton() + buttonInstance.setImage(UIImage(named: "Expandable"), for: .normal) + buttonInstance.backgroundColor = UIColor.white + buttonInstance.layer.cornerRadius = 16 + buttonInstance.clipsToBounds = true + return buttonInstance }() private let promptLabel: UILabel = { - let label = UILabel() - label.text = "지도 앱으로\n바로 찾아볼까요?" - label.font = UIFont.systemFont(ofSize: 15, weight: .medium) - label.textColor = .darkGray - label.numberOfLines = 2 - return label + let labelInstance = UILabel() + labelInstance.text = "지도 앱으로\n바로 찾아볼까요?" + labelInstance.font = UIFont.systemFont(ofSize: 15, weight: .medium) + labelInstance.textColor = .darkGray + labelInstance.numberOfLines = 2 + return labelInstance }() private let naverButton: UIButton = { - let button = UIButton() - button.setImage(UIImage(named: "naver"), for: .normal) - button.layer.cornerRadius = 24 - button.layer.borderWidth = 1 - button.layer.borderColor = UIColor.g100.cgColor - button.clipsToBounds = true - return button + let buttonInstance = UIButton() + buttonInstance.setImage(UIImage(named: "naver"), for: .normal) + buttonInstance.layer.cornerRadius = 24 + buttonInstance.layer.borderWidth = 1 + buttonInstance.layer.borderColor = UIColor.g100.cgColor + buttonInstance.clipsToBounds = true + return buttonInstance }() private let kakaoButton: UIButton = { - let button = UIButton() - button.setImage(UIImage(named: "kakao"), for: .normal) - button.layer.cornerRadius = 24 - button.layer.borderWidth = 1 - button.layer.borderColor = UIColor.g100.cgColor - button.clipsToBounds = true - return button + let buttonInstance = UIButton() + buttonInstance.setImage(UIImage(named: "kakao"), for: .normal) + buttonInstance.layer.cornerRadius = 24 + buttonInstance.layer.borderWidth = 1 + buttonInstance.layer.borderColor = UIColor.g100.cgColor + buttonInstance.clipsToBounds = true + return buttonInstance }() private let appleButton: UIButton = { - let button = UIButton() - button.setImage(UIImage(named: "AppleMap"), for: .normal) - button.layer.cornerRadius = 24 - button.layer.borderWidth = 1 - button.layer.borderColor = UIColor.g100.cgColor - button.clipsToBounds = true - return button + let buttonInstance = UIButton() + buttonInstance.setImage(UIImage(named: "AppleMap"), for: .normal) + buttonInstance.layer.cornerRadius = 24 + buttonInstance.layer.borderWidth = 1 + buttonInstance.layer.borderColor = UIColor.g100.cgColor + buttonInstance.clipsToBounds = true + return buttonInstance }() private var modalCardBottomConstraint: Constraint? // MARK: - Initializer init(popUpStoreId: Int64) { - self.popUpStoreId = popUpStoreId + self.popupStoreIdentifier = popUpStoreId super.init(nibName: nil, bundle: nil) - modalPresentationStyle = .overFullScreen + modalPresentationStyle = .overFullScreen // 모달 스타일 설정 } required init?(coder: NSCoder) { @@ -161,13 +120,145 @@ final class MapGuideViewController: UIViewController, View { override func viewDidLoad() { super.viewDidLoad() setupUI() - setupTapGesture() + setupTapGesture() // 탭 제스처 설정 추가 presentModalCard() } + // MARK: - Gesture Setup + /// 딤드 영역 탭 제스처 설정 + private func setupTapGesture() { + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapOnDimmingView)) + dimmingView.addGestureRecognizer(tapGesture) + dimmingView.isUserInteractionEnabled = true // 중요: 상호작용 활성화 + } + + /// 딤드 영역 탭 처리: 탭 위치가 모달 카드 영역이 아닌 경우에만 닫기 + @objc private func handleTapOnDimmingView(_ sender: UITapGestureRecognizer) { + let tapLocation = sender.location(in: view) + if !modalCardView.frame.contains(tapLocation) { + dismissModalCard() + } + } + + // MARK: - ReactorKit Binding + func bind(reactor: MapGuideReactor) { + reactor.action.onNext(.viewDidLoad(self.popupStoreIdentifier)) + + // 닫기 버튼 + closeButton.rx.tap + .subscribe(onNext: { [weak self] in + self?.dismissModalCard() + }) + .disposed(by: disposeBag) + + // 지도 앱 열기 + naverButton.rx.tap + .map { Reactor.Action.openMapApp("naver") } + .bind(to: reactor.action) + .disposed(by: disposeBag) + kakaoButton.rx.tap + .map { Reactor.Action.openMapApp("kakao") } + .bind(to: reactor.action) + .disposed(by: disposeBag) + appleButton.rx.tap + .map { Reactor.Action.openMapApp("apple") } + .bind(to: reactor.action) + .disposed(by: disposeBag) + + // 확장 버튼 탭 처리 및 지도 풀스크린 전환 + expandButton.rx.tap + .throttle(.milliseconds(300), scheduler: MainScheduler.instance) + .subscribe(onNext: { [weak self] in + guard let strongSelf = self else { return } + + let providerInstance = ProviderImpl() + let repositoryInstance = DefaultMapRepository(provider: providerInstance) + let useCaseInstance = DefaultMapUseCase(repository: repositoryInstance) + let directionRepositoryInstance = DefaultMapDirectionRepository(provider: providerInstance) + let mapReactorInstance = MapReactor(useCase: useCaseInstance, directionRepository: directionRepositoryInstance) + + if let selectedStore = strongSelf.currentCarouselStoreList.first { + mapReactorInstance.action.onNext(.didSelectItem(selectedStore)) + + // 현재 지도에 표시된 마커 생성 또는 가져오기 + let markerInstance = NMFMarker() + markerInstance.position = NMGLatLng(lat: selectedStore.latitude, lng: selectedStore.longitude) + markerInstance.iconImage = NMFOverlayImage(name: "TapMarker") + markerInstance.width = 44 + markerInstance.height = 44 + markerInstance.anchor = CGPoint(x: 0.5, y: 1.0) + markerInstance.userInfo = ["storeData": selectedStore] + + // 풀스크린 지도 뷰 컨트롤러에 선택된 마커 정보 전달 + let fullScreenMapViewController = FullScreenMapViewController(store: selectedStore, existingMarker: markerInstance) + fullScreenMapViewController.reactor = mapReactorInstance + + let navigationController = UINavigationController(rootViewController: fullScreenMapViewController) + navigationController.modalPresentationStyle = .fullScreen + strongSelf.present(navigationController, animated: true) + } else { + mapReactorInstance.action.onNext(.viewDidLoad(strongSelf.popupStoreIdentifier)) + + mapReactorInstance.state + .map { $0.searchResult } + .distinctUntilChanged() + .compactMap { $0 } + .take(1) + .observe(on: MainScheduler.instance) + .subscribe(onNext: { store in + let markerInstance = NMFMarker() + markerInstance.position = NMGLatLng(lat: store.latitude, lng: store.longitude) + markerInstance.iconImage = NMFOverlayImage(name: "TapMarker") + markerInstance.width = 44 + markerInstance.height = 44 + markerInstance.anchor = CGPoint(x: 0.5, y: 1.0) + markerInstance.userInfo = ["storeData": store] + + let fullScreenMapViewController = FullScreenMapViewController(store: store, existingMarker: markerInstance) + fullScreenMapViewController.reactor = mapReactorInstance + + let navigationController = UINavigationController(rootViewController: fullScreenMapViewController) + navigationController.modalPresentationStyle = .fullScreen + strongSelf.present(navigationController, animated: true) + }) + .disposed(by: strongSelf.disposeBag) + } + }) + .disposed(by: disposeBag) + + // 목적지 좌표에 따른 마커 및 카메라 설정 + reactor.state + .map { $0.destinationCoordinate } + .compactMap { $0 } + .subscribe(onNext: { [weak self] coordinate in + self?.setupMarker(at: coordinate) + }) + .disposed(by: disposeBag) + + // searchResult로 현재 캐러셀 스토어 목록 업데이트 + reactor.state + .map { $0.searchResult } + .distinctUntilChanged() + .compactMap { $0 } + .subscribe(onNext: { [weak self] store in + self?.currentCarouselStoreList = [store] + }) + .disposed(by: disposeBag) + + // Dismiss 처리 + reactor.state + .map { $0.shouldDismiss } + .distinctUntilChanged() + .filter { $0 } + .subscribe(onNext: { [weak self] _ in + self?.dismissModalCard() + }) + .disposed(by: disposeBag) + } + // MARK: - UI Setup private func setupUI() { - view.backgroundColor = .clear + view.backgroundColor = .clear // 배경색을 clear로 설정하여 항상 딤드 뷰가 보이도록 함 view.addSubview(dimmingView) dimmingView.snp.makeConstraints { make in make.edges.equalToSuperview() @@ -176,91 +267,86 @@ final class MapGuideViewController: UIViewController, View { view.addSubview(modalCardView) modalCardView.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() - make.height.equalTo(Constant.modalCardHeight) - self.modalCardBottomConstraint = make.bottom.equalToSuperview().offset(Constant.modalCardInitialBottomOffset).constraint + make.height.equalTo(408) + self.modalCardBottomConstraint = make.bottom.equalToSuperview().offset(408).constraint } - let topContainer = UIView() - modalCardView.addSubview(topContainer) - topContainer.snp.makeConstraints { make in - make.top.equalToSuperview().offset(Constant.topContainerTopOffset) + let topContainerView = UIView() + modalCardView.addSubview(topContainerView) + topContainerView.snp.makeConstraints { make in + make.top.equalToSuperview().offset(20) make.leading.trailing.equalToSuperview() - make.height.equalTo(Constant.topContainerHeight) + make.height.equalTo(44) } - topContainer.addSubview(titleLabel) + topContainerView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.centerY.equalToSuperview() - make.leading.equalToSuperview().offset(Constant.titleLabelLeadingOffset) + make.leading.equalToSuperview().offset(20) } - topContainer.addSubview(closeButton) + topContainerView.addSubview(closeButton) closeButton.snp.makeConstraints { make in make.centerY.equalToSuperview() - make.trailing.equalToSuperview().inset(Constant.closeButtonTrailingInset) - make.width.height.equalTo(Constant.closeButtonSize) + make.trailing.equalToSuperview().inset(16) + make.width.height.equalTo(24) } modalCardView.addSubview(mapView) mapView.snp.makeConstraints { make in - make.top.equalTo(topContainer.snp.bottom).offset(Constant.mapViewTopOffset) - make.leading.trailing.equalToSuperview().inset(Constant.mapViewHorizontalInset) - make.height.equalTo(Constant.mapViewHeight) + make.top.equalTo(topContainerView.snp.bottom).offset(20) + make.leading.trailing.equalToSuperview().inset(20) + make.height.equalTo(240) // 높이 약간 조정 } modalCardView.addSubview(expandButton) expandButton.snp.makeConstraints { make in - make.bottom.equalTo(mapView.snp.bottom).offset(Constant.expandButtonBottomOffset) - make.trailing.equalTo(mapView.snp.trailing).offset(Constant.expandButtonTrailingOffset) - make.width.height.equalTo(Constant.expandButtonSize) + make.bottom.equalTo(mapView.snp.bottom).offset(-10) + make.trailing.equalTo(mapView.snp.trailing).offset(-10) + make.width.height.equalTo(32) } - let bottomContainer = UIView() - modalCardView.addSubview(bottomContainer) - bottomContainer.snp.makeConstraints { make in - make.top.equalTo(mapView.snp.bottom).offset(Constant.bottomContainerTopOffset) - make.leading.trailing.equalToSuperview().inset(Constant.bottomContainerHorizontalInset) - make.height.equalTo(Constant.bottomContainerHeight) - make.bottom.equalTo(modalCardView.snp.bottom).inset(Constant.bottomContainerBottomInset) + let bottomContainerView = UIView() + modalCardView.addSubview(bottomContainerView) + bottomContainerView.snp.makeConstraints { make in + make.top.equalTo(mapView.snp.bottom).offset(20) + make.leading.trailing.equalToSuperview().inset(20) + make.height.equalTo(44) + make.bottom.equalTo(modalCardView.snp.bottom).inset(60) } - bottomContainer.addSubview(promptLabel) + bottomContainerView.addSubview(promptLabel) promptLabel.snp.makeConstraints { make in make.leading.equalToSuperview() make.centerY.equalToSuperview() } - let appStack = UIStackView(arrangedSubviews: [naverButton, kakaoButton, appleButton]) - appStack.axis = .horizontal - appStack.alignment = .center - appStack.spacing = 16 - appStack.distribution = .fillEqually + let applicationStackView = UIStackView(arrangedSubviews: [naverButton, kakaoButton, appleButton]) + applicationStackView.axis = .horizontal + applicationStackView.alignment = .center + applicationStackView.spacing = 16 + applicationStackView.distribution = .fillEqually - bottomContainer.addSubview(appStack) - appStack.snp.makeConstraints { make in + bottomContainerView.addSubview(applicationStackView) + applicationStackView.snp.makeConstraints { make in make.trailing.equalToSuperview() make.centerY.equalToSuperview() [naverButton, kakaoButton, appleButton].forEach { button in - button.snp.makeConstraints { make in - make.size.equalTo(CGSize(width: 48, height: 48)) + button.snp.makeConstraints { constraint in + constraint.size.equalTo(CGSize(width: 48, height: 48)) } } } } - private func setupTapGesture() { - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapOnDimmingView)) - dimmingView.addGestureRecognizer(tapGesture) - dimmingView.isUserInteractionEnabled = true - } - private func presentModalCard() { self.dimmingView.alpha = 1 + UIView.animate( - withDuration: Constant.modalCardAnimationDuration, + withDuration: 0.3, delay: 0, - usingSpringWithDamping: Constant.modalCardAnimationDamping, - initialSpringVelocity: Constant.modalCardAnimationInitialVelocity, + usingSpringWithDamping: 0.8, + initialSpringVelocity: 0.5, options: .curveEaseOut ) { self.modalCardBottomConstraint?.update(offset: 0) @@ -269,154 +355,40 @@ final class MapGuideViewController: UIViewController, View { } private func setupMarker(at coordinate: CLLocationCoordinate2D) { - // 기존 마커 제거 - self.mapView.subviews.forEach { subview in + mapView.subviews.forEach { subview in if subview is NMFMarker { subview.removeFromSuperview() } } - let marker = NMFMarker() - marker.position = NMGLatLng(lat: coordinate.latitude, lng: coordinate.longitude) - marker.iconImage = NMFOverlayImage(name: "TapMarker") - marker.width = 44 - marker.height = 44 - marker.anchor = CGPoint(x: 0.5, y: 1.0) - marker.mapView = self.mapView + // 새 마커 생성 및 설정 + let markerInstance = NMFMarker() + markerInstance.position = NMGLatLng(lat: coordinate.latitude, lng: coordinate.longitude) + markerInstance.iconImage = NMFOverlayImage(name: "TapMarker") + markerInstance.width = 44 + markerInstance.height = 44 + markerInstance.anchor = CGPoint(x: 0.5, y: 1.0) + // 먼저 마커를 지도에 추가 + markerInstance.mapView = mapView + + // 카메라 위치 업데이트 let cameraUpdate = NMFCameraUpdate( scrollTo: NMGLatLng(lat: coordinate.latitude, lng: coordinate.longitude), - zoomTo: Constant.mapZoomLevel + zoomTo: 15.0 ) cameraUpdate.animation = .easeIn - cameraUpdate.animationDuration = Constant.modalCardAnimationDuration - self.mapView.moveCamera(cameraUpdate) + cameraUpdate.animationDuration = 0.3 + mapView.moveCamera(cameraUpdate) } private func dismissModalCard() { - UIView.animate(withDuration: Constant.modalCardAnimationDuration, delay: 0, options: .curveEaseIn) { + UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseIn) { self.dimmingView.alpha = 0 - self.modalCardBottomConstraint?.update(offset: Constant.modalCardInitialBottomOffset) + self.modalCardBottomConstraint?.update(offset: 408) self.view.layoutIfNeeded() } completion: { _ in self.dismiss(animated: false) } } - - @objc private func handleTapOnDimmingView(_ sender: UITapGestureRecognizer) { - let location = sender.location(in: self.view) - if !modalCardView.frame.contains(location) { - dismissModalCard() - } - } -} - -// MARK: - ReactorKit Binding -extension MapGuideViewController { - func bind(reactor: MapGuideReactor) { - reactor.action.onNext(.viewDidLoad(self.popUpStoreId)) - - closeButton.rx.tap - .subscribe(onNext: { [weak self] in - self?.dismissModalCard() - }) - .disposed(by: self.disposeBag) - - naverButton.rx.tap - .map { Reactor.Action.openMapApp("naver") } - .bind(to: reactor.action) - .disposed(by: self.disposeBag) - - kakaoButton.rx.tap - .map { Reactor.Action.openMapApp("kakao") } - .bind(to: reactor.action) - .disposed(by: self.disposeBag) - - appleButton.rx.tap - .map { Reactor.Action.openMapApp("apple") } - .bind(to: reactor.action) - .disposed(by: self.disposeBag) - - expandButton.rx.tap - .throttle(.milliseconds(Constant.expandButtonThrottleMilliseconds), scheduler: MainScheduler.instance) - .subscribe(onNext: { [weak self] in - guard let self = self else { return } - let provider = ProviderImpl() - let useCase = DefaultMapUseCase(repository: DefaultMapRepository(provider: provider)) - let directionRepository = DefaultMapDirectionRepository(provider: provider) - let reactorInstance = MapReactor(useCase: useCase, directionRepository: directionRepository) - - if let selectedStore = self.currentCarouselStores.first { - reactorInstance.action.onNext(.didSelectItem(selectedStore)) - - let marker = NMFMarker() - marker.position = NMGLatLng(lat: selectedStore.latitude, lng: selectedStore.longitude) - marker.iconImage = NMFOverlayImage(name: "TapMarker") - marker.width = 44 - marker.height = 44 - marker.anchor = CGPoint(x: 0.5, y: 1.0) - marker.userInfo = ["storeData": selectedStore] - - let fullScreenMapViewController = FullScreenMapViewController(store: selectedStore, existingMarker: marker) - fullScreenMapViewController.reactor = reactorInstance - - let navigationController = UINavigationController(rootViewController: fullScreenMapViewController) - navigationController.modalPresentationStyle = .fullScreen - self.present(navigationController, animated: true) - } else { - reactorInstance.action.onNext(.viewDidLoad(self.popUpStoreId)) - - reactorInstance.state - .map { $0.searchResult } - .distinctUntilChanged() - .compactMap { $0 } - .take(1) - .observe(on: MainScheduler.instance) - .subscribe(onNext: { store in - let marker = NMFMarker() - marker.position = NMGLatLng(lat: store.latitude, lng: store.longitude) - marker.iconImage = NMFOverlayImage(name: "TapMarker") - marker.width = 44 - marker.height = 44 - marker.anchor = CGPoint(x: 0.5, y: 1.0) - marker.userInfo = ["storeData": store] - - let fullScreenMapViewController = FullScreenMapViewController(store: store, existingMarker: marker) - fullScreenMapViewController.reactor = reactorInstance - - let navigationController = UINavigationController(rootViewController: fullScreenMapViewController) - navigationController.modalPresentationStyle = .fullScreen - self.present(navigationController, animated: true) - }) - .disposed(by: self.disposeBag) - } - }) - .disposed(by: self.disposeBag) - - reactor.state - .map { $0.destinationCoordinate } - .compactMap { $0 } - .subscribe(onNext: { [weak self] coordinate in - self?.setupMarker(at: coordinate) - }) - .disposed(by: self.disposeBag) - - reactor.state - .map { $0.searchResult } - .distinctUntilChanged() - .compactMap { $0 } - .subscribe(onNext: { [weak self] store in - self?.currentCarouselStores = [store] - }) - .disposed(by: self.disposeBag) - - reactor.state - .map { $0.shouldDismiss } - .distinctUntilChanged() - .filter { $0 } - .subscribe(onNext: { [weak self] _ in - self?.dismissModalCard() - }) - .disposed(by: self.disposeBag) - } } From ab6df32dd9ee00fc19407f6f649b34d03f5ecd1b Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 2 Apr 2025 16:24:05 +0900 Subject: [PATCH 34/95] =?UTF-8?q?feat/#93:=20Swiftlint=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=20=EC=88=98=EC=A0=95=20=EA=B9=83=ED=97=88=EB=B8=8C=20?= =?UTF-8?q?=EC=95=A1=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/Swiftlint autocorrect.yml | 51 +++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/Swiftlint autocorrect.yml diff --git a/.github/workflows/Swiftlint autocorrect.yml b/.github/workflows/Swiftlint autocorrect.yml new file mode 100644 index 00000000..11cc6761 --- /dev/null +++ b/.github/workflows/Swiftlint autocorrect.yml @@ -0,0 +1,51 @@ +name: SwiftLint Autocorrect + +on: + push: + branches: ['dev/**'] + +jobs: + autocorrect: + name: SwiftLint Autocorrect + runs-on: macos-15 + if: github.actor != 'github-actions[bot]' # 커밋 무한 루프 방지를 위해 봇 커밋은 무시 + + steps: + - name: Checkout Repository # 저장소 코드 체크아웃 + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Xcode # Xcode 16.2 선택 + run: sudo xcode-select -s /Applications/Xcode_16.2.app + + - name: Install SwiftLint # SwiftLint 설치 + run: brew install swiftlint + + - name: Run SwiftLint Autocorrect # SwiftLint 자동 수정 실행 + run: swiftlint autocorrect + + - name: Commit and Push Changes # 변경 사항 자동 커밋 및 푸시 + run: | + LAST_COMMIT_MSG=$(git log -1 --pretty=%B) + if [[ "$LAST_COMMIT_MSG" == style/*Apply\ SwiftLint\ autocorrect* ]]; then + echo "Skipping: triggered by autocorrect commit." + exit 0 + fi + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + if [ -n "$(git status --porcelain)" ]; then + BRANCH_NAME="${GITHUB_REF_NAME}" + if [[ "$BRANCH_NAME" =~ (#([0-9]+)) ]]; then + ISSUE_NUMBER="${BASH_REMATCH[1]}" + else + ISSUE_NUMBER="#??" + fi + git add . + git commit -m "style/${ISSUE_NUMBER}-Apply SwiftLint autocorrect" + git push + else + echo "No changes to commit" + fi From 3fd8a70d6b515dee0ee663abd8e4799dcc62511d Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 2 Apr 2025 16:26:06 +0900 Subject: [PATCH 35/95] =?UTF-8?q?remove/#93:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=83=80=EA=B2=9F=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Poppool/Poppool.xcodeproj/project.pbxproj | 244 ------------------ Poppool/PoppoolTests/PoppoolTests.swift | 36 --- Poppool/PoppoolUITests/PoppoolUITests.swift | 41 --- .../PoppoolUITestsLaunchTests.swift | 32 --- 4 files changed, 353 deletions(-) delete mode 100644 Poppool/PoppoolTests/PoppoolTests.swift delete mode 100644 Poppool/PoppoolUITests/PoppoolUITests.swift delete mode 100644 Poppool/PoppoolUITests/PoppoolUITestsLaunchTests.swift diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index 0026d3f0..43178a19 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -410,7 +410,6 @@ 4E78706F2D37CB2200465FC9 /* PopUpStoreRegisterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E9C12802D2BE0A6006744D6 /* PopUpStoreRegisterViewController.swift */; }; 4E8AA29D2D59A2340029DF75 /* MarkerTooltipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8AA29C2D59A2340029DF75 /* MarkerTooltipView.swift */; }; 4E9790C52D40E13500210499 /* MapGuideViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E9790C42D40E13500210499 /* MapGuideViewController.swift */; }; - 4E9A465E2D50B2DB0010578A /* AdminStoreCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E755B262D2B9C7C00ADFB21 /* AdminStoreCell.swift */; }; 4E9A46602D55D1270010578A /* MapUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E9A465F2D55D1270010578A /* MapUtilities.swift */; }; 4E9C12782D2BC7A0006744D6 /* AdminBottomSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E9C12772D2BC7A0006744D6 /* AdminBottomSheetView.swift */; }; 4E9C127A2D2BC811006744D6 /* AdminBottomSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E9C12792D2BC811006744D6 /* AdminBottomSheetViewController.swift */; }; @@ -423,7 +422,6 @@ 4EAB809D2D3F78AA0041AF30 /* GMSMapViewDelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAB809C2D3F78AA0041AF30 /* GMSMapViewDelegateProxy.swift */; }; 4EAB809F2D3F8EF50041AF30 /* ViewportBounds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAB809E2D3F8EF50041AF30 /* ViewportBounds.swift */; }; 4EDDEFB42D2D285900CFAFA5 /* DateTimePickerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EDDEFB32D2D285900CFAFA5 /* DateTimePickerManager.swift */; }; - 4EDE57012D5E6A5F0014D924 /* MapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E685ECC2D12CEB6001EF91C /* MapViewController.swift */; }; 4EDE57032D5E70650014D924 /* LocationPermissionBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EDE57022D5E70650014D924 /* LocationPermissionBottomSheet.swift */; }; 4EE5A3D32D40E4A600A2469A /* MapGuideReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE5A3D22D40E4A600A2469A /* MapGuideReactor.swift */; }; 4EEA1D8F2D352012003E7DE9 /* ImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EEA1D8E2D352012003E7DE9 /* ImageCell.swift */; }; @@ -460,9 +458,6 @@ BDCA41C52CF35AC0005EECF6 /* TestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDCA41C42CF35AC0005EECF6 /* TestViewController.swift */; }; BDCA41CA2CF35AC1005EECF6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BDCA41C92CF35AC1005EECF6 /* Assets.xcassets */; }; BDCA41CD2CF35AC1005EECF6 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = BDCA41CC2CF35AC1005EECF6 /* Base */; }; - BDCA41D82CF35AC1005EECF6 /* PoppoolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDCA41D72CF35AC1005EECF6 /* PoppoolTests.swift */; }; - BDCA41E22CF35AC1005EECF6 /* PoppoolUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDCA41E12CF35AC1005EECF6 /* PoppoolUITests.swift */; }; - BDCA41E42CF35AC1005EECF6 /* PoppoolUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDCA41E32CF35AC1005EECF6 /* PoppoolUITestsLaunchTests.swift */; }; BDCA41F22CF35D0D005EECF6 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = BDCA41F12CF35D0D005EECF6 /* SnapKit */; }; BDCA41F52CF35D33005EECF6 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = BDCA41F42CF35D33005EECF6 /* Kingfisher */; }; BDCA41F82CF35D9A005EECF6 /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = BDCA41F72CF35D9A005EECF6 /* RxSwift */; }; @@ -475,23 +470,6 @@ BDCA42102CF35FF5005EECF6 /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = BDCA420F2CF35FF5005EECF6 /* Lottie */; }; /* End PBXBuildFile section */ -/* Begin PBXContainerItemProxy section */ - BDCA41D42CF35AC1005EECF6 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BDCA41B52CF35AC0005EECF6 /* Project object */; - proxyType = 1; - remoteGlobalIDString = BDCA41BC2CF35AC0005EECF6; - remoteInfo = Poppool; - }; - BDCA41DE2CF35AC1005EECF6 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BDCA41B52CF35AC0005EECF6 /* Project object */; - proxyType = 1; - remoteGlobalIDString = BDCA41BC2CF35AC0005EECF6; - remoteInfo = Poppool; - }; -/* End PBXContainerItemProxy section */ - /* Begin PBXFileReference section */ 05229DD02D99519200D88E73 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; 0818988D2D295DC30067BF01 /* MyPageLogoutSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageLogoutSection.swift; sourceTree = ""; }; @@ -941,11 +919,6 @@ BDCA41C92CF35AC1005EECF6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; BDCA41CC2CF35AC1005EECF6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; BDCA41CE2CF35AC1005EECF6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - BDCA41D32CF35AC1005EECF6 /* PoppoolTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PoppoolTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - BDCA41D72CF35AC1005EECF6 /* PoppoolTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PoppoolTests.swift; sourceTree = ""; }; - BDCA41DD2CF35AC1005EECF6 /* PoppoolUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PoppoolUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - BDCA41E12CF35AC1005EECF6 /* PoppoolUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PoppoolUITests.swift; sourceTree = ""; }; - BDCA41E32CF35AC1005EECF6 /* PoppoolUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PoppoolUITestsLaunchTests.swift; sourceTree = ""; }; BDE30CE02CF87A9700C21E08 /* Poppool.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Poppool.entitlements; sourceTree = ""; }; /* End PBXFileReference section */ @@ -974,20 +947,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - BDCA41D02CF35AC1005EECF6 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - BDCA41DA2CF35AC1005EECF6 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -2979,8 +2938,6 @@ children = ( 05229DD02D99519200D88E73 /* .swiftlint.yml */, BDCA41BF2CF35AC0005EECF6 /* Poppool */, - BDCA41D62CF35AC1005EECF6 /* PoppoolTests */, - BDCA41E02CF35AC1005EECF6 /* PoppoolUITests */, BDCA41BE2CF35AC0005EECF6 /* Products */, ); sourceTree = ""; @@ -2989,8 +2946,6 @@ isa = PBXGroup; children = ( BDCA41BD2CF35AC0005EECF6 /* Poppool.app */, - BDCA41D32CF35AC1005EECF6 /* PoppoolTests.xctest */, - BDCA41DD2CF35AC1005EECF6 /* PoppoolUITests.xctest */, ); name = Products; sourceTree = ""; @@ -3009,23 +2964,6 @@ path = Poppool; sourceTree = ""; }; - BDCA41D62CF35AC1005EECF6 /* PoppoolTests */ = { - isa = PBXGroup; - children = ( - BDCA41D72CF35AC1005EECF6 /* PoppoolTests.swift */, - ); - path = PoppoolTests; - sourceTree = ""; - }; - BDCA41E02CF35AC1005EECF6 /* PoppoolUITests */ = { - isa = PBXGroup; - children = ( - BDCA41E12CF35AC1005EECF6 /* PoppoolUITests.swift */, - BDCA41E32CF35AC1005EECF6 /* PoppoolUITestsLaunchTests.swift */, - ); - path = PoppoolUITests; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -3066,42 +3004,6 @@ productReference = BDCA41BD2CF35AC0005EECF6 /* Poppool.app */; productType = "com.apple.product-type.application"; }; - BDCA41D22CF35AC1005EECF6 /* PoppoolTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = BDCA41EA2CF35AC2005EECF6 /* Build configuration list for PBXNativeTarget "PoppoolTests" */; - buildPhases = ( - BDCA41CF2CF35AC1005EECF6 /* Sources */, - BDCA41D02CF35AC1005EECF6 /* Frameworks */, - BDCA41D12CF35AC1005EECF6 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - BDCA41D52CF35AC1005EECF6 /* PBXTargetDependency */, - ); - name = PoppoolTests; - productName = PoppoolTests; - productReference = BDCA41D32CF35AC1005EECF6 /* PoppoolTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - BDCA41DC2CF35AC1005EECF6 /* PoppoolUITests */ = { - isa = PBXNativeTarget; - buildConfigurationList = BDCA41ED2CF35AC2005EECF6 /* Build configuration list for PBXNativeTarget "PoppoolUITests" */; - buildPhases = ( - BDCA41D92CF35AC1005EECF6 /* Sources */, - BDCA41DA2CF35AC1005EECF6 /* Frameworks */, - BDCA41DB2CF35AC1005EECF6 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - BDCA41DF2CF35AC1005EECF6 /* PBXTargetDependency */, - ); - name = PoppoolUITests; - productName = PoppoolUITests; - productReference = BDCA41DD2CF35AC1005EECF6 /* PoppoolUITests.xctest */; - productType = "com.apple.product-type.bundle.ui-testing"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -3115,14 +3017,6 @@ BDCA41BC2CF35AC0005EECF6 = { CreatedOnToolsVersion = 15.4; }; - BDCA41D22CF35AC1005EECF6 = { - CreatedOnToolsVersion = 15.4; - TestTargetID = BDCA41BC2CF35AC0005EECF6; - }; - BDCA41DC2CF35AC1005EECF6 = { - CreatedOnToolsVersion = 15.4; - TestTargetID = BDCA41BC2CF35AC0005EECF6; - }; }; }; buildConfigurationList = BDCA41B82CF35AC0005EECF6 /* Build configuration list for PBXProject "Poppool" */; @@ -3158,8 +3052,6 @@ projectRoot = ""; targets = ( BDCA41BC2CF35AC0005EECF6 /* Poppool */, - BDCA41D22CF35AC1005EECF6 /* PoppoolTests */, - BDCA41DC2CF35AC1005EECF6 /* PoppoolUITests */, ); }; /* End PBXProject section */ @@ -3185,20 +3077,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - BDCA41D12CF35AC1005EECF6 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - BDCA41DB2CF35AC1005EECF6 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -3662,40 +3540,8 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - BDCA41CF2CF35AC1005EECF6 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - BDCA41D82CF35AC1005EECF6 /* PoppoolTests.swift in Sources */, - 4EDE57012D5E6A5F0014D924 /* MapViewController.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - BDCA41D92CF35AC1005EECF6 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - BDCA41E42CF35AC1005EECF6 /* PoppoolUITestsLaunchTests.swift in Sources */, - 4E9A465E2D50B2DB0010578A /* AdminStoreCell.swift in Sources */, - BDCA41E22CF35AC1005EECF6 /* PoppoolUITests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXSourcesBuildPhase section */ -/* Begin PBXTargetDependency section */ - BDCA41D52CF35AC1005EECF6 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = BDCA41BC2CF35AC0005EECF6 /* Poppool */; - targetProxy = BDCA41D42CF35AC1005EECF6 /* PBXContainerItemProxy */; - }; - BDCA41DF2CF35AC1005EECF6 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = BDCA41BC2CF35AC0005EECF6 /* Poppool */; - targetProxy = BDCA41DE2CF35AC1005EECF6 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - /* Begin PBXVariantGroup section */ BDCA41CB2CF35AC1005EECF6 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; @@ -3916,78 +3762,6 @@ }; name = Release; }; - BDCA41EB2CF35AC2005EECF6 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 2U86LHQK8Q; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.5; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.poppool.PoppoolTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Poppool.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Poppool"; - }; - name = Debug; - }; - BDCA41EC2CF35AC2005EECF6 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 2U86LHQK8Q; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.5; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.poppool.PoppoolTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Poppool.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Poppool"; - }; - name = Release; - }; - BDCA41EE2CF35AC2005EECF6 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 2U86LHQK8Q; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.poppool.PoppoolUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = Poppool; - }; - name = Debug; - }; - BDCA41EF2CF35AC2005EECF6 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 2U86LHQK8Q; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.poppool.PoppoolUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = Poppool; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -4009,24 +3783,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - BDCA41EA2CF35AC2005EECF6 /* Build configuration list for PBXNativeTarget "PoppoolTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - BDCA41EB2CF35AC2005EECF6 /* Debug */, - BDCA41EC2CF35AC2005EECF6 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - BDCA41ED2CF35AC2005EECF6 /* Build configuration list for PBXNativeTarget "PoppoolUITests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - BDCA41EE2CF35AC2005EECF6 /* Debug */, - BDCA41EF2CF35AC2005EECF6 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ diff --git a/Poppool/PoppoolTests/PoppoolTests.swift b/Poppool/PoppoolTests/PoppoolTests.swift deleted file mode 100644 index 931a598f..00000000 --- a/Poppool/PoppoolTests/PoppoolTests.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// PoppoolTests.swift -// PoppoolTests -// -// Created by Porori on 11/24/24. -// - -import XCTest -@testable import Poppool - -final class PoppoolTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - // Any test you write for XCTest can be annotated as throws and async. - // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. - // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. - } - - func testPerformanceExample() throws { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/Poppool/PoppoolUITests/PoppoolUITests.swift b/Poppool/PoppoolUITests/PoppoolUITests.swift deleted file mode 100644 index cf8347f1..00000000 --- a/Poppool/PoppoolUITests/PoppoolUITests.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// PoppoolUITests.swift -// PoppoolUITests -// -// Created by Porori on 11/24/24. -// - -import XCTest - -final class PoppoolUITests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - - // In UI tests it is usually best to stop immediately when a failure occurs. - continueAfterFailure = false - - // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // UI tests must launch the application that they test. - let app = XCUIApplication() - app.launch() - - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testLaunchPerformance() throws { - if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { - // This measures how long it takes to launch your application. - measure(metrics: [XCTApplicationLaunchMetric()]) { - XCUIApplication().launch() - } - } - } -} diff --git a/Poppool/PoppoolUITests/PoppoolUITestsLaunchTests.swift b/Poppool/PoppoolUITests/PoppoolUITestsLaunchTests.swift deleted file mode 100644 index e80f88dd..00000000 --- a/Poppool/PoppoolUITests/PoppoolUITestsLaunchTests.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// PoppoolUITestsLaunchTests.swift -// PoppoolUITests -// -// Created by Porori on 11/24/24. -// - -import XCTest - -final class PoppoolUITestsLaunchTests: XCTestCase { - - override class var runsForEachTargetApplicationUIConfiguration: Bool { - true - } - - override func setUpWithError() throws { - continueAfterFailure = false - } - - func testLaunch() throws { - let app = XCUIApplication() - app.launch() - - // Insert steps here to perform after app launch but before taking a screenshot, - // such as logging into a test account or navigating somewhere in the app - - let attachment = XCTAttachment(screenshot: app.screenshot()) - attachment.name = "Launch Screen" - attachment.lifetime = .keepAlways - add(attachment) - } -} From e3cdf104b144297f35b46807bcb93e45def3b63b Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 2 Apr 2025 16:27:21 +0900 Subject: [PATCH 36/95] =?UTF-8?q?feat/#93:=20CI=20=EA=B9=83=ED=97=88?= =?UTF-8?q?=EB=B8=8C=20=EC=95=A1=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Build 테스트 포함 - Swiftlint 테스트 포함 --- .github/workflows/CI.yml | 50 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/workflows/CI.yml diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 00000000..abaa4676 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,50 @@ +name: 📱 iOS CI + +on: + pull_request: + branches: [dev] # dev 브랜치로의 PR에 대해 실행 + push: + branches: [dev] # dev 브랜치로의 push에 대해 실행 + +jobs: + build: + name: Build & Lint Workflow + runs-on: macos-15 # 최신 macOS 15 환경에서 실행 + timeout-minutes: 15 # 최대 실행 시간 15분 설정 + + steps: + - name: ✅ Checkout Repository # 저장소 코드 체크아웃 + uses: actions/checkout@v4 + + - name: 🛠️ Select Xcode 16.2 # Xcode 16.2 버전 사용 설정 + run: sudo xcode-select -s /Applications/Xcode_16.2.app + + - name: ⬇️ Install SwiftLint # SwiftLint 설치 + run: brew install swiftlint + + - name: 🎨 Run SwiftLint # SwiftLint 코드 스타일 검사 실행 + run: swiftlint + + - name: 🔍 Detect Default Scheme # 기본 scheme 자동 검지 + id: detect_scheme + run: | + SCHEME=$(xcodebuild -list -json | jq -r '.project.schemes[0]') + echo "Detected scheme: $SCHEME" + echo "scheme=$SCHEME" >> "$GITHUB_OUTPUT" + + - name: 🔍 Detect Latest iPhone Simulator # 최신 사용 가능한 iPhone 시뮬레이터 검지 + id: detect_latest_simulator + run: | + DEVICE=$(xcrun simctl list devices available | grep -Eo 'iPhone .* \([0-9A-F\-]+\)' | head -n 1) + UDID=$(echo "$DEVICE" | grep -Eo '[0-9A-F\-]{36}') + NAME=$(echo "$DEVICE" | cut -d '(' -f1 | xargs) + echo "Detected simulator: $NAME ($UDID)" + echo "sim_name=$NAME" >> "$GITHUB_OUTPUT" + echo "sim_udid=$UDID" >> "$GITHUB_OUTPUT" + + - name: 🏗️ Build the project # 자동 검지된 Scheme과 Simulator로 빌드 수행 + run: | + xcodebuild -scheme "${{ steps.detect_scheme.outputs.scheme }}" \ + -workspace Poppool.xcworkspace \ + -destination "platform=iOS Simulator,id=${{ steps.detect_latest_simulator.outputs.sim_udid }}" \ + clean build | xcpretty From 4420daffa899fb427b3384c024d649fef5de9015 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 2 Apr 2025 16:43:17 +0900 Subject: [PATCH 37/95] =?UTF-8?q?fix/#93:=20CI=EA=B0=80=20=EA=B9=83?= =?UTF-8?q?=EC=95=A1=EC=85=98=20=EB=B4=87=EC=9D=98=20=EC=BB=A4=EB=B0=8B?= =?UTF-8?q?=EC=9D=80=20=EB=AC=B4=EC=8B=9C=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/CI.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index abaa4676..a9735af5 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -11,6 +11,7 @@ jobs: name: Build & Lint Workflow runs-on: macos-15 # 최신 macOS 15 환경에서 실행 timeout-minutes: 15 # 최대 실행 시간 15분 설정 + if: github.actor != 'github-actions[bot]' # Actions 봇 커밋은 무시 steps: - name: ✅ Checkout Repository # 저장소 코드 체크아웃 From 2fcd2cfb7782848751cae5de63e65b9e2771b083 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 2 Apr 2025 18:37:31 +0900 Subject: [PATCH 38/95] =?UTF-8?q?chore/#93:=20Debug.xcconfig=EB=A1=9C=20?= =?UTF-8?q?=EB=8C=80=EC=B2=B4=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EC=84=A4=EC=A0=95=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Debug.xcconfig를 Resource 폴더에 추가함 - 해당 파일은 ignore로 걸러지도록 함 - info에 Key의 이름을 등록해두어 Bundle로 찾도록 설정 --- Poppool/Poppool.xcodeproj/project.pbxproj | 4 ++++ Poppool/Poppool/Resource/Info.plist | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index 43178a19..a29e30d4 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -472,6 +472,7 @@ /* Begin PBXFileReference section */ 05229DD02D99519200D88E73 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; + 057151572D9D2E0800260615 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; 0818988D2D295DC30067BF01 /* MyPageLogoutSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageLogoutSection.swift; sourceTree = ""; }; 0818988F2D295DC80067BF01 /* MyPageLogoutSectionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageLogoutSectionCell.swift; sourceTree = ""; }; 081898932D2965C20067BF01 /* ProfileEditController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileEditController.swift; sourceTree = ""; }; @@ -1338,6 +1339,7 @@ children = ( 08DE8A132D525A4A0049BCAC /* Strings */, 08B191C12CF615CA0057BC04 /* Secrets.swift */, + 057151572D9D2E0800260615 /* Debug.xcconfig */, 08B191532CF41D6F0057BC04 /* PP_loading.json */, 08B191542CF41D6F0057BC04 /* PP_splash.json */, 08B1914A2CF41D680057BC04 /* Font */, @@ -3678,6 +3680,7 @@ }; BDCA41E82CF35AC2005EECF6 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 057151572D9D2E0800260615 /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -3721,6 +3724,7 @@ }; BDCA41E92CF35AC2005EECF6 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 057151572D9D2E0800260615 /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; diff --git a/Poppool/Poppool/Resource/Info.plist b/Poppool/Poppool/Resource/Info.plist index 9b8b88b1..c4884906 100644 --- a/Poppool/Poppool/Resource/Info.plist +++ b/Poppool/Poppool/Resource/Info.plist @@ -2,6 +2,16 @@ + KAKAO_AUTH_APP_KEY + ${KAKAO_AUTH_APP_KEY} + POPPOOL_BASE_URL + ${POPPOOL_BASE_URL} + POPPOOL_S3_BASE_URL + ${POPPOOL_S3_BASE_URL} + POPPOOL_API_KEY + ${POPPOOL_API_KEY} + NAVER_MAP_CLIENT_ID + ${NAVER_MAP_CLIENT_ID} CFBundleURLTypes From 37a7022648ea0a44e3b59d395542dac994d689c6 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 2 Apr 2025 18:38:57 +0900 Subject: [PATCH 39/95] =?UTF-8?q?chore/#93:=20gitignore=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 새로운 Debug.xcconfig를 무시하도록 수정 - 기존 ignore 설정을 보기 쉽게 정리 --- .gitignore | 51 +++++++++++++++------------------------------------ 1 file changed, 15 insertions(+), 36 deletions(-) diff --git a/.gitignore b/.gitignore index df2cb547..67a5e84e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,65 +1,44 @@ -# Xcode -# -# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore - -## User settings +# Xcode 관련 xcuserdata/ +.DS_Store + +# 개인 설정 및 비밀 정보 +Secrets.swift +*.xcconfig -## Obj-C/Swift specific +# Objective-C / Swift 관련 *.hmap -## App packaging +# 앱 패키징 *.ipa *.dSYM.zip *.dSYM -## Playgrounds +# Playgrounds timeline.xctimeline playground.xcworkspace -# Swift Package Manager -# -# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Swift Package Manager (SPM) +.build/ +# 패키지 관련 파일을 무시하고 싶다면 아래 항목을 활성화하세요. # Packages/ # Package.pins # Package.resolved # *.xcodeproj -# -# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata -# hence it is not needed unless you have added a package configuration file to your project # .swiftpm -.build/ - # CocoaPods -# -# We recommend against adding the Pods directory to your .gitignore. However -# you should judge for yourself, the pros and cons are mentioned at: -# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control -# +# Pods 디렉토리를 무시하고 싶다면 아래 항목을 활성화하세요. # Pods/ -# -# Add this line if you want to avoid checking in source code from the Xcode workspace # *.xcworkspace # Carthage -# -# Add this line if you want to avoid checking in source code from Carthage dependencies. -# Carthage/Checkouts - Carthage/Build/ +# Carthage 의존성을 무시하고 싶다면 아래 항목을 활성화하세요. +# Carthage/Checkouts # fastlane -# -# It is recommended to not store the screenshots in the git repo. -# Instead, use fastlane to re-generate the screenshots whenever they are needed. -# For more information about the recommended setup visit: -# https://docs.fastlane.tools/best-practices/source-control/#source-control - fastlane/report.xml fastlane/Preview.html fastlane/screenshots/**/*.png fastlane/test_output - -.DS_Store -Secrets.swift From 46b93119ee25b6521817cd79bda9c25ca84a9749 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 2 Apr 2025 18:42:07 +0900 Subject: [PATCH 40/95] =?UTF-8?q?refactor/#93:=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=90=9C=20=ED=82=A4=20=EC=A0=80=EC=9E=A5=20=EB=B0=A9=EC=8B=9D?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=ED=82=A4=20=ED=98=B8=EC=B6=9C?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Secrets.swift의 enum을 열도록 할 예정 - 해당 파일에서 Key 호출을 위한 여러 컴퓨티드 프로퍼티 제공 - 변경된 프로퍼티에 맞게 호출 코드도 일부 수정 --- Poppool/Poppool/Application/AppDelegate.swift | 4 +- .../Network/AuthAPI/AuthAPIEndPoint.swift | 4 +- .../CommentAPI/CommentAPIEndPoint.swift | 6 +-- .../Network/HomeAPI/HomeAPIEndpoint.swift | 8 ++-- .../Network/PopUpAPI/PopUpAPIEndPoint.swift | 10 ++--- .../Network/SignUpAPI/SignUpAPIEndpoint.swift | 6 +-- .../Network/UserAPI/UserAPIEndPoint.swift | 42 +++++++++---------- .../PreSignedAPIEndPoint.swift | 6 +-- .../PreSignedService/PreSignedService.swift | 2 +- .../PopUpStoreRegisterViewController.swift | 2 +- .../Presentation/Admin/AdminStoreCell.swift | 2 +- .../Admin/Data/MapDomain/MapAPIEndpoint.swift | 4 +- .../Admin/Data/Remote/AdminAPIEndpoint.swift | 16 +++---- .../Presentation/Extension/UIImageView+.swift | 4 +- .../MapGuideView/FindDirectionEndPoint.swift | 2 +- .../Scene/Detail/DetailReactor.swift | 2 +- 16 files changed, 60 insertions(+), 60 deletions(-) diff --git a/Poppool/Poppool/Application/AppDelegate.swift b/Poppool/Poppool/Application/AppDelegate.swift index 40284a66..0480ecd5 100644 --- a/Poppool/Poppool/Application/AppDelegate.swift +++ b/Poppool/Poppool/Application/AppDelegate.swift @@ -8,8 +8,8 @@ import CoreLocation class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - KakaoSDK.initSDK(appKey: Secrets.kakaoAuthAppkey.rawValue) - GMSServices.provideAPIKey(Secrets.popPoolApiKey.rawValue) + KakaoSDK.initSDK(appKey: Secrets.kakaoAuthAppKey) + GMSServices.provideAPIKey(Secrets.popPoolAPIKey) let locationManager = CLLocationManager() locationManager.requestWhenInUseAuthorization() // 권한 요청 초기화 diff --git a/Poppool/Poppool/Data/Network/AuthAPI/AuthAPIEndPoint.swift b/Poppool/Poppool/Data/Network/AuthAPI/AuthAPIEndPoint.swift index ead5cbee..d556b641 100644 --- a/Poppool/Poppool/Data/Network/AuthAPI/AuthAPIEndPoint.swift +++ b/Poppool/Poppool/Data/Network/AuthAPI/AuthAPIEndPoint.swift @@ -18,7 +18,7 @@ struct AuthAPIEndPoint { /// - Returns: Endpoint static func auth_tryLogin(with userCredential: Encodable, path: String) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/auth/\(path)", method: .post, bodyParameters: userCredential, @@ -28,7 +28,7 @@ struct AuthAPIEndPoint { static func postTokenReissue() -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/auth/token/reissue", method: .post ) diff --git a/Poppool/Poppool/Data/Network/CommentAPI/CommentAPIEndPoint.swift b/Poppool/Poppool/Data/Network/CommentAPI/CommentAPIEndPoint.swift index fda16452..9e799b41 100644 --- a/Poppool/Poppool/Data/Network/CommentAPI/CommentAPIEndPoint.swift +++ b/Poppool/Poppool/Data/Network/CommentAPI/CommentAPIEndPoint.swift @@ -13,7 +13,7 @@ struct CommentAPIEndPoint { static func postCommentAdd(request: PostCommentRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/comments", method: .post, bodyParameters: request @@ -22,7 +22,7 @@ struct CommentAPIEndPoint { static func deleteComment(request: DeleteCommentRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/comments", method: .delete, queryParameters: request @@ -31,7 +31,7 @@ struct CommentAPIEndPoint { static func editComment(request: PutCommentRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/comments", method: .put, bodyParameters: request diff --git a/Poppool/Poppool/Data/Network/HomeAPI/HomeAPIEndpoint.swift b/Poppool/Poppool/Data/Network/HomeAPI/HomeAPIEndpoint.swift index 94a63b0c..dba7b377 100644 --- a/Poppool/Poppool/Data/Network/HomeAPI/HomeAPIEndpoint.swift +++ b/Poppool/Poppool/Data/Network/HomeAPI/HomeAPIEndpoint.swift @@ -13,7 +13,7 @@ struct HomeAPIEndpoint { request: SortedRequestDTO ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/home", method: .get, queryParameters: request @@ -24,7 +24,7 @@ struct HomeAPIEndpoint { request: SortedRequestDTO ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/home/popular/popup-stores", method: .get, queryParameters: request @@ -35,7 +35,7 @@ struct HomeAPIEndpoint { request: SortedRequestDTO ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/home/new/popup-stores", method: .get, queryParameters: request @@ -46,7 +46,7 @@ struct HomeAPIEndpoint { request: SortedRequestDTO ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/home/custom/popup-stores", method: .get, queryParameters: request diff --git a/Poppool/Poppool/Data/Network/PopUpAPI/PopUpAPIEndPoint.swift b/Poppool/Poppool/Data/Network/PopUpAPI/PopUpAPIEndPoint.swift index e163f336..4d115a20 100644 --- a/Poppool/Poppool/Data/Network/PopUpAPI/PopUpAPIEndPoint.swift +++ b/Poppool/Poppool/Data/Network/PopUpAPI/PopUpAPIEndPoint.swift @@ -13,7 +13,7 @@ struct PopUpAPIEndPoint { static func getClosePopUpList(request: GetSearchPopUpListRequestDTO) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/popup/closed", method: .get, queryParameters: request @@ -22,7 +22,7 @@ struct PopUpAPIEndPoint { static func getOpenPopUpList(request: GetSearchPopUpListRequestDTO) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/popup/open", method: .get, queryParameters: request @@ -31,7 +31,7 @@ struct PopUpAPIEndPoint { static func getSearchPopUpList(request: GetSearchPopUpListRequestDTO) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/search/popup-stores", method: .get, queryParameters: request @@ -40,7 +40,7 @@ struct PopUpAPIEndPoint { static func getPopUpDetail(request: GetPopUpDetailRequestDTO) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/popup/\(request.popUpStoreId)/detail", method: .get, queryParameters: request @@ -49,7 +49,7 @@ struct PopUpAPIEndPoint { static func getPopUpComment(request: GetPopUpCommentRequestDTO) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/popup/\(request.popUpStoreId)/comments", method: .get, queryParameters: request diff --git a/Poppool/Poppool/Data/Network/SignUpAPI/SignUpAPIEndpoint.swift b/Poppool/Poppool/Data/Network/SignUpAPI/SignUpAPIEndpoint.swift index dc29870e..b587531b 100644 --- a/Poppool/Poppool/Data/Network/SignUpAPI/SignUpAPIEndpoint.swift +++ b/Poppool/Poppool/Data/Network/SignUpAPI/SignUpAPIEndpoint.swift @@ -14,7 +14,7 @@ struct SignUpAPIEndpoint { /// - Returns: Endpoint static func signUp_checkNickName(with request: CheckNickNameRequestDTO) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/signup/check-nickname", method: .get, queryParameters: request @@ -25,7 +25,7 @@ struct SignUpAPIEndpoint { /// - Returns: Endpoint static func signUp_getCategoryList() -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/signup/categories", method: .get ) @@ -36,7 +36,7 @@ struct SignUpAPIEndpoint { /// - Returns: RequestEndpoint static func signUp_trySignUp(with request: SignUpRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/signup", method: .post, bodyParameters: request diff --git a/Poppool/Poppool/Data/Network/UserAPI/UserAPIEndPoint.swift b/Poppool/Poppool/Data/Network/UserAPI/UserAPIEndPoint.swift index fce30a7d..142d7350 100644 --- a/Poppool/Poppool/Data/Network/UserAPI/UserAPIEndPoint.swift +++ b/Poppool/Poppool/Data/Network/UserAPI/UserAPIEndPoint.swift @@ -13,7 +13,7 @@ struct UserAPIEndPoint { static func postBookmarkPopUp(request: PostBookmarkPopUpRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/users/bookmark-popupstores", method: .post, queryParameters: request @@ -22,7 +22,7 @@ struct UserAPIEndPoint { static func deleteBookmarkPopUp(request: PostBookmarkPopUpRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/users/bookmark-popupstores", method: .delete, queryParameters: request @@ -31,7 +31,7 @@ struct UserAPIEndPoint { static func postCommentLike(request: CommentLikeRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/likes", method: .post, queryParameters: request @@ -40,7 +40,7 @@ struct UserAPIEndPoint { static func deleteCommentLike(request: CommentLikeRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/likes", method: .delete, queryParameters: request @@ -49,7 +49,7 @@ struct UserAPIEndPoint { static func postUserBlock(request: PostUserBlockRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/users/block", method: .post, queryParameters: request @@ -58,7 +58,7 @@ struct UserAPIEndPoint { static func deleteUserBlock(request: PostUserBlockRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/users/unblock", method: .delete, queryParameters: request @@ -67,7 +67,7 @@ struct UserAPIEndPoint { static func getOtherUserCommentPopUpList(request: GetOtherUserCommentListRequestDTO) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/users/\(request.commenterId ?? "")/comments", method: .get, queryParameters: request @@ -76,7 +76,7 @@ struct UserAPIEndPoint { static func getMyPage() -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/users/my-page", method: .get ) @@ -84,7 +84,7 @@ struct UserAPIEndPoint { static func postLogout() -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/users/logout", method: .post ) @@ -92,7 +92,7 @@ struct UserAPIEndPoint { static func getWithdrawlList() -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/users/withdrawl/surveys", method: .get ) @@ -100,7 +100,7 @@ struct UserAPIEndPoint { static func postWithdrawl(request: PostWithdrawlListRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/users/delete", method: .post, bodyParameters: request @@ -109,7 +109,7 @@ struct UserAPIEndPoint { static func getMyProfile() -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/users/profiles", method: .get ) @@ -117,7 +117,7 @@ struct UserAPIEndPoint { static func putUserTailoredInfo(request: PutUserTailoredInfoRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/users/tailored-info", method: .put, bodyParameters: request @@ -126,7 +126,7 @@ struct UserAPIEndPoint { static func putUserCategory(request: PutUserCategoryRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/users/interests", method: .put, bodyParameters: request @@ -135,7 +135,7 @@ struct UserAPIEndPoint { static func putUserProfile(request: PutUserProfileRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/users/profiles", method: .put, bodyParameters: request @@ -144,7 +144,7 @@ struct UserAPIEndPoint { static func getMyCommentedPopUp(request: SortedRequestDTO) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/users/commented/popup", method: .get, queryParameters: request @@ -153,7 +153,7 @@ struct UserAPIEndPoint { static func getBlockUserList(request: GetBlockUserListRequestDTO) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/users/blocked", method: .get, queryParameters: request @@ -162,7 +162,7 @@ struct UserAPIEndPoint { static func getNoticeList() -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/notice/list", method: .get ) @@ -170,7 +170,7 @@ struct UserAPIEndPoint { static func getNoticeDetail(noticeID: Int64) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/notice/\(noticeID)", method: .get ) @@ -178,7 +178,7 @@ struct UserAPIEndPoint { static func getRecentPopUp(request: SortedRequestDTO) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/users/recent-popupstores", method: .get, queryParameters: request @@ -187,7 +187,7 @@ struct UserAPIEndPoint { static func getBookmarkPopUp(request: SortedRequestDTO) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/users/bookmark-popupstores", method: .get, queryParameters: request diff --git a/Poppool/Poppool/Infrastructure/PreSignedService/PreSignedAPIEndPoint.swift b/Poppool/Poppool/Infrastructure/PreSignedService/PreSignedAPIEndPoint.swift index a1bba549..556bf5d2 100644 --- a/Poppool/Poppool/Infrastructure/PreSignedService/PreSignedAPIEndPoint.swift +++ b/Poppool/Poppool/Infrastructure/PreSignedService/PreSignedAPIEndPoint.swift @@ -11,7 +11,7 @@ struct PreSignedAPIEndPoint { static func presigned_upload(request: PresignedURLRequestDTO) -> Endpoint { Logger.log(message: "Presigned URL 생성 - Request: \(request)", category: .debug) return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/files/upload-preSignedUrl", method: .post, bodyParameters: request @@ -21,7 +21,7 @@ struct PreSignedAPIEndPoint { static func presigned_download(request: PresignedURLRequestDTO) -> Endpoint { Logger.log(message: "Presigned Download URL 생성 - Request: \(request)", category: .debug) return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/files/download-preSignedUrl", method: .post, bodyParameters: request @@ -31,7 +31,7 @@ struct PreSignedAPIEndPoint { static func presigned_delete(request: PresignedURLRequestDTO) -> RequestEndpoint { Logger.log(message: "Presigned Delete 생성 - Request: \(request)", category: .debug) return RequestEndpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/files/delete", method: .post, bodyParameters: request diff --git a/Poppool/Poppool/Infrastructure/PreSignedService/PreSignedService.swift b/Poppool/Poppool/Infrastructure/PreSignedService/PreSignedService.swift index 73343587..61665b4e 100644 --- a/Poppool/Poppool/Infrastructure/PreSignedService/PreSignedService.swift +++ b/Poppool/Poppool/Infrastructure/PreSignedService/PreSignedService.swift @@ -291,7 +291,7 @@ extension PreSignedService { } } func fullImageURL(from filePath: String) -> URL? { - let baseURL = Secrets.popPoolS3BaseURL.rawValue + let baseURL = Secrets.popPoolS3BaseURL // URL 인코딩 처리를 더 엄격하게 guard let encodedPath = filePath diff --git a/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift b/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift index a559ee54..710f001f 100644 --- a/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift +++ b/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift @@ -1244,7 +1244,7 @@ private extension PopUpStoreRegisterViewController { // 이미지 업로드 private func uploadImages() { let uuid = UUID().uuidString - // let baseS3URL = Secrets.popPoolS3BaseURL.rawValue + // let baseS3URL = Secrets.popPoolS3BaseURL let updatedImages = images.enumerated().map { index, image in let filePath = "PopUpImage/\(nameField?.text ?? "")/\(uuid)/\(index).jpg" return ExtendedImage( diff --git a/Poppool/Poppool/Presentation/Admin/AdminStoreCell.swift b/Poppool/Poppool/Presentation/Admin/AdminStoreCell.swift index 706addaf..46e8d56c 100644 --- a/Poppool/Poppool/Presentation/Admin/AdminStoreCell.swift +++ b/Poppool/Poppool/Presentation/Admin/AdminStoreCell.swift @@ -83,7 +83,7 @@ final class AdminStoreCell: UITableViewCell { statusChip.text = "운영" // mainImageUrl에서 baseURL 부분 제거 - let imagePath = store.mainImageUrl.replacingOccurrences(of: Secrets.popPoolS3BaseURL.rawValue, with: "") + let imagePath = store.mainImageUrl.replacingOccurrences(of: Secrets.popPoolS3BaseURL, with: "") Logger.log(message: "이미지 경로: \(imagePath)", category: .debug) storeImageView.setPPImage(path: imagePath) } diff --git a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapAPIEndpoint.swift b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapAPIEndpoint.swift index e9da01f6..66806958 100644 --- a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapAPIEndpoint.swift +++ b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapAPIEndpoint.swift @@ -26,7 +26,7 @@ struct MapAPIEndpoint { ) return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/locations/popup-stores", method: .get, queryParameters: params @@ -44,7 +44,7 @@ struct MapAPIEndpoint { ) return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/locations/search", method: .get, queryParameters: params diff --git a/Poppool/Poppool/Presentation/Admin/Data/Remote/AdminAPIEndpoint.swift b/Poppool/Poppool/Presentation/Admin/Data/Remote/AdminAPIEndpoint.swift index cfb9baf3..2f9a6433 100644 --- a/Poppool/Poppool/Presentation/Admin/Data/Remote/AdminAPIEndpoint.swift +++ b/Poppool/Poppool/Presentation/Admin/Data/Remote/AdminAPIEndpoint.swift @@ -14,7 +14,7 @@ struct AdminAPIEndpoint { size: size ) return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/admin/popup-stores/list", method: .get, queryParameters: params @@ -26,7 +26,7 @@ struct AdminAPIEndpoint { id: Int64 ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/admin/popup-stores", method: .get, queryParameters: ["popUpStoreId": id] @@ -38,7 +38,7 @@ struct AdminAPIEndpoint { request: CreatePopUpStoreRequestDTO ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/admin/popup-stores", method: .post, bodyParameters: request @@ -50,7 +50,7 @@ struct AdminAPIEndpoint { request: UpdatePopUpStoreRequestDTO ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/admin/popup-stores", method: .put, bodyParameters: request @@ -62,7 +62,7 @@ struct AdminAPIEndpoint { id: Int64 ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/admin/popup-stores", method: .delete, queryParameters: ["popUpStoreId": id] @@ -74,7 +74,7 @@ struct AdminAPIEndpoint { request: CreateNoticeRequestDTO ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/admin/notice", method: .post, bodyParameters: request @@ -86,7 +86,7 @@ struct AdminAPIEndpoint { request: UpdateNoticeRequestDTO ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/admin/notice/\(id)", method: .put, bodyParameters: request @@ -97,7 +97,7 @@ struct AdminAPIEndpoint { id: Int64 ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/admin/notice/\(id)", method: .delete ) diff --git a/Poppool/Poppool/Presentation/Extension/UIImageView+.swift b/Poppool/Poppool/Presentation/Extension/UIImageView+.swift index 8040bddb..03de32eb 100644 --- a/Poppool/Poppool/Presentation/Extension/UIImageView+.swift +++ b/Poppool/Poppool/Presentation/Extension/UIImageView+.swift @@ -15,7 +15,7 @@ extension UIImageView { self.image = UIImage(named: "image_default") return } - let imageURLString = Secrets.popPoolS3BaseURL.rawValue + path + let imageURLString = Secrets.popPoolS3BaseURL + path if let cenvertimageURL = imageURLString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) { let imageURL = URL(string: cenvertimageURL) self.kf.setImage(with: imageURL) { result in @@ -35,7 +35,7 @@ extension UIImageView { completion() return } - let imageURLString = Secrets.popPoolS3BaseURL.rawValue + path + let imageURLString = Secrets.popPoolS3BaseURL + path if let cenvertimageURL = imageURLString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) { let imageURL = URL(string: cenvertimageURL) self.kf.setImage(with: imageURL) { result in diff --git a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FindDirectionEndPoint.swift b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FindDirectionEndPoint.swift index 973a81e5..b9a7c5ea 100644 --- a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FindDirectionEndPoint.swift +++ b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FindDirectionEndPoint.swift @@ -13,7 +13,7 @@ struct FindDirectionEndPoint { popUpStoreId: Int64 ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseUrl.rawValue, + baseURL: Secrets.popPoolBaseURL, path: "/popup/\(popUpStoreId)/directions", method: .get ) diff --git a/Poppool/Poppool/Presentation/Scene/Detail/DetailReactor.swift b/Poppool/Poppool/Presentation/Scene/Detail/DetailReactor.swift index 165a8b7a..3b910a9b 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/DetailReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/DetailReactor.swift @@ -381,7 +381,7 @@ final class DetailReactor: Reactor { func showSharedBoard(controller: BaseViewController) { let storeName = titleSection.inputDataList.first?.title ?? "" - let imagePath = Secrets.popPoolS3BaseURL.rawValue + (imageBannerSection.inputDataList.first?.imagePaths.first ?? "") + let imagePath = Secrets.popPoolS3BaseURL + (imageBannerSection.inputDataList.first?.imagePaths.first ?? "") // URL 인코딩 후 생성 guard let encodedPath = imagePath.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), From 7af96b8af3519b25a2f30e8c584876f59bc4697b Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 2 Apr 2025 19:02:52 +0900 Subject: [PATCH 41/95] =?UTF-8?q?refactor/#93:=20GitHubActions=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=B6=94=EA=B0=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 깃헙 액션에 맞게 파일 이름 수정 - CI → ci - Swiftlint autocorrect → swiftlint-autocorrect - 추가로 autocorrect의 작동 시기를 Push → PR open으로 수정하여 깃액션 작동 빈도를 줄임 --- .github/workflows/Swiftlint autocorrect.yml | 51 --------------------- .github/workflows/{CI.yml => ios-ci.yml} | 2 +- .github/workflows/swiftlint-autocorrect.yml | 47 +++++++++++++++++++ 3 files changed, 48 insertions(+), 52 deletions(-) delete mode 100644 .github/workflows/Swiftlint autocorrect.yml rename .github/workflows/{CI.yml => ios-ci.yml} (96%) create mode 100644 .github/workflows/swiftlint-autocorrect.yml diff --git a/.github/workflows/Swiftlint autocorrect.yml b/.github/workflows/Swiftlint autocorrect.yml deleted file mode 100644 index 11cc6761..00000000 --- a/.github/workflows/Swiftlint autocorrect.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: SwiftLint Autocorrect - -on: - push: - branches: ['dev/**'] - -jobs: - autocorrect: - name: SwiftLint Autocorrect - runs-on: macos-15 - if: github.actor != 'github-actions[bot]' # 커밋 무한 루프 방지를 위해 봇 커밋은 무시 - - steps: - - name: Checkout Repository # 저장소 코드 체크아웃 - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Xcode # Xcode 16.2 선택 - run: sudo xcode-select -s /Applications/Xcode_16.2.app - - - name: Install SwiftLint # SwiftLint 설치 - run: brew install swiftlint - - - name: Run SwiftLint Autocorrect # SwiftLint 자동 수정 실행 - run: swiftlint autocorrect - - - name: Commit and Push Changes # 변경 사항 자동 커밋 및 푸시 - run: | - LAST_COMMIT_MSG=$(git log -1 --pretty=%B) - if [[ "$LAST_COMMIT_MSG" == style/*Apply\ SwiftLint\ autocorrect* ]]; then - echo "Skipping: triggered by autocorrect commit." - exit 0 - fi - - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - if [ -n "$(git status --porcelain)" ]; then - BRANCH_NAME="${GITHUB_REF_NAME}" - if [[ "$BRANCH_NAME" =~ (#([0-9]+)) ]]; then - ISSUE_NUMBER="${BASH_REMATCH[1]}" - else - ISSUE_NUMBER="#??" - fi - git add . - git commit -m "style/${ISSUE_NUMBER}-Apply SwiftLint autocorrect" - git push - else - echo "No changes to commit" - fi diff --git a/.github/workflows/CI.yml b/.github/workflows/ios-ci.yml similarity index 96% rename from .github/workflows/CI.yml rename to .github/workflows/ios-ci.yml index a9735af5..9b39b9c8 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/ios-ci.yml @@ -17,7 +17,7 @@ jobs: - name: ✅ Checkout Repository # 저장소 코드 체크아웃 uses: actions/checkout@v4 - - name: 🛠️ Select Xcode 16.2 # Xcode 16.2 버전 사용 설정 + - name: 🔨 Select Xcode 16.2 # Xcode 16.2 버전 사용 설정 run: sudo xcode-select -s /Applications/Xcode_16.2.app - name: ⬇️ Install SwiftLint # SwiftLint 설치 diff --git a/.github/workflows/swiftlint-autocorrect.yml b/.github/workflows/swiftlint-autocorrect.yml new file mode 100644 index 00000000..895d5868 --- /dev/null +++ b/.github/workflows/swiftlint-autocorrect.yml @@ -0,0 +1,47 @@ +name: 🔧 Swiftlint Autocorrect + +on: + pull_request: + types: [opened] + branches: ['dev/**'] + +jobs: + autocorrect: + name: SwiftLint Autocorrect + runs-on: macos-15 + if: github.actor != 'github-actions[bot]' # 커밋 무한 루프 방지를 위해 봇 커밋은 무시 + + steps: + - name: 📁 Checkout Repository # 저장소 코드 체크아웃 + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: 🔨 Set up Xcode # Xcode 16.2 선택 + run: sudo xcode-select -s /Applications/Xcode_16.2.app + + - name: ⬇️ Install SwiftLint # SwiftLint 설치 + run: brew install swiftlint + + - name: 🔧 Run SwiftLint Autocorrect # SwiftLint 자동 수정 실행 + run: swiftlint autocorrect + + - name: 🚀 Commit and Push Changes # 변경 사항 자동 커밋 및 푸시 + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + BRANCH_NAME="${GITHUB_REF_NAME}" + if [[ "$BRANCH_NAME" =~ (#([0-9]+)) ]]; then + ISSUE_NUMBER="${BASH_REMATCH[1]}" + else + ISSUE_NUMBER="#??" + fi + + if [ -n "$(git status --porcelain)" ]; then + git add . + git commit -m "style/${ISSUE_NUMBER}: Apply SwiftLint autocorrect" + git push + else + echo "No changes to commit" + fi From fb7918fae2c20a9c793288e7d2355cc4c8a81f00 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 2 Apr 2025 22:57:10 +0900 Subject: [PATCH 42/95] =?UTF-8?q?chore/#93:=20CI=EC=97=90=20Debug.xcconfig?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20=EC=8A=A4=ED=85=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ios-ci.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 9b39b9c8..98f1a3a9 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -17,6 +17,16 @@ jobs: - name: ✅ Checkout Repository # 저장소 코드 체크아웃 uses: actions/checkout@v4 + - name: 🧾 Generate Debug.xcconfig + run: | + cat < Poppool/Poppool/Resource/Debug.xcconfig + KAKAO_AUTH_APP_KEY=${{ secrets.KAKAO_AUTH_APP_KEY }} + NAVER_MAP_CLIENT_ID=${{ secrets.NAVER_MAP_CLIENT_ID }} + POPPOOL_BASE_URL=${{ secrets.POPPOOL_BASE_URL }} + POPPOOL_S3_BASE_URL=${{ secrets.POPPOOL_S3_BASE_URL }} + POPPOOL_API_KEY=${{ secrets.POPPOOL_API_KEY }} + EOF + - name: 🔨 Select Xcode 16.2 # Xcode 16.2 버전 사용 설정 run: sudo xcode-select -s /Applications/Xcode_16.2.app From b00d50bd35616eaad8504918f26b0afcd4870946 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 2 Apr 2025 23:15:07 +0900 Subject: [PATCH 43/95] =?UTF-8?q?refactor/#93:=20Secrets.swift=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Secrets enum을 대체할 KeyPath enum을 추가 - gitignore에서 Secrets.swift 추적 제한 제거 - 빌드 파일 수정 --- .gitignore | 1 - Poppool/Poppool.xcodeproj/project.pbxproj | 8 +++--- Poppool/Poppool/Resource/KeyPath.swift | 30 +++++++++++++++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 Poppool/Poppool/Resource/KeyPath.swift diff --git a/.gitignore b/.gitignore index 67a5e84e..3dd82707 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ xcuserdata/ .DS_Store # 개인 설정 및 비밀 정보 -Secrets.swift *.xcconfig # Objective-C / Swift 관련 diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index a29e30d4..d0a41648 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -336,7 +336,7 @@ 08B191B42CF609260057BC04 /* KakaoLoginService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08B191B32CF609260057BC04 /* KakaoLoginService.swift */; }; 08B191B62CF6092B0057BC04 /* AppleLoginService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08B191B52CF6092B0057BC04 /* AppleLoginService.swift */; }; 08B191B82CF6092F0057BC04 /* AuthServiceable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08B191B72CF6092F0057BC04 /* AuthServiceable.swift */; }; - 08B191C22CF615CA0057BC04 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08B191C12CF615CA0057BC04 /* Secrets.swift */; }; + 08B191C22CF615CA0057BC04 /* KeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08B191C12CF615CA0057BC04 /* KeyPath.swift */; }; 08CBEA032D38989E00248007 /* PostTokenReissueResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBEA022D38989E00248007 /* PostTokenReissueResponseDTO.swift */; }; 08CBEA062D38991600248007 /* PostTokenReissueResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBEA052D38991600248007 /* PostTokenReissueResponse.swift */; }; 08CBEA0B2D38DBD600248007 /* LastLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBEA0A2D38DBD600248007 /* LastLoginView.swift */; }; @@ -799,7 +799,7 @@ 08B191B32CF609260057BC04 /* KakaoLoginService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KakaoLoginService.swift; sourceTree = ""; }; 08B191B52CF6092B0057BC04 /* AppleLoginService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppleLoginService.swift; sourceTree = ""; }; 08B191B72CF6092F0057BC04 /* AuthServiceable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthServiceable.swift; sourceTree = ""; }; - 08B191C12CF615CA0057BC04 /* Secrets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Secrets.swift; sourceTree = ""; }; + 08B191C12CF615CA0057BC04 /* KeyPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyPath.swift; sourceTree = ""; }; 08CBEA022D38989E00248007 /* PostTokenReissueResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostTokenReissueResponseDTO.swift; sourceTree = ""; }; 08CBEA052D38991600248007 /* PostTokenReissueResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostTokenReissueResponse.swift; sourceTree = ""; }; 08CBEA0A2D38DBD600248007 /* LastLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastLoginView.swift; sourceTree = ""; }; @@ -1338,7 +1338,7 @@ isa = PBXGroup; children = ( 08DE8A132D525A4A0049BCAC /* Strings */, - 08B191C12CF615CA0057BC04 /* Secrets.swift */, + 08B191C12CF615CA0057BC04 /* KeyPath.swift */, 057151572D9D2E0800260615 /* Debug.xcconfig */, 08B191532CF41D6F0057BC04 /* PP_loading.json */, 08B191542CF41D6F0057BC04 /* PP_splash.json */, @@ -3218,7 +3218,7 @@ 086DD9402D01EEEB00B97D3B /* SearchCountTitleSection.swift in Sources */, 083C864C2D0DCF9B003F441C /* AddCommentDescriptionSectionCell.swift in Sources */, 086DD8CE2CFDFEB000B97D3B /* HomeListView.swift in Sources */, - 08B191C22CF615CA0057BC04 /* Secrets.swift in Sources */, + 08B191C22CF615CA0057BC04 /* KeyPath.swift in Sources */, 083C86242D087A44003F441C /* DetailTitleSection.swift in Sources */, 08A2E4822D1BCDEA00102313 /* CommentDetailController.swift in Sources */, 0841BAA32CFA31A300049E31 /* SpacingSection.swift in Sources */, diff --git a/Poppool/Poppool/Resource/KeyPath.swift b/Poppool/Poppool/Resource/KeyPath.swift new file mode 100644 index 00000000..4bc23b90 --- /dev/null +++ b/Poppool/Poppool/Resource/KeyPath.swift @@ -0,0 +1,30 @@ +import Foundation + +enum KeyPath { + static var kakaoAuthAppKey: String { + return getValue(forKey: "KAKAO_AUTH_APP_KEY") + } + + static var popPoolBaseURL: String { + return getValue(forKey: "POPPOOL_BASE_URL") + } + + static var popPoolS3BaseURL: String { + return getValue(forKey: "POPPOOL_S3_BASE_URL") + } + + static var popPoolAPIKey: String { + return getValue(forKey: "POPPOOL_API_KEY") + } + + static var naverMapClientID: String { + return getValue(forKey: "NAVER_MAP_CLIENT_ID") + } + + private static func getValue(forKey key: String) -> String { + guard let value = Bundle.main.object(forInfoDictionaryKey: key) as? String else { + fatalError("Missing key: \(key) in Info.plist") + } + return value + } +} From f958ff67ac5ce5a521e3dc780223d4a3f9547d8e Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 2 Apr 2025 23:15:39 +0900 Subject: [PATCH 44/95] =?UTF-8?q?refactor/#93:=20Secrets=20enum=20?= =?UTF-8?q?=E2=86=92=20KeyPath=20enum=20=EB=B3=80=EA=B2=BD=20=EB=8C=80?= =?UTF-8?q?=EC=9D=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Poppool/Poppool/Application/AppDelegate.swift | 4 +- .../Network/AuthAPI/AuthAPIEndPoint.swift | 4 +- .../CommentAPI/CommentAPIEndPoint.swift | 6 +-- .../Network/HomeAPI/HomeAPIEndpoint.swift | 8 ++-- .../Network/PopUpAPI/PopUpAPIEndPoint.swift | 10 ++--- .../Network/SignUpAPI/SignUpAPIEndpoint.swift | 6 +-- .../Network/UserAPI/UserAPIEndPoint.swift | 42 +++++++++---------- .../PreSignedAPIEndPoint.swift | 6 +-- .../PreSignedService/PreSignedService.swift | 2 +- .../Presentation/Admin/AdminStoreCell.swift | 2 +- .../Admin/Data/MapDomain/MapAPIEndpoint.swift | 4 +- .../Admin/Data/Remote/AdminAPIEndpoint.swift | 16 +++---- .../Presentation/Extension/UIImageView+.swift | 4 +- .../MapGuideView/FindDirectionEndPoint.swift | 2 +- .../Scene/Detail/DetailReactor.swift | 2 +- 15 files changed, 59 insertions(+), 59 deletions(-) diff --git a/Poppool/Poppool/Application/AppDelegate.swift b/Poppool/Poppool/Application/AppDelegate.swift index 0480ecd5..200881dc 100644 --- a/Poppool/Poppool/Application/AppDelegate.swift +++ b/Poppool/Poppool/Application/AppDelegate.swift @@ -8,8 +8,8 @@ import CoreLocation class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - KakaoSDK.initSDK(appKey: Secrets.kakaoAuthAppKey) - GMSServices.provideAPIKey(Secrets.popPoolAPIKey) + KakaoSDK.initSDK(appKey: KeyPath.kakaoAuthAppKey) + GMSServices.provideAPIKey(KeyPath.popPoolAPIKey) let locationManager = CLLocationManager() locationManager.requestWhenInUseAuthorization() // 권한 요청 초기화 diff --git a/Poppool/Poppool/Data/Network/AuthAPI/AuthAPIEndPoint.swift b/Poppool/Poppool/Data/Network/AuthAPI/AuthAPIEndPoint.swift index d556b641..860fb485 100644 --- a/Poppool/Poppool/Data/Network/AuthAPI/AuthAPIEndPoint.swift +++ b/Poppool/Poppool/Data/Network/AuthAPI/AuthAPIEndPoint.swift @@ -18,7 +18,7 @@ struct AuthAPIEndPoint { /// - Returns: Endpoint static func auth_tryLogin(with userCredential: Encodable, path: String) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/auth/\(path)", method: .post, bodyParameters: userCredential, @@ -28,7 +28,7 @@ struct AuthAPIEndPoint { static func postTokenReissue() -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/auth/token/reissue", method: .post ) diff --git a/Poppool/Poppool/Data/Network/CommentAPI/CommentAPIEndPoint.swift b/Poppool/Poppool/Data/Network/CommentAPI/CommentAPIEndPoint.swift index 9e799b41..6c91aefb 100644 --- a/Poppool/Poppool/Data/Network/CommentAPI/CommentAPIEndPoint.swift +++ b/Poppool/Poppool/Data/Network/CommentAPI/CommentAPIEndPoint.swift @@ -13,7 +13,7 @@ struct CommentAPIEndPoint { static func postCommentAdd(request: PostCommentRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/comments", method: .post, bodyParameters: request @@ -22,7 +22,7 @@ struct CommentAPIEndPoint { static func deleteComment(request: DeleteCommentRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/comments", method: .delete, queryParameters: request @@ -31,7 +31,7 @@ struct CommentAPIEndPoint { static func editComment(request: PutCommentRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/comments", method: .put, bodyParameters: request diff --git a/Poppool/Poppool/Data/Network/HomeAPI/HomeAPIEndpoint.swift b/Poppool/Poppool/Data/Network/HomeAPI/HomeAPIEndpoint.swift index dba7b377..4140756c 100644 --- a/Poppool/Poppool/Data/Network/HomeAPI/HomeAPIEndpoint.swift +++ b/Poppool/Poppool/Data/Network/HomeAPI/HomeAPIEndpoint.swift @@ -13,7 +13,7 @@ struct HomeAPIEndpoint { request: SortedRequestDTO ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/home", method: .get, queryParameters: request @@ -24,7 +24,7 @@ struct HomeAPIEndpoint { request: SortedRequestDTO ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/home/popular/popup-stores", method: .get, queryParameters: request @@ -35,7 +35,7 @@ struct HomeAPIEndpoint { request: SortedRequestDTO ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/home/new/popup-stores", method: .get, queryParameters: request @@ -46,7 +46,7 @@ struct HomeAPIEndpoint { request: SortedRequestDTO ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/home/custom/popup-stores", method: .get, queryParameters: request diff --git a/Poppool/Poppool/Data/Network/PopUpAPI/PopUpAPIEndPoint.swift b/Poppool/Poppool/Data/Network/PopUpAPI/PopUpAPIEndPoint.swift index 4d115a20..6e12dd8d 100644 --- a/Poppool/Poppool/Data/Network/PopUpAPI/PopUpAPIEndPoint.swift +++ b/Poppool/Poppool/Data/Network/PopUpAPI/PopUpAPIEndPoint.swift @@ -13,7 +13,7 @@ struct PopUpAPIEndPoint { static func getClosePopUpList(request: GetSearchPopUpListRequestDTO) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/popup/closed", method: .get, queryParameters: request @@ -22,7 +22,7 @@ struct PopUpAPIEndPoint { static func getOpenPopUpList(request: GetSearchPopUpListRequestDTO) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/popup/open", method: .get, queryParameters: request @@ -31,7 +31,7 @@ struct PopUpAPIEndPoint { static func getSearchPopUpList(request: GetSearchPopUpListRequestDTO) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/search/popup-stores", method: .get, queryParameters: request @@ -40,7 +40,7 @@ struct PopUpAPIEndPoint { static func getPopUpDetail(request: GetPopUpDetailRequestDTO) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/popup/\(request.popUpStoreId)/detail", method: .get, queryParameters: request @@ -49,7 +49,7 @@ struct PopUpAPIEndPoint { static func getPopUpComment(request: GetPopUpCommentRequestDTO) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/popup/\(request.popUpStoreId)/comments", method: .get, queryParameters: request diff --git a/Poppool/Poppool/Data/Network/SignUpAPI/SignUpAPIEndpoint.swift b/Poppool/Poppool/Data/Network/SignUpAPI/SignUpAPIEndpoint.swift index b587531b..d36cc605 100644 --- a/Poppool/Poppool/Data/Network/SignUpAPI/SignUpAPIEndpoint.swift +++ b/Poppool/Poppool/Data/Network/SignUpAPI/SignUpAPIEndpoint.swift @@ -14,7 +14,7 @@ struct SignUpAPIEndpoint { /// - Returns: Endpoint static func signUp_checkNickName(with request: CheckNickNameRequestDTO) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/signup/check-nickname", method: .get, queryParameters: request @@ -25,7 +25,7 @@ struct SignUpAPIEndpoint { /// - Returns: Endpoint static func signUp_getCategoryList() -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/signup/categories", method: .get ) @@ -36,7 +36,7 @@ struct SignUpAPIEndpoint { /// - Returns: RequestEndpoint static func signUp_trySignUp(with request: SignUpRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/signup", method: .post, bodyParameters: request diff --git a/Poppool/Poppool/Data/Network/UserAPI/UserAPIEndPoint.swift b/Poppool/Poppool/Data/Network/UserAPI/UserAPIEndPoint.swift index 142d7350..4916c847 100644 --- a/Poppool/Poppool/Data/Network/UserAPI/UserAPIEndPoint.swift +++ b/Poppool/Poppool/Data/Network/UserAPI/UserAPIEndPoint.swift @@ -13,7 +13,7 @@ struct UserAPIEndPoint { static func postBookmarkPopUp(request: PostBookmarkPopUpRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/users/bookmark-popupstores", method: .post, queryParameters: request @@ -22,7 +22,7 @@ struct UserAPIEndPoint { static func deleteBookmarkPopUp(request: PostBookmarkPopUpRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/users/bookmark-popupstores", method: .delete, queryParameters: request @@ -31,7 +31,7 @@ struct UserAPIEndPoint { static func postCommentLike(request: CommentLikeRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/likes", method: .post, queryParameters: request @@ -40,7 +40,7 @@ struct UserAPIEndPoint { static func deleteCommentLike(request: CommentLikeRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/likes", method: .delete, queryParameters: request @@ -49,7 +49,7 @@ struct UserAPIEndPoint { static func postUserBlock(request: PostUserBlockRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/users/block", method: .post, queryParameters: request @@ -58,7 +58,7 @@ struct UserAPIEndPoint { static func deleteUserBlock(request: PostUserBlockRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/users/unblock", method: .delete, queryParameters: request @@ -67,7 +67,7 @@ struct UserAPIEndPoint { static func getOtherUserCommentPopUpList(request: GetOtherUserCommentListRequestDTO) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/users/\(request.commenterId ?? "")/comments", method: .get, queryParameters: request @@ -76,7 +76,7 @@ struct UserAPIEndPoint { static func getMyPage() -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/users/my-page", method: .get ) @@ -84,7 +84,7 @@ struct UserAPIEndPoint { static func postLogout() -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/users/logout", method: .post ) @@ -92,7 +92,7 @@ struct UserAPIEndPoint { static func getWithdrawlList() -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/users/withdrawl/surveys", method: .get ) @@ -100,7 +100,7 @@ struct UserAPIEndPoint { static func postWithdrawl(request: PostWithdrawlListRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/users/delete", method: .post, bodyParameters: request @@ -109,7 +109,7 @@ struct UserAPIEndPoint { static func getMyProfile() -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/users/profiles", method: .get ) @@ -117,7 +117,7 @@ struct UserAPIEndPoint { static func putUserTailoredInfo(request: PutUserTailoredInfoRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/users/tailored-info", method: .put, bodyParameters: request @@ -126,7 +126,7 @@ struct UserAPIEndPoint { static func putUserCategory(request: PutUserCategoryRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/users/interests", method: .put, bodyParameters: request @@ -135,7 +135,7 @@ struct UserAPIEndPoint { static func putUserProfile(request: PutUserProfileRequestDTO) -> RequestEndpoint { return RequestEndpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/users/profiles", method: .put, bodyParameters: request @@ -144,7 +144,7 @@ struct UserAPIEndPoint { static func getMyCommentedPopUp(request: SortedRequestDTO) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/users/commented/popup", method: .get, queryParameters: request @@ -153,7 +153,7 @@ struct UserAPIEndPoint { static func getBlockUserList(request: GetBlockUserListRequestDTO) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/users/blocked", method: .get, queryParameters: request @@ -162,7 +162,7 @@ struct UserAPIEndPoint { static func getNoticeList() -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/notice/list", method: .get ) @@ -170,7 +170,7 @@ struct UserAPIEndPoint { static func getNoticeDetail(noticeID: Int64) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/notice/\(noticeID)", method: .get ) @@ -178,7 +178,7 @@ struct UserAPIEndPoint { static func getRecentPopUp(request: SortedRequestDTO) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/users/recent-popupstores", method: .get, queryParameters: request @@ -187,7 +187,7 @@ struct UserAPIEndPoint { static func getBookmarkPopUp(request: SortedRequestDTO) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/users/bookmark-popupstores", method: .get, queryParameters: request diff --git a/Poppool/Poppool/Infrastructure/PreSignedService/PreSignedAPIEndPoint.swift b/Poppool/Poppool/Infrastructure/PreSignedService/PreSignedAPIEndPoint.swift index 556bf5d2..e6e12f83 100644 --- a/Poppool/Poppool/Infrastructure/PreSignedService/PreSignedAPIEndPoint.swift +++ b/Poppool/Poppool/Infrastructure/PreSignedService/PreSignedAPIEndPoint.swift @@ -11,7 +11,7 @@ struct PreSignedAPIEndPoint { static func presigned_upload(request: PresignedURLRequestDTO) -> Endpoint { Logger.log(message: "Presigned URL 생성 - Request: \(request)", category: .debug) return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/files/upload-preSignedUrl", method: .post, bodyParameters: request @@ -21,7 +21,7 @@ struct PreSignedAPIEndPoint { static func presigned_download(request: PresignedURLRequestDTO) -> Endpoint { Logger.log(message: "Presigned Download URL 생성 - Request: \(request)", category: .debug) return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/files/download-preSignedUrl", method: .post, bodyParameters: request @@ -31,7 +31,7 @@ struct PreSignedAPIEndPoint { static func presigned_delete(request: PresignedURLRequestDTO) -> RequestEndpoint { Logger.log(message: "Presigned Delete 생성 - Request: \(request)", category: .debug) return RequestEndpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/files/delete", method: .post, bodyParameters: request diff --git a/Poppool/Poppool/Infrastructure/PreSignedService/PreSignedService.swift b/Poppool/Poppool/Infrastructure/PreSignedService/PreSignedService.swift index 61665b4e..b1a6912f 100644 --- a/Poppool/Poppool/Infrastructure/PreSignedService/PreSignedService.swift +++ b/Poppool/Poppool/Infrastructure/PreSignedService/PreSignedService.swift @@ -291,7 +291,7 @@ extension PreSignedService { } } func fullImageURL(from filePath: String) -> URL? { - let baseURL = Secrets.popPoolS3BaseURL + let baseURL = KeyPath.popPoolS3BaseURL // URL 인코딩 처리를 더 엄격하게 guard let encodedPath = filePath diff --git a/Poppool/Poppool/Presentation/Admin/AdminStoreCell.swift b/Poppool/Poppool/Presentation/Admin/AdminStoreCell.swift index 46e8d56c..ed2815db 100644 --- a/Poppool/Poppool/Presentation/Admin/AdminStoreCell.swift +++ b/Poppool/Poppool/Presentation/Admin/AdminStoreCell.swift @@ -83,7 +83,7 @@ final class AdminStoreCell: UITableViewCell { statusChip.text = "운영" // mainImageUrl에서 baseURL 부분 제거 - let imagePath = store.mainImageUrl.replacingOccurrences(of: Secrets.popPoolS3BaseURL, with: "") + let imagePath = store.mainImageUrl.replacingOccurrences(of: KeyPath.popPoolS3BaseURL, with: "") Logger.log(message: "이미지 경로: \(imagePath)", category: .debug) storeImageView.setPPImage(path: imagePath) } diff --git a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapAPIEndpoint.swift b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapAPIEndpoint.swift index 66806958..5c4a4bdc 100644 --- a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapAPIEndpoint.swift +++ b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapAPIEndpoint.swift @@ -26,7 +26,7 @@ struct MapAPIEndpoint { ) return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/locations/popup-stores", method: .get, queryParameters: params @@ -44,7 +44,7 @@ struct MapAPIEndpoint { ) return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/locations/search", method: .get, queryParameters: params diff --git a/Poppool/Poppool/Presentation/Admin/Data/Remote/AdminAPIEndpoint.swift b/Poppool/Poppool/Presentation/Admin/Data/Remote/AdminAPIEndpoint.swift index 2f9a6433..b5ba885d 100644 --- a/Poppool/Poppool/Presentation/Admin/Data/Remote/AdminAPIEndpoint.swift +++ b/Poppool/Poppool/Presentation/Admin/Data/Remote/AdminAPIEndpoint.swift @@ -14,7 +14,7 @@ struct AdminAPIEndpoint { size: size ) return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/admin/popup-stores/list", method: .get, queryParameters: params @@ -26,7 +26,7 @@ struct AdminAPIEndpoint { id: Int64 ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/admin/popup-stores", method: .get, queryParameters: ["popUpStoreId": id] @@ -38,7 +38,7 @@ struct AdminAPIEndpoint { request: CreatePopUpStoreRequestDTO ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/admin/popup-stores", method: .post, bodyParameters: request @@ -50,7 +50,7 @@ struct AdminAPIEndpoint { request: UpdatePopUpStoreRequestDTO ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/admin/popup-stores", method: .put, bodyParameters: request @@ -62,7 +62,7 @@ struct AdminAPIEndpoint { id: Int64 ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/admin/popup-stores", method: .delete, queryParameters: ["popUpStoreId": id] @@ -74,7 +74,7 @@ struct AdminAPIEndpoint { request: CreateNoticeRequestDTO ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/admin/notice", method: .post, bodyParameters: request @@ -86,7 +86,7 @@ struct AdminAPIEndpoint { request: UpdateNoticeRequestDTO ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/admin/notice/\(id)", method: .put, bodyParameters: request @@ -97,7 +97,7 @@ struct AdminAPIEndpoint { id: Int64 ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/admin/notice/\(id)", method: .delete ) diff --git a/Poppool/Poppool/Presentation/Extension/UIImageView+.swift b/Poppool/Poppool/Presentation/Extension/UIImageView+.swift index 03de32eb..a74655f3 100644 --- a/Poppool/Poppool/Presentation/Extension/UIImageView+.swift +++ b/Poppool/Poppool/Presentation/Extension/UIImageView+.swift @@ -15,7 +15,7 @@ extension UIImageView { self.image = UIImage(named: "image_default") return } - let imageURLString = Secrets.popPoolS3BaseURL + path + let imageURLString = KeyPath.popPoolS3BaseURL + path if let cenvertimageURL = imageURLString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) { let imageURL = URL(string: cenvertimageURL) self.kf.setImage(with: imageURL) { result in @@ -35,7 +35,7 @@ extension UIImageView { completion() return } - let imageURLString = Secrets.popPoolS3BaseURL + path + let imageURLString = KeyPath.popPoolS3BaseURL + path if let cenvertimageURL = imageURLString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) { let imageURL = URL(string: cenvertimageURL) self.kf.setImage(with: imageURL) { result in diff --git a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FindDirectionEndPoint.swift b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FindDirectionEndPoint.swift index b9a7c5ea..430b5a91 100644 --- a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FindDirectionEndPoint.swift +++ b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FindDirectionEndPoint.swift @@ -13,7 +13,7 @@ struct FindDirectionEndPoint { popUpStoreId: Int64 ) -> Endpoint { return Endpoint( - baseURL: Secrets.popPoolBaseURL, + baseURL: KeyPath.popPoolBaseURL, path: "/popup/\(popUpStoreId)/directions", method: .get ) diff --git a/Poppool/Poppool/Presentation/Scene/Detail/DetailReactor.swift b/Poppool/Poppool/Presentation/Scene/Detail/DetailReactor.swift index 3b910a9b..61e5c3d8 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/DetailReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/DetailReactor.swift @@ -381,7 +381,7 @@ final class DetailReactor: Reactor { func showSharedBoard(controller: BaseViewController) { let storeName = titleSection.inputDataList.first?.title ?? "" - let imagePath = Secrets.popPoolS3BaseURL + (imageBannerSection.inputDataList.first?.imagePaths.first ?? "") + let imagePath = KeyPath.popPoolS3BaseURL + (imageBannerSection.inputDataList.first?.imagePaths.first ?? "") // URL 인코딩 후 생성 guard let encodedPath = imagePath.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), From 334a4a3a0f0eb7b6a1f7873e176e5fac2cff1af8 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Thu, 3 Apr 2025 00:16:23 +0900 Subject: [PATCH 45/95] =?UTF-8?q?chore/#93:=20CI=20=ED=83=9C=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=20=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 몇몇 이모지가 생각보다 촌스러워서 수정함 --- .github/workflows/ios-ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 98f1a3a9..8ea61826 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -1,4 +1,4 @@ -name: 📱 iOS CI +name: Run CI on: pull_request: @@ -14,10 +14,10 @@ jobs: if: github.actor != 'github-actions[bot]' # Actions 봇 커밋은 무시 steps: - - name: ✅ Checkout Repository # 저장소 코드 체크아웃 + - name: Checkout Repository # 저장소 코드 체크아웃 uses: actions/checkout@v4 - - name: 🧾 Generate Debug.xcconfig + - name: ⚙️ Generate xcconfig run: | cat < Poppool/Poppool/Resource/Debug.xcconfig KAKAO_AUTH_APP_KEY=${{ secrets.KAKAO_AUTH_APP_KEY }} @@ -27,7 +27,7 @@ jobs: POPPOOL_API_KEY=${{ secrets.POPPOOL_API_KEY }} EOF - - name: 🔨 Select Xcode 16.2 # Xcode 16.2 버전 사용 설정 + - name: 🛠️ Select Xcode 16.2 # Xcode 16.2 버전 사용 설정 run: sudo xcode-select -s /Applications/Xcode_16.2.app - name: ⬇️ Install SwiftLint # SwiftLint 설치 From 2253ee840d9772fe590e4470ba8d70e1ac071a70 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Thu, 3 Apr 2025 00:19:11 +0900 Subject: [PATCH 46/95] =?UTF-8?q?test/#93:=20CI=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EC=9E=84=EC=8B=9C=20?= =?UTF-8?q?swiftlint=EB=A3=B0=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Poppool/.swiftlint.yml | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/Poppool/.swiftlint.yml b/Poppool/.swiftlint.yml index 0686d111..9e132e30 100644 --- a/Poppool/.swiftlint.yml +++ b/Poppool/.swiftlint.yml @@ -1,5 +1,14 @@ # 기본 활성화된 룰 중에 비활성화할 룰을 지정 disabled_rules: + - type_body_length + - function_body_length + - file_length + - line_length + - force_cast + - force_try + - duplicate_conditions + - identifier_name + - cyclomatic_complexity - redundant_optional_initialization # 기본(default) 룰이 아닌 룰들을 활성화 @@ -8,14 +17,3 @@ opt_in_rules: - direct_return - file_header - weak_delegate - -# 기본 활성화된 룰 중에 조건을 변경할 룰 -file_length: 400 - -function_body_length: - warning: 100 - error: 150 - -cyclomatic_complexity: - warning: 15 - error: 30 From 3d31395b8f680281fb6966ad9ca7991079697e23 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Thu, 3 Apr 2025 00:47:10 +0900 Subject: [PATCH 47/95] =?UTF-8?q?refactor/#93:=20CI,=20AutoCorrect=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/{ios-ci.yml => ci.yml} | 49 ++++++++++++++++++--- .github/workflows/swiftlint-autocorrect.yml | 47 -------------------- 2 files changed, 43 insertions(+), 53 deletions(-) rename .github/workflows/{ios-ci.yml => ci.yml} (61%) delete mode 100644 .github/workflows/swiftlint-autocorrect.yml diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ci.yml similarity index 61% rename from .github/workflows/ios-ci.yml rename to .github/workflows/ci.yml index 8ea61826..25fe74ca 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ci.yml @@ -1,16 +1,53 @@ -name: Run CI +name: CI on: pull_request: - branches: [dev] # dev 브랜치로의 PR에 대해 실행 + branches: [dev] push: - branches: [dev] # dev 브랜치로의 push에 대해 실행 + branches: [dev] jobs: + autocorrect: + name: 🤖 Autocorrect Workflow + runs-on: macos-15 # 최신 macOS 15 환경에서 실행 + if: github.actor != 'github-actions[bot]' # Actions 봇 커밋은 무시 + + steps: + - name: 📁 Checkout Repository # 저장소 코드 체크아웃 + uses: actions/checkout@v4 + + - name: 🛠️ Set up Xcode # Xcode 16.2 선택 + run: sudo xcode-select -s /Applications/Xcode_16.2.app + + - name: ⬇️ Install SwiftLint # SwiftLint 설치 + run: brew install swiftlint + + - name: 🎨 Run SwiftLint Autocorrect # SwiftLint 자동 수정 실행 + run: swiftlint autocorrect + + - name: 🚀 Commit and Push Changes # 변경 사항 자동 커밋 및 푸시 + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + BRANCH_NAME="${GITHUB_REF_NAME}" + if [[ "$BRANCH_NAME" =~ (#([0-9]+)) ]]; then + ISSUE_NUMBER="${BASH_REMATCH[1]}" + else + ISSUE_NUMBER="#??" + fi + + if [ -n "$(git status --porcelain)" ]; then + git add . + git commit -m "style/${ISSUE_NUMBER}: Apply SwiftLint autocorrect" + git push + else + echo "No changes to commit" + fi + build: - name: Build & Lint Workflow + name: 🏗️ Build Workflow runs-on: macos-15 # 최신 macOS 15 환경에서 실행 - timeout-minutes: 15 # 최대 실행 시간 15분 설정 if: github.actor != 'github-actions[bot]' # Actions 봇 커밋은 무시 steps: @@ -32,7 +69,7 @@ jobs: - name: ⬇️ Install SwiftLint # SwiftLint 설치 run: brew install swiftlint - + - name: 🎨 Run SwiftLint # SwiftLint 코드 스타일 검사 실행 run: swiftlint diff --git a/.github/workflows/swiftlint-autocorrect.yml b/.github/workflows/swiftlint-autocorrect.yml deleted file mode 100644 index 895d5868..00000000 --- a/.github/workflows/swiftlint-autocorrect.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: 🔧 Swiftlint Autocorrect - -on: - pull_request: - types: [opened] - branches: ['dev/**'] - -jobs: - autocorrect: - name: SwiftLint Autocorrect - runs-on: macos-15 - if: github.actor != 'github-actions[bot]' # 커밋 무한 루프 방지를 위해 봇 커밋은 무시 - - steps: - - name: 📁 Checkout Repository # 저장소 코드 체크아웃 - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - - name: 🔨 Set up Xcode # Xcode 16.2 선택 - run: sudo xcode-select -s /Applications/Xcode_16.2.app - - - name: ⬇️ Install SwiftLint # SwiftLint 설치 - run: brew install swiftlint - - - name: 🔧 Run SwiftLint Autocorrect # SwiftLint 자동 수정 실행 - run: swiftlint autocorrect - - - name: 🚀 Commit and Push Changes # 변경 사항 자동 커밋 및 푸시 - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - BRANCH_NAME="${GITHUB_REF_NAME}" - if [[ "$BRANCH_NAME" =~ (#([0-9]+)) ]]; then - ISSUE_NUMBER="${BASH_REMATCH[1]}" - else - ISSUE_NUMBER="#??" - fi - - if [ -n "$(git status --porcelain)" ]; then - git add . - git commit -m "style/${ISSUE_NUMBER}: Apply SwiftLint autocorrect" - git push - else - echo "No changes to commit" - fi From 05e9118503a799c05c8545f8f9a336aeedd14dda Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Thu, 3 Apr 2025 00:51:51 +0900 Subject: [PATCH 48/95] =?UTF-8?q?fix/#93:=20=EB=A6=B0=ED=8A=B8=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=EC=88=98=EC=A0=95=20=EB=AA=85=EB=A0=B9=EC=96=B4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 25fe74ca..e8d391f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: run: brew install swiftlint - name: 🎨 Run SwiftLint Autocorrect # SwiftLint 자동 수정 실행 - run: swiftlint autocorrect + run: swiftlint --fix - name: 🚀 Commit and Push Changes # 변경 사항 자동 커밋 및 푸시 run: | From eac393c7fb41555cc5d2e86b3434b5f15e51d22a Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Thu, 3 Apr 2025 01:02:02 +0900 Subject: [PATCH 49/95] =?UTF-8?q?fix/#93:=20=EB=A6=B0=ED=8A=B8=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=EC=88=98=EC=A0=95=20=EC=97=90=EB=9F=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EB=B9=8C=EB=93=9C=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=ED=83=90=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8d391f6..14c9fb25 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: if: github.actor != 'github-actions[bot]' # Actions 봇 커밋은 무시 steps: - - name: 📁 Checkout Repository # 저장소 코드 체크아웃 + - name: Checkout Repository # 저장소 코드 체크아웃 uses: actions/checkout@v4 - name: 🛠️ Set up Xcode # Xcode 16.2 선택 @@ -30,16 +30,18 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - BRANCH_NAME="${GITHUB_REF_NAME}" - if [[ "$BRANCH_NAME" =~ (#([0-9]+)) ]]; then + git checkout "${GITHUB_HEAD_REF}" + + BRANCH_NAME="${GITHUB_HEAD_REF}" + if [[ "$BRANCH_NAME" =~ ([0-9]+) ]]; then ISSUE_NUMBER="${BASH_REMATCH[1]}" else - ISSUE_NUMBER="#??" + ISSUE_NUMBER="" fi if [ -n "$(git status --porcelain)" ]; then git add . - git commit -m "style/${ISSUE_NUMBER}: Apply SwiftLint autocorrect" + git commit -m "style/#${ISSUE_NUMBER}: Apply SwiftLint autocorrect" git push else echo "No changes to commit" @@ -92,7 +94,8 @@ jobs: - name: 🏗️ Build the project # 자동 검지된 Scheme과 Simulator로 빌드 수행 run: | + WORKSPACE=$(find . -name "*.xcworkspace" | head -n 1) xcodebuild -scheme "${{ steps.detect_scheme.outputs.scheme }}" \ - -workspace Poppool.xcworkspace \ + -workspace "$WORKSPACE" \ -destination "platform=iOS Simulator,id=${{ steps.detect_latest_simulator.outputs.sim_udid }}" \ clean build | xcpretty From ee45fe3ef5a44ce7d8cfdc82eb8539cb8f8069d2 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Thu, 3 Apr 2025 01:07:23 +0900 Subject: [PATCH 50/95] =?UTF-8?q?fix/#93:=20=EC=9D=B4=EC=8A=88=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EA=B2=80=EC=B6=9C=20=EC=A0=95=EA=B7=9C=ED=91=9C?= =?UTF-8?q?=ED=98=84=EC=8B=9D=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=EB=A6=B0?= =?UTF-8?q?=ED=8A=B8=20=EC=9E=90=EB=8F=99=EC=88=98=EC=A0=95=20=EC=BB=A4?= =?UTF-8?q?=EB=B0=8B=20=EC=97=90=EB=9F=AC=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14c9fb25..a11ad67a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,10 +30,11 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" + git fetch origin "${GITHUB_HEAD_REF}:${GITHUB_HEAD_REF}" git checkout "${GITHUB_HEAD_REF}" BRANCH_NAME="${GITHUB_HEAD_REF}" - if [[ "$BRANCH_NAME" =~ ([0-9]+) ]]; then + if [[ "$BRANCH_NAME" =~ \#([0-9]+) ]]; then ISSUE_NUMBER="${BASH_REMATCH[1]}" else ISSUE_NUMBER="" From 50f48c8a9657571a8c52ca04021220fe6a719070 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Thu, 3 Apr 2025 01:09:34 +0900 Subject: [PATCH 51/95] =?UTF-8?q?fix/#93:=20=EC=9E=90=EB=8F=99=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=BB=A4=EB=B0=8B=20=EC=95=A1=EC=85=98=20No=20upst?= =?UTF-8?q?ream=20branch=20=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a11ad67a..01cd77eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,7 @@ jobs: if [ -n "$(git status --porcelain)" ]; then git add . git commit -m "style/#${ISSUE_NUMBER}: Apply SwiftLint autocorrect" - git push + git push --set-upstream origin "${GITHUB_HEAD_REF}" else echo "No changes to commit" fi From 13b2b6431243501e75f25765d0baf34a16fe3ec5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 2 Apr 2025 16:10:06 +0000 Subject: [PATCH 52/95] style/#93: Apply SwiftLint autocorrect --- Poppool/Poppool/Application/AppDelegate.swift | 9 +- .../Poppool/Application/SceneDelegate.swift | 3 +- .../Application/TestViewController.swift | 49 ++++---- .../Network/AuthAPI/AuthAPIEndPoint.swift | 6 +- .../CommentAPI/CommentAPIEndPoint.swift | 6 +- .../Network/HomeAPI/HomeAPIEndpoint.swift | 8 +- .../ResponseDTO/GetHomeInfoResponseDTO.swift | 1 - .../Network/PopUpAPI/PopUpAPIEndPoint.swift | 10 +- .../GetSearchPopUpListResponseDTO.swift | 2 - .../Network/SignUpAPI/SignUpAPIEndpoint.swift | 6 +- .../GetMyCommentedPopUpResponseDTO.swift | 1 - ...herUserCommentedPopUpListResponseDTO.swift | 3 - .../GetWithdrawlListResponseDTO.swift | 2 - .../Network/UserAPI/UserAPIEndPoint.swift | 48 ++++---- .../Repository/AuthAPIRepositoryImpl.swift | 10 +- .../Repository/CommentAPIRepository.swift | 10 +- .../Data/Repository/HomeAPIRepository.swift | 12 +- .../Repository/PopUpAPIRepositoryImpl.swift | 14 +-- .../Repository/SignUpRepositoryImpl.swift | 10 +- .../Repository/UserAPIRepositoryImpl.swift | 46 ++++---- .../Entities/GetMyCommentResponse.swift | 1 - .../Domain/Repository/AuthRepository.swift | 4 +- .../Domain/UseCase/AuthAPIUseCaseImpl.swift | 8 +- .../UseCase/CommentAPIUseCaseImpl.swift | 10 +- .../Domain/UseCase/HomeAPIUseCaseImpl.swift | 8 +- .../Domain/UseCase/PopUpAPIUseCaseImpl.swift | 14 +-- .../Domain/UseCase/SignUpAPIUseCaseImpl.swift | 4 +- .../Domain/UseCase/UserAPIUseCaseImpl.swift | 50 ++++----- .../Infrastructure/AppleLoginService.swift | 14 +-- .../Infrastructure/KakaoLoginService.swift | 28 ++--- .../Infrastructure/KeyChainService.swift | 20 ++-- .../Infrastructure/Logger/Logger.swift | 10 +- .../NetworkLayer/Common/Requestable.swift | 8 +- .../NetworkLayer/EndPoint/Endpoint.swift | 2 +- .../EndPoint/MultipartEndPoint.swift | 12 +- .../IndicatorMaker/IndicatorMaker.swift | 10 +- .../Interceptor/FormDataInterceptor.swift | 8 +- .../Interceptor/TokenInterceptor.swift | 6 +- .../NetworkLayer/Provider/Provider.swift | 8 +- .../NetworkLayer/Provider/ProviderImpl.swift | 2 +- .../PreSignedService/PreSignedService.swift | 13 +-- .../Infrastructure/UserDefaultService.swift | 14 +-- .../AdminBottomSheetView.swift | 85 +++++++------- .../AdminBottomSheetViewController.swift | 15 +-- .../Presentation/Admin/AdminReactor.swift | 2 +- .../PopUpStoreRegisterReactor.swift | 3 +- .../PopUpStoreRegisterView.swift | 6 +- .../PopUpStoreRegisterViewController.swift | 105 +++++++----------- .../Presentation/Admin/AdminStoreCell.swift | 4 +- .../Presentation/Admin/AdminView.swift | 4 +- .../Admin/AdminViewController.swift | 7 +- .../Admin/Common/DateTimePickerManager.swift | 2 +- .../Admin/Data/DTO/AdminResponseDTO.swift | 1 - .../GetAdminPopUpStoreListResponseDTO.swift | 5 +- .../Admin/Data/MapDomain/MapAPIEndpoint.swift | 3 +- .../Admin/Data/MapDomain/MapPopUpStore.swift | 5 +- .../MapDomain/Repository/MapRepository.swift | 2 - .../Data/MapDomain/UseCase/MapUseCase.swift | 3 +- .../Data/Repository/AdminRepository.swift | 9 +- .../Presentation/Admin/ImageCell.swift | 2 +- .../Admin/PopUpStoreRegisterReactor.swift | 12 +- .../Admin/PopUpStoreRegisterView.swift | 22 ++-- .../Presentation/Components/PPButton.swift | 27 +++-- .../Components/PPCancelHeaderView.swift | 12 +- .../Presentation/Components/PPLabel.swift | 6 +- .../Presentation/Components/PPPicker.swift | 22 ++-- .../PPProgressIndicator.swift | 21 ++-- .../PPProgressIndicator/PPProgressView.swift | 29 +++-- .../Components/PPReturnHeaderView.swift | 16 +-- .../Components/PPSegmentedControl.swift | 18 +-- .../Presentation/Extension/Date?+.swift | 4 +- .../Presentation/Extension/Reactive+.swift | 10 +- .../Presentation/Extension/String?+.swift | 8 +- .../Presentation/Extension/UIColor+.swift | 23 ++-- .../Presentation/Extension/UIFont+.swift | 5 +- .../Presentation/Extension/UIImage+.swift | 14 +-- .../Presentation/Extension/UIImageView+.swift | 2 +- .../Map/Common/ClusteringModels.swift | 1 - .../Presentation/Map/Common/FilterType.swift | 2 - .../Map/Common/GMSMapViewDelegateProxy.swift | 4 +- .../LocationPermissionBottomSheet.swift | 2 +- .../Map/Common/MapFilterChips.swift | 3 +- .../MapPopupCarouselView.swift | 5 +- .../MapPopupCardView/PopupCardCell.swift | 7 +- .../Map/Common/MapUtilities.swift | 3 - .../Map/CustomClusterRenderer.swift | 6 +- .../BalloonBackgroundView.swift | 10 +- .../FillterSheetView/BalloonChipCell.swift | 3 +- .../FillterSheetView/CategoryFilterView.swift | 8 +- .../FilterBottomSheetReactor.swift | 26 ++--- .../FilterBottomSheetView.swift | 31 ++---- .../FilterBottomSheetViewController.swift | 22 +--- .../Map/FillterSheetView/FilterCell.swift | 2 +- .../Map/FillterSheetView/FilterChip.swift | 2 +- .../FillterSheetView/FilterChipsView.swift | 4 +- .../Map/FillterSheetView/FilterTabsView.swift | 14 +-- .../FillterSheetView/LocationFilterView.swift | 8 +- .../MapGuideView/FindDirectionEndPoint.swift | 1 - .../FullScreenMapViewController.swift | 11 +- .../GetPopUpDirectionResponseDTO.swift | 1 - .../MapGuideView/MapDirectionRepository.swift | 3 +- .../MapGuideView/MapGuideReactor.swift | 2 +- .../MapGuideView/MapGuideViewController.swift | 8 +- .../Presentation/Map/MapStoreCard.swift | 16 +-- .../Presentation/Map/MapView/MapMarker.swift | 7 +- .../Presentation/Map/MapView/MapReactor.swift | 8 +- .../Map/MapView/MapSearchInput.swift | 2 +- .../Presentation/Map/MapView/MapView.swift | 9 +- .../Map/MapView/MapViewController.swift | 64 ++--------- .../Map/MapView/MarkerTooltipView.swift | 2 +- .../Map/MicroClusterMarkerView.swift | 1 - .../Map/StoreListPanelLayout.swift | 8 +- .../Map/StoreListView/StoreListCell.swift | 8 +- .../StoreListView/StoreListHeaderView.swift | 10 +- .../StoreListView/StoreListPanelLayout.swift | 3 - .../Map/StoreListView/StoreListReactor.swift | 22 +--- .../Map/StoreListView/StoreListView.swift | 2 +- .../StoreListViewController.swift | 19 ++-- .../Presentation/Map/TestViewController.swift | 49 ++++---- .../CommentCheck/CommentCheckController.swift | 14 +-- .../CommentCheck/CommentCheckReactor.swift | 20 ++-- .../CommentCheck/CommentCheckView.swift | 29 +++-- .../CommentDetailController.swift | 41 ++++--- .../CommentDetail/CommentDetailReactor.swift | 36 +++--- .../CommentDetailContentSection.swift | 14 +-- .../CommentDetailContentSectionCell.swift | 12 +- .../View/CommentDetailImageSection.swift | 12 +- .../View/CommentDetailView.swift | 30 +++-- .../CommentMyMenuController.swift | 16 +-- .../CommentMyMenu/CommentMyMenuReactor.swift | 18 +-- .../CommentMyMenu/CommentMyMenuView.swift | 24 ++-- .../CommentUserInfoController.swift | 18 +-- .../CommentUserInfoReactor.swift | 18 +-- .../CommentUserInfo/CommentUserInfoView.swift | 24 ++-- .../CommentList/CommentListController.swift | 34 +++--- .../CommentList/CommentListReactor.swift | 38 +++---- .../CommentListTitleSection.swift | 15 ++- .../CommentListTitleSectionCell.swift | 10 +- .../CommentList/View/CommentListView.swift | 18 ++- .../CommentSelectedController.swift | 16 +-- .../CommentSelectedReactor.swift | 18 +-- .../CommentSelected/CommentSelectedView.swift | 24 ++-- .../CommentUserBlockController.swift | 16 +-- .../CommentUserBlockReactor.swift | 20 ++-- .../CommentUserBlockView.swift | 29 +++-- .../InstaCommentAddController.swift | 24 ++-- .../InstaComment/InstaCommentAddReactor.swift | 30 ++--- .../View/InstaCommentAddView.swift | 22 ++-- .../InstaGuideChildSection.swift | 12 +- .../InstaGuideChildSectionCell.swift | 24 ++-- .../InstaGuideSection/InstaGuideSection.swift | 17 ++- .../InstaGuideSectionCell.swift | 51 +++++---- .../NormalCommentAddController.swift | 38 +++---- .../NormalCommentAddReactor.swift | 38 +++---- .../AddCommentDescriptionSection.swift | 14 +-- .../AddCommentDescriptionSectionCell.swift | 12 +- .../AddCommentImageSection.swift | 12 +- .../AddCommentImageSectionCell.swift | 31 +++--- .../AddCommentSection/AddCommentSection.swift | 12 +- .../AddCommentSectionCell.swift | 56 +++++----- .../AddCommentTitleSection.swift | 14 +-- .../AddCommentTitleSectionCell.swift | 15 ++- .../View/NormalCommentAddView.swift | 21 ++-- .../NormalCommentEditController.swift | 42 +++---- .../NormalCommentEditReactor.swift | 48 ++++---- .../NormalCommentEditView.swift | 21 ++-- .../OtherUserCommentController.swift | 39 ++++--- .../OtherUserCommentReactor.swift | 24 ++-- .../OtherUserCommentSection.swift | 12 +- .../OtherUserCommentSectionCell.swift | 34 +++--- .../View/OtherUserCommentView.swift | 12 +- .../Scene/Detail/DetailController.swift | 7 +- .../Scene/Detail/DetailReactor.swift | 74 ++++++------ .../DetailCommentImageCell.swift | 12 +- .../DetailCommentProfileView.swift | 21 ++-- .../DetailCommentSection.swift | 14 +-- .../DetailCommentSectionCell.swift | 83 +++++++------- .../DetailCommentTitleSection.swift | 14 +-- .../DetailCommentTitleSectionCell.swift | 23 ++-- .../DetailContentSection.swift | 14 +-- .../DetailContentSectionCell.swift | 25 ++--- .../DetailEmptyCommetSection.swift | 14 +-- .../DetailEmptyCommetSectionCell.swift | 20 ++-- .../DetailInfoSection/DetailInfoSection.swift | 14 +-- .../DetailInfoSectionCell.swift | 75 ++++++------- .../DetailSimilarSection.swift | 14 +-- .../DetailSimilarSectionCell.swift | 47 ++++---- .../DetailTitleSection.swift | 14 +-- .../DetailTitleSectionCell.swift | 25 ++--- .../Scene/Detail/View/DetailView.swift | 18 ++- .../Scene/Home/List/HomeListController.swift | 42 +++---- .../Scene/Home/List/HomeListReactor.swift | 32 +++--- .../Scene/Home/List/HomePopUpType.swift | 4 +- .../Home/List/View/HomeCardGridSection.swift | 12 +- .../Scene/Home/List/View/HomeListView.swift | 18 ++- .../Scene/Home/Main/HomeController.swift | 60 +++++----- .../Scene/Home/Main/HomeReactor.swift | 44 ++++---- .../HomeCardSection/HomeCardSection.swift | 12 +- .../HomeCardSection/HomeCardSectionCell.swift | 49 ++++---- .../Scene/Home/Main/View/HomeHeaderView.swift | 22 ++-- .../HomePopularCardSection.swift | 16 +-- .../HomePopularCardSectionCell.swift | 40 +++---- .../HomeTitleSection/HomeTitleSection.swift | 17 ++- .../HomeTitleSectionCell.swift | 28 +++-- .../Scene/Home/Main/View/HomeView.swift | 8 +- .../ImageBannerChildSection.swift | 12 +- .../ImageBannerChildSectionCell.swift | 14 +-- .../ImageBannerSection.swift | 17 ++- .../ImageBannerSectionCell.swift | 76 ++++++------- .../SectionBackGroundDecorationView.swift | 4 +- .../View/SpacingSection/SpacingSection.swift | 17 ++- .../SpacingSection/SpacingSectionCell.swift | 14 +-- .../ImageDetail/ImageDetailController.swift | 16 +-- .../ImageDetail/ImageDetailReactor.swift | 18 +-- .../Scene/ImageDetail/ImageDetailView.swift | 8 +- .../Scene/Login/LastLoginView.swift | 71 ++++++------ .../Scene/Login/Main/LoginController.swift | 22 ++-- .../Scene/Login/Main/LoginReactor.swift | 30 ++--- .../Scene/Login/Main/LoginView.swift | 42 ++++--- .../Scene/Login/Sub/SubLoginController.swift | 20 ++-- .../Scene/Login/Sub/SubLoginReactor.swift | 26 ++--- .../Scene/Login/Sub/SubLoginView.swift | 42 ++++--- .../Block/BlockUserManageController.swift | 26 ++--- .../MyPage/Block/BlockUserManageReactor.swift | 26 ++--- .../BlockUserListSection.swift | 12 +- .../BlockUserListSectionCell.swift | 29 +++-- .../Block/View/BlockUserManageView.swift | 17 ++- .../Main/MyPageBookmarkController.swift | 56 +++++----- .../Bookmark/Main/MyPageBookmarkReactor.swift | 26 ++--- .../Bookmark/Main/MyPageBookmarkView.swift | 29 +++-- .../Bookmark/Main/View/CountButtonView.swift | 24 ++-- .../PopUpCardSection/PopUpCardSection.swift | 12 +- .../PopUpCardSectionCell.swift | 53 +++++---- .../View/PopUpCardSection/PopUpCardView.swift | 58 +++++----- ...BookMarkPopUpViewTypeModalController.swift | 23 ++-- .../BookMarkPopUpViewTypeModalReactor.swift | 19 ++-- .../BookMarkPopUpViewTypeModalView.swift | 31 +++--- .../Scene/MyPage/FAQ/FAQController.swift | 26 ++--- .../Scene/MyPage/FAQ/FAQReactor.swift | 32 +++--- .../FAQDropdownSection.swift | 14 +-- .../FAQDropdownSectionCell.swift | 34 +++--- .../Scene/MyPage/FAQ/View/FAQView.swift | 12 +- .../Scene/MyPage/Main/MyPageController.swift | 67 ++++++----- .../Scene/MyPage/Main/MyPageReactor.swift | 10 +- .../MyPageCommentSection.swift | 12 +- .../MyPageCommentSectionCell.swift | 51 +++++---- .../MyPageListSection/MyPageListSection.swift | 14 +-- .../MyPageListSectionCell.swift | 25 ++--- .../MyPageLogoutSection.swift | 14 +-- .../MyPageLogoutSectionCell.swift | 17 ++- .../MyPageMyCommentTitleSection.swift | 14 +-- .../MyPageMyCommentTitleSectionCell.swift | 32 +++--- .../MyPageProfileSection.swift | 17 ++- .../MyPageProfileSectionCell.swift | 103 ++++++++--------- .../Scene/MyPage/Main/View/MyPageView.swift | 6 +- .../MyComment/Main/MyCommentController.swift | 33 +++--- .../MyComment/Main/MyCommentReactor.swift | 26 ++--- .../ListCountButtonSection.swift | 14 +-- .../ListCountButtonSectionCell.swift | 34 +++--- .../MyComment/Main/View/MyCommentView.swift | 12 +- .../MyCommentedPopUpGridSection.swift | 14 +-- .../MyCommentedPopUpGridSectionCell.swift | 29 +++-- .../MyCommentSortedModalController.swift | 23 ++-- .../MyCommentSortedModalReactor.swift | 19 ++-- .../MyCommentSortedModalView.swift | 31 +++--- .../Detail/MyPageNoticeDetailController.swift | 14 +-- .../Detail/MyPageNoticeDetailReactor.swift | 18 +-- .../Detail/MyPageNoticeDetailView.swift | 21 ++-- .../Notice/List/MyPageNoticeController.swift | 34 +++--- .../Notice/List/MyPageNoticeReactor.swift | 21 ++-- .../Notice/List/View/MyPageNoticeView.swift | 14 +-- .../NoticeListSection/NoticeListSection.swift | 14 +-- .../NoticeListSectionCell.swift | 22 ++-- .../CategoryEditModalController.swift | 33 +++--- .../CategoryEditModalReactor.swift | 26 ++--- .../CategoryEditModalView.swift | 26 ++--- .../InfoEditModalController.swift | 22 ++-- .../InfoEditModal/InfoEditModalReactor.swift | 23 ++-- .../InfoEditModal/InfoEditModalView.swift | 50 ++++----- .../Main/ProfileEditController.swift | 92 ++++++++------- .../ProfileEdit/Main/ProfileEditReactor.swift | 52 ++++----- .../Main/View/ProfileEditListButton.swift | 19 ++-- .../Main/View/ProfileEditView.swift | 49 ++++---- .../Recent/MyPageRecentController.swift | 36 +++--- .../MyPage/Recent/MyPageRecentReactor.swift | 26 ++--- .../MyPage/Recent/View/MyPageRecentView.swift | 10 +- .../RecentPopUpSection.swift | 15 ++- .../MyPage/Terms/MyPageTermsController.swift | 37 +++--- .../MyPage/Terms/MyPageTermsReactor.swift | 30 ++--- .../WithdrawlCheckModalController.swift | 20 ++-- .../WithdrawlCheckModalReactor.swift | 20 ++-- .../CheckModal/WithdrawlCheckModalView.swift | 41 ++++--- .../WithdrawlCompleteController.swift | 8 +- .../Complete/WithdrawlCompleteView.swift | 23 ++-- .../View/WithdrawlCheckSection.swift | 12 +- .../View/WithdrawlCheckSectionCell.swift | 54 +++++---- .../View/WithdrawlReasonView.swift | 31 +++--- .../WithdrawlReasonController.swift | 40 +++---- .../WithdrawlReasonReactor.swift | 26 ++--- .../AfterSearch/SearchResultController.swift | 24 ++-- .../AfterSearch/SearchResultReactor.swift | 29 +++-- .../SearchResultCountSection.swift | 14 +-- .../SearchResultCountSectionCell.swift | 16 +-- .../AfterSearch/View/SearchResultView.swift | 15 ++- .../BeforeSearch/SearchController.swift | 34 +++--- .../Search/BeforeSearch/SearchReactor.swift | 46 ++++---- .../CancelableTagSection.swift | 12 +- .../CancelableTagSectionCell.swift | 32 +++--- .../SearchCountTitleSection.swift | 14 +-- .../SearchCountTitleSectionCell.swift | 34 +++--- .../SearchTitleSection.swift | 14 +-- .../SearchTitleSectionCell.swift | 21 ++-- .../Search/BeforeSearch/View/SearchView.swift | 11 +- .../SearchCategoryController.swift | 28 ++--- .../SearchCategoryReactor.swift | 20 ++-- .../SearchCategoryView.swift | 29 +++-- .../Search/Main/SearchMainController.swift | 39 ++++--- .../Scene/Search/Main/SearchMainReactor.swift | 16 +-- .../Scene/Search/Main/SearchMainView.swift | 26 ++--- .../SearchSortedController.swift | 20 ++-- .../SearchSortedReactor.swift | 20 ++-- .../SortedController/SearchSortedView.swift | 50 ++++----- .../SignUp/Main/SignUpMainController.swift | 52 +++++---- .../Scene/SignUp/Main/SignUpMainReactor.swift | 28 ++--- .../SignUp/Main/View/SignUpMainView.swift | 10 +- .../SignUpCompleteController.swift | 16 +-- .../SignUpCompleteReactor.swift | 16 +-- .../SignUpComplete/SignUpCompleteView.swift | 51 ++++----- .../SignUp/Step1/SignUpStep1Controller.swift | 24 ++-- .../SignUp/Step1/SignUpStep1Reactor.swift | 22 ++-- .../Step1/View/SignUpCheckBoxButton.swift | 34 +++--- .../SignUp/Step1/View/SignUpStep1View.swift | 30 +++-- .../SignUp/Step1/View/SignUpTermsView.swift | 35 +++--- .../Scene/SignUp/Step2/IntroState.swift | 10 +- .../Scene/SignUp/Step2/NickNameState.swift | 24 ++-- .../SignUp/Step2/SignUpStep2Controller.swift | 35 +++--- .../SignUp/Step2/SignUpStep2Reactor.swift | 28 +++-- .../Scene/SignUp/Step2/SignUpStep2View.swift | 36 +++--- .../SignUp/Step3/SignUpStep3Controller.swift | 29 +++-- .../SignUp/Step3/SignUpStep3Reactor.swift | 26 ++--- .../SignUp/Step3/View/SignUpStep3View.swift | 44 ++++---- .../Step3/View/TagSection/TagSection.swift | 14 +-- .../View/TagSection/TagSectionCell.swift | 17 ++- .../AgeSelectedController.swift | 16 +-- .../AgeSelectedModal/AgeSelectedReactor.swift | 18 +-- .../AgeSelectedModal/AgeSelectedView.swift | 30 +++-- .../Step4/Main/SignUpStep4Controller.swift | 14 +-- .../Step4/Main/SignUpStep4Reactor.swift | 18 +-- .../Step4/Main/View/AgeSelectedButton.swift | 31 +++--- .../Step4/Main/View/SignUpStep4View.swift | 62 +++++------ .../TermsDetail/TermsDetailController.swift | 14 +-- .../SignUp/TermsDetail/TermsDetailView.swift | 18 ++- .../Scene/Splash/SplashController.swift | 20 ++-- .../Scene/Splash/View/SplashView.swift | 12 +- .../TabbarController/TabbarController.swift | 58 +++++----- .../Controllers/BaseTabmanController.swift | 8 +- .../Controllers/BaseViewController.swift | 16 +-- .../Sectionable/SectionDecorationItem.swift | 14 +-- .../SectionSupplementaryItem.swift | 20 ++-- .../Interfaces/Sectionable/Sectionable.swift | 57 +++++----- .../Utills/ToastMaker/BookMarkToastView.swift | 16 +-- .../Utills/ToastMaker/ToastMaker.swift | 33 +++--- .../Utills/ToastMaker/ToastView.swift | 20 ++-- 363 files changed, 3736 insertions(+), 4122 deletions(-) diff --git a/Poppool/Poppool/Application/AppDelegate.swift b/Poppool/Poppool/Application/AppDelegate.swift index 200881dc..ccd55bea 100644 --- a/Poppool/Poppool/Application/AppDelegate.swift +++ b/Poppool/Poppool/Application/AppDelegate.swift @@ -1,8 +1,8 @@ import UIKit -import KakaoSDKCommon -import GoogleMaps import CoreLocation +import GoogleMaps +import KakaoSDKCommon @main class AppDelegate: UIResponder, UIApplicationDelegate { @@ -10,10 +10,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { KakaoSDK.initSDK(appKey: KeyPath.kakaoAuthAppKey) GMSServices.provideAPIKey(KeyPath.popPoolAPIKey) - + let locationManager = CLLocationManager() locationManager.requestWhenInUseAuthorization() // 권한 요청 초기화 - + return true } @@ -22,4 +22,3 @@ class AppDelegate: UIResponder, UIApplicationDelegate { return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } } - diff --git a/Poppool/Poppool/Application/SceneDelegate.swift b/Poppool/Poppool/Application/SceneDelegate.swift index a23213ca..5a7be40e 100644 --- a/Poppool/Poppool/Application/SceneDelegate.swift +++ b/Poppool/Poppool/Application/SceneDelegate.swift @@ -10,7 +10,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { static let appDidBecomeActive = PublishSubject() static let appDidDisconnect = PublishSubject() private let disposeBag = DisposeBag() - + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } window = UIWindow(windowScene: windowScene) @@ -43,4 +43,3 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } } } - diff --git a/Poppool/Poppool/Application/TestViewController.swift b/Poppool/Poppool/Application/TestViewController.swift index c6847136..528e61d1 100644 --- a/Poppool/Poppool/Application/TestViewController.swift +++ b/Poppool/Poppool/Application/TestViewController.swift @@ -7,87 +7,86 @@ import UIKit -import SnapKit -import RxSwift -import RxGesture import RxCocoa +import RxGesture +import RxSwift +import SnapKit class TestViewController: UIViewController { - + private let topView: UIView = { let view = UIView() view.backgroundColor = .w100 view.alpha = 0 return view }() - + private let topViewLabel: UILabel = { let label = UILabel() label.text = "Top View Label" return label }() - + private let bottomView: UIView = { let view = UIView() view.backgroundColor = .w100 return view }() - + private let gestureBar: UIView = { let view = UIView() view.backgroundColor = .g200 return view }() - + private let listButton: PPButton = { - let button = PPButton(style: .secondary, text: "리스트 버튼") - return button + return PPButton(style: .secondary, text: "리스트 버튼") }() - + private let disposeBag = DisposeBag() - + private var bottomViewTopConstraints: Constraint? - + enum ModalState { case top case middle case bottom } - + var modalState: ModalState = .bottom - + override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .blue setUpConstratins() bind() } - + func setUpConstratins() { view.addSubview(listButton) listButton.snp.makeConstraints { make in make.leading.trailing.bottom.equalToSuperview().inset(20) make.height.equalTo(50) } - + view.addSubview(topView) topView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() make.bottom.equalTo(view.safeAreaLayoutGuide.snp.top).offset(104) } - + topView.addSubview(topViewLabel) topViewLabel.snp.makeConstraints { make in make.center.equalToSuperview() } - + view.addSubview(bottomView) bottomView.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() bottomViewTopConstraints = make.top.equalTo(topView.snp.bottom).offset(700).constraint make.height.equalTo(700) } - + bottomView.addSubview(gestureBar) gestureBar.snp.makeConstraints { make in make.width.equalTo(50) @@ -96,7 +95,7 @@ class TestViewController: UIViewController { make.centerX.equalToSuperview() } } - + func bind() { listButton.rx.tap .withUnretained(self) @@ -110,11 +109,11 @@ class TestViewController: UIViewController { } } .disposed(by: disposeBag) - + gestureBar.rx.swipeGesture(.up) .skip(1) .withUnretained(self) - .subscribe { (owner, gesture) in + .subscribe { (owner, _) in print("swipe up") UIView.animate(withDuration: 0.3) { owner.bottomViewTopConstraints?.update(offset: 0) @@ -124,11 +123,11 @@ class TestViewController: UIViewController { } } .disposed(by: disposeBag) - + gestureBar.rx.swipeGesture(.down) .skip(1) .withUnretained(self) - .subscribe { (owner, gesture) in + .subscribe { (owner, _) in print("swipe down") switch owner.modalState { case .top: diff --git a/Poppool/Poppool/Data/Network/AuthAPI/AuthAPIEndPoint.swift b/Poppool/Poppool/Data/Network/AuthAPI/AuthAPIEndPoint.swift index 860fb485..4647490b 100644 --- a/Poppool/Poppool/Data/Network/AuthAPI/AuthAPIEndPoint.swift +++ b/Poppool/Poppool/Data/Network/AuthAPI/AuthAPIEndPoint.swift @@ -8,9 +8,9 @@ import Foundation struct AuthAPIEndPoint { - + // MARK: - Auth API - + /// 로그인을 시도합니다. /// - Parameters: /// - userCredential: 사용자 자격 증명 @@ -25,7 +25,7 @@ struct AuthAPIEndPoint { headers: ["Content-Type": "application/json"] ) } - + static func postTokenReissue() -> Endpoint { return Endpoint( baseURL: KeyPath.popPoolBaseURL, diff --git a/Poppool/Poppool/Data/Network/CommentAPI/CommentAPIEndPoint.swift b/Poppool/Poppool/Data/Network/CommentAPI/CommentAPIEndPoint.swift index 6c91aefb..02f4d689 100644 --- a/Poppool/Poppool/Data/Network/CommentAPI/CommentAPIEndPoint.swift +++ b/Poppool/Poppool/Data/Network/CommentAPI/CommentAPIEndPoint.swift @@ -10,7 +10,7 @@ import Foundation import RxSwift struct CommentAPIEndPoint { - + static func postCommentAdd(request: PostCommentRequestDTO) -> RequestEndpoint { return RequestEndpoint( baseURL: KeyPath.popPoolBaseURL, @@ -19,7 +19,7 @@ struct CommentAPIEndPoint { bodyParameters: request ) } - + static func deleteComment(request: DeleteCommentRequestDTO) -> RequestEndpoint { return RequestEndpoint( baseURL: KeyPath.popPoolBaseURL, @@ -28,7 +28,7 @@ struct CommentAPIEndPoint { queryParameters: request ) } - + static func editComment(request: PutCommentRequestDTO) -> RequestEndpoint { return RequestEndpoint( baseURL: KeyPath.popPoolBaseURL, diff --git a/Poppool/Poppool/Data/Network/HomeAPI/HomeAPIEndpoint.swift b/Poppool/Poppool/Data/Network/HomeAPI/HomeAPIEndpoint.swift index 4140756c..30e19486 100644 --- a/Poppool/Poppool/Data/Network/HomeAPI/HomeAPIEndpoint.swift +++ b/Poppool/Poppool/Data/Network/HomeAPI/HomeAPIEndpoint.swift @@ -8,7 +8,7 @@ import Foundation struct HomeAPIEndpoint { - + static func fetchHome( request: SortedRequestDTO ) -> Endpoint { @@ -19,7 +19,7 @@ struct HomeAPIEndpoint { queryParameters: request ) } - + static func fetchPopularPopUp( request: SortedRequestDTO ) -> Endpoint { @@ -30,7 +30,7 @@ struct HomeAPIEndpoint { queryParameters: request ) } - + static func fetchNewPopUp( request: SortedRequestDTO ) -> Endpoint { @@ -41,7 +41,7 @@ struct HomeAPIEndpoint { queryParameters: request ) } - + static func fetchCustomPopUp( request: SortedRequestDTO ) -> Endpoint { diff --git a/Poppool/Poppool/Data/Network/HomeAPI/ResponseDTO/GetHomeInfoResponseDTO.swift b/Poppool/Poppool/Data/Network/HomeAPI/ResponseDTO/GetHomeInfoResponseDTO.swift index 01660653..d33857b7 100644 --- a/Poppool/Poppool/Data/Network/HomeAPI/ResponseDTO/GetHomeInfoResponseDTO.swift +++ b/Poppool/Poppool/Data/Network/HomeAPI/ResponseDTO/GetHomeInfoResponseDTO.swift @@ -40,4 +40,3 @@ extension GetHomeInfoResponseDTO { ) } } - diff --git a/Poppool/Poppool/Data/Network/PopUpAPI/PopUpAPIEndPoint.swift b/Poppool/Poppool/Data/Network/PopUpAPI/PopUpAPIEndPoint.swift index 6e12dd8d..788b52d8 100644 --- a/Poppool/Poppool/Data/Network/PopUpAPI/PopUpAPIEndPoint.swift +++ b/Poppool/Poppool/Data/Network/PopUpAPI/PopUpAPIEndPoint.swift @@ -10,7 +10,7 @@ import Foundation import RxSwift struct PopUpAPIEndPoint { - + static func getClosePopUpList(request: GetSearchPopUpListRequestDTO) -> Endpoint { return Endpoint( baseURL: KeyPath.popPoolBaseURL, @@ -19,7 +19,7 @@ struct PopUpAPIEndPoint { queryParameters: request ) } - + static func getOpenPopUpList(request: GetSearchPopUpListRequestDTO) -> Endpoint { return Endpoint( baseURL: KeyPath.popPoolBaseURL, @@ -28,7 +28,7 @@ struct PopUpAPIEndPoint { queryParameters: request ) } - + static func getSearchPopUpList(request: GetSearchPopUpListRequestDTO) -> Endpoint { return Endpoint( baseURL: KeyPath.popPoolBaseURL, @@ -37,7 +37,7 @@ struct PopUpAPIEndPoint { queryParameters: request ) } - + static func getPopUpDetail(request: GetPopUpDetailRequestDTO) -> Endpoint { return Endpoint( baseURL: KeyPath.popPoolBaseURL, @@ -46,7 +46,7 @@ struct PopUpAPIEndPoint { queryParameters: request ) } - + static func getPopUpComment(request: GetPopUpCommentRequestDTO) -> Endpoint { return Endpoint( baseURL: KeyPath.popPoolBaseURL, diff --git a/Poppool/Poppool/Data/Network/PopUpAPI/ResponseDTO/GetSearchPopUpListResponseDTO.swift b/Poppool/Poppool/Data/Network/PopUpAPI/ResponseDTO/GetSearchPopUpListResponseDTO.swift index b3deaf5f..60495f23 100644 --- a/Poppool/Poppool/Data/Network/PopUpAPI/ResponseDTO/GetSearchPopUpListResponseDTO.swift +++ b/Poppool/Poppool/Data/Network/PopUpAPI/ResponseDTO/GetSearchPopUpListResponseDTO.swift @@ -17,5 +17,3 @@ extension GetSearchPopUpListResponseDTO { return .init(popUpStoreList: popUpStoreList.map { $0.toDomain() }, loginYn: loginYn) } } - - diff --git a/Poppool/Poppool/Data/Network/SignUpAPI/SignUpAPIEndpoint.swift b/Poppool/Poppool/Data/Network/SignUpAPI/SignUpAPIEndpoint.swift index d36cc605..147877c2 100644 --- a/Poppool/Poppool/Data/Network/SignUpAPI/SignUpAPIEndpoint.swift +++ b/Poppool/Poppool/Data/Network/SignUpAPI/SignUpAPIEndpoint.swift @@ -8,7 +8,7 @@ import Foundation struct SignUpAPIEndpoint { - + /// 닉네임 중복을 확인합니다. /// - Parameter request: 닉네임 체크 요청 DTO /// - Returns: Endpoint @@ -20,7 +20,7 @@ struct SignUpAPIEndpoint { queryParameters: request ) } - + /// 관심사 목록을 가져옵니다. /// - Returns: Endpoint static func signUp_getCategoryList() -> Endpoint { @@ -30,7 +30,7 @@ struct SignUpAPIEndpoint { method: .get ) } - + /// 회원가입을 시도합니다. /// - Parameter request: 회원가입 요청 DTO /// - Returns: RequestEndpoint diff --git a/Poppool/Poppool/Data/Network/UserAPI/ResponseDTO/GetMyCommentedPopUpResponseDTO.swift b/Poppool/Poppool/Data/Network/UserAPI/ResponseDTO/GetMyCommentedPopUpResponseDTO.swift index 26323fbd..cd3b71ea 100644 --- a/Poppool/Poppool/Data/Network/UserAPI/ResponseDTO/GetMyCommentedPopUpResponseDTO.swift +++ b/Poppool/Poppool/Data/Network/UserAPI/ResponseDTO/GetMyCommentedPopUpResponseDTO.swift @@ -5,7 +5,6 @@ // Created by SeoJunYoung on 1/12/25. // - import Foundation struct GetMyCommentedPopUpResponseDTO: Decodable { diff --git a/Poppool/Poppool/Data/Network/UserAPI/ResponseDTO/GetOtherUserCommentedPopUpListResponseDTO.swift b/Poppool/Poppool/Data/Network/UserAPI/ResponseDTO/GetOtherUserCommentedPopUpListResponseDTO.swift index 45948742..455b5d26 100644 --- a/Poppool/Poppool/Data/Network/UserAPI/ResponseDTO/GetOtherUserCommentedPopUpListResponseDTO.swift +++ b/Poppool/Poppool/Data/Network/UserAPI/ResponseDTO/GetOtherUserCommentedPopUpListResponseDTO.swift @@ -42,6 +42,3 @@ extension GetOtherUserCommentedPopUpResponseDTO { ) } } - - - diff --git a/Poppool/Poppool/Data/Network/UserAPI/ResponseDTO/GetWithdrawlListResponseDTO.swift b/Poppool/Poppool/Data/Network/UserAPI/ResponseDTO/GetWithdrawlListResponseDTO.swift index f50372d7..862bccb1 100644 --- a/Poppool/Poppool/Data/Network/UserAPI/ResponseDTO/GetWithdrawlListResponseDTO.swift +++ b/Poppool/Poppool/Data/Network/UserAPI/ResponseDTO/GetWithdrawlListResponseDTO.swift @@ -26,8 +26,6 @@ extension GetWithdrawlListDataResponseDTO { } } - - struct PostWithdrawlListRequestDTO: Encodable { var checkedSurveyList: [GetWithdrawlListDataResponseDTO] } diff --git a/Poppool/Poppool/Data/Network/UserAPI/UserAPIEndPoint.swift b/Poppool/Poppool/Data/Network/UserAPI/UserAPIEndPoint.swift index 4916c847..9ca2708d 100644 --- a/Poppool/Poppool/Data/Network/UserAPI/UserAPIEndPoint.swift +++ b/Poppool/Poppool/Data/Network/UserAPI/UserAPIEndPoint.swift @@ -10,7 +10,7 @@ import Foundation import RxSwift struct UserAPIEndPoint { - + static func postBookmarkPopUp(request: PostBookmarkPopUpRequestDTO) -> RequestEndpoint { return RequestEndpoint( baseURL: KeyPath.popPoolBaseURL, @@ -19,7 +19,7 @@ struct UserAPIEndPoint { queryParameters: request ) } - + static func deleteBookmarkPopUp(request: PostBookmarkPopUpRequestDTO) -> RequestEndpoint { return RequestEndpoint( baseURL: KeyPath.popPoolBaseURL, @@ -28,7 +28,7 @@ struct UserAPIEndPoint { queryParameters: request ) } - + static func postCommentLike(request: CommentLikeRequestDTO) -> RequestEndpoint { return RequestEndpoint( baseURL: KeyPath.popPoolBaseURL, @@ -37,7 +37,7 @@ struct UserAPIEndPoint { queryParameters: request ) } - + static func deleteCommentLike(request: CommentLikeRequestDTO) -> RequestEndpoint { return RequestEndpoint( baseURL: KeyPath.popPoolBaseURL, @@ -46,7 +46,7 @@ struct UserAPIEndPoint { queryParameters: request ) } - + static func postUserBlock(request: PostUserBlockRequestDTO) -> RequestEndpoint { return RequestEndpoint( baseURL: KeyPath.popPoolBaseURL, @@ -55,7 +55,7 @@ struct UserAPIEndPoint { queryParameters: request ) } - + static func deleteUserBlock(request: PostUserBlockRequestDTO) -> RequestEndpoint { return RequestEndpoint( baseURL: KeyPath.popPoolBaseURL, @@ -64,7 +64,7 @@ struct UserAPIEndPoint { queryParameters: request ) } - + static func getOtherUserCommentPopUpList(request: GetOtherUserCommentListRequestDTO) -> Endpoint { return Endpoint( baseURL: KeyPath.popPoolBaseURL, @@ -73,7 +73,7 @@ struct UserAPIEndPoint { queryParameters: request ) } - + static func getMyPage() -> Endpoint { return Endpoint( baseURL: KeyPath.popPoolBaseURL, @@ -81,7 +81,7 @@ struct UserAPIEndPoint { method: .get ) } - + static func postLogout() -> RequestEndpoint { return RequestEndpoint( baseURL: KeyPath.popPoolBaseURL, @@ -89,7 +89,7 @@ struct UserAPIEndPoint { method: .post ) } - + static func getWithdrawlList() -> Endpoint { return Endpoint( baseURL: KeyPath.popPoolBaseURL, @@ -97,7 +97,7 @@ struct UserAPIEndPoint { method: .get ) } - + static func postWithdrawl(request: PostWithdrawlListRequestDTO) -> RequestEndpoint { return RequestEndpoint( baseURL: KeyPath.popPoolBaseURL, @@ -106,7 +106,7 @@ struct UserAPIEndPoint { bodyParameters: request ) } - + static func getMyProfile() -> Endpoint { return Endpoint( baseURL: KeyPath.popPoolBaseURL, @@ -114,7 +114,7 @@ struct UserAPIEndPoint { method: .get ) } - + static func putUserTailoredInfo(request: PutUserTailoredInfoRequestDTO) -> RequestEndpoint { return RequestEndpoint( baseURL: KeyPath.popPoolBaseURL, @@ -122,8 +122,8 @@ struct UserAPIEndPoint { method: .put, bodyParameters: request ) - } - + } + static func putUserCategory(request: PutUserCategoryRequestDTO) -> RequestEndpoint { return RequestEndpoint( baseURL: KeyPath.popPoolBaseURL, @@ -131,8 +131,8 @@ struct UserAPIEndPoint { method: .put, bodyParameters: request ) - } - + } + static func putUserProfile(request: PutUserProfileRequestDTO) -> RequestEndpoint { return RequestEndpoint( baseURL: KeyPath.popPoolBaseURL, @@ -141,7 +141,7 @@ struct UserAPIEndPoint { bodyParameters: request ) } - + static func getMyCommentedPopUp(request: SortedRequestDTO) -> Endpoint { return Endpoint( baseURL: KeyPath.popPoolBaseURL, @@ -150,7 +150,7 @@ struct UserAPIEndPoint { queryParameters: request ) } - + static func getBlockUserList(request: GetBlockUserListRequestDTO) -> Endpoint { return Endpoint( baseURL: KeyPath.popPoolBaseURL, @@ -159,7 +159,7 @@ struct UserAPIEndPoint { queryParameters: request ) } - + static func getNoticeList() -> Endpoint { return Endpoint( baseURL: KeyPath.popPoolBaseURL, @@ -167,15 +167,15 @@ struct UserAPIEndPoint { method: .get ) } - + static func getNoticeDetail(noticeID: Int64) -> Endpoint { return Endpoint( baseURL: KeyPath.popPoolBaseURL, path: "/notice/\(noticeID)", method: .get ) - } - + } + static func getRecentPopUp(request: SortedRequestDTO) -> Endpoint { return Endpoint( baseURL: KeyPath.popPoolBaseURL, @@ -184,7 +184,7 @@ struct UserAPIEndPoint { queryParameters: request ) } - + static func getBookmarkPopUp(request: SortedRequestDTO) -> Endpoint { return Endpoint( baseURL: KeyPath.popPoolBaseURL, diff --git a/Poppool/Poppool/Data/Repository/AuthAPIRepositoryImpl.swift b/Poppool/Poppool/Data/Repository/AuthAPIRepositoryImpl.swift index 7c9e2628..7e01f977 100644 --- a/Poppool/Poppool/Data/Repository/AuthAPIRepositoryImpl.swift +++ b/Poppool/Poppool/Data/Repository/AuthAPIRepositoryImpl.swift @@ -9,15 +9,15 @@ import Foundation import RxSwift final class AuthAPIRepositoryImpl { - + var provider: Provider - + var tokenInterceptor = TokenInterceptor() - + init(provider: Provider) { self.provider = provider } - + func tryLogIn(userCredential: Encodable, socialType: String) -> Observable { let endPoint = AuthAPIEndPoint.auth_tryLogin(with: userCredential, path: socialType) return provider @@ -26,7 +26,7 @@ final class AuthAPIRepositoryImpl { return responseDTO.toDomain() } } - + func postTokenReissue() -> Observable { let endPoint = AuthAPIEndPoint.postTokenReissue() return provider.requestData(with: endPoint, interceptor: tokenInterceptor) diff --git a/Poppool/Poppool/Data/Repository/CommentAPIRepository.swift b/Poppool/Poppool/Data/Repository/CommentAPIRepository.swift index 0f3bd9ae..f5264dbf 100644 --- a/Poppool/Poppool/Data/Repository/CommentAPIRepository.swift +++ b/Poppool/Poppool/Data/Repository/CommentAPIRepository.swift @@ -10,24 +10,24 @@ import Foundation import RxSwift final class CommentAPIRepository { - + private let provider: Provider private let tokenInterceptor = TokenInterceptor() - + init(provider: Provider) { self.provider = provider } - + func postCommentAdd(request: PostCommentRequestDTO) -> Completable { let endPoint = CommentAPIEndPoint.postCommentAdd(request: request) return provider.request(with: endPoint, interceptor: tokenInterceptor) } - + func deleteComment(request: DeleteCommentRequestDTO) -> Completable { let endPoint = CommentAPIEndPoint.deleteComment(request: request) return provider.request(with: endPoint, interceptor: tokenInterceptor) } - + func editComment(request: PutCommentRequestDTO) -> Completable { let endPoint = CommentAPIEndPoint.editComment(request: request) return provider.request(with: endPoint, interceptor: tokenInterceptor) diff --git a/Poppool/Poppool/Data/Repository/HomeAPIRepository.swift b/Poppool/Poppool/Data/Repository/HomeAPIRepository.swift index 17c9a453..2bd55980 100644 --- a/Poppool/Poppool/Data/Repository/HomeAPIRepository.swift +++ b/Poppool/Poppool/Data/Repository/HomeAPIRepository.swift @@ -9,29 +9,29 @@ import Foundation import RxSwift final class HomeAPIRepository { - + private let provider: Provider private let tokenInterceptor = TokenInterceptor() - + init(provider: Provider) { self.provider = provider } - + func fetchHome(request: SortedRequestDTO) -> Observable { let endPoint = HomeAPIEndpoint.fetchHome(request: request) return provider.requestData(with: endPoint, interceptor: tokenInterceptor).map({ $0.toDomain() }) } - + func fetchCustomPopUp(request: SortedRequestDTO) -> Observable { let endPoint = HomeAPIEndpoint.fetchCustomPopUp(request: request) return provider.requestData(with: endPoint, interceptor: tokenInterceptor).map({ $0.toDomain() }) } - + func fetchNewPopUp(request: SortedRequestDTO) -> Observable { let endPoint = HomeAPIEndpoint.fetchNewPopUp(request: request) return provider.requestData(with: endPoint, interceptor: tokenInterceptor).map({ $0.toDomain() }) } - + func fetchPopularPopUp(request: SortedRequestDTO) -> Observable { let endPoint = HomeAPIEndpoint.fetchPopularPopUp(request: request) return provider.requestData(with: endPoint, interceptor: tokenInterceptor).map({ $0.toDomain() }) diff --git a/Poppool/Poppool/Data/Repository/PopUpAPIRepositoryImpl.swift b/Poppool/Poppool/Data/Repository/PopUpAPIRepositoryImpl.swift index 2ee2ed50..3c786005 100644 --- a/Poppool/Poppool/Data/Repository/PopUpAPIRepositoryImpl.swift +++ b/Poppool/Poppool/Data/Repository/PopUpAPIRepositoryImpl.swift @@ -12,36 +12,36 @@ import RxSwift struct PopUpAPIRepositoryImpl { private let provider: Provider private let tokenInterceptor = TokenInterceptor() - + init(provider: Provider) { self.provider = provider } - + func postBookmarkPopUp(request: PostBookmarkPopUpRequestDTO) -> Completable { let endPoint = UserAPIEndPoint.postBookmarkPopUp(request: request) return provider.request(with: endPoint, interceptor: tokenInterceptor) } - + func getClosePopUpList(request: GetSearchPopUpListRequestDTO) -> Observable { let endPoint = PopUpAPIEndPoint.getClosePopUpList(request: request) return provider.requestData(with: endPoint, interceptor: tokenInterceptor) } - + func getOpenPopUpList(request: GetSearchPopUpListRequestDTO) -> Observable { let endPoint = PopUpAPIEndPoint.getOpenPopUpList(request: request) return provider.requestData(with: endPoint, interceptor: tokenInterceptor) } - + func getSearchPopUpList(request: GetSearchPopUpListRequestDTO) -> Observable { let endPoint = PopUpAPIEndPoint.getSearchPopUpList(request: request) return provider.requestData(with: endPoint, interceptor: tokenInterceptor) } - + func getPopUpDetail(request: GetPopUpDetailRequestDTO) -> Observable { let endPoint = PopUpAPIEndPoint.getPopUpDetail(request: request) return provider.requestData(with: endPoint, interceptor: tokenInterceptor) } - + func getPopUpComment(request: GetPopUpCommentRequestDTO) -> Observable { let endPoint = PopUpAPIEndPoint.getPopUpComment(request: request) return provider.requestData(with: endPoint, interceptor: tokenInterceptor) diff --git a/Poppool/Poppool/Data/Repository/SignUpRepositoryImpl.swift b/Poppool/Poppool/Data/Repository/SignUpRepositoryImpl.swift index 87455fbd..6402b3dc 100644 --- a/Poppool/Poppool/Data/Repository/SignUpRepositoryImpl.swift +++ b/Poppool/Poppool/Data/Repository/SignUpRepositoryImpl.swift @@ -9,25 +9,25 @@ import Foundation import RxSwift final class SignUpRepositoryImpl { - + var provider: Provider - + init(provider: Provider) { self.provider = provider } - + func checkNickName(nickName: String) -> Observable { let endPoint = SignUpAPIEndpoint.signUp_checkNickName(with: .init(nickName: nickName)) return provider.requestData(with: endPoint, interceptor: TokenInterceptor()) } - + func fetchCategoryList() -> Observable<[Category]> { let endPoint = SignUpAPIEndpoint.signUp_getCategoryList() return provider.requestData(with: endPoint, interceptor: TokenInterceptor()).map { responseDTO in return responseDTO.categoryResponseList.map({ $0.toDomain() }) } } - + func trySignUp( nickName: String, gender: String, diff --git a/Poppool/Poppool/Data/Repository/UserAPIRepositoryImpl.swift b/Poppool/Poppool/Data/Repository/UserAPIRepositoryImpl.swift index 5e4cdab2..55a66ddb 100644 --- a/Poppool/Poppool/Data/Repository/UserAPIRepositoryImpl.swift +++ b/Poppool/Poppool/Data/Repository/UserAPIRepositoryImpl.swift @@ -10,64 +10,64 @@ import Foundation import RxSwift final class UserAPIRepositoryImpl { - + private let provider: Provider private let tokenInterceptor = TokenInterceptor() - + init(provider: Provider) { self.provider = provider } - + func postBookmarkPopUp(request: PostBookmarkPopUpRequestDTO) -> Completable { let endPoint = UserAPIEndPoint.postBookmarkPopUp(request: request) return provider.request(with: endPoint, interceptor: tokenInterceptor) } - + func deleteBookmarkPopUp(request: PostBookmarkPopUpRequestDTO) -> Completable { let endPoint = UserAPIEndPoint.deleteBookmarkPopUp(request: request) return provider.request(with: endPoint, interceptor: tokenInterceptor) } - + func postCommentLike(request: CommentLikeRequestDTO) -> Completable { let endPoint = UserAPIEndPoint.postCommentLike(request: request) return provider.request(with: endPoint, interceptor: tokenInterceptor) } - + func deleteCommentLike(request: CommentLikeRequestDTO) -> Completable { let endPoint = UserAPIEndPoint.deleteCommentLike(request: request) return provider.request(with: endPoint, interceptor: tokenInterceptor) } - + func postUserBlock(request: PostUserBlockRequestDTO) -> Completable { let endPoint = UserAPIEndPoint.postUserBlock(request: request) return provider.request(with: endPoint, interceptor: tokenInterceptor) } - + func deleteUserBlock(request: PostUserBlockRequestDTO) -> Completable { let endPoint = UserAPIEndPoint.deleteUserBlock(request: request) return provider.request(with: endPoint, interceptor: tokenInterceptor) } - + func getOtherUserCommentList(request: GetOtherUserCommentListRequestDTO) -> Observable { let endPoint = UserAPIEndPoint.getOtherUserCommentPopUpList(request: request) return provider.requestData(with: endPoint, interceptor: tokenInterceptor) } - + func getMyPage() -> Observable { let endPoint = UserAPIEndPoint.getMyPage() return provider.requestData(with: endPoint, interceptor: tokenInterceptor) } - + func postLogout() -> Completable { let endPoint = UserAPIEndPoint.postLogout() return provider.request(with: endPoint, interceptor: tokenInterceptor) } - + func getWithdrawlList() -> Observable { let endPoint = UserAPIEndPoint.getWithdrawlList() return provider.requestData(with: endPoint, interceptor: tokenInterceptor) } - + func postWithdrawl(request: PostWithdrawlListRequestDTO) -> Completable { let endPoint = UserAPIEndPoint.postWithdrawl(request: request) return provider.request(with: endPoint, interceptor: tokenInterceptor) @@ -77,47 +77,47 @@ final class UserAPIRepositoryImpl { let endPoint = UserAPIEndPoint.getMyProfile() return provider.requestData(with: endPoint, interceptor: tokenInterceptor) } - + func putUserTailoredInfo(request: PutUserTailoredInfoRequestDTO) -> Completable { let endPoint = UserAPIEndPoint.putUserTailoredInfo(request: request) return provider.request(with: endPoint, interceptor: tokenInterceptor) } - + func putUserCategory(request: PutUserCategoryRequestDTO) -> Completable { let endPoint = UserAPIEndPoint.putUserCategory(request: request) return provider.request(with: endPoint, interceptor: tokenInterceptor) - } - + } + func putUserProfile(request: PutUserProfileRequestDTO) -> Completable { let endPoint = UserAPIEndPoint.putUserProfile(request: request) return provider.request(with: endPoint, interceptor: tokenInterceptor) } - + func getMyCommentedPopUp(request: SortedRequestDTO) -> Observable { let endPoint = UserAPIEndPoint.getMyCommentedPopUp(request: request) return provider.requestData(with: endPoint, interceptor: tokenInterceptor) } - + func getBlockUserList(request: GetBlockUserListRequestDTO) -> Observable { let endPoint = UserAPIEndPoint.getBlockUserList(request: request) return provider.requestData(with: endPoint, interceptor: tokenInterceptor) } - + func getNoticeList() -> Observable { let endPoint = UserAPIEndPoint.getNoticeList() return provider.requestData(with: endPoint, interceptor: tokenInterceptor) } - + func getNoticeDetail(noticeID: Int64) -> Observable { let endPoint = UserAPIEndPoint.getNoticeDetail(noticeID: noticeID) return provider.requestData(with: endPoint, interceptor: tokenInterceptor) } - + func getRecentPopUp(request: SortedRequestDTO) -> Observable { let endPoint = UserAPIEndPoint.getRecentPopUp(request: request) return provider.requestData(with: endPoint, interceptor: tokenInterceptor) } - + func getBookmarkPopUp(request: SortedRequestDTO) -> Observable { let endPoint = UserAPIEndPoint.getBookmarkPopUp(request: request) return provider.requestData(with: endPoint, interceptor: tokenInterceptor) diff --git a/Poppool/Poppool/Domain/Entities/GetMyCommentResponse.swift b/Poppool/Poppool/Domain/Entities/GetMyCommentResponse.swift index 85d346d8..6d47e920 100644 --- a/Poppool/Poppool/Domain/Entities/GetMyCommentResponse.swift +++ b/Poppool/Poppool/Domain/Entities/GetMyCommentResponse.swift @@ -11,7 +11,6 @@ struct GetMyCommentedPopUpResponse { var popUpInfoList: [GetMyCommentedPopUpDataResponse] } - struct GetMyCommentedPopUpDataResponse { var popUpStoreId: Int64 var popUpStoreName: String? diff --git a/Poppool/Poppool/Domain/Repository/AuthRepository.swift b/Poppool/Poppool/Domain/Repository/AuthRepository.swift index 4aea0f51..0a8c43be 100644 --- a/Poppool/Poppool/Domain/Repository/AuthRepository.swift +++ b/Poppool/Poppool/Domain/Repository/AuthRepository.swift @@ -8,7 +8,7 @@ import Foundation import RxSwift -//protocol AuthRepository { +// protocol AuthRepository { // // /// 네트워크 요청을 처리하는 프로바이더 // var provider: Provider { get set } @@ -19,4 +19,4 @@ import RxSwift // /// - socialType: 소셜 로그인 타입 (예: "google", "facebook") // /// - Returns: 로그인 응답을 나타내는 Observable 객체 // func tryLogIn(userCredential: Encodable, socialType: String) -> Observable -//} +// } diff --git a/Poppool/Poppool/Domain/UseCase/AuthAPIUseCaseImpl.swift b/Poppool/Poppool/Domain/UseCase/AuthAPIUseCaseImpl.swift index bb96f2c2..e15877e6 100644 --- a/Poppool/Poppool/Domain/UseCase/AuthAPIUseCaseImpl.swift +++ b/Poppool/Poppool/Domain/UseCase/AuthAPIUseCaseImpl.swift @@ -9,17 +9,17 @@ import Foundation import RxSwift final class AuthAPIUseCaseImpl { - + var repository: AuthAPIRepositoryImpl - + init(repository: AuthAPIRepositoryImpl) { self.repository = repository } - + func postTryLogin(userCredential: Encodable, socialType: String) -> Observable { return repository.tryLogIn(userCredential: userCredential, socialType: socialType) } - + func postTokenReissue() -> Observable { let endPoint = AuthAPIEndPoint.postTokenReissue() return repository.postTokenReissue().map { $0.toDomain() } diff --git a/Poppool/Poppool/Domain/UseCase/CommentAPIUseCaseImpl.swift b/Poppool/Poppool/Domain/UseCase/CommentAPIUseCaseImpl.swift index 1cd85b34..f6d83409 100644 --- a/Poppool/Poppool/Domain/UseCase/CommentAPIUseCaseImpl.swift +++ b/Poppool/Poppool/Domain/UseCase/CommentAPIUseCaseImpl.swift @@ -10,21 +10,21 @@ import Foundation import RxSwift final class CommentAPIUseCaseImpl { - + var repository: CommentAPIRepository - + init(repository: CommentAPIRepository) { self.repository = repository } - + func postCommentAdd(popUpStoreId: Int64, content: String?, commentType: String?, imageUrlList: [String?]) -> Completable { return repository.postCommentAdd(request: .init(popUpStoreId: popUpStoreId, content: content, commentType: commentType, imageUrlList: imageUrlList)) } - + func deleteComment(popUpStoreId: Int64, commentId: Int64) -> Completable { return repository.deleteComment(request: .init(popUpStoreId: popUpStoreId, commentId: commentId)) } - + func editComment(popUpStoreId: Int64, commentId: Int64, content: String?, imageUrlList: [PutCommentImageDataRequestDTO]?) -> Completable { return repository.editComment(request: .init(popUpStoreId: popUpStoreId, commentId: commentId, content: content, imageUrlList: imageUrlList)) } diff --git a/Poppool/Poppool/Domain/UseCase/HomeAPIUseCaseImpl.swift b/Poppool/Poppool/Domain/UseCase/HomeAPIUseCaseImpl.swift index 3c28efac..1664dd60 100644 --- a/Poppool/Poppool/Domain/UseCase/HomeAPIUseCaseImpl.swift +++ b/Poppool/Poppool/Domain/UseCase/HomeAPIUseCaseImpl.swift @@ -10,7 +10,7 @@ import RxSwift final class HomeAPIUseCaseImpl { var repository = HomeAPIRepository(provider: ProviderImpl()) - + func fetchHome( page: Int32?, size: Int32?, @@ -18,7 +18,7 @@ final class HomeAPIUseCaseImpl { ) -> Observable { return repository.fetchHome(request: .init(page: page, size: size, sort: sort)) } - + func fetchCustomPopUp( page: Int32?, size: Int32?, @@ -26,7 +26,7 @@ final class HomeAPIUseCaseImpl { ) -> Observable { return repository.fetchCustomPopUp(request: .init(page: page, size: size, sort: sort)) } - + func fetchNewPopUp( page: Int32?, size: Int32?, @@ -34,7 +34,7 @@ final class HomeAPIUseCaseImpl { ) -> Observable { return repository.fetchNewPopUp(request: .init(page: page, size: size, sort: sort)) } - + func fetchPopularPopUp( page: Int32?, size: Int32?, diff --git a/Poppool/Poppool/Domain/UseCase/PopUpAPIUseCaseImpl.swift b/Poppool/Poppool/Domain/UseCase/PopUpAPIUseCaseImpl.swift index 2b00740c..6c405d4a 100644 --- a/Poppool/Poppool/Domain/UseCase/PopUpAPIUseCaseImpl.swift +++ b/Poppool/Poppool/Domain/UseCase/PopUpAPIUseCaseImpl.swift @@ -10,13 +10,13 @@ import Foundation import RxSwift final class PopUpAPIUseCaseImpl { - + var repository: PopUpAPIRepositoryImpl - + init(repository: PopUpAPIRepositoryImpl) { self.repository = repository } - + func getSearchBottomPopUpList(isOpen: Bool, categories: [Int64], page: Int32?, size: Int32, sort: String?) -> Observable { var categoryString: String? if !categories.isEmpty { @@ -29,17 +29,17 @@ final class PopUpAPIUseCaseImpl { return repository.getClosePopUpList(request: request).map { $0.toDomain() } } } - + func getSearchPopUpList(query: String?) -> Observable { return repository.getSearchPopUpList(request: .init(query: query)).map { $0.toDomain() } } - + func getPopUpDetail(commentType: String?, popUpStoredId: Int64, isViewCount: Bool? = true) -> Observable { return repository.getPopUpDetail(request: .init(commentType: commentType, popUpStoreId: popUpStoredId, viewCountYn: isViewCount)).map { $0.toDomain() } } - + func getPopUpComment(commentType: String?, page: Int32?, size: Int32?, sort: String?, popUpStoreId: Int64) -> Observable { - let request:GetPopUpCommentRequestDTO = .init(commentType: commentType, page: page, size: size, sort: sort, popUpStoreId: popUpStoreId) + let request: GetPopUpCommentRequestDTO = .init(commentType: commentType, page: page, size: size, sort: sort, popUpStoreId: popUpStoreId) return repository.getPopUpComment(request: request).map { $0.toDomain() } } } diff --git a/Poppool/Poppool/Domain/UseCase/SignUpAPIUseCaseImpl.swift b/Poppool/Poppool/Domain/UseCase/SignUpAPIUseCaseImpl.swift index e67b1868..006d48fe 100644 --- a/Poppool/Poppool/Domain/UseCase/SignUpAPIUseCaseImpl.swift +++ b/Poppool/Poppool/Domain/UseCase/SignUpAPIUseCaseImpl.swift @@ -10,7 +10,7 @@ import RxSwift final class SignUpAPIUseCaseImpl { var repository: SignUpRepositoryImpl - + init(repository: SignUpRepositoryImpl) { self.repository = repository } @@ -36,7 +36,7 @@ final class SignUpAPIUseCaseImpl { func checkNickName(nickName: String) -> Observable { return repository.checkNickName(nickName: nickName) } - + func fetchCategoryList() -> Observable<[Category]> { return repository.fetchCategoryList() } diff --git a/Poppool/Poppool/Domain/UseCase/UserAPIUseCaseImpl.swift b/Poppool/Poppool/Domain/UseCase/UserAPIUseCaseImpl.swift index 2eab0c45..1c907205 100644 --- a/Poppool/Poppool/Domain/UseCase/UserAPIUseCaseImpl.swift +++ b/Poppool/Poppool/Domain/UseCase/UserAPIUseCaseImpl.swift @@ -8,37 +8,37 @@ import RxSwift final class UserAPIUseCaseImpl { - + var repository: UserAPIRepositoryImpl - + init(repository: UserAPIRepositoryImpl) { self.repository = repository } - + func postBookmarkPopUp(popUpID: Int64) -> Completable { return repository.postBookmarkPopUp(request: .init(popUpStoreId: popUpID)) } - + func deleteBookmarkPopUp(popUpID: Int64) -> Completable { return repository.deleteBookmarkPopUp(request: .init(popUpStoreId: popUpID)) } - + func postCommentLike(commentId: Int64) -> Completable { return repository.postCommentLike(request: .init(commentId: commentId)) } - + func deleteCommentLike(commentId: Int64) -> Completable { return repository.deleteCommentLike(request: .init(commentId: commentId)) } - + func postUserBlock(blockedUserId: String?) -> Completable { return repository.postUserBlock(request: .init(blockedUserId: blockedUserId)) } - + func deleteUserBlock(blockedUserId: String?) -> Completable { return repository.deleteUserBlock(request: .init(blockedUserId: blockedUserId)) } - + func getOtherUserCommentedPopUpList( commenterId: String?, commentType: String?, @@ -56,31 +56,31 @@ final class UserAPIUseCaseImpl { ) .map { $0.toDomain() } } - + func getMyPage() -> Observable { return repository.getMyPage().map { $0.toDomain() } } - + func postLogout() -> Completable { return repository.postLogout() } - + func getWithdrawlList() -> Observable { return repository.getWithdrawlList().map { $0.toDomain() } } - + func postWithdrawl(surveyList: [GetWithdrawlListDataResponse]) -> Completable { return repository.postWithdrawl(request: .init(checkedSurveyList: surveyList.map { .init(id: $0.id, survey: $0.survey)})) } - + func getMyProfile() -> Observable { return repository.getMyProfile().map { $0.toDomain() } } - + func putUserTailoredInfo(gender: String?, age: Int32) -> Completable { return repository.putUserTailoredInfo(request: .init(gender: gender, age: age)) } - + func putUserCategory( interestCategoriesToAdd: [Int64], interestCategoriesToDelete: [Int64], @@ -94,31 +94,31 @@ final class UserAPIUseCaseImpl { ) ) } - + func putUserProfile(profileImageUrl: String?, nickname: String?, email: String?, instagramId: String?, intro: String?) -> Completable { return repository.putUserProfile(request: .init(profileImageUrl: profileImageUrl, nickname: nickname, email: email, instagramId: instagramId, intro: intro)) } - - func getMyCommentedPopUp(page: Int32?, size:Int32?, sort: String?) -> Observable { + + func getMyCommentedPopUp(page: Int32?, size: Int32?, sort: String?) -> Observable { return repository.getMyCommentedPopUp(request: .init(page: page, size: size, sort: sort)).map { $0.toDomain() } } - + func getBlockUserList(page: Int32?, size: Int32?, sort: String?) -> Observable { return repository.getBlockUserList(request: .init(page: page, size: size, sort: sort)).map { $0.toDomain() } } - + func getNoticeList() -> Observable { return repository.getNoticeList().map { $0.toDomain() } } - + func getNoticeDetail(noticeID: Int64) -> Observable { return repository.getNoticeDetail(noticeID: noticeID).map { $0.toDomain() } } - + func getRecentPopUp(page: Int32?, size: Int32?, sort: String?) -> Observable { return repository.getRecentPopUp(request: .init(page: page, size: size, sort: sort)).map { $0.toDomain() } - } - + } + func getBookmarkPopUp(page: Int32?, size: Int32?, sort: String?) -> Observable { return repository.getBookmarkPopUp(request: .init(page: page, size: size, sort: sort)).map { $0.toDomain() } } diff --git a/Poppool/Poppool/Infrastructure/AppleLoginService.swift b/Poppool/Poppool/Infrastructure/AppleLoginService.swift index a26f2e0c..ecee526f 100644 --- a/Poppool/Poppool/Infrastructure/AppleLoginService.swift +++ b/Poppool/Poppool/Infrastructure/AppleLoginService.swift @@ -5,25 +5,25 @@ // Created by SeoJunYoung on 8/20/24. // -import RxSwift import AuthenticationServices +import RxSwift final class AppleLoginService: NSObject, AuthServiceable { - + // 사용자 자격 증명 정보를 방출할 subject private var authServiceResponse: PublishSubject = .init() - + func fetchUserCredential() -> Observable { performRequest() return authServiceResponse } - + // Apple 인증 요청을 수행하는 함수 private func performRequest() { let appleIDProvider = ASAuthorizationAppleIDProvider() let request = appleIDProvider.createRequest() request.requestedScopes = [.fullName, .email] - + let authorizationController = ASAuthorizationController(authorizationRequests: [request]) authorizationController.delegate = self authorizationController.presentationContextProvider = self @@ -48,7 +48,7 @@ extension AppleLoginService: ASAuthorizationControllerPresentationContextProvidi } return window } - + // 인증 성공 시 호출되는 함수 func authorizationController( controller: ASAuthorizationController, @@ -69,7 +69,7 @@ extension AppleLoginService: ASAuthorizationControllerPresentationContextProvidi guard let authorizationCode = appleIDCredential.authorizationCode else { return } - + guard let convertAuthorizationCode = String(data: authorizationCode, encoding: .utf8) else { return } diff --git a/Poppool/Poppool/Infrastructure/KakaoLoginService.swift b/Poppool/Poppool/Infrastructure/KakaoLoginService.swift index 4d234418..382133e9 100644 --- a/Poppool/Poppool/Infrastructure/KakaoLoginService.swift +++ b/Poppool/Poppool/Infrastructure/KakaoLoginService.swift @@ -1,11 +1,11 @@ -import RxSwift -import KakaoSDKUser import KakaoSDKAuth +import KakaoSDKUser +import RxSwift final class KakaoLoginService: AuthServiceable { - + var disposeBag = DisposeBag() - + func unlink() -> Observable { return Observable.create { observer in UserApi.shared.unlink { error in @@ -17,11 +17,11 @@ final class KakaoLoginService: AuthServiceable { observer.onCompleted() } } - + return Disposables.create() } } - + func fetchUserCredential() -> Observable { return Observable.create { [weak self] observer in guard let self else { @@ -33,7 +33,7 @@ final class KakaoLoginService: AuthServiceable { ) return Disposables.create() } - + // 카카오톡 설치 유무 확인 guard UserApi.isKakaoTalkLoginAvailable() else { Logger.log( @@ -42,28 +42,28 @@ final class KakaoLoginService: AuthServiceable { fileName: #file, line: #line ) - + // 카카오톡 미설치시 웹으로 인증 시도 loginWithKakaoTalkWeb(observer: observer) return Disposables.create() } - + // 카카오톡 설치시 앱으로 인증 시도 loginWithKakaoTalkApp(observer: observer) - + return Disposables.create() } } } private extension KakaoLoginService { - + /// 제공된 액세스 토큰을 사용하여 사용자의 카카오 ID를 가져옵니다. /// - Parameters: /// - observer: 인증 응답을 처리할 옵저버. /// - accessToken: 카카오 로그인 과정에서 얻은 액세스 토큰. func fetchUserId(observer: AnyObserver, accessToken: String) { - UserApi.shared.me() { user, error in + UserApi.shared.me { user, error in if let error = error { observer.onError(AuthError.unknownError(description: error.localizedDescription)) } else { @@ -72,7 +72,7 @@ private extension KakaoLoginService { } } } - + /// 카카오톡 앱을 사용하여 로그인하고 액세스 토큰을 가져옵니다. /// - Parameter observer: 인증 응답을 처리할 옵저버. func loginWithKakaoTalkApp(observer: AnyObserver) { @@ -86,7 +86,7 @@ private extension KakaoLoginService { } } } - + /// 카카오톡 웹을 사용하여 로그인하고 액세스 토큰을 가져옵니다. /// - Parameter observer: 인증 응답을 처리할 옵저버. func loginWithKakaoTalkWeb(observer: AnyObserver) { diff --git a/Poppool/Poppool/Infrastructure/KeyChainService.swift b/Poppool/Poppool/Infrastructure/KeyChainService.swift index 7717acea..ebcfcb70 100644 --- a/Poppool/Poppool/Infrastructure/KeyChainService.swift +++ b/Poppool/Poppool/Infrastructure/KeyChainService.swift @@ -18,10 +18,10 @@ final class KeyChainService { case unhandledError(status: OSStatus) // 예상치 못한 OSStatus 오류가 발생했을 때 발생 case dataConversionError(message: String) // 데이터 변환 중 오류가 발생했을 때 발생 } - + // KeyChain 서비스 이름 private let service = "keyChain" - + /// KeyChain에서 특정 타입의 토큰을 가져오는 메서드 /// - Parameter type: 가져오려는 토큰의 타입 (`accessToken` 또는 `refreshToken`) /// - Returns: 가져온 토큰을 담은 `Single` @@ -34,11 +34,11 @@ final class KeyChainService { kSecReturnData: true, // CFData 타입으로 불러오라는 의미 kSecMatchLimit: kSecMatchLimitOne // 중복되는 경우 하나의 값만 가져오라는 의미 ] - + // 2. Read var dataTypeRef: AnyObject? let status = SecItemCopyMatching(keyChainQuery, &dataTypeRef) - + // 3. Result if status == errSecItemNotFound { return .failure(KeyChainError.noValueFound(message: "No value found for the specified key.")) @@ -72,7 +72,7 @@ final class KeyChainService { guard let convertValue = value.data(using: .utf8, allowLossyConversion: false) else { return .failure(KeyChainError.dataConversionError(message: "Failed to convert value to Data.")) } - + // 1. query 작성 let keyChainQuery: NSDictionary = [ kSecClass: kSecClassGenericPassword, @@ -80,11 +80,11 @@ final class KeyChainService { kSecAttrAccount: type.rawValue, kSecValueData: convertValue ] - + // 2. Delete // KeyChain은 Key값에 중복이 생기면 저장할 수 없기 때문에 먼저 Delete SecItemDelete(keyChainQuery) - + // 3. Create let status = SecItemAdd(keyChainQuery, nil) if status == errSecSuccess { @@ -99,7 +99,7 @@ final class KeyChainService { return .failure(KeyChainError.unhandledError(status: status)) } } - + /// KeyChain에서 특정 타입의 토큰을 삭제하는 메서드 /// - Parameter type: 삭제하려는 토큰의 타입 (`accessToken` 또는 `refreshToken`) /// - Returns: 완료 시 `Completable` @@ -110,10 +110,10 @@ final class KeyChainService { kSecAttrService: self.service, kSecAttrAccount: type.rawValue ] - + // 2. Delete let status = SecItemDelete(keyChainQuery) - + if status == errSecSuccess { Logger.log( message: "Successfully deleted \(type.rawValue) from KeyChain", diff --git a/Poppool/Poppool/Infrastructure/Logger/Logger.swift b/Poppool/Poppool/Infrastructure/Logger/Logger.swift index cf3cbadb..9ad233b2 100644 --- a/Poppool/Poppool/Infrastructure/Logger/Logger.swift +++ b/Poppool/Poppool/Infrastructure/Logger/Logger.swift @@ -15,7 +15,7 @@ struct Logger { case error case event case custom(categoryName: String) - + var categoryName: String { switch self { case .info: @@ -32,7 +32,7 @@ struct Logger { return categoryName } } - + var categoryIcon: String { switch self { case .info: @@ -50,13 +50,13 @@ struct Logger { } } } - + static var isShowFileName: Bool = false static var isShowLine: Bool = false static var isShowLog: Bool = true - + static private let noInputText = "Input is not found" - + static func log( message: Any, category: Level, diff --git a/Poppool/Poppool/Infrastructure/NetworkLayer/Common/Requestable.swift b/Poppool/Poppool/Infrastructure/NetworkLayer/Common/Requestable.swift index cd902b29..9933fba3 100644 --- a/Poppool/Poppool/Infrastructure/NetworkLayer/Common/Requestable.swift +++ b/Poppool/Poppool/Infrastructure/NetworkLayer/Common/Requestable.swift @@ -24,14 +24,14 @@ extension Requestable { /// - Returns: URLRequest 반환 func getUrlRequest() throws -> URLRequest { let url = try url() - + Logger.log( message: "\(url) URL 생성", category: .network, fileName: #file, line: #line ) - + var urlRequest = URLRequest(url: url) // httpBody if let bodyParameters = try bodyParameters?.toDictionary() { @@ -46,7 +46,7 @@ extension Requestable { headers?.forEach { urlRequest.setValue($1, forHTTPHeaderField: $0) } return urlRequest } - + /// APIEndpoint에서 전달받은 DTO를 URL로 변환하는 메서드 /// - Returns: URL 반환 func url() throws -> URL { @@ -80,7 +80,7 @@ extension Requestable { } extension Encodable { - + /// URL에 요청할 쿼리 데이터를 JSON 형식에 맞게 딕셔너리 구조로 변환하는 메서드 /// - Returns: jsonData func toDictionary() throws -> [String: Any]? { diff --git a/Poppool/Poppool/Infrastructure/NetworkLayer/EndPoint/Endpoint.swift b/Poppool/Poppool/Infrastructure/NetworkLayer/EndPoint/Endpoint.swift index cdbe56d6..fcc83980 100644 --- a/Poppool/Poppool/Infrastructure/NetworkLayer/EndPoint/Endpoint.swift +++ b/Poppool/Poppool/Infrastructure/NetworkLayer/EndPoint/Endpoint.swift @@ -13,7 +13,7 @@ protocol RequesteResponsable: Requestable, Responsable where Response: Decodable class Endpoint: RequesteResponsable { typealias Response = R - + var baseURL: String var path: String var method: HTTPMethod diff --git a/Poppool/Poppool/Infrastructure/NetworkLayer/EndPoint/MultipartEndPoint.swift b/Poppool/Poppool/Infrastructure/NetworkLayer/EndPoint/MultipartEndPoint.swift index 42d83dd0..75b4ef2e 100644 --- a/Poppool/Poppool/Infrastructure/NetworkLayer/EndPoint/MultipartEndPoint.swift +++ b/Poppool/Poppool/Infrastructure/NetworkLayer/EndPoint/MultipartEndPoint.swift @@ -17,7 +17,7 @@ class MultipartEndPoint: URLRequestConvertible { var jsonData: [String: Any]? var images: [UIImage] var headers: [String: String]? - + init( baseURL: String, path: String, @@ -35,22 +35,22 @@ class MultipartEndPoint: URLRequestConvertible { self.images = images self.headers = headers } - + func asURLRequest() throws -> URLRequest { let url = try baseURL.asURL().appendingPathComponent(path) var request = URLRequest(url: url) Logger.log(message: "\(request) URL 생성", category: .network) request.method = method - + if let headers = headers { for (key, value) in headers { request.setValue(value, forHTTPHeaderField: key) } } - + return request } - + func asMultipartFormData(multipartFormData: MultipartFormData) { // JSON 데이터를 data 필드로 추가 if let jsonData = jsonData { @@ -65,7 +65,7 @@ class MultipartEndPoint: URLRequestConvertible { Logger.log(message: "JSON 변환 오류: \(error)", category: .network) } } - + // 이미지 파일 추가 for (index, image) in images.enumerated() { if let imageData = image.jpegData(compressionQuality: 0.8) { diff --git a/Poppool/Poppool/Infrastructure/NetworkLayer/IndicatorMaker/IndicatorMaker.swift b/Poppool/Poppool/Infrastructure/NetworkLayer/IndicatorMaker/IndicatorMaker.swift index f25705e3..997125a1 100644 --- a/Poppool/Poppool/Infrastructure/NetworkLayer/IndicatorMaker/IndicatorMaker.swift +++ b/Poppool/Poppool/Infrastructure/NetworkLayer/IndicatorMaker/IndicatorMaker.swift @@ -11,26 +11,26 @@ import Lottie import SnapKit struct IndicatorMaker { - + static let indicatorImageView: LottieAnimationView = { let view = LottieAnimationView(name: "indicator") view.loopMode = .loop return view }() - + static let overlayView: UIView = { let view = UIView() view.backgroundColor = .black.withAlphaComponent(0.1) return view }() - + static func showIndicator() { DispatchQueue.main.async { guard let topVC = UIApplication.topViewController() else { print("Error: Cannot find top view controller") return } - + topVC.view.addSubview(overlayView) overlayView.snp.makeConstraints { make in make.edges.equalToSuperview() @@ -44,7 +44,7 @@ struct IndicatorMaker { topVC.view.isUserInteractionEnabled = false } } - + static func hideIndicator() { DispatchQueue.main.async { indicatorImageView.stop() diff --git a/Poppool/Poppool/Infrastructure/NetworkLayer/Interceptor/FormDataInterceptor.swift b/Poppool/Poppool/Infrastructure/NetworkLayer/Interceptor/FormDataInterceptor.swift index efd1d123..f057df7d 100644 --- a/Poppool/Poppool/Infrastructure/NetworkLayer/Interceptor/FormDataInterceptor.swift +++ b/Poppool/Poppool/Infrastructure/NetworkLayer/Interceptor/FormDataInterceptor.swift @@ -5,20 +5,20 @@ // Created by SeoJunYoung on 10/25/24. // -import Foundation import Alamofire +import Foundation import RxSwift final class FormDataInterceptor: RequestInterceptor { - + private var disposeBag = DisposeBag() - + func adapt( _ urlRequest: URLRequest, for session: Session, completion: @escaping (Result) -> Void) { } - + func retry( _ request: Request, for session: Session, diff --git a/Poppool/Poppool/Infrastructure/NetworkLayer/Interceptor/TokenInterceptor.swift b/Poppool/Poppool/Infrastructure/NetworkLayer/Interceptor/TokenInterceptor.swift index 9b4d8c55..9ad8e40f 100644 --- a/Poppool/Poppool/Infrastructure/NetworkLayer/Interceptor/TokenInterceptor.swift +++ b/Poppool/Poppool/Infrastructure/NetworkLayer/Interceptor/TokenInterceptor.swift @@ -5,12 +5,12 @@ // Created by SeoJunYoung on 10/14/24. // -import Foundation import Alamofire +import Foundation import RxSwift final class TokenInterceptor: RequestInterceptor { - + func adapt( _ urlRequest: URLRequest, for session: Session, @@ -29,7 +29,7 @@ final class TokenInterceptor: RequestInterceptor { completion(.success(urlRequest)) } } - + func retry( _ request: Request, for session: Session, diff --git a/Poppool/Poppool/Infrastructure/NetworkLayer/Provider/Provider.swift b/Poppool/Poppool/Infrastructure/NetworkLayer/Provider/Provider.swift index edc12bfd..1a2e4394 100644 --- a/Poppool/Poppool/Infrastructure/NetworkLayer/Provider/Provider.swift +++ b/Poppool/Poppool/Infrastructure/NetworkLayer/Provider/Provider.swift @@ -7,11 +7,11 @@ import Foundation -import RxSwift import Alamofire +import RxSwift protocol Provider { - + /// 네트워크 요청을 수행하고 결과를 반환하는 메서드 /// - Parameters: /// - endpoint: 요청할 엔드포인트 @@ -22,7 +22,7 @@ protocol Provider { with endpoint: E, interceptor: RequestInterceptor? ) -> Observable where R == E.Response - + /// 네트워크 요청을 수행하고 결과를 반환하는 메서드 /// - Parameters: /// - request: 요청할 Requestable 객체 @@ -33,7 +33,7 @@ protocol Provider { with request: E, interceptor: RequestInterceptor? ) -> Completable - + /// 이미지와 데이터를 `multipart/form-data`로 업로드하는 메서드 func uploadImages( with request: MultipartEndPoint, diff --git a/Poppool/Poppool/Infrastructure/NetworkLayer/Provider/ProviderImpl.swift b/Poppool/Poppool/Infrastructure/NetworkLayer/Provider/ProviderImpl.swift index efa81ca6..643a0e1f 100644 --- a/Poppool/Poppool/Infrastructure/NetworkLayer/Provider/ProviderImpl.swift +++ b/Poppool/Poppool/Infrastructure/NetworkLayer/Provider/ProviderImpl.swift @@ -5,9 +5,9 @@ // Created by SeoJunYoung on 8/16/24. // +import Alamofire import Foundation import RxSwift -import Alamofire final class ProviderImpl: Provider { diff --git a/Poppool/Poppool/Infrastructure/PreSignedService/PreSignedService.swift b/Poppool/Poppool/Infrastructure/PreSignedService/PreSignedService.swift index b1a6912f..26efb286 100644 --- a/Poppool/Poppool/Infrastructure/PreSignedService/PreSignedService.swift +++ b/Poppool/Poppool/Infrastructure/PreSignedService/PreSignedService.swift @@ -8,9 +8,9 @@ import Foundation import UIKit -import RxSwift -import RxCocoa import Alamofire +import RxCocoa +import RxSwift class ImageCache { static let shared = NSCache() @@ -24,9 +24,9 @@ class PreSignedService { } let tokenInterceptor = TokenInterceptor() - + let provider = ProviderImpl() - + let disposeBag = DisposeBag() func tryDelete(targetPaths: PresignedURLRequestDTO) -> Completable { @@ -82,7 +82,6 @@ class PreSignedService { } } - func tryDownload(filePaths: [String]) -> Single<[UIImage]> { return Single.create { [weak self] observer in @@ -149,7 +148,6 @@ class PreSignedService { } } - private extension PreSignedService { func uploadFromS3(url: String, image: UIImage) -> Single { @@ -204,8 +202,6 @@ private extension PreSignedService { } } - - func getUploadLinks(request: PresignedURLRequestDTO) -> Observable { Logger.log(message: "Presigned URL 생성 요청 데이터: \(request)", category: .debug) let provider = ProviderImpl() @@ -308,4 +304,3 @@ extension PreSignedService { } } - diff --git a/Poppool/Poppool/Infrastructure/UserDefaultService.swift b/Poppool/Poppool/Infrastructure/UserDefaultService.swift index ffb4c2de..ff64c2a6 100644 --- a/Poppool/Poppool/Infrastructure/UserDefaultService.swift +++ b/Poppool/Poppool/Infrastructure/UserDefaultService.swift @@ -10,27 +10,27 @@ import Foundation import RxSwift final class UserDefaultService { - + /// Userdefault 데이터 저장 메서드 /// - Parameters: /// - key: 저장하는 데이터의 키 값 i.e) 유저 id 등 /// - value: 저장하는 데이터 값 i.e) access token 등 /// - to: 로컬 데이터베이스 타입 - DatabaseType /// - Returns: 별도 안내 없음 - func save(key: String, value: String) { + func save(key: String, value: String) { UserDefaults.standard.set(value, forKey: key) } - + /// Userdefault 데이터 저장 메서드 /// - Parameters: /// - key: 저장하는 데이터의 키 값 i.e) 유저 id 등 /// - value: 저장하는 데이터 값 i.e) access token 등 /// - to: 로컬 데이터베이스 타입 - DatabaseType /// - Returns: 별도 안내 없음 - func save(key: String, value: [String]) { + func save(key: String, value: [String]) { UserDefaults.standard.set(value, forKey: key) } - + /// Userdefault 데이터 발견 메서드 /// - Parameters: /// - key: 찾는 데이터의 키 값 i.e) 유저 id 등 @@ -42,7 +42,7 @@ final class UserDefaultService { } return nil } - + /// Userdefault 데이터 발견 메서드 /// - Parameters: /// - key: 찾는 데이터의 키 값 i.e) 유저 id 등 @@ -54,7 +54,7 @@ final class UserDefaultService { } return nil } - + /// Userdefault 데이터 삭제 메서드 /// - Parameters: /// - key: 삭제하는 데이터의 키 값 i.e) 유저 id 등 diff --git a/Poppool/Poppool/Presentation/Admin/AdminBottomSheet/AdminBottomSheetView.swift b/Poppool/Poppool/Presentation/Admin/AdminBottomSheet/AdminBottomSheetView.swift index 5876db70..88d37c32 100644 --- a/Poppool/Poppool/Presentation/Admin/AdminBottomSheet/AdminBottomSheetView.swift +++ b/Poppool/Poppool/Presentation/Admin/AdminBottomSheet/AdminBottomSheetView.swift @@ -1,15 +1,15 @@ -import UIKit -import SnapKit -import RxSwift -import RxCocoa import ReactorKit +import RxCocoa +import RxSwift +import SnapKit +import UIKit final class AdminBottomSheetView: UIView { - + // MARK: - Properties private var contentHeightConstraint: Constraint? typealias Reactor = AdminBottomSheetReactor - + // MARK: - Components let containerView: UIView = { let view = UIView() @@ -19,44 +19,43 @@ final class AdminBottomSheetView: UIView { view.layer.masksToBounds = true return view }() - + let headerView: UIView = { let view = UIView() view.backgroundColor = .white return view }() - + let titleLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 18, text: "보기 옵션을 선택해주세요") label.textColor = .black return label }() - + let closeButton: UIButton = { let button = UIButton(type: .system) button.setImage(UIImage(named: "icon_xmark"), for: .normal) - + button.tintColor = .black return button }() - + let segmentedControl: PPSegmentedControl = { - let control = PPSegmentedControl( + return PPSegmentedControl( type: .tab, segments: ["상태값", "카테고리"], selectedSegmentIndex: 0 ) - return control }() - + let contentCollectionView: UICollectionView = { - let layout = UICollectionViewCompositionalLayout { section, env in + let layout = UICollectionViewCompositionalLayout { section, _ in let itemSize = NSCollectionLayoutSize( widthDimension: .estimated(26), heightDimension: .absolute(36) ) let item = NSCollectionLayoutItem(layoutSize: itemSize) - + let groupSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(36) @@ -66,7 +65,7 @@ final class AdminBottomSheetView: UIView { subitems: [item] ) group.interItemSpacing = .fixed(12) - + let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init( top: 20, @@ -75,10 +74,10 @@ final class AdminBottomSheetView: UIView { trailing: 20 ) section.interGroupSpacing = 16 - + return section } - + let collectionView = UICollectionView( frame: .zero, collectionViewLayout: layout @@ -87,9 +86,9 @@ final class AdminBottomSheetView: UIView { collectionView.isScrollEnabled = false return collectionView }() - + let filterChipsView = FilterChipsView() - + let resetButton: PPButton = { let button = PPButton( style: .secondary, @@ -106,7 +105,7 @@ final class AdminBottomSheetView: UIView { ) return button }() - + let saveButton: PPButton = { let button = PPButton( style: .primary, @@ -124,7 +123,7 @@ final class AdminBottomSheetView: UIView { ) return button }() - + private let buttonStack: UIStackView = { let stack = UIStackView() stack.axis = .horizontal @@ -132,74 +131,74 @@ final class AdminBottomSheetView: UIView { stack.distribution = .fillEqually return stack }() - + // MARK: - Initialization override init(frame: CGRect) { super.init(frame: frame) setupLayout() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + // MARK: - Setup private func setupLayout() { backgroundColor = .clear addSubview(containerView) - + containerView.addSubview(headerView) headerView.addSubview(titleLabel) headerView.addSubview(closeButton) - + [segmentedControl, contentCollectionView, filterChipsView, buttonStack].forEach { containerView.addSubview($0) } - + buttonStack.addArrangedSubview(resetButton) buttonStack.addArrangedSubview(saveButton) - + setupConstraints() } - + private func setupConstraints() { containerView.snp.makeConstraints { make in make.left.right.bottom.equalToSuperview() make.top.equalTo(headerView.snp.top) } - + headerView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() make.height.equalTo(60) } - + titleLabel.snp.makeConstraints { make in make.leading.equalToSuperview().offset(16) make.centerY.equalToSuperview() } - + closeButton.snp.makeConstraints { make in make.trailing.equalToSuperview().inset(16) make.centerY.equalToSuperview() make.size.equalTo(24) } - + segmentedControl.snp.makeConstraints { make in make.top.equalTo(headerView.snp.bottom).offset(16) make.leading.trailing.equalToSuperview() } - + contentCollectionView.snp.makeConstraints { make in make.top.equalTo(segmentedControl.snp.bottom).offset(16) make.leading.trailing.equalToSuperview() contentHeightConstraint = make.height.equalTo(160).constraint } - + filterChipsView.snp.makeConstraints { make in make.top.equalTo(contentCollectionView.snp.bottom).offset(24) make.leading.trailing.equalToSuperview().inset(16) } - + buttonStack.snp.makeConstraints { make in make.top.equalTo(filterChipsView.snp.bottom).offset(24) make.leading.trailing.equalToSuperview().inset(16) @@ -207,20 +206,20 @@ final class AdminBottomSheetView: UIView { make.height.equalTo(52) } } - + // MARK: - Public Methods func updateContentVisibility(isCategorySelected: Bool) { Logger.log(message: "높이 변경 시작: \(isCategorySelected ? "카테고리" : "상태값")", category: .debug) - + let newHeight: CGFloat = isCategorySelected ? 200 : 160 - + // 애니메이션 없이 바로 적용 contentHeightConstraint?.update(offset: newHeight) contentCollectionView.invalidateIntrinsicContentSize() - + setNeedsLayout() layoutIfNeeded() - + Logger.log(message: "높이 변경 완료", category: .debug) } } diff --git a/Poppool/Poppool/Presentation/Admin/AdminBottomSheet/AdminBottomSheetViewController.swift b/Poppool/Poppool/Presentation/Admin/AdminBottomSheet/AdminBottomSheetViewController.swift index dbee91f3..4f3a1a7b 100644 --- a/Poppool/Poppool/Presentation/Admin/AdminBottomSheet/AdminBottomSheetViewController.swift +++ b/Poppool/Poppool/Presentation/Admin/AdminBottomSheet/AdminBottomSheetViewController.swift @@ -1,9 +1,8 @@ -import UIKit -import SnapKit -import RxSwift -import RxCocoa import ReactorKit - +import RxCocoa +import RxSwift +import SnapKit +import UIKit final class AdminBottomSheetViewController: BaseViewController, View { @@ -28,8 +27,6 @@ final class AdminBottomSheetViewController: BaseViewController, View { fatalError("init(coder:) has not been implemented") } - - // MARK: - Life Cycle override func viewDidLoad() { super.viewDidLoad() @@ -74,7 +71,7 @@ final class AdminBottomSheetViewController: BaseViewController, View { Logger.log(message: "최종 뷰 계층:", category: .debug) } - + private func setupCollectionView() { mainView.contentCollectionView.register( TagSectionCell.self, @@ -85,7 +82,7 @@ final class AdminBottomSheetViewController: BaseViewController, View { // MARK: - Binding func bind(reactor: Reactor) { mainView.segmentedControl.rx.selectedSegmentIndex - .do(onNext: { index in + .do(onNext: { _ in }) .map { Reactor.Action.segmentChanged($0) } .bind(to: reactor.action) diff --git a/Poppool/Poppool/Presentation/Admin/AdminReactor.swift b/Poppool/Poppool/Presentation/Admin/AdminReactor.swift index 1d0f2cbd..e258e6df 100644 --- a/Poppool/Poppool/Presentation/Admin/AdminReactor.swift +++ b/Poppool/Poppool/Presentation/Admin/AdminReactor.swift @@ -1,6 +1,6 @@ import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class AdminReactor: Reactor { diff --git a/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterReactor.swift b/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterReactor.swift index b02ef0fe..f3acf044 100644 --- a/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterReactor.swift +++ b/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterReactor.swift @@ -1,7 +1,6 @@ import ReactorKit -import RxSwift import RxCocoa - +import RxSwift final class PopUpStoreRegisterReactor: Reactor { diff --git a/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterView.swift b/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterView.swift index 5c25cf83..5b4b04fc 100644 --- a/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterView.swift +++ b/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterView.swift @@ -1,7 +1,7 @@ -import UIKit -import SnapKit -import RxSwift import RxCocoa +import RxSwift +import SnapKit +import UIKit final class PopUpStoreRegisterView: UIView { // 상단 네비게이션 영역 diff --git a/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift b/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift index 710f001f..e47cb8fe 100644 --- a/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift +++ b/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift @@ -1,12 +1,12 @@ -import UIKit -import SnapKit -import ReactorKit -import RxSwift -import RxCocoa -import PhotosUI import Alamofire -import GoogleMaps import CoreLocation +import GoogleMaps +import PhotosUI +import ReactorKit +import RxCocoa +import RxSwift +import SnapKit +import UIKit final class PopUpStoreRegisterViewController: BaseViewController { @@ -24,7 +24,6 @@ final class PopUpStoreRegisterViewController: BaseViewController { private var lonField: UITextField? private var descTV: UITextView? - private let popupName: String = "" private let editingStore: GetAdminPopUpStoreListResponseDTO.PopUpStore? let presignedService = PreSignedService() @@ -76,7 +75,6 @@ final class PopUpStoreRegisterViewController: BaseViewController { return lbl }() - private let menuButton: UIButton = { let btn = UIButton(type: .system) btn.setImage(UIImage(systemName: "adminlist"), for: .normal) @@ -101,7 +99,6 @@ final class PopUpStoreRegisterViewController: BaseViewController { return lbl }() - private let addImageButton = UIButton(type: .system).then { $0.setTitle("이미지 추가", for: .normal) $0.setTitleColor(.systemBlue, for: .normal) @@ -153,12 +150,12 @@ final class PopUpStoreRegisterViewController: BaseViewController { let btn = UIButton(type: .system) btn.setTitle("카테고리 선택 ▾", for: .normal) btn.setTitleColor(.darkGray, for: .normal) - btn.titleLabel?.font = UIFont.systemFont(ofSize:14) + btn.titleLabel?.font = UIFont.systemFont(ofSize: 14) btn.layer.cornerRadius = 8 btn.layer.borderWidth = 1 btn.layer.borderColor = UIColor.lightGray.cgColor btn.contentHorizontalAlignment = .left - btn.contentEdgeInsets = UIEdgeInsets(top:7, left:8, bottom:7, right:8) + btn.contentEdgeInsets = UIEdgeInsets(top: 7, left: 8, bottom: 7, right: 8) return btn }() @@ -166,12 +163,12 @@ final class PopUpStoreRegisterViewController: BaseViewController { let btn = UIButton(type: .system) btn.setTitle("기간 선택 ▾", for: .normal) btn.setTitleColor(.darkGray, for: .normal) - btn.titleLabel?.font = UIFont.systemFont(ofSize:14) + btn.titleLabel?.font = UIFont.systemFont(ofSize: 14) btn.layer.cornerRadius = 8 btn.layer.borderWidth = 1 btn.layer.borderColor = UIColor.lightGray.cgColor btn.contentHorizontalAlignment = .left - btn.contentEdgeInsets = UIEdgeInsets(top:7, left:8, bottom:7, right:8) + btn.contentEdgeInsets = UIEdgeInsets(top: 7, left: 8, bottom: 7, right: 8) return btn }() @@ -179,19 +176,19 @@ final class PopUpStoreRegisterViewController: BaseViewController { let btn = UIButton(type: .system) btn.setTitle("시간 선택 ▾", for: .normal) btn.setTitleColor(.darkGray, for: .normal) - btn.titleLabel?.font = UIFont.systemFont(ofSize:14) + btn.titleLabel?.font = UIFont.systemFont(ofSize: 14) btn.layer.cornerRadius = 8 btn.layer.borderWidth = 1 btn.layer.borderColor = UIColor.lightGray.cgColor btn.contentHorizontalAlignment = .left - btn.contentEdgeInsets = UIEdgeInsets(top:7, left:8, bottom:7, right:8) + btn.contentEdgeInsets = UIEdgeInsets(top: 7, left: 8, bottom: 7, right: 8) return btn }() // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = UIColor(white:0.95, alpha:1) + view.backgroundColor = UIColor(white: 0.95, alpha: 1) let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap)) tapGesture.cancelsTouchesInView = false @@ -208,10 +205,8 @@ final class PopUpStoreRegisterViewController: BaseViewController { setupKeyboardHandling() setupAddressField() - } - // MARK: - Navigation private func setupNavigation() { backButton.addTarget(self, action: #selector(onBack), for: .touchUpInside) @@ -243,7 +238,7 @@ final class PopUpStoreRegisterViewController: BaseViewController { descTV?.text = storeDetail.desc let isoFormatter = ISO8601DateFormatter() - + if let startDate = isoFormatter.date(from: storeDetail.startDate), let endDate = isoFormatter.date(from: storeDetail.endDate) { self.selectedStartDate = startDate @@ -253,7 +248,7 @@ final class PopUpStoreRegisterViewController: BaseViewController { // 대표 이미지 로드 (생략된 부분은 기존 코드 참고) if let mainImageURL = presignedService.fullImageURL(from: storeDetail.mainImageUrl) { - URLSession.shared.dataTask(with: mainImageURL) { [weak self] data, response, error in + URLSession.shared.dataTask(with: mainImageURL) { [weak self] data, _, _ in guard let self = self, let data = data, let image = UIImage(data: data) else { return } @@ -338,8 +333,6 @@ final class PopUpStoreRegisterViewController: BaseViewController { } } - - // MARK: - Layout private func setupLayout() { // (1) 상단 컨테이너 @@ -461,7 +454,7 @@ final class PopUpStoreRegisterViewController: BaseViewController { // 1) 주소 (TextField) let addressField = makeRoundedTextField("팝업스토어 주소를 입력해 주세요.") self.addressField = addressField - addressField.snp.makeConstraints { make in + addressField.snp.makeConstraints { _ in addressField.addTarget(self, action: #selector(fieldDidChange(_:)), for: .editingChanged) } @@ -473,14 +466,12 @@ final class PopUpStoreRegisterViewController: BaseViewController { self.latField = latField // latField와 연결 latField.addTarget(self, action: #selector(fieldDidChange(_:)), for: .editingChanged) - let lonLabel = makePlainLabel("경도") let lonField = makeRoundedTextField("") self.lonField = lonField // lonField와 연결 lonField.textAlignment = .center lonField.addTarget(self, action: #selector(fieldDidChange(_:)), for: .editingChanged) - let latStack = UIStackView(arrangedSubviews: [latLabel, latField]) latStack.axis = .horizontal latStack.spacing = 8 @@ -502,16 +493,15 @@ final class PopUpStoreRegisterViewController: BaseViewController { locationVStack.spacing = 8 locationVStack.distribution = .fillEqually - // 한 행에 왼쪽 "위치", 오른쪽 2줄(주소 / 위도경도) addRowCustom(leftTitle: "위치", rightView: locationVStack, rowHeight: nil, totalHeight: 80) // (마커) => 2줄 // 1) (마커명 Label + TF) let markerLabel = makePlainLabel("마커명") - + let markerField = makeRoundedTextField("") - + let markerStackH = UIStackView(arrangedSubviews: [markerLabel, markerField]) markerStackH.axis = .horizontal markerStackH.spacing = 8 @@ -531,7 +521,6 @@ final class PopUpStoreRegisterViewController: BaseViewController { markerVStack.spacing = 8 markerVStack.distribution = .fillEqually - // 한 행 => "마커" 라벨, 오른쪽 2줄 (마커명, 스니펫) addRowCustom(leftTitle: "마커", rightView: markerVStack, rowHeight: nil, totalHeight: 80) @@ -562,7 +551,6 @@ final class PopUpStoreRegisterViewController: BaseViewController { } - // MARK: - Row private func addRowTextField(leftTitle: String, placeholder: String) { @@ -744,7 +732,6 @@ final class PopUpStoreRegisterViewController: BaseViewController { } } - private func updatePeriodButtonTitle() { guard let s = selectedStartDate, let e = selectedEndDate else { return } let df = DateFormatter() @@ -851,7 +838,7 @@ final class PopUpStoreRegisterViewController: BaseViewController { private func makeRoundedTextField(_ placeholder: String) -> UITextField { let tf = UITextField() tf.placeholder = placeholder - tf.font = UIFont.systemFont(ofSize:14) + tf.font = UIFont.systemFont(ofSize: 14) tf.textColor = .darkGray tf.borderStyle = .none tf.layer.cornerRadius = 8 @@ -865,12 +852,12 @@ final class PopUpStoreRegisterViewController: BaseViewController { let btn = UIButton(type: .system) btn.setTitle(title, for: .normal) btn.setTitleColor(.darkGray, for: .normal) - btn.titleLabel?.font = UIFont.systemFont(ofSize:14) + btn.titleLabel?.font = UIFont.systemFont(ofSize: 14) btn.layer.cornerRadius = 8 btn.layer.borderWidth = 1 btn.layer.borderColor = UIColor.lightGray.cgColor btn.contentHorizontalAlignment = .left - btn.contentEdgeInsets = UIEdgeInsets(top:7, left:8, bottom:7, right:8) + btn.contentEdgeInsets = UIEdgeInsets(top: 7, left: 8, bottom: 7, right: 8) return btn } @@ -879,7 +866,7 @@ final class PopUpStoreRegisterViewController: BaseViewController { if let icon = UIImage(named: iconName) { btn.setImage(icon, for: .normal) btn.imageView?.contentMode = .scaleAspectFit - btn.titleEdgeInsets = UIEdgeInsets(top:0, left:6, bottom:0, right:0) + btn.titleEdgeInsets = UIEdgeInsets(top: 0, left: 6, bottom: 0, right: 0) } return btn } @@ -887,7 +874,7 @@ final class PopUpStoreRegisterViewController: BaseViewController { private func makeSimpleLabel(_ text: String) -> UILabel { let lbl = UILabel() lbl.text = text - lbl.font = UIFont.systemFont(ofSize:14) + lbl.font = UIFont.systemFont(ofSize: 14) lbl.textColor = .darkGray return lbl } @@ -896,7 +883,7 @@ final class PopUpStoreRegisterViewController: BaseViewController { // 작은 라벨(위도/경도/마커명/스니펫 등) let lbl = UILabel() lbl.text = text - lbl.font = UIFont.systemFont(ofSize:14) + lbl.font = UIFont.systemFont(ofSize: 14) lbl.textColor = .darkGray lbl.textAlignment = .right lbl.setContentHuggingPriority(.required, for: .horizontal) @@ -905,12 +892,12 @@ final class PopUpStoreRegisterViewController: BaseViewController { private func makeRoundedTextView() -> UITextView { let tv = UITextView() - tv.font = UIFont.systemFont(ofSize:14) + tv.font = UIFont.systemFont(ofSize: 14) tv.textColor = .darkGray tv.layer.cornerRadius = 8 tv.layer.borderWidth = 1 tv.layer.borderColor = UIColor.lightGray.cgColor - tv.textContainerInset = UIEdgeInsets(top:7, left:7, bottom:7, right:7) + tv.textContainerInset = UIEdgeInsets(top: 7, left: 7, bottom: 7, right: 7) tv.isScrollEnabled = true return tv } @@ -918,8 +905,8 @@ final class PopUpStoreRegisterViewController: BaseViewController { // MARK: - Padding private extension UITextField { - func setLeftPaddingPoints(_ amount: CGFloat){ - let paddingView = UIView(frame: CGRect(x:0, y:0, width:amount, height: frame.size.height)) + func setLeftPaddingPoints(_ amount: CGFloat) { + let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: amount, height: frame.size.height)) leftView = paddingView leftViewMode = .always } @@ -986,7 +973,7 @@ extension PopUpStoreRegisterViewController: PHPickerViewControllerDelegate { for (i, provider) in itemProviders.enumerated() { if provider.canLoadObject(ofClass: UIImage.self) { dispatchGroup.enter() - provider.loadObject(ofClass: UIImage.self) { [weak self] object, error in + provider.loadObject(ofClass: UIImage.self) { [weak self] object, _ in defer { dispatchGroup.leave() } guard let self = self, let image = object as? UIImage else { return } @@ -1046,7 +1033,6 @@ private extension PopUpStoreRegisterViewController { return false } - // (4) 위도/경도 Logger.log(message: "latField.text = \(latField?.text ?? "nil")", category: .debug) Logger.log(message: "lonField.text = \(lonField?.text ?? "nil")", category: .debug) @@ -1122,7 +1108,6 @@ private extension PopUpStoreRegisterViewController { } } - // 폼 데이터 검증 private func validateFormData() -> Bool { guard let name = nameField?.text, @@ -1211,8 +1196,6 @@ private extension PopUpStoreRegisterViewController { imagesToDelete: [] // 필요한 경우 기존 이미지 삭제 로직 추가 ) - - adminUseCase.updateStore(request: request) .subscribe( onNext: { [weak self] _ in @@ -1325,7 +1308,6 @@ private extension PopUpStoreRegisterViewController { startDateBeforeEndDate: isValidDateOrder ) - adminUseCase.createStore(request: request) .subscribe( onNext: { [weak self] _ in @@ -1366,7 +1348,6 @@ private extension PopUpStoreRegisterViewController { } } - private func createDateTime(date: Date?, time: Date?) -> Date? { guard let date = date else { return nil } @@ -1405,9 +1386,6 @@ private extension PopUpStoreRegisterViewController { return formatter.string(from: date) } - - - private func prepareDateTime() -> (startDate: String, endDate: String) { // 시작일/시간 결합 let startDateTime = createDateTime(date: selectedStartDate, time: selectedStartTime) @@ -1446,10 +1424,6 @@ private extension PopUpStoreRegisterViewController { return start < end } - - - - private func showSuccessAlert() { let alert = UIAlertController( title: "등록 성공", @@ -1499,7 +1473,7 @@ extension PopUpStoreRegisterViewController: UITextFieldDelegate { return Observable.create { observer in let geocoder = CLGeocoder() let fullAddress = "\(address), Korea" - + geocoder.geocodeAddressString( fullAddress, in: nil, @@ -1511,7 +1485,7 @@ extension PopUpStoreRegisterViewController: UITextFieldDelegate { observer.onCompleted() return } - + if let location = placemarks?.first?.location { observer.onNext(location) } else { @@ -1519,7 +1493,7 @@ extension PopUpStoreRegisterViewController: UITextFieldDelegate { } observer.onCompleted() } - + return Disposables.create() } } @@ -1532,23 +1506,22 @@ extension PopUpStoreRegisterViewController: UITextFieldDelegate { }) .disposed(by: disposeBag) } - - + @objc private func addressFieldDidChange(_ textField: UITextField) { guard let address = textField.text, !address.isEmpty else { return } - + // 한국 주소임을 명시 let geocoder = CLGeocoder() let addressWithCountry = address + ", South Korea" - + geocoder.geocodeAddressString(addressWithCountry) { [weak self] placemarks, error in if let error = error { print("Geocoding error: \(error.localizedDescription)") return } - + guard let location = placemarks?.first?.location else { return } - + DispatchQueue.main.async { self?.latField?.text = String(format: "%.6f", location.coordinate.latitude) self?.lonField?.text = String(format: "%.6f", location.coordinate.longitude) @@ -1556,5 +1529,5 @@ extension PopUpStoreRegisterViewController: UITextFieldDelegate { } } } - + } diff --git a/Poppool/Poppool/Presentation/Admin/AdminStoreCell.swift b/Poppool/Poppool/Presentation/Admin/AdminStoreCell.swift index ed2815db..5ab3d4c4 100644 --- a/Poppool/Poppool/Presentation/Admin/AdminStoreCell.swift +++ b/Poppool/Poppool/Presentation/Admin/AdminStoreCell.swift @@ -1,6 +1,6 @@ -import UIKit -import SnapKit import RxSwift +import SnapKit +import UIKit final class AdminStoreCell: UITableViewCell { private let disposeBag = DisposeBag() diff --git a/Poppool/Poppool/Presentation/Admin/AdminView.swift b/Poppool/Poppool/Presentation/Admin/AdminView.swift index 3ec4149e..2d13cc17 100644 --- a/Poppool/Poppool/Presentation/Admin/AdminView.swift +++ b/Poppool/Poppool/Presentation/Admin/AdminView.swift @@ -1,6 +1,6 @@ -import UIKit import SnapKit import Then +import UIKit final class AdminView: UIView { @@ -18,7 +18,7 @@ final class AdminView: UIView { let usernameLabel = PPLabel( style: .bold, fontSize: 14, - text: "" + text: "" ) let menuButton = UIButton(type: .system).then { diff --git a/Poppool/Poppool/Presentation/Admin/AdminViewController.swift b/Poppool/Poppool/Presentation/Admin/AdminViewController.swift index 24e86807..48ad02f8 100644 --- a/Poppool/Poppool/Presentation/Admin/AdminViewController.swift +++ b/Poppool/Poppool/Presentation/Admin/AdminViewController.swift @@ -1,7 +1,7 @@ -import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift +import UIKit final class AdminViewController: BaseViewController, View { @@ -238,11 +238,10 @@ final class AdminViewController: BaseViewController, View { .compactMap { $0 } .subscribe(onNext: { [weak self] store in guard let self = self else { return } - self.editStore(store) + self.editStore(store) }) .disposed(by: disposeBag) - reactor.state.map { $0.storeList } .map { "총 \($0.count)개" } .bind(to: mainView.popupCountLabel.rx.text) diff --git a/Poppool/Poppool/Presentation/Admin/Common/DateTimePickerManager.swift b/Poppool/Poppool/Presentation/Admin/Common/DateTimePickerManager.swift index 3e251d01..b1de4138 100644 --- a/Poppool/Poppool/Presentation/Admin/Common/DateTimePickerManager.swift +++ b/Poppool/Poppool/Presentation/Admin/Common/DateTimePickerManager.swift @@ -1,5 +1,5 @@ -import UIKit import SnapKit +import UIKit final class DateTimePickerManager { diff --git a/Poppool/Poppool/Presentation/Admin/Data/DTO/AdminResponseDTO.swift b/Poppool/Poppool/Presentation/Admin/Data/DTO/AdminResponseDTO.swift index 5924f809..329934c5 100644 --- a/Poppool/Poppool/Presentation/Admin/Data/DTO/AdminResponseDTO.swift +++ b/Poppool/Poppool/Presentation/Admin/Data/DTO/AdminResponseDTO.swift @@ -13,7 +13,6 @@ struct GetAdminPopUpStoreListResponseDTO: Decodable { let mainImageUrl: String } - } // MARK: - Store Detail Response diff --git a/Poppool/Poppool/Presentation/Admin/Data/DTO/GetAdminPopUpStoreListResponseDTO.swift b/Poppool/Poppool/Presentation/Admin/Data/DTO/GetAdminPopUpStoreListResponseDTO.swift index 8be73a2b..6d14302a 100644 --- a/Poppool/Poppool/Presentation/Admin/Data/DTO/GetAdminPopUpStoreListResponseDTO.swift +++ b/Poppool/Poppool/Presentation/Admin/Data/DTO/GetAdminPopUpStoreListResponseDTO.swift @@ -1,4 +1,3 @@ - import Foundation // MARK: - Store List Request @@ -10,7 +9,7 @@ struct StoreListRequestDTO: Encodable { enum CodingKeys: String, CodingKey { case query case page - case size + case size } } @@ -62,7 +61,6 @@ struct CreatePopUpStoreRequestDTO: Encodable { } } - // MARK: - Update Store Request struct UpdatePopUpStoreRequestDTO: Encodable { let popUpStore: PopUpStore @@ -123,7 +121,6 @@ struct UpdatePopUpStoreRequestDTO: Encodable { } } - // MARK: - Notice Request struct CreateNoticeRequestDTO: Encodable { let title: String diff --git a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapAPIEndpoint.swift b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapAPIEndpoint.swift index 5c4a4bdc..3b5c7107 100644 --- a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapAPIEndpoint.swift +++ b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapAPIEndpoint.swift @@ -5,8 +5,8 @@ // Created by 김기현 on 12/4/24. // -import Foundation import Alamofire +import Foundation struct MapAPIEndpoint { /// 뷰 바운즈 내에 있는 팝업 스토어 정보를 조회 @@ -84,4 +84,3 @@ struct SearchQueryDTO: Encodable { let query: String let categories: [Int64]? } - diff --git a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapPopUpStore.swift b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapPopUpStore.swift index bcc448f4..f448e161 100644 --- a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapPopUpStore.swift +++ b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapPopUpStore.swift @@ -4,8 +4,8 @@ // // Created by 김기현 on 12/3/24. // -import Foundation import CoreLocation +import Foundation struct MapPopUpStore: Equatable { let id: Int64 @@ -20,7 +20,6 @@ struct MapPopUpStore: Equatable { let markerTitle: String let markerSnippet: String let mainImageUrl: String? // 이미지 URL 추가 - var coordinate: CLLocationCoordinate2D { CLLocationCoordinate2D(latitude: latitude, longitude: longitude) @@ -36,8 +35,6 @@ struct MapPopUpStore: Equatable { count: 0 ) } - - func toStoreItem() -> StoreItem { return StoreItem( diff --git a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/Repository/MapRepository.swift b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/Repository/MapRepository.swift index 53164882..07bbce5d 100644 --- a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/Repository/MapRepository.swift +++ b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/Repository/MapRepository.swift @@ -33,7 +33,6 @@ class DefaultMapRepository: MapRepository { self.provider = provider } - func fetchStoresInBounds( northEastLat: Double, northEastLon: Double, @@ -68,7 +67,6 @@ class DefaultMapRepository: MapRepository { .map { $0.popUpStoreList } } - func fetchCategories() -> Observable<[Category]> { Logger.log(message: "카테고리 매핑 요청을 시작합니다.", category: .network) diff --git a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/UseCase/MapUseCase.swift b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/UseCase/MapUseCase.swift index 1d68c933..660ee9d0 100644 --- a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/UseCase/MapUseCase.swift +++ b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/UseCase/MapUseCase.swift @@ -36,7 +36,7 @@ class DefaultMapUseCase: MapUseCase { northEastLon: Double, southWestLat: Double, southWestLon: Double, - categories: [Int64] + categories: [Int64] ) -> Observable<[MapPopUpStore]> { return repository.fetchStoresInBounds( @@ -49,7 +49,6 @@ class DefaultMapUseCase: MapUseCase { .map { $0.map { $0.toDomain() } } } - func searchStores( query: String, categories: [Int64] diff --git a/Poppool/Poppool/Presentation/Admin/Data/Repository/AdminRepository.swift b/Poppool/Poppool/Presentation/Admin/Data/Repository/AdminRepository.swift index c11757bb..7d0bd1e6 100644 --- a/Poppool/Poppool/Presentation/Admin/Data/Repository/AdminRepository.swift +++ b/Poppool/Poppool/Presentation/Admin/Data/Repository/AdminRepository.swift @@ -1,7 +1,7 @@ +import Alamofire import Foundation import RxSwift import UIKit -import Alamofire protocol AdminRepository { func fetchStoreList(query: String?, page: Int, size: Int) -> Observable @@ -54,7 +54,6 @@ final class DefaultAdminRepository: AdminRepository { } } - func createStore(request: CreatePopUpStoreRequestDTO) -> Observable { Logger.log(message: "createStore API 호출 시작", category: .info) let endpoint = AdminAPIEndpoint.createStore(request: request) @@ -84,8 +83,6 @@ final class DefaultAdminRepository: AdminRepository { ) } - - func updateStore(request: UpdatePopUpStoreRequestDTO) -> Observable { let endpoint = AdminAPIEndpoint.updateStore(request: request) @@ -125,12 +122,11 @@ final class DefaultAdminRepository: AdminRepository { }) } - func deleteStore(id: Int64) -> Observable { Logger.log(message: "deleteStore API 호출 시작", category: .info) let endpoint = AdminAPIEndpoint.deleteStore(id: id) return provider.request(with: endpoint, interceptor: tokenInterceptor) - .andThen(Observable.just(EmptyResponse())) + .andThen(Observable.just(EmptyResponse())) .do( onNext: { _ in Logger.log(message: "deleteStore API 호출 성공", category: .info) @@ -140,7 +136,6 @@ final class DefaultAdminRepository: AdminRepository { } ) } - // MARK: - Notice Methods func createNotice(request: CreateNoticeRequestDTO) -> Observable { diff --git a/Poppool/Poppool/Presentation/Admin/ImageCell.swift b/Poppool/Poppool/Presentation/Admin/ImageCell.swift index 98f6fbf1..04704d15 100644 --- a/Poppool/Poppool/Presentation/Admin/ImageCell.swift +++ b/Poppool/Poppool/Presentation/Admin/ImageCell.swift @@ -1,5 +1,5 @@ -import UIKit import SnapKit +import UIKit final class ImageCell: UICollectionViewCell { static let identifier = "ImageCell" diff --git a/Poppool/Poppool/Presentation/Admin/PopUpStoreRegisterReactor.swift b/Poppool/Poppool/Presentation/Admin/PopUpStoreRegisterReactor.swift index 99d29f89..6e355394 100644 --- a/Poppool/Poppool/Presentation/Admin/PopUpStoreRegisterReactor.swift +++ b/Poppool/Poppool/Presentation/Admin/PopUpStoreRegisterReactor.swift @@ -5,12 +5,12 @@ //// Created by 김기현 on 1/14/25. //// // -//import Foundation -//import ReactorKit -//import RxSwift -//import UIKit +// import Foundation +// import ReactorKit +// import RxSwift +// import UIKit // -//final class PopUpStoreRegisterReactor: Reactor { +// final class PopUpStoreRegisterReactor: Reactor { // // // MARK: - Action // enum Action { @@ -303,4 +303,4 @@ // default: return 100 // } // } -//} +// } diff --git a/Poppool/Poppool/Presentation/Admin/PopUpStoreRegisterView.swift b/Poppool/Poppool/Presentation/Admin/PopUpStoreRegisterView.swift index 55c20d1c..6255da1a 100644 --- a/Poppool/Poppool/Presentation/Admin/PopUpStoreRegisterView.swift +++ b/Poppool/Poppool/Presentation/Admin/PopUpStoreRegisterView.swift @@ -5,11 +5,11 @@ //// Created by 김기현 on 1/14/25. //// // -//import UIKit -//import SnapKit -//import Then +// import UIKit +// import SnapKit +// import Then // -//final class PopUpStoreRegisterView: UIView { +// final class PopUpStoreRegisterView: UIView { // // // MARK: - Callbacks (Closure) // /// "이미지 추가" 버튼 탭 @@ -320,10 +320,10 @@ // // return row // } -//} +// } // //// MARK: - UICollectionViewDataSource -//extension PopUpStoreRegisterView: UICollectionViewDataSource { +// extension PopUpStoreRegisterView: UICollectionViewDataSource { // func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { // return images.count // } @@ -347,15 +347,15 @@ // } // return cell // } -//} +// } // //// MARK: - UICollectionViewDelegateFlowLayout -//extension PopUpStoreRegisterView: UICollectionViewDelegateFlowLayout { +// extension PopUpStoreRegisterView: UICollectionViewDelegateFlowLayout { // // 혹시 셀 사이즈/간격을 동적으로 조정하고 싶다면 여기서 -//} +// } // //// MARK: - PopUpImageCell (같은 파일) -//final class PopUpImageCell: UICollectionViewCell { +// final class PopUpImageCell: UICollectionViewCell { // static let identifier = "PopUpImageCell" // // // 콜백 @@ -421,4 +421,4 @@ // thumbImageView.image = item.image // mainCheckButton.backgroundColor = item.isMain ? .systemRed : .gray // } -//} +// } diff --git a/Poppool/Poppool/Presentation/Components/PPButton.swift b/Poppool/Poppool/Presentation/Components/PPButton.swift index 87063272..5d446403 100644 --- a/Poppool/Poppool/Presentation/Components/PPButton.swift +++ b/Poppool/Poppool/Presentation/Components/PPButton.swift @@ -8,14 +8,14 @@ import UIKit class PPButton: UIButton { - + enum ButtonStyle { case primary case secondary case tertiary case kakao case apple - + var backgroundColor: UIColor { switch self { case .primary: @@ -30,7 +30,7 @@ class PPButton: UIButton { return .g900 } } - + var textColor: UIColor { switch self { case .primary: @@ -45,7 +45,7 @@ class PPButton: UIButton { return .w100 } } - + var disabledBackgroundColor: UIColor { switch self { case .primary: @@ -56,7 +56,7 @@ class PPButton: UIButton { return .blu500 } } - + var disabledTextColor: UIColor { switch self { case .primary: @@ -68,8 +68,7 @@ class PPButton: UIButton { } } } - - + init( style: ButtonStyle, text: String, @@ -78,25 +77,25 @@ class PPButton: UIButton { cornerRadius: CGFloat = 4 ) { super.init(frame: .zero) - + self.setTitle(text, for: .normal) self.setTitle(disabledText, for: .disabled) - + self.setTitleColor(style.textColor, for: .normal) self.setTitleColor(style.disabledTextColor, for: .disabled) - + self.setBackgroundColor(style.backgroundColor, for: .normal) self.setBackgroundColor(style.disabledBackgroundColor, for: .disabled) - + self.titleLabel?.font = font self.layer.cornerRadius = cornerRadius self.clipsToBounds = true } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + /// 버튼 배경색 설정 /// - Parameters: /// - color: 색상 @@ -109,7 +108,7 @@ class PPButton: UIButton { let backgroundImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() - + self.setBackgroundImage(backgroundImage, for: state) } } diff --git a/Poppool/Poppool/Presentation/Components/PPCancelHeaderView.swift b/Poppool/Poppool/Presentation/Components/PPCancelHeaderView.swift index a033274a..eac352f6 100644 --- a/Poppool/Poppool/Presentation/Components/PPCancelHeaderView.swift +++ b/Poppool/Poppool/Presentation/Components/PPCancelHeaderView.swift @@ -10,7 +10,7 @@ import UIKit import SnapKit final class PPCancelHeaderView: UIView { - + // MARK: - Components let backButton: UIButton = { let button = UIButton(type: .system) @@ -18,7 +18,7 @@ final class PPCancelHeaderView: UIView { button.tintColor = .black return button }() - + let cancelButton: UIButton = { let button = UIButton(type: .system) button.setTitle("취소", for: .normal) @@ -26,13 +26,13 @@ final class PPCancelHeaderView: UIView { button.setTitleColor(.black, for: .normal) return button }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -40,7 +40,7 @@ final class PPCancelHeaderView: UIView { // MARK: - SetUp private extension PPCancelHeaderView { - + func setUpConstraints() { self.addSubview(backButton) backButton.snp.makeConstraints { make in @@ -48,7 +48,7 @@ private extension PPCancelHeaderView { make.top.bottom.equalToSuperview().inset(8) make.leading.equalToSuperview().inset(12) } - + self.addSubview(cancelButton) cancelButton.snp.makeConstraints { make in make.centerY.equalTo(backButton) diff --git a/Poppool/Poppool/Presentation/Components/PPLabel.swift b/Poppool/Poppool/Presentation/Components/PPLabel.swift index 41030705..25f16a0d 100644 --- a/Poppool/Poppool/Presentation/Components/PPLabel.swift +++ b/Poppool/Poppool/Presentation/Components/PPLabel.swift @@ -8,9 +8,9 @@ import UIKit class PPLabel: UILabel { - + init( - style: UIFont.FontStyle, + style: UIFont.FontStyle, fontSize: CGFloat, text: String = "", lineHeight: CGFloat = 1.2 @@ -24,7 +24,7 @@ class PPLabel: UILabel { attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle] ) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/Poppool/Poppool/Presentation/Components/PPPicker.swift b/Poppool/Poppool/Presentation/Components/PPPicker.swift index e5164c9c..82fa75fc 100644 --- a/Poppool/Poppool/Presentation/Components/PPPicker.swift +++ b/Poppool/Poppool/Presentation/Components/PPPicker.swift @@ -5,13 +5,13 @@ // Created by SeoJunYoung on 7/3/24. // +import RxCocoa +import RxSwift import SnapKit import UIKit -import RxSwift -import RxCocoa final class PPPicker: UIView { - + // MARK: - Components private let components: [String] let pickerView = UIPickerView() @@ -24,7 +24,7 @@ final class PPPicker: UIView { private let disposeBag = DisposeBag() /// 항목 선택 이벤트를 전달하는 PublishSubject입니다. let itemSelectObserver: PublishSubject = .init() - + // MARK: - init /// PickerCPNT init /// - Parameter components: UIPickerView에 표시할 문자열 배열입니다. @@ -35,7 +35,7 @@ final class PPPicker: UIView { setUpConstraints() bind() } - + @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") @@ -49,7 +49,7 @@ extension PPPicker { pickerView.delegate = self pickerView.dataSource = self } - + func setUpConstraints() { self.addSubview(selectView) self.addSubview(pickerView) @@ -62,7 +62,7 @@ extension PPPicker { make.center.equalToSuperview() } } - + func bind() { pickerView.rx.itemSelected .withUnretained(self) @@ -75,7 +75,7 @@ extension PPPicker { // MARK: - Methods extension PPPicker { - + /// 지정된 인덱스로 UIPickerView를 설정 /// - Parameter index: 설정할 인덱스 값 func setIndex(index: Int) { @@ -88,11 +88,11 @@ extension PPPicker: UIPickerViewDelegate, UIPickerViewDataSource { func numberOfComponents(in pickerView: UIPickerView) -> Int { return 1 } - + func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return components.count } - + func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { let label = UILabel() label.text = components[row] @@ -110,7 +110,7 @@ extension PPPicker: UIPickerViewDelegate, UIPickerViewDataSource { func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { return 48 } - + func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { pickerView.reloadAllComponents() } diff --git a/Poppool/Poppool/Presentation/Components/PPProgressIndicator/PPProgressIndicator.swift b/Poppool/Poppool/Presentation/Components/PPProgressIndicator/PPProgressIndicator.swift index 4908fec6..ec709b83 100644 --- a/Poppool/Poppool/Presentation/Components/PPProgressIndicator/PPProgressIndicator.swift +++ b/Poppool/Poppool/Presentation/Components/PPProgressIndicator/PPProgressIndicator.swift @@ -6,18 +6,17 @@ // import Foundation -import UIKit -import SnapKit -import RxSwift import RxCocoa - +import RxSwift +import SnapKit +import UIKit final class PPProgressIndicator: UIStackView { - + // MARK: - Properties private var progressViews: [PPProgressView] private var progressIndex: Int - + // MARK: - init /// 전체 단계 수와 시작 지점을 기반으로 CMPTProgressIndicator를 초기화 /// - Parameters: @@ -32,7 +31,7 @@ final class PPProgressIndicator: UIStackView { setUp() setUpConstraints() } - + required init(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -40,14 +39,14 @@ final class PPProgressIndicator: UIStackView { // MARK: - SetUp private extension PPProgressIndicator { - + /// 스택 뷰 속성 설정 func setUp() { self.axis = .horizontal self.distribution = .fillEqually self.spacing = 6 } - + /// 진행 뷰의 제약 조건을 설정 func setUpConstraints() { progressViews.forEach { views in @@ -58,7 +57,7 @@ private extension PPProgressIndicator { // MARK: - Methods extension PPProgressIndicator { - + /// 진행 인디케이터를 한 단계 앞으로 이동 func increaseIndicator() { if progressIndex < progressViews.count { @@ -67,7 +66,7 @@ extension PPProgressIndicator { progressViews[progressIndex - 1].fillAnimation(option: .fromLeft) } } - + /// 진행 인디케이터를 한 단계 뒤로 이동 func decreaseIndicator() { if progressIndex - 1 > 0 { diff --git a/Poppool/Poppool/Presentation/Components/PPProgressIndicator/PPProgressView.swift b/Poppool/Poppool/Presentation/Components/PPProgressIndicator/PPProgressView.swift index 46cf39cc..60a3137a 100644 --- a/Poppool/Poppool/Presentation/Components/PPProgressIndicator/PPProgressView.swift +++ b/Poppool/Poppool/Presentation/Components/PPProgressIndicator/PPProgressView.swift @@ -6,33 +6,32 @@ // import Foundation -import UIKit import SnapKit +import UIKit final class PPProgressView: UIView { - - + /// CMTPProgressView Animation Type enum ProgressFillAnimation { case fromLeft case fromRight } - + // MARK: - Components private var selectedView: UIView = { let view = UIView() - view.backgroundColor = . systemBlue //수정 필요 + view.backgroundColor = . systemBlue // 수정 필요 view.layer.cornerRadius = 1 return view }() - + private var normalView: UIView = { let view = UIView() view.backgroundColor = .secondarySystemBackground // 수정 필요 view.layer.cornerRadius = 1 return view }() - + /// CMTPProgressView 초기화 /// - Parameter isSelected: 선택 여부 init(isSelected: Bool) { @@ -41,7 +40,7 @@ final class PPProgressView: UIView { setUpConstraints() setUp() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -49,17 +48,17 @@ final class PPProgressView: UIView { // MARK: - SetUp private extension PPProgressView { - + /// 뷰 설정 func setUp() { self.clipsToBounds = true } - + /// 제약 조건 설정 func setUpConstraints() { self.addSubview(normalView) self.addSubview(selectedView) - + self.snp.makeConstraints { make in make.height.equalTo(4) } @@ -75,7 +74,7 @@ private extension PPProgressView { } extension PPProgressView { - + /// 선택된 뷰를 채우는 애니메이션 /// - Parameter option: 애니메이션 시작 방향 func fillAnimation(option: ProgressFillAnimation) { @@ -89,7 +88,7 @@ extension PPProgressView { }) self.layoutIfNeeded() self.selectedView.isHidden = false - + UIView.animate(withDuration: 0.2, animations: { self.selectedView.snp.updateConstraints { make in make.leading.equalToSuperview() @@ -104,7 +103,7 @@ extension PPProgressView { }) self.layoutIfNeeded() self.selectedView.isHidden = false - + UIView.animate(withDuration: 0.2, animations: { self.selectedView.snp.updateConstraints { make in make.leading.equalToSuperview() @@ -113,7 +112,7 @@ extension PPProgressView { }) } } - + /// 선택된 뷰를 사라지게 하는 애니메이션 /// - Parameter option: 애니메이션 시작 방향 func disappearAnimation(option: ProgressFillAnimation) { diff --git a/Poppool/Poppool/Presentation/Components/PPReturnHeaderView.swift b/Poppool/Poppool/Presentation/Components/PPReturnHeaderView.swift index f56b0aae..eb91144d 100644 --- a/Poppool/Poppool/Presentation/Components/PPReturnHeaderView.swift +++ b/Poppool/Poppool/Presentation/Components/PPReturnHeaderView.swift @@ -5,11 +5,11 @@ // Created by Porori on 11/27/24. // -import UIKit import SnapKit +import UIKit final class PPReturnHeaderView: UIView { - + // MARK: - Components let backButton: UIButton = { let button = UIButton(type: .system) @@ -17,24 +17,24 @@ final class PPReturnHeaderView: UIView { button.tintColor = .black return button }() - + let headerLabel: UILabel = { let label = UILabel() label.font = .KorFont(style: .regular, size: 15) label.textColor = .g1000 return label }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + func configure(with text: String) { headerLabel.text = text } @@ -42,7 +42,7 @@ final class PPReturnHeaderView: UIView { // MARK: - SetUp private extension PPReturnHeaderView { - + func setUpConstraints() { self.addSubview(backButton) backButton.snp.makeConstraints { make in @@ -50,7 +50,7 @@ private extension PPReturnHeaderView { make.leading.equalToSuperview().inset(12) make.size.equalTo(28) } - + self.addSubview(headerLabel) headerLabel.snp.makeConstraints { make in make.centerY.equalTo(backButton) diff --git a/Poppool/Poppool/Presentation/Components/PPSegmentedControl.swift b/Poppool/Poppool/Presentation/Components/PPSegmentedControl.swift index 7699c7fe..7566851e 100644 --- a/Poppool/Poppool/Presentation/Components/PPSegmentedControl.swift +++ b/Poppool/Poppool/Presentation/Components/PPSegmentedControl.swift @@ -5,11 +5,11 @@ // Created by SeoJunYoung on 6/27/24. // -import UIKit import SnapKit +import UIKit final class PPSegmentedControl: UISegmentedControl { - + /// 세그먼트 컨트롤 타입 enum SegmentedControlType { case radio @@ -31,24 +31,24 @@ final class PPSegmentedControl: UISegmentedControl { bottomLineView.addSubview(view) return view }() - + init(type: SegmentedControlType, segments: [String], selectedSegmentIndex: Int? = nil) { super.init(frame: .zero) setUpSegments(type: type, segments: segments) if let selectedSegmentIndex = selectedSegmentIndex { self.selectedSegmentIndex = selectedSegmentIndex } - + } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + /// 서브뷰 레이아웃 설정 override func layoutSubviews() { super.layoutSubviews() - //layout이 업데이트 될 때 underbar 업데이트 + // layout이 업데이트 될 때 underbar 업데이트 let underlineFinalXPosition = (self.bounds.width / CGFloat(self.numberOfSegments)) * CGFloat(self.selectedSegmentIndex) self.underlineView.snp.updateConstraints { make in make.leading.equalTo(underlineFinalXPosition) @@ -61,7 +61,7 @@ final class PPSegmentedControl: UISegmentedControl { // MARK: - SetUp private extension PPSegmentedControl { - + /// 세그먼트 설정 메서드 /// - Parameters: /// - type: 세그먼트 컨트롤 타입 @@ -122,7 +122,7 @@ private extension PPSegmentedControl { setFont(color: .g400, font: .KorFont(style: .medium, size: 15), state: .normal) } } - + /// 폰트 설정 메서드 /// - Parameters: /// - color: 폰트 색상 diff --git a/Poppool/Poppool/Presentation/Extension/Date?+.swift b/Poppool/Poppool/Presentation/Extension/Date?+.swift index f8117e7e..550cd427 100644 --- a/Poppool/Poppool/Presentation/Extension/Date?+.swift +++ b/Poppool/Poppool/Presentation/Extension/Date?+.swift @@ -19,7 +19,7 @@ extension Optional where Wrapped == Date { formatter.dateFormat = "yyyy. MM. dd" return formatter.string(from: date) } - + func toPPDateMonthString(defaultString: String = "") -> String { guard let date = self else { return defaultString @@ -28,7 +28,7 @@ extension Optional where Wrapped == Date { formatter.dateFormat = "MM월 dd일" return formatter.string(from: date) } - + func toPPTimeeString(defaultString: String = "") -> String { guard let date = self else { return defaultString diff --git a/Poppool/Poppool/Presentation/Extension/Reactive+.swift b/Poppool/Poppool/Presentation/Extension/Reactive+.swift index b3c44553..74665b05 100644 --- a/Poppool/Poppool/Presentation/Extension/Reactive+.swift +++ b/Poppool/Poppool/Presentation/Extension/Reactive+.swift @@ -11,27 +11,27 @@ import RxCocoa import RxSwift extension Reactive where Base: UIViewController { - + var viewDidLoad: ControlEvent { let source = self.methodInvoked(#selector(Base.viewDidLoad)).map( { _ in }) return ControlEvent(events: source) } - + var viewWillAppear: ControlEvent { let source = self.methodInvoked(#selector(Base.viewWillAppear)).map( { _ in }) return ControlEvent(events: source) } - + var viewDidAppear: ControlEvent { let source = self.methodInvoked(#selector(Base.viewDidAppear)).map( { _ in }) return ControlEvent(events: source) } - + var viewWillDisappear: ControlEvent { let source = self.methodInvoked(#selector(Base.viewWillDisappear)).map( { _ in }) return ControlEvent(events: source) } - + var viewDidDisappear: ControlEvent { let source = self.methodInvoked(#selector(Base.viewDidDisappear)).map( { _ in }) return ControlEvent(events: source) diff --git a/Poppool/Poppool/Presentation/Extension/String?+.swift b/Poppool/Poppool/Presentation/Extension/String?+.swift index c1c5a916..feab8506 100644 --- a/Poppool/Poppool/Presentation/Extension/String?+.swift +++ b/Poppool/Poppool/Presentation/Extension/String?+.swift @@ -13,10 +13,10 @@ extension Optional where Wrapped == String { /// ISO 8601 형식의 문자열을 `Date`로 변환하는 메서드 func toDate() -> Date? { guard let self = self else { return nil } // 옵셔널 해제 - + let dateFormatter = DateFormatter() dateFormatter.locale = Locale(identifier: "en_US_POSIX") - + if self.contains(".") { // 밀리초 포함 형식 dateFormatter.dateFormat = "yyyy.MM.dd'T'HH:mm:ss.SSS" @@ -24,10 +24,10 @@ extension Optional where Wrapped == String { // 밀리초 없는 형식 dateFormatter.dateFormat = "yyyy.MM.dd'T'HH:mm:ss" } - + return dateFormatter.date(from: self) } - + func isBrightImagePath(completion: @escaping (Bool) -> Void) { if let self = self { let imageView = UIImageView() diff --git a/Poppool/Poppool/Presentation/Extension/UIColor+.swift b/Poppool/Poppool/Presentation/Extension/UIColor+.swift index 5de4edb1..1ea48e23 100644 --- a/Poppool/Poppool/Presentation/Extension/UIColor+.swift +++ b/Poppool/Poppool/Presentation/Extension/UIColor+.swift @@ -8,7 +8,7 @@ import UIKit extension UIColor { - + // 무채색 컬러 static let g50 = UIColor(hexCode: "F2F5F7") static let g100 = UIColor(hexCode: "DFE2E6") @@ -21,7 +21,7 @@ extension UIColor { static let g800 = UIColor(hexCode: "1F242B") static let g900 = UIColor(hexCode: "17191C") static let g1000 = UIColor(hexCode: "141414") - + // 화이트 톤 static let w4 = UIColor(hexCode: "ffffff", alpha: 0.04) static let w7 = UIColor(hexCode: "ffffff", alpha: 0.07) @@ -31,8 +31,7 @@ extension UIColor { static let w70 = UIColor(hexCode: "ffffff", alpha: 0.7) static let w90 = UIColor(hexCode: "ffffff", alpha: 0.9) static let w100 = UIColor(hexCode: "ffffff", alpha: 1.0) - - + // 퓨어 블랙 static let pb4 = UIColor(hexCode: "141414", alpha: 0.04) static let pb7 = UIColor(hexCode: "141414", alpha: 0.07) @@ -43,7 +42,7 @@ extension UIColor { static let pb70 = UIColor(hexCode: "141414", alpha: 0.7) static let pb90 = UIColor(hexCode: "141414", alpha: 0.9) static let pb100 = UIColor(hexCode: "141414", alpha: 1.0) - + // 블루 static let blu100 = UIColor(hexCode: "E5EEFF") static let blu200 = UIColor(hexCode: "B5CCFE") @@ -54,7 +53,7 @@ extension UIColor { static let blu700 = UIColor(hexCode: "023197") static let blu800 = UIColor(hexCode: "022364") static let blu900 = UIColor(hexCode: "011132") - + // 제이드 static let jd100 = UIColor(hexCode: "E6FFFA") static let jd200 = UIColor(hexCode: "CCFFF6") @@ -66,7 +65,7 @@ extension UIColor { static let jd800 = UIColor(hexCode: "00997D") static let jd900 = UIColor(hexCode: "00997D") static let jd1000 = UIColor(hexCode: "004D3E") - + // 레드 static let re100 = UIColor(hexCode: "FFE6E5") static let re200 = UIColor(hexCode: "FFCCCC") @@ -77,19 +76,19 @@ extension UIColor { static let re700 = UIColor(hexCode: "B30100") static let re800 = UIColor(hexCode: "800000") static let re900 = UIColor(hexCode: "4D0000") - + convenience init(hexCode: String, alpha: CGFloat = 1.0) { var hexFormatted: String = hexCode.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).uppercased() - + if hexFormatted.hasPrefix("#") { hexFormatted = String(hexFormatted.dropFirst()) } - + assert(hexFormatted.count == 6, "Invalid hex code used.") - + var rgbValue: UInt64 = 0 Scanner(string: hexFormatted).scanHexInt64(&rgbValue) - + self.init(red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, blue: CGFloat(rgbValue & 0x0000FF) / 255.0, diff --git a/Poppool/Poppool/Presentation/Extension/UIFont+.swift b/Poppool/Poppool/Presentation/Extension/UIFont+.swift index 4d131df8..098bc0da 100644 --- a/Poppool/Poppool/Presentation/Extension/UIFont+.swift +++ b/Poppool/Poppool/Presentation/Extension/UIFont+.swift @@ -9,7 +9,7 @@ import Foundation import UIKit extension UIFont { - + static func KorFont(style: FontStyle, size: CGFloat) -> UIFont? { return UIFont(name: "GothicA1\(style.rawValue)", size: size) } @@ -17,7 +17,7 @@ extension UIFont { static func EngFont(style: FontStyle, size: CGFloat) -> UIFont? { return UIFont(name: "Poppins\(style.rawValue)", size: size) } - + enum FontStyle: String { case bold = "-Bold" case medium = "-Medium" @@ -25,4 +25,3 @@ extension UIFont { case light = "-Light" } } - diff --git a/Poppool/Poppool/Presentation/Extension/UIImage+.swift b/Poppool/Poppool/Presentation/Extension/UIImage+.swift index be80b2a2..dfeb7e32 100644 --- a/Poppool/Poppool/Presentation/Extension/UIImage+.swift +++ b/Poppool/Poppool/Presentation/Extension/UIImage+.swift @@ -22,7 +22,7 @@ extension UIImage { extension UIImage { func isBright(threshold: CGFloat = 0.5) -> Bool? { guard let cgImage = self.cgImage else { return nil } - + let width = 1 let height = 1 let bitsPerComponent = 8 @@ -30,9 +30,9 @@ extension UIImage { let bytesPerRow = bytesPerPixel * width let colorSpace = CGColorSpaceCreateDeviceRGB() let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue - + var pixelData = [UInt8](repeating: 0, count: width * height * bytesPerPixel) - + guard let context = CGContext( data: &pixelData, width: width, @@ -42,16 +42,16 @@ extension UIImage { space: colorSpace, bitmapInfo: bitmapInfo ) else { return nil } - + context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height)) - + let red = CGFloat(pixelData[0]) / 255.0 let green = CGFloat(pixelData[1]) / 255.0 let blue = CGFloat(pixelData[2]) / 255.0 - + // Brightness calculation formula let brightness = (red * 0.299 + green * 0.587 + blue * 0.114) - + return brightness > threshold } } diff --git a/Poppool/Poppool/Presentation/Extension/UIImageView+.swift b/Poppool/Poppool/Presentation/Extension/UIImageView+.swift index a74655f3..761ae557 100644 --- a/Poppool/Poppool/Presentation/Extension/UIImageView+.swift +++ b/Poppool/Poppool/Presentation/Extension/UIImageView+.swift @@ -28,7 +28,7 @@ extension UIImageView { } } } - + func setPPImage(path: String?, completion: @escaping () -> Void) { guard let path = path else { self.image = UIImage(named: "image_default") diff --git a/Poppool/Poppool/Presentation/Map/Common/ClusteringModels.swift b/Poppool/Poppool/Presentation/Map/Common/ClusteringModels.swift index 3d5a1c94..3716fd40 100644 --- a/Poppool/Poppool/Presentation/Map/Common/ClusteringModels.swift +++ b/Poppool/Poppool/Presentation/Map/Common/ClusteringModels.swift @@ -20,7 +20,6 @@ enum MapZoomLevel { } } - struct RegionCluster { let name: String let subRegions: [String] diff --git a/Poppool/Poppool/Presentation/Map/Common/FilterType.swift b/Poppool/Poppool/Presentation/Map/Common/FilterType.swift index 354d319a..b19e7c64 100644 --- a/Poppool/Poppool/Presentation/Map/Common/FilterType.swift +++ b/Poppool/Poppool/Presentation/Map/Common/FilterType.swift @@ -1,5 +1,3 @@ - - import Foundation import UIKit diff --git a/Poppool/Poppool/Presentation/Map/Common/GMSMapViewDelegateProxy.swift b/Poppool/Poppool/Presentation/Map/Common/GMSMapViewDelegateProxy.swift index a3e7064b..3d0622d3 100644 --- a/Poppool/Poppool/Presentation/Map/Common/GMSMapViewDelegateProxy.swift +++ b/Poppool/Poppool/Presentation/Map/Common/GMSMapViewDelegateProxy.swift @@ -1,6 +1,6 @@ -import RxSwift -import RxCocoa import GoogleMaps +import RxCocoa +import RxSwift class GMSMapViewDelegateProxy: DelegateProxy, DelegateProxyType, GMSMapViewDelegate { diff --git a/Poppool/Poppool/Presentation/Map/Common/LocationPermissionBottomSheet.swift b/Poppool/Poppool/Presentation/Map/Common/LocationPermissionBottomSheet.swift index 88dcc961..4362eb92 100644 --- a/Poppool/Poppool/Presentation/Map/Common/LocationPermissionBottomSheet.swift +++ b/Poppool/Poppool/Presentation/Map/Common/LocationPermissionBottomSheet.swift @@ -1,5 +1,5 @@ -import UIKit import SnapKit +import UIKit final class LocationPermissionBottomSheet: UIViewController { diff --git a/Poppool/Poppool/Presentation/Map/Common/MapFilterChips.swift b/Poppool/Poppool/Presentation/Map/Common/MapFilterChips.swift index 7745525c..8006ce31 100644 --- a/Poppool/Poppool/Presentation/Map/Common/MapFilterChips.swift +++ b/Poppool/Poppool/Presentation/Map/Common/MapFilterChips.swift @@ -1,6 +1,5 @@ -import UIKit import SnapKit - +import UIKit class MapFilterChips: UIView { // MARK: - Components diff --git a/Poppool/Poppool/Presentation/Map/Common/MapPopupCardView/MapPopupCarouselView.swift b/Poppool/Poppool/Presentation/Map/Common/MapPopupCardView/MapPopupCarouselView.swift index cd217101..a8310561 100644 --- a/Poppool/Poppool/Presentation/Map/Common/MapPopupCardView/MapPopupCarouselView.swift +++ b/Poppool/Poppool/Presentation/Map/Common/MapPopupCardView/MapPopupCarouselView.swift @@ -1,6 +1,6 @@ -import UIKit -import SnapKit import FloatingPanel +import SnapKit +import UIKit final class MapPopupCarouselView: UICollectionView { // 스크롤 멈췄을 때의 콜백 @@ -44,7 +44,6 @@ final class MapPopupCarouselView: UICollectionView { ) super.init(frame: frame, collectionViewLayout: layout) - showsHorizontalScrollIndicator = false backgroundColor = .clear decelerationRate = .fast diff --git a/Poppool/Poppool/Presentation/Map/Common/MapPopupCardView/PopupCardCell.swift b/Poppool/Poppool/Presentation/Map/Common/MapPopupCardView/PopupCardCell.swift index 4d1ee89d..27350c36 100644 --- a/Poppool/Poppool/Presentation/Map/Common/MapPopupCardView/PopupCardCell.swift +++ b/Poppool/Poppool/Presentation/Map/Common/MapPopupCardView/PopupCardCell.swift @@ -1,6 +1,6 @@ -import UIKit -import SnapKit import Kingfisher +import SnapKit +import UIKit final class PopupCardCell: UICollectionViewCell { static let identifier = "PopupCardCell" @@ -22,8 +22,6 @@ final class PopupCardCell: UICollectionViewCell { configureUI() } - - required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -86,7 +84,6 @@ final class PopupCardCell: UICollectionViewCell { } } - private func configureUI() { contentView.backgroundColor = UIColor.white categoryLabel.textColor = .systemBlue diff --git a/Poppool/Poppool/Presentation/Map/Common/MapUtilities.swift b/Poppool/Poppool/Presentation/Map/Common/MapUtilities.swift index ff6f6557..0860239c 100644 --- a/Poppool/Poppool/Presentation/Map/Common/MapUtilities.swift +++ b/Poppool/Poppool/Presentation/Map/Common/MapUtilities.swift @@ -1,5 +1,3 @@ - - import CoreLocation public func extractCity(from address: String) -> String { @@ -51,4 +49,3 @@ public struct RepresentativeScope { radius: 4000.0 ) } - diff --git a/Poppool/Poppool/Presentation/Map/CustomClusterRenderer.swift b/Poppool/Poppool/Presentation/Map/CustomClusterRenderer.swift index 611d0f3a..82fa4b7d 100644 --- a/Poppool/Poppool/Presentation/Map/CustomClusterRenderer.swift +++ b/Poppool/Poppool/Presentation/Map/CustomClusterRenderer.swift @@ -1,6 +1,6 @@ -//import GoogleMaps +// import GoogleMaps // -//class CustomClusterRenderer: GMUDefaultClusterRenderer { +// class CustomClusterRenderer: GMUDefaultClusterRenderer { // override func willRenderMarker(_ marker: GMSMarker) { // super.willRenderMarker(marker) // // 클러스터일 경우 처리 @@ -12,4 +12,4 @@ // marker.iconView = customView // } // } -//} +// } diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonBackgroundView.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonBackgroundView.swift index 3c673979..92956c8e 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonBackgroundView.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonBackgroundView.swift @@ -1,5 +1,5 @@ -import UIKit import SnapKit +import UIKit final class BalloonBackgroundView: UIView { @@ -15,7 +15,7 @@ final class BalloonBackgroundView: UIView { // 기존 말풍선 UI: 서브 지역을 나열하는 CollectionView (서울/경기/부산용) private let collectionView: UICollectionView = { - let layout = UICollectionViewCompositionalLayout { section, env in + let layout = UICollectionViewCompositionalLayout { section, _ in let itemSize = NSCollectionLayoutSize( widthDimension: .estimated(30), heightDimension: .absolute(30) @@ -177,7 +177,6 @@ final class BalloonBackgroundView: UIView { // layer.shadowRadius = 4 } - // MARK: - Public /// configure 메서드 @@ -214,7 +213,6 @@ final class BalloonBackgroundView: UIView { } } - private func setupTagSection() { let allKey = "\(mainRegionTitle)전체" @@ -236,7 +234,6 @@ final class BalloonBackgroundView: UIView { ) } - func calculateHeight() -> CGFloat { if collectionView.isHidden { return 145 @@ -293,8 +290,7 @@ final class BalloonBackgroundView: UIView { let iconWidth: CGFloat = isSelected ? 16 : 0 let iconGap: CGFloat = isSelected ? 4 : 0 let horizontalPadding: CGFloat = 24 - let calculatedWidth = textWidth + iconWidth + iconGap + horizontalPadding - return calculatedWidth + return textWidth + iconWidth + iconGap + horizontalPadding } } diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonChipCell.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonChipCell.swift index 3919e1bf..e8c7dcd7 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonChipCell.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonChipCell.swift @@ -1,5 +1,5 @@ -import UIKit import SnapKit +import UIKit final class BalloonChipCell: UICollectionViewCell { static let identifier = "BalloonChipCell" @@ -46,7 +46,6 @@ final class BalloonChipCell: UICollectionViewCell { button.layer.borderWidth = 0 button.titleLabel?.font = .KorFont(style: .bold, size: 11) - } else { button.setImage(nil, for: .normal) button.semanticContentAttribute = .unspecified diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/CategoryFilterView.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/CategoryFilterView.swift index b131a1b2..06fd3d99 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/CategoryFilterView.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/CategoryFilterView.swift @@ -1,7 +1,7 @@ -//import UIKit -//import SnapKit +// import UIKit +// import SnapKit // -//final class CategoryFilterView: UIView { +// final class CategoryFilterView: UIView { // private let stackView = UIStackView() // private let categories = ["게임", "라이프스타일", "엔터테인먼트", "패션", "음식/요리", "키즈"] // @@ -32,4 +32,4 @@ // stackView.addArrangedSubview(chip) // } // } -//} +// } diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetReactor.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetReactor.swift index 190ad281..ed37ee77 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetReactor.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetReactor.swift @@ -1,5 +1,5 @@ -import ReactorKit import Foundation +import ReactorKit import RxSwift struct Location: Equatable { @@ -59,27 +59,27 @@ final class FilterBottomSheetReactor: Reactor { Location( main: "서울", sub: [ - "도봉/노원","강북/중랑","동대문/성북","중구/종로","성동/광진", - "송파/강동","동작/관악","서초/강남","은평/서대문/마포", - "영등포/구로","용산","양천/강서/금천" + "도봉/노원", "강북/중랑", "동대문/성북", "중구/종로", "성동/광진", + "송파/강동", "동작/관악", "서초/강남", "은평/서대문/마포", + "영등포/구로", "용산", "양천/강서/금천" ] ), Location( main: "경기", sub: [ - "포천/연천","동두천/양주/의정부","구리/남양주/가평", - "파주/고양/김포","용인/화성/수원","군포/의왕", - "과천/안양","부천/광명","시흥/안산", - "안성/평택/오산","성남/하남/광주","이천/여주/양평" + "포천/연천", "동두천/양주/의정부", "구리/남양주/가평", + "파주/고양/김포", "용인/화성/수원", "군포/의왕", + "과천/안양", "부천/광명", "시흥/안산", + "안성/평택/오산", "성남/하남/광주", "이천/여주/양평" ] ), Location(main: "인천", sub: ["부평", "송도"]), Location( main: "부산", sub: [ - "중구","서구","동구","영도구","부산진구", - "동래구","남구","북구","해운대구","사하구", - "금정구","강서구","연제구","수영구","사상구", + "중구", "서구", "동구", "영도구", "부산진구", + "동래구", "남구", "북구", "해운대구", "사하구", + "금정구", "강서구", "연제구", "수영구", "사상구", "기장군" ] ), @@ -177,14 +177,12 @@ final class FilterBottomSheetReactor: Reactor { switch mutation { case .setActiveSegment(let index): newState.activeSegment = index - - case .resetFilters: + case .resetFilters: newState.selectedSubRegions = [] newState.selectedCategories = [] newState.savedSubRegions = [] newState.savedCategories = [] // 여기서 forceSaveEnabled는 나중에 setForceSaveEnabled가 적용됨 - break case .applyFilters(let combined): print("필터 적용: \(newState.selectedSubRegions + newState.selectedCategories)") diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift index 3301184c..15e91513 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift @@ -1,5 +1,5 @@ -import UIKit import SnapKit +import UIKit final class FilterBottomSheetView: UIView { // MARK: - UI Components @@ -32,8 +32,7 @@ final class FilterBottomSheetView: UIView { }() let segmentedControl: PPSegmentedControl = { - let control = PPSegmentedControl(type: .tab, segments: ["지역", "카테고리"], selectedSegmentIndex: 0) - return control + return PPSegmentedControl(type: .tab, segments: ["지역", "카테고리"], selectedSegmentIndex: 0) }() let locationScrollView: UIScrollView = { @@ -45,7 +44,7 @@ final class FilterBottomSheetView: UIView { var categoryHeightConstraint: Constraint? let categoryCollectionView: UICollectionView = { - let layout = UICollectionViewCompositionalLayout { section, env in + let layout = UICollectionViewCompositionalLayout { section, _ in let itemSize = NSCollectionLayoutSize( widthDimension: .estimated(26), heightDimension: .absolute(36) @@ -81,21 +80,16 @@ final class FilterBottomSheetView: UIView { return collectionView }() - let balloonBackgroundView = BalloonBackgroundView() let resetButton: PPButton = { - let button = PPButton(style: .secondary, text: "초기화") - return button + return PPButton(style: .secondary, text: "초기화") }() - let saveButton: PPButton = { - let button = PPButton(style: .primary, text: "옵션저장", disabledText: "옵션저장") - return button + return PPButton(style: .primary, text: "옵션저장", disabledText: "옵션저장") }() - private let buttonStack: UIStackView = { let stack = UIStackView() stack.axis = .horizontal @@ -105,14 +99,13 @@ final class FilterBottomSheetView: UIView { }() let filterChipsView: FilterChipsView = { - let view = FilterChipsView() - return view + return FilterChipsView() }() private var balloonHeightConstraint: Constraint? // MARK: - Initialization - + override init(frame: CGRect) { super.init(frame: frame) setupLayout() @@ -330,9 +323,6 @@ final class FilterBottomSheetView: UIView { } } - - - private func createStyledButton(title: String, isSelected: Bool = false) -> PPButton { let button = PPButton( style: .secondary, @@ -368,7 +358,7 @@ final class FilterBottomSheetView: UIView { button.setBackgroundColor(.w100, for: .normal) button.setTitleColor(.g400, for: .normal) button.layer.borderColor = UIColor.g200.cgColor - button.titleLabel?.font = .KorFont(style: .medium, size: 13) + button.titleLabel?.font = .KorFont(style: .medium, size: 13) button.layer.borderWidth = 1 } } @@ -382,7 +372,6 @@ final class FilterBottomSheetView: UIView { } } - func updateBalloonPosition(for button: UIButton) { guard let window = button.window else { return } @@ -424,7 +413,7 @@ extension FilterBottomSheetView { filterChipsView.updateChips(with: filters) } -} +} extension FilterBottomSheetView: UIScrollViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { // 선택된 버튼 찾기 @@ -432,7 +421,7 @@ extension FilterBottomSheetView: UIScrollViewDelegate { guard let button = view as? PPButton else { return false } return button.backgroundColor == .blu500 }) as? PPButton else { return } - + // 스크롤 중에도 실시간으로 위치 업데이트 DispatchQueue.main.async { [weak self] in self?.updateBalloonPosition(for: selectedButton) diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetViewController.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetViewController.swift index c398fc99..c767efbc 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetViewController.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetViewController.swift @@ -1,8 +1,8 @@ -import UIKit -import SnapKit -import RxSwift -import RxCocoa import ReactorKit +import RxCocoa +import RxSwift +import SnapKit +import UIKit final class FilterBottomSheetViewController: UIViewController, View { typealias Reactor = FilterBottomSheetReactor @@ -114,14 +114,11 @@ final class FilterBottomSheetViewController: UIViewController, View { } ) - }) .map { Reactor.Action.resetFilters } .bind(to: reactor.action) .disposed(by: disposeBag) - - containerView.saveButton.rx.tap .bind { [weak self] _ in guard let self = self, let reactor = self.reactor else { return } @@ -138,7 +135,6 @@ final class FilterBottomSheetViewController: UIViewController, View { } .disposed(by: disposeBag) - containerView.closeButton.rx.tap .bind { [weak self] _ in guard let self = self, let reactor = self.reactor else { return } @@ -151,7 +147,6 @@ final class FilterBottomSheetViewController: UIViewController, View { } .disposed(by: disposeBag) - // 5. 탭 변경 reactor.state.map { $0.activeSegment } .distinctUntilChanged() @@ -193,7 +188,6 @@ final class FilterBottomSheetViewController: UIViewController, View { } .disposed(by: disposeBag) - let locationAndSubRegions = reactor.state .map { ($0.selectedLocationIndex, $0.selectedSubRegions) } .distinctUntilChanged { prev, curr in @@ -209,7 +203,6 @@ final class FilterBottomSheetViewController: UIViewController, View { guard let self = self, let reactor = self.reactor else { return } let (selectedIndexOptional, selectedSubRegions) = data - guard let selectedIndex = selectedIndexOptional, selectedIndex >= 0, selectedIndex < reactor.currentState.locations.count else { return } @@ -227,7 +220,6 @@ final class FilterBottomSheetViewController: UIViewController, View { } ) - if let button = self.containerView.locationContentView.subviews[selectedIndex] as? UIButton { self.containerView.updateBalloonPosition(for: button) } @@ -266,8 +258,6 @@ final class FilterBottomSheetViewController: UIViewController, View { } .disposed(by: disposeBag) - - reactor.state.map { $0.selectedSubRegions + $0.selectedCategories } .distinctUntilChanged() .bind { [weak self] selectedOptions in @@ -288,7 +278,6 @@ final class FilterBottomSheetViewController: UIViewController, View { } .disposed(by: disposeBag) - reactor.state.map { $0.isSaveEnabled } .distinctUntilChanged() .observe(on: MainScheduler.instance) @@ -386,7 +375,6 @@ final class FilterBottomSheetViewController: UIViewController, View { let index = reactor.currentState.locations.firstIndex(where: { $0.main == locations }) { reactor.action.onNext(.selectLocation(index)) - } // 4. 필터 칩 뷰 업데이트 @@ -403,8 +391,6 @@ final class FilterBottomSheetViewController: UIViewController, View { } } - - func hideBottomSheet() { UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseIn) { self.dimmedView.alpha = 0 diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterCell.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterCell.swift index f5783bfa..5c8981b7 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterCell.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterCell.swift @@ -1,5 +1,5 @@ -import UIKit import SnapKit +import UIKit final class FilterCell: UICollectionViewCell { // MARK: - Properties diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterChip.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterChip.swift index 0aa4964c..3ad18fc0 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterChip.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterChip.swift @@ -1,5 +1,5 @@ -import UIKit import SnapKit +import UIKit final class FilterChip: UIButton { enum Style { diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterChipsView.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterChipsView.swift index 68aa555a..e0f439c6 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterChipsView.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterChipsView.swift @@ -1,5 +1,5 @@ -import UIKit import SnapKit +import UIKit final class FilterChipsView: UIView { // MARK: - Components @@ -97,7 +97,7 @@ final class FilterChipsView: UIView { let removedFilter = filters[index] filters.remove(at: index) updateUI() - + // 콜백 호출 onRemoveChip?(removedFilter) } diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterTabsView.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterTabsView.swift index 85a3812c..99dfe49c 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterTabsView.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterTabsView.swift @@ -1,8 +1,8 @@ -//import UIKit -//import RxSwift -//import RxCocoa +// import UIKit +// import RxSwift +// import RxCocoa // -//final class FilterTabsView: UIView { +// final class FilterTabsView: UIView { // private let tabs = ["지역", "카테고리"] // let segmentedControl = UISegmentedControl() // @@ -30,10 +30,10 @@ // make.edges.equalToSuperview() // } // } -//} +// } // -//extension Reactive where Base: FilterTabsView { +// extension Reactive where Base: FilterTabsView { // var selectedIndex: ControlProperty { // return base.segmentedControl.rx.selectedSegmentIndex // } -//} +// } diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/LocationFilterView.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/LocationFilterView.swift index 4f4bbdf4..567dfd1d 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/LocationFilterView.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/LocationFilterView.swift @@ -1,7 +1,7 @@ -//import UIKit -//import SnapKit +// import UIKit +// import SnapKit // -//final class LocationFilterView: UIView { +// final class LocationFilterView: UIView { // private let scrollView = UIScrollView() // private let contentStack = UIStackView() // @@ -37,4 +37,4 @@ // contentStack.addArrangedSubview(chip) // } // } -//} +// } diff --git a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FindDirectionEndPoint.swift b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FindDirectionEndPoint.swift index 430b5a91..691cf788 100644 --- a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FindDirectionEndPoint.swift +++ b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FindDirectionEndPoint.swift @@ -19,4 +19,3 @@ struct FindDirectionEndPoint { ) } } - diff --git a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FullScreenMapViewController.swift b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FullScreenMapViewController.swift index 690afbd7..e788bb2f 100644 --- a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FullScreenMapViewController.swift +++ b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FullScreenMapViewController.swift @@ -1,9 +1,9 @@ -import Foundation -import UIKit -import RxSwift -import ReactorKit import CoreLocation +import Foundation import GoogleMaps +import ReactorKit +import RxSwift +import UIKit final class FullScreenMapViewController: MapViewController { var selectedStore: MapPopUpStore? @@ -29,8 +29,6 @@ final class FullScreenMapViewController: MapViewController { } } - - // MARK: - Binding override func bind(reactor: Reactor) { super.bind(reactor: reactor) @@ -158,7 +156,6 @@ final class FullScreenMapViewController: MapViewController { } } - @objc private func backButtonTapped() { dismiss(animated: true) } diff --git a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/GetPopUpDirectionResponseDTO.swift b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/GetPopUpDirectionResponseDTO.swift index fa31b5d8..cd67dfee 100644 --- a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/GetPopUpDirectionResponseDTO.swift +++ b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/GetPopUpDirectionResponseDTO.swift @@ -7,7 +7,6 @@ import Foundation - struct GetPopUpDirectionResponseDTO: Decodable { let id: Int64 let categoryName: String diff --git a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapDirectionRepository.swift b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapDirectionRepository.swift index 39b05082..fcb8e508 100644 --- a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapDirectionRepository.swift +++ b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapDirectionRepository.swift @@ -8,7 +8,6 @@ import Foundation import RxSwift - protocol MapDirectionRepository { func getPopUpDirection(popUpStoreId: Int64) -> Observable } @@ -25,7 +24,7 @@ final class DefaultMapDirectionRepository: MapDirectionRepository { let endpoint = FindDirectionEndPoint.fetchDirection(popUpStoreId: popUpStoreId) // print("🌎 [Repository]: 요청 생성 - \(endpoint)") return provider.requestData(with: endpoint, interceptor: TokenInterceptor()) - .do(onNext: { response in + .do(onNext: { _ in // print("✅ [Repository]: 응답 수신 - \(response)") }, onError: { error in print("❌ [Repository]: 요청 실패 - \(error)") diff --git a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideReactor.swift b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideReactor.swift index 089cb950..dd3fef65 100644 --- a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideReactor.swift +++ b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideReactor.swift @@ -1,6 +1,6 @@ +import CoreLocation import ReactorKit import RxSwift -import CoreLocation import UIKit final class MapGuideReactor: Reactor { diff --git a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift index c07ed2b8..cec51aea 100644 --- a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift +++ b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift @@ -1,9 +1,9 @@ -import UIKit -import SnapKit +import CoreLocation import GoogleMaps import ReactorKit import RxSwift -import CoreLocation +import SnapKit +import UIKit final class MapGuideViewController: UIViewController, View { // MARK: - Properties @@ -171,7 +171,7 @@ final class MapGuideViewController: UIViewController, View { .distinctUntilChanged() .compactMap { $0 } .take(1) - .subscribe(onNext: { [weak self] store in + .subscribe(onNext: { [weak self] _ in let fullScreenMapVC = FullScreenMapViewController() fullScreenMapVC.reactor = reactor diff --git a/Poppool/Poppool/Presentation/Map/MapStoreCard.swift b/Poppool/Poppool/Presentation/Map/MapStoreCard.swift index 9647c3d4..a4c86d8f 100644 --- a/Poppool/Poppool/Presentation/Map/MapStoreCard.swift +++ b/Poppool/Poppool/Presentation/Map/MapStoreCard.swift @@ -1,7 +1,7 @@ -//import UIKit -//import SnapKit +// import UIKit +// import SnapKit // -//final class MapStoreCard: UIView { +// final class MapStoreCard: UIView { // // MARK: - Components // private let containerView: UIView = { // let view = UIView() @@ -43,10 +43,10 @@ // required init?(coder: NSCoder) { // fatalError("init(coder:) has not been implemented") // } -//} +// } // //// MARK: - Setup -//private extension MapStoreCard { +// private extension MapStoreCard { // func setupLayout() { // addSubview(containerView) // @@ -94,10 +94,10 @@ // locationLabel.textColor = .g500 // dateLabel.textColor = .g500 // } -//} +// } // //// MARK: - Inputable -//extension MapStoreCard: Inputable { +// extension MapStoreCard: Inputable { // struct Input { // let image: UIImage? // let category: String @@ -113,4 +113,4 @@ // locationLabel.text = input.location // dateLabel.text = input.date // } -//} +// } diff --git a/Poppool/Poppool/Presentation/Map/MapView/MapMarker.swift b/Poppool/Poppool/Presentation/Map/MapView/MapMarker.swift index a90eb377..b3c06309 100644 --- a/Poppool/Poppool/Presentation/Map/MapView/MapMarker.swift +++ b/Poppool/Poppool/Presentation/Map/MapView/MapMarker.swift @@ -1,13 +1,12 @@ -import UIKit -import SnapKit import GoogleMaps +import SnapKit +import UIKit final class MapMarker: UIView { // MARK: - Components private(set) var isSelected: Bool = false var currentInput: Input? - private let markerImageView: UIImageView = { let imageView = UIImageView() imageView.image = UIImage(named: "Marker") @@ -181,8 +180,6 @@ extension MapMarker: Inputable { CATransaction.commit() } - - private func setupClusterMarker(_ input: Input) { markerImageView.isHidden = true clusterContainer.isHidden = false diff --git a/Poppool/Poppool/Presentation/Map/MapView/MapReactor.swift b/Poppool/Poppool/Presentation/Map/MapView/MapReactor.swift index 8603c691..0342470b 100644 --- a/Poppool/Poppool/Presentation/Map/MapView/MapReactor.swift +++ b/Poppool/Poppool/Presentation/Map/MapView/MapReactor.swift @@ -1,6 +1,6 @@ +import CoreLocation import ReactorKit import RxSwift -import CoreLocation final class MapReactor: Reactor { // MARK: - Reactor @@ -166,8 +166,6 @@ final class MapReactor: Reactor { .just(.setLoading(false)) ]) - - case let .updateBothFilters(locations, categories): return .concat([ .just(.setLocationFilters(locations)), @@ -302,10 +300,9 @@ final class MapReactor: Reactor { case let .didSelectItem(store): return .concat([ .just(.setSelectedStore(store)), - .just(.setViewportStores(currentState.viewportStores)), // ✅ 선택된 마커를 캐러셀에서 최우선으로 반영 + .just(.setViewportStores(currentState.viewportStores)) // ✅ 선택된 마커를 캐러셀에서 최우선으로 반영 ]) - default: return .empty() } @@ -387,7 +384,6 @@ final class MapReactor: Reactor { newState.viewportStores = updatedStores - case let .setSelectedStore(store): newState.selectedStore = store print("[DEBUG] 📍 Selected Store: \(store.name)") diff --git a/Poppool/Poppool/Presentation/Map/MapView/MapSearchInput.swift b/Poppool/Poppool/Presentation/Map/MapView/MapSearchInput.swift index f10a3973..e5532286 100644 --- a/Poppool/Poppool/Presentation/Map/MapView/MapSearchInput.swift +++ b/Poppool/Poppool/Presentation/Map/MapView/MapSearchInput.swift @@ -1,7 +1,7 @@ -import UIKit import ReactorKit import RxCocoa import RxSwift +import UIKit final class MapSearchInput: UIView, View { // MARK: - Components diff --git a/Poppool/Poppool/Presentation/Map/MapView/MapView.swift b/Poppool/Poppool/Presentation/Map/MapView/MapView.swift index 30d4f7c2..a624a152 100644 --- a/Poppool/Poppool/Presentation/Map/MapView/MapView.swift +++ b/Poppool/Poppool/Presentation/Map/MapView/MapView.swift @@ -1,6 +1,6 @@ -import UIKit -import SnapKit import GoogleMaps +import SnapKit +import UIKit final class MapView: UIView { // MARK: - Components @@ -52,8 +52,7 @@ final class MapView: UIView { }() var storeCard: MapPopupCarouselView = { - let view = MapPopupCarouselView() - return view + return MapPopupCarouselView() }() // MARK: - Init @@ -104,7 +103,7 @@ private extension MapView { searchFilterContainer.addSubview(searchInput) searchInput.snp.makeConstraints { make in make.top.equalToSuperview() - make.leading.trailing.equalToSuperview().inset(20) + make.leading.trailing.equalToSuperview().inset(20) make.height.equalTo(37) } diff --git a/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift b/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift index 52bf64b6..ebed5d27 100644 --- a/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift +++ b/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift @@ -1,18 +1,16 @@ -import UIKit +import CoreLocation import FloatingPanel -import SnapKit -import RxSwift -import RxCocoa -import ReactorKit import GoogleMaps -import CoreLocation +import ReactorKit +import RxCocoa import RxGesture - +import RxSwift +import SnapKit +import UIKit class MapViewController: BaseViewController, View { typealias Reactor = MapReactor - fileprivate struct CoordinateKey: Hashable { let lat: Int let lng: Int @@ -28,7 +26,6 @@ class MapViewController: BaseViewController, View { var currentTooltipStores: [MapPopUpStore] = [] var currentTooltipCoordinate: CLLocationCoordinate2D? - // MARK: - Properties private var storeDetailsCache: [Int64: StoreItem] = [:] private var isMovingToMarker = false @@ -82,7 +79,6 @@ class MapViewController: BaseViewController, View { setUp() mainView.mapView.padding = UIEdgeInsets(top: 0, left: 0, bottom: 10, right: 0) - locationManager.delegate = self locationManager.requestWhenInUseAuthorization() locationManager.desiredAccuracy = kCLLocationAccuracyBest @@ -133,8 +129,6 @@ class MapViewController: BaseViewController, View { .disposed(by: disposeBag) } - - carouselView.rx.observe(Bool.self, "hidden") .distinctUntilChanged() .subscribe(onNext: { [weak self] isHidden in @@ -171,9 +165,6 @@ class MapViewController: BaseViewController, View { }) .disposed(by: disposeBag) - - - carouselView.onCardScrolled = { [weak self] pageIndex in guard let self = self, pageIndex >= 0, @@ -302,7 +293,7 @@ class MapViewController: BaseViewController, View { let markerPoint = self.mainView.mapView.projection.point(for: marker.position) let markerHeight = (marker.iconView as? MapMarker)?.imageView.frame.height ?? 32 tooltipView.frame = CGRect( - x: markerPoint.x , // 마커 오른쪽 10포인트 + x: markerPoint.x, y: markerPoint.y - markerHeight - tooltipView.frame.height - 14, width: tooltipView.frame.width, height: tooltipView.frame.height @@ -447,10 +438,6 @@ class MapViewController: BaseViewController, View { } .disposed(by: disposeBag) - - - - mainView.filterChips.onRemoveLocation = { [weak self] in guard let self = self else { return } // 필터 제거 액션 @@ -545,7 +532,6 @@ class MapViewController: BaseViewController, View { } .disposed(by: disposeBag) - reactor.state.map { $0.activeFilterType } .distinctUntilChanged() .observe(on: MainScheduler.instance) @@ -599,8 +585,6 @@ class MapViewController: BaseViewController, View { }) .disposed(by: disposeBag) - - reactor.state.map { $0.searchResults } .distinctUntilChanged() .observe(on: MainScheduler.instance) @@ -653,9 +637,6 @@ class MapViewController: BaseViewController, View { } .disposed(by: disposeBag) - - - // reactor.state.map { $0.searchResults.isEmpty } // .distinctUntilChanged() // .skip(1) // 초기값 스킵 @@ -672,7 +653,6 @@ class MapViewController: BaseViewController, View { // .disposed(by: disposeBag) } - // MARK: - List View Control private func toggleListView() { UIView.animate(withDuration: 0.3) { @@ -685,7 +665,6 @@ class MapViewController: BaseViewController, View { } - func addMarker(for store: MapPopUpStore) { let marker = GMSMarker() marker.position = store.coordinate @@ -772,7 +751,6 @@ class MapViewController: BaseViewController, View { } } - private func updateMapViewAlpha(for offset: CGFloat, minOffset: CGFloat, maxOffset: CGFloat) { let progress = (maxOffset - offset) / (maxOffset - minOffset) // 0(탑) ~ 1(바텀) mainView.mapView.alpha = max(0, min(progress, 1)) // 0(완전히 가림) ~ 1(완전히 보임) @@ -793,8 +771,6 @@ class MapViewController: BaseViewController, View { self.listViewTopConstraint?.update(offset: filterChipsFrame.maxY) self.mainView.searchInput.setBackgroundColor(.g50) - - case .middle: self.storeListViewController.setGrabberHandleVisible(true) let offset = max(self.view.frame.height * 0.3, self.filterContainerBottomY) @@ -860,7 +836,6 @@ class MapViewController: BaseViewController, View { } } - // updateMapWithClustering() 메서드 전체 구현 private func updateMapWithClustering() { let currentZoom = mainView.mapView.camera.zoom @@ -876,7 +851,6 @@ class MapViewController: BaseViewController, View { // 현재 화면에 보이는 스토어 업데이트 currentStores = visibleStores - CATransaction.begin() CATransaction.setDisableActions(true) @@ -1041,7 +1015,6 @@ class MapViewController: BaseViewController, View { return dict } - private func updateIndividualMarkers(_ stores: [MapPopUpStore]) { var newMarkerIDs = Set() @@ -1101,8 +1074,6 @@ class MapViewController: BaseViewController, View { } } - - func presentFilterBottomSheet(for filterType: FilterType) { guard let reactor = self.reactor else { return } @@ -1150,7 +1121,7 @@ class MapViewController: BaseViewController, View { } currentFilterBottomSheet = nil } - //기본 마커 + // 기본 마커 private func addMarkers(for stores: [MapPopUpStore]) { mainView.mapView.clear() markerDictionary.removeAll() @@ -1215,8 +1186,6 @@ class MapViewController: BaseViewController, View { ) } - - // MARK: - Location private func checkLocationAuthorization() { switch locationManager.authorizationStatus { @@ -1264,7 +1233,6 @@ extension MapViewController: CLLocationManagerDelegate { locationManager.stopUpdatingLocation() } - private func findAndShowNearestStore(from location: CLLocation) { guard !currentStores.isEmpty else { Logger.log(message: "현재위치 표기할 스토어가 없습니다", category: .debug) @@ -1341,7 +1309,6 @@ extension MapViewController: GMSMapViewDelegate { return false } - func mapView(_ mapView: GMSMapView, didChange position: GMSCameraPosition) { if !isMovingToMarker { currentTooltipView?.removeFromSuperview() @@ -1356,8 +1323,6 @@ extension MapViewController: GMSMapViewDelegate { } } - - func mapView(_ mapView: GMSMapView, willMove gesture: Bool) { if gesture && !isMovingToMarker { resetSelectedMarker() @@ -1396,9 +1361,6 @@ extension MapViewController: GMSMapViewDelegate { updateMapWithClustering() } - - - // MARK: - Helper for single marker tap func handleSingleStoreTap(_ marker: GMSMarker, store: MapPopUpStore) -> Bool { isMovingToMarker = true @@ -1477,9 +1439,6 @@ extension MapViewController: GMSMapViewDelegate { return true } - - - func handleRegionalClusterTap(_ marker: GMSMarker, clusterData: ClusterMarkerData) -> Bool { let currentZoom = mainView.mapView.camera.zoom let currentLevel = MapZoomLevel.getLevel(from: currentZoom) @@ -1633,10 +1592,8 @@ extension MapViewController: GMSMapViewDelegate { self.currentMarker = nil } - } - extension MapViewController { func bindViewport(reactor: MapReactor) { let cameraObservable = Observable.merge([ @@ -1693,7 +1650,6 @@ extension MapViewController { }) .disposed(by: disposeBag) - // 뷰포트 내 마커 업데이트 및 캐러셀 표시 (수정된 부분) reactor.state .map { $0.viewportStores } @@ -1841,7 +1797,6 @@ extension MapViewController { } } - private func findMarkerForStore(for store: MapPopUpStore) -> GMSMarker? { // individualMarkerDictionary에 저장된 모든 마커를 순회 for marker in individualMarkerDictionary.values { @@ -1878,8 +1833,6 @@ extension MapViewController { } } - - private func handleMarkerTap(_ marker: GMSMarker) -> Bool { isMovingToMarker = true @@ -1931,7 +1884,6 @@ private func handleMarkerTap(_ marker: GMSMarker) -> Bool { return true } - private func getCurrentViewportBounds() -> (northEast: CLLocationCoordinate2D, southWest: CLLocationCoordinate2D) { let region = mainView.mapView.projection.visibleRegion() return (northEast: region.farRight, southWest: region.nearLeft) diff --git a/Poppool/Poppool/Presentation/Map/MapView/MarkerTooltipView.swift b/Poppool/Poppool/Presentation/Map/MapView/MarkerTooltipView.swift index 936b8848..a1c2bffe 100644 --- a/Poppool/Poppool/Presentation/Map/MapView/MarkerTooltipView.swift +++ b/Poppool/Poppool/Presentation/Map/MapView/MarkerTooltipView.swift @@ -1,5 +1,5 @@ -import UIKit import SnapKit +import UIKit final class MarkerTooltipView: UIView, UIGestureRecognizerDelegate { diff --git a/Poppool/Poppool/Presentation/Map/MicroClusterMarkerView.swift b/Poppool/Poppool/Presentation/Map/MicroClusterMarkerView.swift index f41f6569..f7d21f38 100644 --- a/Poppool/Poppool/Presentation/Map/MicroClusterMarkerView.swift +++ b/Poppool/Poppool/Presentation/Map/MicroClusterMarkerView.swift @@ -1,3 +1,2 @@ - import Foundation import UIKit diff --git a/Poppool/Poppool/Presentation/Map/StoreListPanelLayout.swift b/Poppool/Poppool/Presentation/Map/StoreListPanelLayout.swift index 842bc1de..5dd5201d 100644 --- a/Poppool/Poppool/Presentation/Map/StoreListPanelLayout.swift +++ b/Poppool/Poppool/Presentation/Map/StoreListPanelLayout.swift @@ -1,8 +1,8 @@ // -//import FloatingPanel -//import UIKit +// import FloatingPanel +// import UIKit // -//class StoreListPanelLayout: FloatingPanelLayout { +// class StoreListPanelLayout: FloatingPanelLayout { // let position: FloatingPanelPosition = .bottom // let initialState: FloatingPanelState = .half // @@ -27,4 +27,4 @@ // func surfaceLayout(for size: CGSize) -> NSCollectionLayoutDimension { // return .fractionalWidth(1.0) // } -//} +// } diff --git a/Poppool/Poppool/Presentation/Map/StoreListView/StoreListCell.swift b/Poppool/Poppool/Presentation/Map/StoreListView/StoreListCell.swift index e8e5e003..7b12cfb5 100644 --- a/Poppool/Poppool/Presentation/Map/StoreListView/StoreListCell.swift +++ b/Poppool/Poppool/Presentation/Map/StoreListView/StoreListCell.swift @@ -1,7 +1,7 @@ -import UIKit -import SnapKit -import RxSwift import ReactorKit +import RxSwift +import SnapKit +import UIKit final class StoreListCell: UICollectionViewCell { static let identifier = "StoreListCell" @@ -48,7 +48,7 @@ final class StoreListCell: UICollectionViewCell { private let dateLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 12, text: "") label.textColor = .g400 - label.numberOfLines = 2 + label.numberOfLines = 2 return label }() diff --git a/Poppool/Poppool/Presentation/Map/StoreListView/StoreListHeaderView.swift b/Poppool/Poppool/Presentation/Map/StoreListView/StoreListHeaderView.swift index 7911caca..65f574b0 100644 --- a/Poppool/Poppool/Presentation/Map/StoreListView/StoreListHeaderView.swift +++ b/Poppool/Poppool/Presentation/Map/StoreListView/StoreListHeaderView.swift @@ -1,10 +1,10 @@ // -//import UIKit -//import SnapKit -//import RxSwift +// import UIKit +// import SnapKit +// import RxSwift // // -//class StoreListHeaderView: UICollectionReusableView { +// class StoreListHeaderView: UICollectionReusableView { // static let identifier = "StoreListHeaderView" // // let searchInput = MapSearchInput() @@ -51,4 +51,4 @@ // // // } -//} +// } diff --git a/Poppool/Poppool/Presentation/Map/StoreListView/StoreListPanelLayout.swift b/Poppool/Poppool/Presentation/Map/StoreListView/StoreListPanelLayout.swift index f5eee867..ab3b649d 100644 --- a/Poppool/Poppool/Presentation/Map/StoreListView/StoreListPanelLayout.swift +++ b/Poppool/Poppool/Presentation/Map/StoreListView/StoreListPanelLayout.swift @@ -20,9 +20,6 @@ class StoreListPanelLayout: FloatingPanelLayout { ] } - - - func backdropAlpha(for state: FloatingPanelState) -> CGFloat { return 0.0 } diff --git a/Poppool/Poppool/Presentation/Map/StoreListView/StoreListReactor.swift b/Poppool/Poppool/Presentation/Map/StoreListView/StoreListReactor.swift index 864d2426..f335e42c 100644 --- a/Poppool/Poppool/Presentation/Map/StoreListView/StoreListReactor.swift +++ b/Poppool/Poppool/Presentation/Map/StoreListView/StoreListReactor.swift @@ -1,8 +1,7 @@ -import ReactorKit -import RxSwift import Foundation +import ReactorKit import RxCocoa - +import RxSwift final class StoreListReactor: Reactor { // MARK: - Reactor @@ -10,7 +9,6 @@ final class StoreListReactor: Reactor { private let popUpAPIUseCase: PopUpAPIUseCaseImpl private let bookmarkStateRelay = PublishRelay<(Int64, Bool)>() - // private var currentPage = 0 // private let pageSize = 10 // private var hasMorePages = true @@ -25,7 +23,6 @@ final class StoreListReactor: Reactor { case clearFilters(FilterType) case updateStoreBookmark(id: Int64, isBookmarked: Bool) // 추가 - } enum Mutation { @@ -59,7 +56,6 @@ final class StoreListReactor: Reactor { self.initialState = State() } - // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -84,7 +80,7 @@ final class StoreListReactor: Reactor { return .empty() } - let bookmarkRequest = popUpAPIUseCase.getPopUpDetail( + return popUpAPIUseCase.getPopUpDetail( commentType: "NORMAL", popUpStoredId: Int64(idInt32) // Int32 → Int64 변환 ) @@ -102,10 +98,6 @@ final class StoreListReactor: Reactor { ])) } - return bookmarkRequest - - - // // case let .setStores(storeItems): // return Observable.from(storeItems) @@ -160,7 +152,6 @@ final class StoreListReactor: Reactor { } return .empty() - case let .filterTapped(filterType): return .just(.setActiveFilter(filterType)) @@ -182,7 +173,6 @@ final class StoreListReactor: Reactor { } } - func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { @@ -206,10 +196,9 @@ final class StoreListReactor: Reactor { ) } - case let .showBookmarkToast(isBookmarked): if currentState.stores.isEmpty { - break + break } newState.shouldShowBookmarkToast = isBookmarked @@ -236,11 +225,8 @@ final class StoreListReactor: Reactor { bookmarkStateRelay.accept((storeId, isBookmarked)) } - - } - // MARK: - Model struct StoreItem { let id: Int64 diff --git a/Poppool/Poppool/Presentation/Map/StoreListView/StoreListView.swift b/Poppool/Poppool/Presentation/Map/StoreListView/StoreListView.swift index 63f88bc5..79f01619 100644 --- a/Poppool/Poppool/Presentation/Map/StoreListView/StoreListView.swift +++ b/Poppool/Poppool/Presentation/Map/StoreListView/StoreListView.swift @@ -1,5 +1,5 @@ -import UIKit import SnapKit +import UIKit final class StoreListView: UIView { // MARK: - Components diff --git a/Poppool/Poppool/Presentation/Map/StoreListView/StoreListViewController.swift b/Poppool/Poppool/Presentation/Map/StoreListView/StoreListViewController.swift index e8fb9c69..5ffbaa5e 100644 --- a/Poppool/Poppool/Presentation/Map/StoreListView/StoreListViewController.swift +++ b/Poppool/Poppool/Presentation/Map/StoreListView/StoreListViewController.swift @@ -1,10 +1,10 @@ -import UIKit -import SnapKit -import RxCocoa -import RxSwift -import ReactorKit import FloatingPanel +import ReactorKit +import RxCocoa import RxDataSources +import RxSwift +import SnapKit +import UIKit final class StoreListViewController: UIViewController, View { typealias Reactor = StoreListReactor @@ -56,7 +56,7 @@ final class StoreListViewController: UIViewController, View { func bind(reactor: Reactor) { let dataSource = RxCollectionViewSectionedReloadDataSource( - configureCell: { [weak self] ds, cv, indexPath, item in + configureCell: { [weak self] _, cv, indexPath, item in guard let self = self else { return UICollectionViewCell() } let cell = cv.dequeueReusableCell( withReuseIdentifier: StoreListCell.identifier, @@ -64,7 +64,7 @@ final class StoreListViewController: UIViewController, View { ) as! StoreListCell cell.injection(with: .init( - thumbnailURL: item.thumbnailURL, + thumbnailURL: item.thumbnailURL, category: item.category, title: item.title, location: item.location, @@ -124,7 +124,6 @@ final class StoreListViewController: UIViewController, View { }) .disposed(by: disposeBag) - // 4) viewWillAppear -> viewDidLoad // rx.viewWillAppear // .map { _ in Reactor.Action.viewDidLoad } @@ -155,7 +154,7 @@ final class StoreListViewController: UIViewController, View { // .compactMap { $0 } // .bind(to: reactor.action) // .disposed(by: disposeBag) - + } private func presentFilterBottomSheet(for filterType: FilterType) { @@ -167,7 +166,7 @@ final class StoreListViewController: UIViewController, View { viewController.containerView.segmentedControl.selectedSegmentIndex = initialIndex sheetReactor.action.onNext(.segmentChanged(initialIndex)) - viewController.onSave = { [weak self] selectedOptions in + viewController.onSave = { [weak self] _ in guard let self = self else { return } // 닫기 self.reactor?.action.onNext(.filterTapped(nil)) diff --git a/Poppool/Poppool/Presentation/Map/TestViewController.swift b/Poppool/Poppool/Presentation/Map/TestViewController.swift index 8a35c50d..82bd615b 100644 --- a/Poppool/Poppool/Presentation/Map/TestViewController.swift +++ b/Poppool/Poppool/Presentation/Map/TestViewController.swift @@ -7,87 +7,86 @@ import UIKit -import SnapKit -import RxSwift -import RxGesture import RxCocoa +import RxGesture +import RxSwift +import SnapKit class TestViewController: UIViewController { - + private let topView: UIView = { let view = UIView() view.backgroundColor = .w100 view.alpha = 0 return view }() - + private let topViewLabel: UILabel = { let label = UILabel() label.text = "Top View Label" return label }() - + private let bottomView: UIView = { let view = UIView() view.backgroundColor = .w100 return view }() - + private let gestureBar: UIView = { let view = UIView() view.backgroundColor = .g200 return view }() - + private let listButton: PPButton = { - let button = PPButton(style: .secondary, text: "리스트 버튼") - return button + return PPButton(style: .secondary, text: "리스트 버튼") }() - + private let disposeBag = DisposeBag() - + private var bottomViewTopConstraints: Constraint? - + enum ModalState { case top case middle case bottom } - + var modalState: ModalState = .bottom - + override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .blue setUpConstratins() bind() } - + func setUpConstratins() { view.addSubview(listButton) listButton.snp.makeConstraints { make in make.leading.trailing.bottom.equalToSuperview().inset(20) make.height.equalTo(50) } - + view.addSubview(topView) topView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() make.bottom.equalTo(view.safeAreaLayoutGuide.snp.top).offset(104) } - + topView.addSubview(topViewLabel) topViewLabel.snp.makeConstraints { make in make.center.equalToSuperview() } - + view.addSubview(bottomView) bottomView.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() bottomViewTopConstraints = make.top.equalTo(topView.snp.bottom).offset(700).constraint make.height.equalTo(700) } - + bottomView.addSubview(gestureBar) gestureBar.snp.makeConstraints { make in make.width.equalTo(50) @@ -96,7 +95,7 @@ class TestViewController: UIViewController { make.centerX.equalToSuperview() } } - + func bind() { listButton.rx.tap .withUnretained(self) @@ -109,11 +108,11 @@ class TestViewController: UIViewController { } } .disposed(by: disposeBag) - + gestureBar.rx.swipeGesture(.up) .skip(1) .withUnretained(self) - .subscribe { (owner, gesture) in + .subscribe { (owner, _) in print("swipe up") UIView.animate(withDuration: 0.3) { owner.bottomViewTopConstraints?.update(offset: 0) @@ -123,11 +122,11 @@ class TestViewController: UIViewController { } } .disposed(by: disposeBag) - + gestureBar.rx.swipeGesture(.down) .skip(1) .withUnretained(self) - .subscribe { (owner, gesture) in + .subscribe { (owner, _) in print("swipe down") switch owner.modalState { case .top: diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentCheck/CommentCheckController.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentCheck/CommentCheckController.swift index 3bee6c71..f1e20e9f 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentCheck/CommentCheckController.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentCheck/CommentCheckController.swift @@ -7,19 +7,19 @@ import UIKit -import SnapKit +import PanModal +import ReactorKit import RxCocoa import RxSwift -import ReactorKit -import PanModal +import SnapKit final class CommentCheckController: BaseViewController, View { - + typealias Reactor = CommentCheckReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = CommentCheckView() } @@ -48,7 +48,7 @@ extension CommentCheckController { .map { Reactor.Action.continueButtonTapped } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.stopButton.rx.tap .map { Reactor.Action.stopButtonTapped } .bind(to: reactor.action) diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentCheck/CommentCheckReactor.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentCheck/CommentCheckReactor.swift index 4afbec9d..9653a396 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentCheck/CommentCheckReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentCheck/CommentCheckReactor.swift @@ -6,41 +6,41 @@ // import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class CommentCheckReactor: Reactor { - + // MARK: - Reactor enum Action { case continueButtonTapped case stopButtonTapped } - + enum Mutation { case setSelectedType(type: SelectedType) } - + struct State { var selectedType: SelectedType = .none } - + enum SelectedType { case none case continues case stop } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - + // MARK: - init init() { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -50,7 +50,7 @@ final class CommentCheckReactor: Reactor { return Observable.just(.setSelectedType(type: .stop)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentCheck/CommentCheckView.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentCheck/CommentCheckView.swift index 51d6458e..c16fb508 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentCheck/CommentCheckView.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentCheck/CommentCheckView.swift @@ -10,42 +10,39 @@ import UIKit import SnapKit final class CommentCheckView: UIView { - + // MARK: - Components private let titleLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 18, text: "코멘트 작성을 그만하시겠어요?") - return label + return PPLabel(style: .bold, fontSize: 18, text: "코멘트 작성을 그만하시겠어요?") }() - + private let descriptionLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 14, text: "화면을 나가실 경우 작성중인 내용은 저장되지 않아요.") label.textColor = .g600 return label }() - + private let buttonStackView: UIStackView = { let view = UIStackView() view.distribution = .fillEqually view.spacing = 12 return view }() - + let continueButton: PPButton = { - let button = PPButton(style: .secondary, text: "계속하기") - return button + return PPButton(style: .secondary, text: "계속하기") }() - + let stopButton: PPButton = { - let button = PPButton(style: .primary, text: "그만하기") - return button + return PPButton(style: .primary, text: "그만하기") }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -53,20 +50,20 @@ final class CommentCheckView: UIView { // MARK: - SetUp private extension CommentCheckView { - + func setUpConstraints() { self.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.top.equalToSuperview().inset(32) make.leading.trailing.equalToSuperview().inset(20) } - + self.addSubview(descriptionLabel) descriptionLabel.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(8) make.leading.trailing.equalToSuperview().inset(20) } - + buttonStackView.addArrangedSubview(continueButton) buttonStackView.addArrangedSubview(stopButton) self.addSubview(buttonStackView) diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/CommentDetailController.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/CommentDetailController.swift index e4fb63a0..4196ffe8 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/CommentDetailController.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/CommentDetailController.swift @@ -7,23 +7,23 @@ import UIKit -import SnapKit +import PanModal +import ReactorKit import RxCocoa import RxSwift -import ReactorKit -import PanModal +import SnapKit final class CommentDetailController: BaseViewController, View { - + typealias Reactor = CommentDetailReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + var mainView = CommentDetailView() - + private var sections: [any Sectionable] = [] - + private var cellTapped: PublishSubject = .init() } @@ -47,16 +47,16 @@ private extension CommentDetailController { DetailCommentImageCell.self, forCellWithReuseIdentifier: DetailCommentImageCell.identifiers ) - + mainView.contentCollectionView.register( SpacingSectionCell.self, forCellWithReuseIdentifier: SpacingSectionCell.identifiers - ) + ) mainView.contentCollectionView.register( CommentDetailContentSectionCell.self, forCellWithReuseIdentifier: CommentDetailContentSectionCell.identifiers ) - + view.addSubview(mainView) mainView.snp.makeConstraints { make in make.edges.equalTo(view.safeAreaLayoutGuide) @@ -72,7 +72,7 @@ extension CommentDetailController { .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + cellTapped .withUnretained(self) .map { (owner, indexPath) in @@ -80,12 +80,12 @@ extension CommentDetailController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.likeButton.rx.tap .map { Reactor.Action.likeButtonTapped } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -112,19 +112,18 @@ extension CommentDetailController: UICollectionViewDelegate, UICollectionViewDat func numberOfSections(in collectionView: UICollectionView) -> Int { return sections.count } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sections[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath ) -> UICollectionViewCell { - let cell = sections[indexPath.section].getCell(collectionView: collectionView, indexPath: indexPath) - return cell + return sections[indexPath.section].getCell(collectionView: collectionView, indexPath: indexPath) } - + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if let _ = collectionView.cellForItem(at: indexPath) as? DetailCommentImageCell { cellTapped.onNext(indexPath) @@ -139,11 +138,11 @@ extension CommentDetailController: PanModalPresentable { var longFormHeight: PanModalHeight { return .contentHeight(UIScreen.main.bounds.height - 68) } - + var shortFormHeight: PanModalHeight { return .contentHeight(UIScreen.main.bounds.height - 68) } - + var showDragIndicator: Bool { return false } diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/CommentDetailReactor.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/CommentDetailReactor.swift index 978cb6bf..8b7d28f3 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/CommentDetailReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/CommentDetailReactor.swift @@ -8,36 +8,36 @@ import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class CommentDetailReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear case imageCellTapped(controller: BaseViewController, row: Int) case likeButtonTapped } - + enum Mutation { case loadView case presentImageDetailView(controller: BaseViewController, row: Int) case likeChange } - + struct State { var commentData: DetailCommentSection.CellType.Input var sections: [any Sectionable] = [] } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - + private let userAPIUseCase = UserAPIUseCaseImpl(repository: UserAPIRepositoryImpl(provider: ProviderImpl())) - + lazy var compositionalLayout: UICollectionViewCompositionalLayout = { UICollectionViewCompositionalLayout { [weak self] section, env in guard let self = self else { @@ -51,17 +51,17 @@ final class CommentDetailReactor: Reactor { return getSection()[section].getSection(section: section, env: env) } }() - + private var imageSection = CommentDetailImageSection(inputDataList: []) private var contentSection = CommentDetailContentSection(inputDataList: []) - + private let spacing16Section = SpacingSection(inputDataList: [.init(spacing: 16)]) - + // MARK: - init init(comment: DetailCommentSection.CellType.Input) { self.initialState = State(commentData: comment) } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -73,7 +73,7 @@ final class CommentDetailReactor: Reactor { return Observable.just(.presentImageDetailView(controller: controller, row: row)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { @@ -93,26 +93,26 @@ final class CommentDetailReactor: Reactor { if newState.commentData.isLike { newState.commentData.likeCount += 1 userAPIUseCase.postCommentLike(commentId: newState.commentData.commentID) - .subscribe(onDisposed: { + .subscribe(onDisposed: { Logger.log(message: "CommentLike", category: .info) }) .disposed(by: disposeBag) } else { newState.commentData.likeCount -= 1 userAPIUseCase.deleteCommentLike(commentId: newState.commentData.commentID) - .subscribe(onDisposed: { + .subscribe(onDisposed: { Logger.log(message: "CommentLikeDelete", category: .info) }) .disposed(by: disposeBag) } newState.sections = getSection() } - + return newState } - + func getSection() -> [any Sectionable] { - if imageSection.isEmpty { + if imageSection.isEmpty { return [ contentSection ] diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/View/CommentDetailContentSection/CommentDetailContentSection.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/View/CommentDetailContentSection/CommentDetailContentSection.swift index 1bb2c50e..b127b716 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/View/CommentDetailContentSection/CommentDetailContentSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/View/CommentDetailContentSection/CommentDetailContentSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct CommentDetailContentSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = CommentDetailContentSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -37,7 +37,7 @@ struct CommentDetailContentSection: Sectionable { // 섹션 생성 let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 0, leading: 20, bottom: 0, trailing: 20) - + return section } } diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/View/CommentDetailContentSection/CommentDetailContentSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/View/CommentDetailContentSection/CommentDetailContentSectionCell.swift index 17650a73..0d814d0a 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/View/CommentDetailContentSection/CommentDetailContentSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/View/CommentDetailContentSection/CommentDetailContentSectionCell.swift @@ -7,11 +7,11 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class CommentDetailContentSectionCell: UICollectionViewCell { - + // MARK: - Components private let contentLabel: PPLabel = { @@ -19,15 +19,15 @@ final class CommentDetailContentSectionCell: UICollectionViewCell { label.numberOfLines = 0 return label }() - + let disposeBag = DisposeBag() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } @@ -47,7 +47,7 @@ extension CommentDetailContentSectionCell: Inputable { struct Input { var content: String? } - + func injection(with input: Input) { contentLabel.setLineHeightText(text: input.content, font: .KorFont(style: .medium, size: 13)) } diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/View/CommentDetailImageSection.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/View/CommentDetailImageSection.swift index a2644557..a3c61126 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/View/CommentDetailImageSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/View/CommentDetailImageSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct CommentDetailImageSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = DetailCommentImageCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .absolute(80), diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/View/CommentDetailView.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/View/CommentDetailView.swift index 6bf879f4..c75acd0f 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/View/CommentDetailView.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/View/CommentDetailView.swift @@ -10,42 +10,40 @@ import UIKit import SnapKit final class CommentDetailView: UIView { - + // MARK: - Components let profileView: DetailCommentProfileView = { let view = DetailCommentProfileView() view.button.isHidden = true return view }() - + let likeButton: UIButton = { - let button = UIButton() - return button + return UIButton() }() - + let likeButtonTitleLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 13, text: "도움돼요") label.textColor = .g400 return label }() - + let likeButtonImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_like_gray") return view }() - + let contentCollectionView: UICollectionView = { - let view = UICollectionView(frame: .zero, collectionViewLayout: .init()) - return view + return UICollectionView(frame: .zero, collectionViewLayout: .init()) }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -53,32 +51,32 @@ final class CommentDetailView: UIView { // MARK: - SetUp private extension CommentDetailView { - + func setUpConstraints() { self.addSubview(profileView) profileView.snp.makeConstraints { make in make.leading.trailing.equalToSuperview().inset(20) make.top.equalToSuperview().inset(40) } - + likeButton.addSubview(likeButtonTitleLabel) likeButtonTitleLabel.snp.makeConstraints { make in make.height.equalTo(20).priority(.high) make.top.bottom.trailing.equalToSuperview() } - + likeButton.addSubview(likeButtonImageView) likeButtonImageView.snp.makeConstraints { make in make.size.equalTo(20) make.leading.centerY.equalToSuperview() make.trailing.equalTo(likeButtonTitleLabel.snp.leading) } - + self.addSubview(likeButton) likeButton.snp.makeConstraints { make in make.bottom.trailing.equalToSuperview().inset(20) } - + self.addSubview(contentCollectionView) contentCollectionView.snp.makeConstraints { make in make.top.equalTo(profileView.snp.bottom).offset(16) diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentMyMenu/CommentMyMenuController.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentMyMenu/CommentMyMenuController.swift index 3dcc05c4..088653fe 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentMyMenu/CommentMyMenuController.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentMyMenu/CommentMyMenuController.swift @@ -7,19 +7,19 @@ import UIKit -import SnapKit +import PanModal +import ReactorKit import RxCocoa import RxSwift -import ReactorKit -import PanModal +import SnapKit final class CommentMyMenuController: BaseViewController, View { - + typealias Reactor = CommentMyMenuReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = CommentMyMenuView() } @@ -50,14 +50,14 @@ extension CommentMyMenuController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.commentRemoveButton.rx.tap .map { _ in Reactor.Action.removeButtonTapped } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.commentEditButton.rx.tap .map { _ in Reactor.Action.editButtonTapped diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentMyMenu/CommentMyMenuReactor.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentMyMenu/CommentMyMenuReactor.swift index 25324ff7..caebe2ca 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentMyMenu/CommentMyMenuReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentMyMenu/CommentMyMenuReactor.swift @@ -6,28 +6,28 @@ // import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class CommentMyMenuReactor: Reactor { - + // MARK: - Reactor enum Action { case cancelButtonTapped case removeButtonTapped case editButtonTapped } - + enum Mutation { case moveToRecentScene case setRemoveType case setEditType } - + struct State { var selectedType: SelectedType = .none } - + enum SelectedType { case none case cancel @@ -35,15 +35,15 @@ final class CommentMyMenuReactor: Reactor { case edit } // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - + // MARK: - init init(nickName: String?) { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -55,7 +55,7 @@ final class CommentMyMenuReactor: Reactor { return Observable.just(.setEditType) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentMyMenu/CommentMyMenuView.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentMyMenu/CommentMyMenuView.swift index 59563034..93153e71 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentMyMenu/CommentMyMenuView.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentMyMenu/CommentMyMenuView.swift @@ -10,20 +10,20 @@ import UIKit import SnapKit final class CommentMyMenuView: UIView { - + // MARK: - Components let titleLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 18) label.setLineHeightText(text: "내가 작성한 코멘트", font: .KorFont(style: .bold, size: 18)) return label }() - + let cancelButton: UIButton = { let button = UIButton() button.setImage(UIImage(named: "icon_xmark"), for: .normal) return button }() - + let commentRemoveButton: UIButton = { let button = UIButton() button.setTitle("코멘트 삭제하기", for: .normal) @@ -32,13 +32,13 @@ final class CommentMyMenuView: UIView { button.contentHorizontalAlignment = .leading return button }() - + private let lineView: UIView = { let view = UIView() view.backgroundColor = .g50 return view }() - + let commentEditButton: UIButton = { let button = UIButton() button.setTitle("코멘트 수정하기", for: .normal) @@ -47,13 +47,13 @@ final class CommentMyMenuView: UIView { button.contentHorizontalAlignment = .leading return button }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -61,35 +61,35 @@ final class CommentMyMenuView: UIView { // MARK: - SetUp private extension CommentMyMenuView { - + func setUpConstraints() { self.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.top.equalToSuperview().inset(32) make.leading.equalToSuperview().inset(20) } - + self.addSubview(cancelButton) cancelButton.snp.makeConstraints { make in make.size.equalTo(24) make.trailing.equalToSuperview().inset(20) make.centerY.equalTo(titleLabel) } - + self.addSubview(commentRemoveButton) commentRemoveButton.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(24) make.leading.trailing.equalToSuperview().inset(20) make.height.equalTo(57) } - + self.addSubview(lineView) lineView.snp.makeConstraints { make in make.top.equalTo(commentRemoveButton.snp.bottom) make.height.equalTo(1) make.leading.trailing.equalToSuperview().inset(20) } - + self.addSubview(commentEditButton) commentEditButton.snp.makeConstraints { make in make.top.equalTo(lineView.snp.bottom) diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentUserInfo/CommentUserInfoController.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentUserInfo/CommentUserInfoController.swift index 8c0ffa1d..190de3ae 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentUserInfo/CommentUserInfoController.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentUserInfo/CommentUserInfoController.swift @@ -7,19 +7,19 @@ import UIKit -import SnapKit +import PanModal +import ReactorKit import RxCocoa import RxSwift -import ReactorKit -import PanModal +import SnapKit final class CommentUserInfoController: BaseViewController, View { - + typealias Reactor = CommentUserInfoReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = CommentUserInfoView() } @@ -50,21 +50,21 @@ extension CommentUserInfoController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.normalCommentButton.rx.tap .map { _ in Reactor.Action.normalButtonTapped } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.instaCommentButton.rx.tap .map { _ in Reactor.Action.instaButtonTapped } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentUserInfo/CommentUserInfoReactor.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentUserInfo/CommentUserInfoReactor.swift index 9b12bdb6..76562c2b 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentUserInfo/CommentUserInfoReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentUserInfo/CommentUserInfoReactor.swift @@ -6,29 +6,29 @@ // import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class CommentUserInfoReactor: Reactor { - + // MARK: - Reactor enum Action { case cancelButtonTapped case normalButtonTapped case instaButtonTapped } - + enum Mutation { case moveToRecentScene case moveToCommentScene case moveToBlockScene } - + struct State { var selectedType: SelectedType = .none var nickName: String? } - + enum SelectedType { case none case cancel @@ -36,15 +36,15 @@ final class CommentUserInfoReactor: Reactor { case block } // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - + // MARK: - init init(nickName: String?) { self.initialState = State(nickName: nickName) } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -56,7 +56,7 @@ final class CommentUserInfoReactor: Reactor { return Observable.just(.moveToBlockScene) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentUserInfo/CommentUserInfoView.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentUserInfo/CommentUserInfoView.swift index 25356672..0ee09c62 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentUserInfo/CommentUserInfoView.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentUserInfo/CommentUserInfoView.swift @@ -10,20 +10,20 @@ import UIKit import SnapKit final class CommentUserInfoView: UIView { - + // MARK: - Components let titleLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 18) label.setLineHeightText(text: "님에 대해 더 알아보기", font: .KorFont(style: .bold, size: 18)) return label }() - + let cancelButton: UIButton = { let button = UIButton() button.setImage(UIImage(named: "icon_xmark"), for: .normal) return button }() - + let normalCommentButton: UIButton = { let button = UIButton() button.setTitle("코멘트를 작성한 팝업 모두보기", for: .normal) @@ -32,13 +32,13 @@ final class CommentUserInfoView: UIView { button.contentHorizontalAlignment = .leading return button }() - + private let lineView: UIView = { let view = UIView() view.backgroundColor = .g50 return view }() - + let instaCommentButton: UIButton = { let button = UIButton() button.setTitle("이 유저 차단하기", for: .normal) @@ -47,13 +47,13 @@ final class CommentUserInfoView: UIView { button.contentHorizontalAlignment = .leading return button }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -61,35 +61,35 @@ final class CommentUserInfoView: UIView { // MARK: - SetUp private extension CommentUserInfoView { - + func setUpConstraints() { self.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.top.equalToSuperview().inset(32) make.leading.equalToSuperview().inset(20) } - + self.addSubview(cancelButton) cancelButton.snp.makeConstraints { make in make.size.equalTo(24) make.trailing.equalToSuperview().inset(20) make.centerY.equalTo(titleLabel) } - + self.addSubview(normalCommentButton) normalCommentButton.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(24) make.leading.trailing.equalToSuperview().inset(20) make.height.equalTo(57) } - + self.addSubview(lineView) lineView.snp.makeConstraints { make in make.top.equalTo(normalCommentButton.snp.bottom) make.height.equalTo(1) make.leading.trailing.equalToSuperview().inset(20) } - + self.addSubview(instaCommentButton) instaCommentButton.snp.makeConstraints { make in make.top.equalTo(lineView.snp.bottom) diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentList/CommentListController.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentList/CommentListController.swift index 14a2f784..a11d2f53 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentList/CommentListController.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentList/CommentListController.swift @@ -7,18 +7,18 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit final class CommentListController: BaseViewController, View { - + typealias Reactor = CommentListReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = CommentListView() private var sections: [any Sectionable] = [] private let scrollObserver: PublishSubject = .init() @@ -30,7 +30,7 @@ extension CommentListController { super.viewDidLoad() setUp() } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tabBarController?.tabBar.isHidden = true @@ -58,18 +58,18 @@ private extension CommentListController { // MARK: - Methods extension CommentListController { func bind(reactor: Reactor) { - + scrollObserver .throttle(.seconds(1), scheduler: MainScheduler.instance) .map { Reactor.Action.scrollDidEndPoint } .bind(to: reactor.action) .disposed(by: disposeBag) - + rx.viewWillAppear .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.headerView.backButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -77,7 +77,7 @@ extension CommentListController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -85,7 +85,7 @@ extension CommentListController { if state.isReloadView { owner.mainView.contentCollectionView.reloadData()} } .disposed(by: disposeBag) - + } } @@ -94,11 +94,11 @@ extension CommentListController: UICollectionViewDelegate, UICollectionViewDataS func numberOfSections(in collectionView: UICollectionView) -> Int { return sections.count } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sections[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath @@ -113,7 +113,7 @@ extension CommentListController: UICollectionViewDelegate, UICollectionViewDataS } .bind(to: reactor.action) .disposed(by: cell.disposeBag) - + cell.profileView.button.rx.tap .withUnretained(self) .map { (owner, _) in @@ -121,7 +121,7 @@ extension CommentListController: UICollectionViewDelegate, UICollectionViewDataS } .bind(to: reactor.action) .disposed(by: cell.disposeBag) - + cell.totalViewButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -129,7 +129,7 @@ extension CommentListController: UICollectionViewDelegate, UICollectionViewDataS } .bind(to: reactor.action) .disposed(by: cell.disposeBag) - + cell.likeButton.rx.tap .map { Reactor.Action.likeButtonTapped(row: indexPath.row) } .bind(to: reactor.action) @@ -137,7 +137,7 @@ extension CommentListController: UICollectionViewDelegate, UICollectionViewDataS } return cell } - + func scrollViewDidScroll(_ scrollView: UIScrollView) { let contentHeight = scrollView.contentSize.height let scrollViewHeight = scrollView.frame.size.height diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentList/CommentListReactor.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentList/CommentListReactor.swift index d62b4b1c..3823d183 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentList/CommentListReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentList/CommentListReactor.swift @@ -8,11 +8,11 @@ import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class CommentListReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear @@ -24,7 +24,7 @@ final class CommentListReactor: Reactor { case profileButtonTapped(controller: BaseViewController, row: Int) case detailSceneLikeButtonTapped(row: Int) } - + enum Mutation { case loadView case moveToRecentScene(controller: BaseViewController) @@ -33,26 +33,26 @@ final class CommentListReactor: Reactor { case presentImageScene(controller: BaseViewController, commentRow: Int, imageRow: Int) case presentCommentMenuScene(controller: BaseViewController, row: Int) } - + struct State { var sections: [any Sectionable] = [] var isReloadView: Bool = false } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() private let popUpID: Int64 private let popUpName: String? private var page: Int32 = 0 private var appendDataIsEmpty: Bool = false - + private var imageService = PreSignedService() private let popUpAPIUseCase = PopUpAPIUseCaseImpl(repository: PopUpAPIRepositoryImpl(provider: ProviderImpl())) private let userAPIUseCase = UserAPIUseCaseImpl(repository: UserAPIRepositoryImpl(provider: ProviderImpl())) private let commentAPIUseCase = CommentAPIUseCaseImpl(repository: CommentAPIRepository(provider: ProviderImpl())) - + lazy var compositionalLayout: UICollectionViewCompositionalLayout = { UICollectionViewCompositionalLayout { [weak self] section, env in guard let self = self else { @@ -66,10 +66,10 @@ final class CommentListReactor: Reactor { return getSection()[section].getSection(section: section, env: env) } }() - + private var commentTitleSection = CommentListTitleSection(inputDataList: []) private var commentSection = DetailCommentSection(inputDataList: []) - + private let spacing24Section = SpacingSection(inputDataList: [.init(spacing: 24)]) private let spacing28Section = SpacingSection(inputDataList: [.init(spacing: 28)]) // MARK: - init @@ -78,7 +78,7 @@ final class CommentListReactor: Reactor { self.popUpID = popUpID self.popUpName = popUpName } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -173,7 +173,7 @@ final class CommentListReactor: Reactor { return Observable.just(.loadView) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state newState.isReloadView = false @@ -207,11 +207,11 @@ final class CommentListReactor: Reactor { } else { showOtherUserCommentMenu(controller: controller, comment: comment) } - + } return newState } - + func getSection() -> [any Sectionable] { return [ spacing24Section, @@ -220,7 +220,7 @@ final class CommentListReactor: Reactor { commentSection ] } - + func showOtherUserCommentMenu(controller: BaseViewController, comment: DetailCommentSection.CellType.Input) { let nextController = CommentUserInfoController() nextController.reactor = CommentUserInfoReactor(nickName: comment.nickName) @@ -250,7 +250,7 @@ final class CommentListReactor: Reactor { case .block: ToastMaker.createToast(message: "\(comment.nickName ?? "")을 차단했어요") self.userAPIUseCase.postUserBlock(blockedUserId: comment.creator) - .subscribe(onDisposed: { + .subscribe(onDisposed: { blockController.dismiss(animated: true) }) .disposed(by: self.disposeBag) @@ -268,13 +268,13 @@ final class CommentListReactor: Reactor { }) .disposed(by: disposeBag) } - + func showMyCommentMenu(controller: BaseViewController, comment: DetailCommentSection.CellType.Input) { let nextController = CommentMyMenuController() nextController.reactor = CommentMyMenuReactor(nickName: comment.nickName) imageService = PreSignedService() controller.presentPanModal(nextController) - + nextController.reactor?.state .withUnretained(nextController) .subscribe(onNext: { [weak self] (owner, state) in @@ -287,7 +287,7 @@ final class CommentListReactor: Reactor { ToastMaker.createToast(message: "작성한 코멘트를 삭제했어요") }) .disposed(by: self.disposeBag) - + let commentList = comment.imageList.compactMap { $0 } self.imageService.tryDelete(targetPaths: .init(objectKeyList: commentList)) .subscribe { diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentList/View/CommentListTitleSection/CommentListTitleSection.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentList/View/CommentListTitleSection/CommentListTitleSection.swift index 18057b37..74440198 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentList/View/CommentListTitleSection/CommentListTitleSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentList/View/CommentListTitleSection/CommentListTitleSection.swift @@ -5,23 +5,22 @@ // Created by SeoJunYoung on 12/25/24. // - import UIKit import RxSwift struct CommentListTitleSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = CommentListTitleSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -38,7 +37,7 @@ struct CommentListTitleSection: Sectionable { // 섹션 생성 let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 0, leading: 20, bottom: 0, trailing: 20) - + return section } } diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentList/View/CommentListTitleSection/CommentListTitleSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentList/View/CommentListTitleSection/CommentListTitleSectionCell.swift index 708c4b59..f8b33c01 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentList/View/CommentListTitleSection/CommentListTitleSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentList/View/CommentListTitleSection/CommentListTitleSectionCell.swift @@ -7,11 +7,11 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class CommentListTitleSectionCell: UICollectionViewCell { - + // MARK: - Components private let countLabel: PPLabel = { @@ -21,12 +21,12 @@ final class CommentListTitleSectionCell: UICollectionViewCell { }() let disposeBag = DisposeBag() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } @@ -47,7 +47,7 @@ extension CommentListTitleSectionCell: Inputable { var count: Int var unit: String = "개" } - + func injection(with input: Input) { countLabel.setLineHeightText(text: "총 \(input.count)\(input.unit)", font: .KorFont(style: .regular, size: 13)) } diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentList/View/CommentListView.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentList/View/CommentListView.swift index faa57203..66c5c957 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentList/View/CommentListView.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentList/View/CommentListView.swift @@ -10,24 +10,22 @@ import UIKit import SnapKit final class CommentListView: UIView { - + // MARK: - Components let headerView: PPReturnHeaderView = { - let view = PPReturnHeaderView() - return view + return PPReturnHeaderView() }() - + let contentCollectionView: UICollectionView = { - let view = UICollectionView(frame: .zero, collectionViewLayout: .init()) - return view + return UICollectionView(frame: .zero, collectionViewLayout: .init()) }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -35,14 +33,14 @@ final class CommentListView: UIView { // MARK: - SetUp private extension CommentListView { - + func setUpConstraints() { self.addSubview(headerView) headerView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() } - + self.addSubview(contentCollectionView) contentCollectionView.snp.makeConstraints { make in make.top.equalTo(headerView.snp.bottom) diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentSelected/CommentSelectedController.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentSelected/CommentSelectedController.swift index 603c2dcd..0756496e 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentSelected/CommentSelectedController.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentSelected/CommentSelectedController.swift @@ -7,19 +7,19 @@ import UIKit -import SnapKit +import PanModal +import ReactorKit import RxCocoa import RxSwift -import ReactorKit -import PanModal +import SnapKit final class CommentSelectedController: BaseViewController, View { - + typealias Reactor = CommentSelectedReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = CommentSelectedView() } @@ -50,14 +50,14 @@ extension CommentSelectedController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.normalCommentButton.rx.tap .map { _ in Reactor.Action.normalButtonTapped } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.instaCommentButton.rx.tap .map { _ in Reactor.Action.instaButtonTapped diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentSelected/CommentSelectedReactor.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentSelected/CommentSelectedReactor.swift index 9ca6bd7a..271a84b6 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentSelected/CommentSelectedReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentSelected/CommentSelectedReactor.swift @@ -6,28 +6,28 @@ // import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class CommentSelectedReactor: Reactor { - + // MARK: - Reactor enum Action { case cancelButtonTapped case normalButtonTapped case instaButtonTapped } - + enum Mutation { case moveToRecentScene case moveToNormalScene case moveToInstaScene } - + struct State { var selectedType: SelectedType = .none } - + enum SelectedType { case none case cancel @@ -35,15 +35,15 @@ final class CommentSelectedReactor: Reactor { case insta } // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - + // MARK: - init init() { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -55,7 +55,7 @@ final class CommentSelectedReactor: Reactor { return Observable.just(.moveToInstaScene) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentSelected/CommentSelectedView.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentSelected/CommentSelectedView.swift index 9ca4bc6e..7c41e5bd 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentSelected/CommentSelectedView.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentSelected/CommentSelectedView.swift @@ -10,20 +10,20 @@ import UIKit import SnapKit final class CommentSelectedView: UIView { - + // MARK: - Components private let titleLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 18) label.setLineHeightText(text: "코멘트 작성 방법 선택", font: .KorFont(style: .bold, size: 18)) return label }() - + let cancelButton: UIButton = { let button = UIButton() button.setImage(UIImage(named: "icon_xmark"), for: .normal) return button }() - + let normalCommentButton: UIButton = { let button = UIButton() button.setTitle("일반 코멘트 작성하기", for: .normal) @@ -32,13 +32,13 @@ final class CommentSelectedView: UIView { button.contentHorizontalAlignment = .leading return button }() - + private let lineView: UIView = { let view = UIView() view.backgroundColor = .g50 return view }() - + let instaCommentButton: UIButton = { let button = UIButton() button.setTitle("인스타그램 연동 코멘트 작성하기", for: .normal) @@ -47,13 +47,13 @@ final class CommentSelectedView: UIView { button.contentHorizontalAlignment = .leading return button }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -61,34 +61,34 @@ final class CommentSelectedView: UIView { // MARK: - SetUp private extension CommentSelectedView { - + func setUpConstraints() { self.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.top.equalToSuperview().inset(34) make.leading.equalToSuperview().inset(20) } - + self.addSubview(cancelButton) cancelButton.snp.makeConstraints { make in make.size.equalTo(24) make.trailing.equalToSuperview().inset(20) make.centerY.equalTo(titleLabel) } - + self.addSubview(normalCommentButton) normalCommentButton.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(40) make.leading.trailing.equalToSuperview().inset(20) } - + self.addSubview(lineView) lineView.snp.makeConstraints { make in make.top.equalTo(normalCommentButton.snp.bottom).offset(16) make.height.equalTo(2) make.leading.trailing.equalToSuperview().inset(20) } - + self.addSubview(instaCommentButton) instaCommentButton.snp.makeConstraints { make in make.top.equalTo(lineView.snp.bottom).offset(16) diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentUserBlock/CommentUserBlockController.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentUserBlock/CommentUserBlockController.swift index a62c93ce..1c4871af 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentUserBlock/CommentUserBlockController.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentUserBlock/CommentUserBlockController.swift @@ -7,19 +7,19 @@ import UIKit -import SnapKit +import PanModal +import ReactorKit import RxCocoa import RxSwift -import ReactorKit -import PanModal +import SnapKit final class CommentUserBlockController: BaseViewController, View { - + typealias Reactor = CommentUserBlockReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = CommentUserBlockView() } @@ -48,12 +48,12 @@ extension CommentUserBlockController { .map { Reactor.Action.stopButtonTapped } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.blockButton.rx.tap .map { Reactor.Action.continueButtonTapped } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentUserBlock/CommentUserBlockReactor.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentUserBlock/CommentUserBlockReactor.swift index 7400fa39..72c9b9fa 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentUserBlock/CommentUserBlockReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentUserBlock/CommentUserBlockReactor.swift @@ -6,42 +6,42 @@ // import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class CommentUserBlockReactor: Reactor { - + // MARK: - Reactor enum Action { case continueButtonTapped case stopButtonTapped } - + enum Mutation { case setSelectedType(type: SelectedType) } - + struct State { var selectedType: SelectedType = .none var nickName: String? } - + enum SelectedType { case none case cancel case block } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - + // MARK: - init init(nickName: String?) { self.initialState = State(nickName: nickName) } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -51,7 +51,7 @@ final class CommentUserBlockReactor: Reactor { return Observable.just(.setSelectedType(type: .cancel)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentUserBlock/CommentUserBlockView.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentUserBlock/CommentUserBlockView.swift index 1906ff04..3ca9705b 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentUserBlock/CommentUserBlockView.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentUserBlock/CommentUserBlockView.swift @@ -10,43 +10,40 @@ import UIKit import SnapKit final class CommentUserBlockView: UIView { - + // MARK: - Components let titleLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 18, text: "님을 차단할까요?") - return label + return PPLabel(style: .bold, fontSize: 18, text: "님을 차단할까요?") }() - + private let descriptionLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 14, text: "차단하시면 앞으로 이 유저가 남긴\n코멘트와 반응을 볼 수 없어요.") label.numberOfLines = 2 label.textColor = .g600 return label }() - + private let buttonStackView: UIStackView = { let view = UIStackView() view.distribution = .fillEqually view.spacing = 12 return view }() - + let cancelButton: PPButton = { - let button = PPButton(style: .secondary, text: "취소") - return button + return PPButton(style: .secondary, text: "취소") }() - + let blockButton: PPButton = { - let button = PPButton(style: .primary, text: "차단하기") - return button + return PPButton(style: .primary, text: "차단하기") }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -54,20 +51,20 @@ final class CommentUserBlockView: UIView { // MARK: - SetUp private extension CommentUserBlockView { - + func setUpConstraints() { self.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.top.equalToSuperview().inset(32) make.leading.trailing.equalToSuperview().inset(20) } - + self.addSubview(descriptionLabel) descriptionLabel.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(8) make.leading.trailing.equalToSuperview().inset(20) } - + buttonStackView.addArrangedSubview(cancelButton) buttonStackView.addArrangedSubview(blockButton) self.addSubview(buttonStackView) diff --git a/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/InstaCommentAddController.swift b/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/InstaCommentAddController.swift index 7318d844..68c6327b 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/InstaCommentAddController.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/InstaCommentAddController.swift @@ -7,19 +7,19 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit import SwiftSoup final class InstaCommentAddController: BaseViewController, View { - + typealias Reactor = InstaCommentAddReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = InstaCommentAddView() private var sections: [any Sectionable] = [] } @@ -52,7 +52,7 @@ private extension InstaCommentAddController { // MARK: - Methods extension InstaCommentAddController { func bind(reactor: Reactor) { - + SceneDelegate.appDidBecomeActive .subscribe { _ in if let url = UIPasteboard.general.string { @@ -64,17 +64,17 @@ extension InstaCommentAddController { } } .disposed(by: disposeBag) - + rx.viewWillAppear .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.instaButton.rx.tap .map { Reactor.Action.instaButtonTapped } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -90,18 +90,18 @@ extension InstaCommentAddController: UICollectionViewDelegate, UICollectionViewD func numberOfSections(in collectionView: UICollectionView) -> Int { return sections.count } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sections[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath ) -> UICollectionViewCell { let cell = sections[indexPath.section].getCell(collectionView: collectionView, indexPath: indexPath) guard let reactor = reactor else { return cell } - + return cell } } diff --git a/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/InstaCommentAddReactor.swift b/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/InstaCommentAddReactor.swift index 6546f5d2..e05674c3 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/InstaCommentAddReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/InstaCommentAddReactor.swift @@ -8,31 +8,31 @@ import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class InstaCommentAddReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear case instaButtonTapped } - + enum Mutation { case loadView case moveToInsta } - + struct State { var sections: [any Sectionable] = [] } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - + lazy var compositionalLayout: UICollectionViewCompositionalLayout = { UICollectionViewCompositionalLayout { [weak self] section, env in guard let self = self else { @@ -46,7 +46,7 @@ final class InstaCommentAddReactor: Reactor { return getSection()[section].getSection(section: section, env: env) } }() - + private let guideSection = InstaGuideSection(inputDataList: [ .init( imageList: [ @@ -103,12 +103,12 @@ final class InstaCommentAddReactor: Reactor { ] ) ]) - + // MARK: - init init() { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -118,7 +118,7 @@ final class InstaCommentAddReactor: Reactor { return Observable.just(.moveToInsta) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { @@ -126,21 +126,21 @@ final class InstaCommentAddReactor: Reactor { newState.sections = getSection() case .moveToInsta: openInstagram() - + } return newState } - + func getSection() -> [any Sectionable] { return [ guideSection ] } - + func openInstagram() { // Instagram 앱의 URL Scheme let instagramURL = URL(string: "instagram://app")! - + if UIApplication.shared.canOpenURL(instagramURL) { // Instagram 앱 열기 UIApplication.shared.open(instagramURL, options: [:], completionHandler: nil) diff --git a/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaCommentAddView.swift b/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaCommentAddView.swift index daf48dfe..9e7fc499 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaCommentAddView.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaCommentAddView.swift @@ -10,14 +10,14 @@ import UIKit import SnapKit final class InstaCommentAddView: UIView { - + // MARK: - Components let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() view.headerLabel.setLineHeightText(text: "코멘트 작성하기", font: .KorFont(style: .regular, size: 15)) return view }() - + let instaButton: UIButton = { let button = UIButton() button.setTitleColor(.white, for: .normal) @@ -26,7 +26,7 @@ final class InstaCommentAddView: UIView { let title = "Instagram 열기" let attributedTitle = NSMutableAttributedString(string: title) - + let englishFont = UIFont.EngFont(style: .medium, size: 15)! attributedTitle.addAttribute(.font, value: englishFont, range: (title as NSString).range(of: "Instagram")) @@ -37,25 +37,25 @@ final class InstaCommentAddView: UIView { return button }() - + private let instaImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_instagram") return view }() - + let contentCollectionView: UICollectionView = { let view = UICollectionView(frame: .zero, collectionViewLayout: .init()) view.backgroundColor = .g50 return view }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -63,26 +63,26 @@ final class InstaCommentAddView: UIView { // MARK: - SetUp private extension InstaCommentAddView { - + func setUpConstraints() { self.addSubview(headerView) headerView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() } - + self.addSubview(instaButton) instaButton.snp.makeConstraints { make in make.leading.trailing.bottom.equalToSuperview().inset(20) make.height.equalTo(52) } - + instaButton.addSubview(instaImageView) instaImageView.snp.makeConstraints { make in make.leading.equalToSuperview().inset(20) make.centerY.equalToSuperview() make.size.equalTo(22) } - + self.addSubview(contentCollectionView) contentCollectionView.snp.makeConstraints { make in make.top.equalTo(headerView.snp.bottom) diff --git a/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaGuideSection/InstaGuideChildSection/InstaGuideChildSection.swift b/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaGuideSection/InstaGuideChildSection/InstaGuideChildSection.swift index 77095f97..cb88aa60 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaGuideSection/InstaGuideChildSection/InstaGuideChildSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaGuideSection/InstaGuideChildSection/InstaGuideChildSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct InstaGuideChildSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = InstaGuideChildSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), diff --git a/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaGuideSection/InstaGuideChildSection/InstaGuideChildSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaGuideSection/InstaGuideChildSection/InstaGuideChildSectionCell.swift index 46596746..508c459f 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaGuideSection/InstaGuideChildSection/InstaGuideChildSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaGuideSection/InstaGuideChildSection/InstaGuideChildSectionCell.swift @@ -7,15 +7,15 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class InstaGuideChildSectionCell: UICollectionViewCell { - + // MARK: - Components let disposeBag = DisposeBag() - + private let indexTrailgView: UIView = { let view = UIView() view.backgroundColor = .g900 @@ -23,7 +23,7 @@ final class InstaGuideChildSectionCell: UICollectionViewCell { view.layer.cornerRadius = 4 return view }() - + private let indexLabel: UILabel = { let label = UILabel() label.font = .EngFont(style: .medium, size: 16) @@ -31,27 +31,27 @@ final class InstaGuideChildSectionCell: UICollectionViewCell { label.textAlignment = .center return label }() - + private let titleLabel: UILabel = { let label = UILabel() label.numberOfLines = 2 return label }() - + private let imageView: UIImageView = { let view = UIImageView() view.layer.cornerRadius = 8 view.clipsToBounds = true return view }() - + // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } @@ -67,12 +67,12 @@ private extension InstaGuideChildSectionCell { make.width.equalTo(40) make.height.equalTo(33) } - + indexTrailgView.addSubview(indexLabel) indexLabel.snp.makeConstraints { make in make.center.equalToSuperview() } - + contentView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.leading.trailing.equalToSuperview().inset(20) @@ -94,7 +94,7 @@ extension InstaGuideChildSectionCell: Inputable { var title: NSMutableAttributedString? var index: Int } - + func injection(with input: Input) { indexLabel.text = "#\(input.index + 1)" titleLabel.attributedText = input.title diff --git a/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaGuideSection/InstaGuideSection/InstaGuideSection.swift b/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaGuideSection/InstaGuideSection/InstaGuideSection.swift index 98801b83..13bfca92 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaGuideSection/InstaGuideSection/InstaGuideSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaGuideSection/InstaGuideSection/InstaGuideSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct InstaGuideSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = InstaGuideSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -35,8 +35,7 @@ struct InstaGuideSection: Sectionable { let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) // 섹션 생성 - let section = NSCollectionLayoutSection(group: group) - - return section + + return NSCollectionLayoutSection(group: group) } } diff --git a/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaGuideSection/InstaGuideSection/InstaGuideSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaGuideSection/InstaGuideSection/InstaGuideSectionCell.swift index aed527cb..d3c3371e 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaGuideSection/InstaGuideSection/InstaGuideSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaGuideSection/InstaGuideSection/InstaGuideSectionCell.swift @@ -7,24 +7,24 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class InstaGuideSectionCell: UICollectionViewCell { - + // MARK: - Components let disposeBag = DisposeBag() - + private var autoScrollTimer: Timer? - + private lazy var contentCollectionView: UICollectionView = { let view = UICollectionView(frame: .zero, collectionViewLayout: compositionalLayout) view.isScrollEnabled = false view.backgroundColor = .g50 return view }() - + var pageControl: UIPageControl = { let controller = UIPageControl() controller.currentPage = 0 @@ -36,17 +36,17 @@ final class InstaGuideSectionCell: UICollectionViewCell { controller.transform = CGAffineTransform(scaleX: 0.8, y: 0.8) return controller }() - + let stopButton: UIButton = { let button = UIButton() button.setImage(UIImage(named: "icon_banner_stopButton_gray"), for: .normal) return button }() - + private var isAutoBannerPlay: Bool = false - + private var imageSection = InstaGuideChildSection(inputDataList: []) - + lazy var compositionalLayout: UICollectionViewCompositionalLayout = { UICollectionViewCompositionalLayout { [weak self] section, env in guard let self = self else { @@ -60,25 +60,25 @@ final class InstaGuideSectionCell: UICollectionViewCell { return getSection()[section].getSection(section: section, env: env) } }() - + // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUp() setUpConstraints() bind() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() stopAutoScroll() } - + deinit { stopAutoScroll() } @@ -89,7 +89,7 @@ private extension InstaGuideSectionCell { func setUp() { contentCollectionView.delegate = self contentCollectionView.dataSource = self - + contentCollectionView.register( InstaGuideChildSectionCell.self, forCellWithReuseIdentifier: InstaGuideChildSectionCell.identifiers @@ -101,21 +101,21 @@ private extension InstaGuideSectionCell { } .disposed(by: disposeBag) } - + func setUpConstraints() { contentView.addSubview(contentCollectionView) contentCollectionView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() make.height.equalTo(504) } - + contentView.addSubview(pageControl) pageControl.snp.makeConstraints { make in make.top.equalTo(contentCollectionView.snp.bottom) make.centerX.equalToSuperview() make.bottom.equalToSuperview() } - + contentView.addSubview(stopButton) stopButton.snp.makeConstraints { make in make.size.equalTo(8) @@ -123,11 +123,11 @@ private extension InstaGuideSectionCell { make.leading.equalTo(pageControl.snp.trailing).offset(-36) } } - + func getSection() -> [any Sectionable] { return [imageSection] } - + func startAutoScroll(interval: TimeInterval = 3.0) { stopAutoScroll() // 기존 타이머를 중지 isAutoBannerPlay = true @@ -157,7 +157,7 @@ private extension InstaGuideSectionCell { contentCollectionView.scrollToItem(at: nextIndex, at: .centeredHorizontally, animated: true) pageControl.currentPage = nextIndex.item } - + func bind() { stopButton.rx.tap .withUnretained(self) @@ -177,7 +177,7 @@ extension InstaGuideSectionCell: Inputable { var imageList: [UIImage?] var title: [NSMutableAttributedString?] } - + func injection(with input: Input) { pageControl.numberOfPages = input.imageList.count let datas = zip(input.imageList, input.title).enumerated().map { $0 } @@ -189,19 +189,18 @@ extension InstaGuideSectionCell: Inputable { // MARK: - UICollectionViewDelegate, UICollectionViewDataSource extension InstaGuideSectionCell: UICollectionViewDelegate, UICollectionViewDataSource { - + func numberOfSections(in collectionView: UICollectionView) -> Int { return getSection().count } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return getSection()[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath ) -> UICollectionViewCell { - let cell = getSection()[indexPath.section].getCell(collectionView: collectionView, indexPath: indexPath) - return cell + return getSection()[indexPath.section].getCell(collectionView: collectionView, indexPath: indexPath) } } diff --git a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/NormalCommentAddController.swift b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/NormalCommentAddController.swift index b14cf4de..c2cf2ca4 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/NormalCommentAddController.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/NormalCommentAddController.swift @@ -7,23 +7,23 @@ import UIKit -import SnapKit -import RxCocoa -import RxSwift import ReactorKit +import RxCocoa import RxKeyboard +import RxSwift +import SnapKit final class NormalCommentAddController: BaseViewController, View { - + typealias Reactor = NormalCommentAddReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var keyBoardDisposeBag = DisposeBag() - + private var mainView = NormalCommentAddView() - + private var sections: [any Sectionable] = [] } @@ -33,7 +33,7 @@ extension NormalCommentAddController { super.viewDidLoad() setUp() } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tabBarController?.tabBar.isHidden = true @@ -79,12 +79,12 @@ private extension NormalCommentAddController { // MARK: - Methods extension NormalCommentAddController { func bind(reactor: Reactor) { - + rx.viewWillAppear .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.headerView.backButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -92,7 +92,7 @@ extension NormalCommentAddController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + // RxKeyboard로 키보드 높이 감지 RxKeyboard.instance.visibleHeight .skip(1) @@ -103,7 +103,7 @@ extension NormalCommentAddController { UIView.animate(withDuration: 0.3) { self.mainView.transform = .identity } - + } else { UIView.animate(withDuration: 0.3) { self.mainView.transform = .init(translationX: 0, y: -100) @@ -111,7 +111,7 @@ extension NormalCommentAddController { } }) .disposed(by: keyBoardDisposeBag) - + mainView.saveButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -120,7 +120,7 @@ extension NormalCommentAddController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -136,7 +136,7 @@ extension NormalCommentAddController { } owner.sections = state.sections if state.isReloadView { owner.mainView.contentCollectionView.reloadData() } - + } .disposed(by: disposeBag) } @@ -147,11 +147,11 @@ extension NormalCommentAddController: UICollectionViewDelegate, UICollectionView func numberOfSections(in collectionView: UICollectionView) -> Int { return sections.count } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sections[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath @@ -164,7 +164,7 @@ extension NormalCommentAddController: UICollectionViewDelegate, UICollectionView .bind(to: reactor.action) .disposed(by: cell.disposeBag) } - + if let cell = cell as? AddCommentSectionCell { cell.commentTextView.rx.didChange .withUnretained(cell) diff --git a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/NormalCommentAddReactor.swift b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/NormalCommentAddReactor.swift index 04bb9027..63b51884 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/NormalCommentAddReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/NormalCommentAddReactor.swift @@ -5,15 +5,15 @@ // Created by SeoJunYoung on 12/14/24. // -import UIKit import PhotosUI +import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class NormalCommentAddReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear @@ -24,7 +24,7 @@ final class NormalCommentAddReactor: Reactor { case inputComment(text: String?) case saveButtonTapped(controller: BaseViewController) } - + enum Mutation { case loadView case showImagePicker(controller: BaseViewController) @@ -32,24 +32,24 @@ final class NormalCommentAddReactor: Reactor { case setComment(text: String?) case save(controller: BaseViewController) } - + struct State { var sections: [any Sectionable] = [] var text: String? var isReloadView: Bool = true var isSaving: Bool = false } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() private var popUpID: Int64 private var popUpName: String - + private let commentAPIUseCase = CommentAPIUseCaseImpl(repository: CommentAPIRepository(provider: ProviderImpl())) private let imageService = PreSignedService() - + lazy var compositionalLayout: UICollectionViewCompositionalLayout = { UICollectionViewCompositionalLayout { [weak self] section, env in guard let self = self else { @@ -79,7 +79,7 @@ final class NormalCommentAddReactor: Reactor { self.popUpID = popUpID self.popUpName = popUpName } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -102,7 +102,7 @@ final class NormalCommentAddReactor: Reactor { return Observable.just(.save(controller: controller)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state newState.isReloadView = false @@ -154,7 +154,7 @@ final class NormalCommentAddReactor: Reactor { let images = imageSection.inputDataList.compactMap { $0.image }.enumerated().map { $0 } let uuid = UUID().uuidString let pathList = images.map { "PopUpComment/\(popUpName)/\(uuid)/\($0.offset).jpg" } - + imageService.tryUpload(datas: images.map { .init(filePath: "PopUpComment/\(popUpName)/\(uuid)/\($0.offset).jpg", image: $0.element)}) .subscribe(onSuccess: { [weak self] _ in guard let self = self else { return } @@ -170,11 +170,11 @@ final class NormalCommentAddReactor: Reactor { }) .disposed(by: disposeBag) } - + } return newState } - + func getSection() -> [any Sectionable] { return [ spacing25Section, @@ -199,19 +199,19 @@ final class NormalCommentAddReactor: Reactor { extension NormalCommentAddReactor: PHPickerViewControllerDelegate { func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { picker.dismiss(animated: true) - + // 이미지가 로드된 순서를 보장하기 위해 선택한 이미지 개수만큼의 nil 배열을 생성 var originImageList = [UIImage?](repeating: nil, count: results.count) let dispatchGroup = DispatchGroup() // 모든 이미지를 로드할 때까지 대기 - + // results에서 이미지를 비동기적으로 로드 for (index, result) in results.enumerated() { if result.itemProvider.canLoadObject(ofClass: UIImage.self) { dispatchGroup.enter() // 이미지 로드가 시작될 때 그룹에 등록 - + result.itemProvider.loadObject(ofClass: UIImage.self) { image, _ in defer { dispatchGroup.leave() } // 이미지 로드가 끝날 때 그룹에서 제거 - + if let image = image as? UIImage { originImageList[index] = image // 로드된 이미지를 해당 인덱스에 저장 } else { @@ -222,7 +222,7 @@ extension NormalCommentAddReactor: PHPickerViewControllerDelegate { Logger.log(message: "ItemProvider Can Not Load Object", category: .error) } } - + // 모든 이미지가 로드된 후에 한 번에 choiceImageList 업데이트 dispatchGroup.notify(queue: .main) { let filteredImages = originImageList.compactMap { $0 } diff --git a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentDescriptionSection/AddCommentDescriptionSection.swift b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentDescriptionSection/AddCommentDescriptionSection.swift index 3fb3c140..b7559602 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentDescriptionSection/AddCommentDescriptionSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentDescriptionSection/AddCommentDescriptionSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct AddCommentDescriptionSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = AddCommentDescriptionSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -36,7 +36,7 @@ struct AddCommentDescriptionSection: Sectionable { // 섹션 생성 let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 0, leading: 20, bottom: 0, trailing: 20) - + return section } } diff --git a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentDescriptionSection/AddCommentDescriptionSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentDescriptionSection/AddCommentDescriptionSectionCell.swift index e83fe204..910cf5f2 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentDescriptionSection/AddCommentDescriptionSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentDescriptionSection/AddCommentDescriptionSectionCell.swift @@ -7,15 +7,15 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class AddCommentDescriptionSectionCell: UICollectionViewCell { - + // MARK: - Components let disposeBag = DisposeBag() - + private let descriptionLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 13) label.textColor = .g600 @@ -23,12 +23,12 @@ final class AddCommentDescriptionSectionCell: UICollectionViewCell { return label }() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } @@ -48,7 +48,7 @@ extension AddCommentDescriptionSectionCell: Inputable { struct Input { var description: String? } - + func injection(with input: Input) { descriptionLabel.setLineHeightText(text: input.description, font: .KorFont(style: .regular, size: 13)) } diff --git a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentImageSection/AddCommentImageSection.swift b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentImageSection/AddCommentImageSection.swift index 09d0822d..f1df7177 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentImageSection/AddCommentImageSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentImageSection/AddCommentImageSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct AddCommentImageSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = AddCommentImageSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .absolute(80), diff --git a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentImageSection/AddCommentImageSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentImageSection/AddCommentImageSectionCell.swift index d9b4d329..dc954aa0 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentImageSection/AddCommentImageSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentImageSection/AddCommentImageSectionCell.swift @@ -7,20 +7,19 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class AddCommentImageSectionCell: UICollectionViewCell { - + // MARK: - Components var disposeBag = DisposeBag() - + private let imageView: UIImageView = { - let view = UIImageView() - return view + return UIImageView() }() - + let deleteButton: UIButton = { let button = UIButton() button.backgroundColor = .w100 @@ -28,24 +27,24 @@ final class AddCommentImageSectionCell: UICollectionViewCell { button.clipsToBounds = true return button }() - + private let xmarkImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_xmark") return view }() - + // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() @@ -58,18 +57,18 @@ private extension AddCommentImageSectionCell { contentView.layer.cornerRadius = 4 contentView.clipsToBounds = true contentView.layer.borderColor = UIColor.blu400.cgColor - + contentView.addSubview(imageView) imageView.snp.makeConstraints { make in make.edges.equalToSuperview() } - + contentView.addSubview(deleteButton) deleteButton.snp.makeConstraints { make in make.size.equalTo(16) make.top.trailing.equalToSuperview().inset(6) } - + deleteButton.addSubview(xmarkImageView) xmarkImageView.snp.makeConstraints { make in make.center.equalToSuperview() @@ -86,9 +85,9 @@ extension AddCommentImageSectionCell: Inputable { var imageURL: String? var imageID: Int64? } - + func injection(with input: Input) { - + if input.isFirstCell { imageView.image = UIImage(named: "icon_camera_blue") contentView.layer.borderWidth = 1 diff --git a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentSection/AddCommentSection.swift b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentSection/AddCommentSection.swift index 051d1370..9ef3468c 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentSection/AddCommentSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentSection/AddCommentSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct AddCommentSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = AddCommentSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), diff --git a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentSection/AddCommentSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentSection/AddCommentSectionCell.swift index 8bc041f7..b264381b 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentSection/AddCommentSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentSection/AddCommentSectionCell.swift @@ -7,16 +7,16 @@ import UIKit -import SnapKit -import RxSwift import RxCocoa +import RxSwift +import SnapKit final class AddCommentSectionCell: UICollectionViewCell { - + // MARK: - Components - + var disposeBag = DisposeBag() - + let commentTextView: UITextView = { let view = UITextView() view.textContainerInset = .zero @@ -24,42 +24,42 @@ final class AddCommentSectionCell: UICollectionViewCell { view.font = .KorFont(style: .medium, size: 14) return view }() - + let countLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 12) label.textColor = .g500 return label }() - + private let placeHolderLabel: PPLabel = { let label = PPLabel(style: .medium, fontSize: 14, text: "최소 10자 이상 입력해주세요") label.textColor = .g200 return label }() - + private let noticeLabel: PPLabel = { let label = PPLabel(style: .medium, fontSize: 12, text: "최대 500자까지 입력해주세요") label.textColor = .re500 label.isHidden = true return label }() - + private var isActiveComment: Bool = false - + private var commentState: BehaviorRelay = .init(value: .empty) - + // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() bind() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() @@ -70,7 +70,7 @@ final class AddCommentSectionCell: UICollectionViewCell { // MARK: - SetUp private extension AddCommentSectionCell { func bind() { - + commentTextView.rx.didBeginEditing .withUnretained(self) .subscribe { (owner, _) in @@ -78,7 +78,7 @@ private extension AddCommentSectionCell { owner.commentState.accept(owner.checkValidation(text: owner.commentTextView.text)) } .disposed(by: disposeBag) - + commentTextView.rx.didEndEditing .withUnretained(self) .subscribe { (owner, _) in @@ -86,7 +86,7 @@ private extension AddCommentSectionCell { owner.commentState.accept(owner.checkValidation(text: owner.commentTextView.text)) } .disposed(by: disposeBag) - + commentTextView.rx.didChange .debounce(.milliseconds(5), scheduler: MainScheduler.instance) .withUnretained(self) @@ -94,7 +94,7 @@ private extension AddCommentSectionCell { owner.commentState.accept(owner.checkValidation(text: owner.commentTextView.text)) } .disposed(by: disposeBag) - + commentState .withUnretained(self) .subscribe { (owner, state) in @@ -109,7 +109,7 @@ private extension AddCommentSectionCell { } .disposed(by: disposeBag) } - + func setUpConstraints() { contentView.layer.cornerRadius = 4 contentView.clipsToBounds = true @@ -136,13 +136,13 @@ private extension AddCommentSectionCell { make.bottom.equalToSuperview().inset(16) } } - + func checkValidation(text: String?) -> CommentState { guard let text = text else { return .empty } if text.isEmpty { return isActiveComment ? .emptyActive : .empty } - + switch text.count { case 1...9: return isActiveComment ? .shortLengthActive : .shortLength @@ -158,7 +158,7 @@ extension AddCommentSectionCell: Inputable { struct Input { var text: String? } - + func injection(with input: Input) { commentTextView.text = input.text commentState.accept(checkValidation(text: input.text)) @@ -174,7 +174,7 @@ enum CommentState { case longLengthActive case normal case normalActive - + var borderColor: UIColor? { switch self { case .shortLength, .longLength, .longLengthActive: @@ -183,7 +183,7 @@ enum CommentState { return .g100 } } - + var countLabelColor: UIColor? { switch self { case .shortLength, .longLength, .longLengthActive: @@ -192,7 +192,7 @@ enum CommentState { return .g500 } } - + var textColor: UIColor? { switch self { case .shortLength, .longLength, .longLengthActive: @@ -201,7 +201,7 @@ enum CommentState { return .g1000 } } - + var description: String? { switch self { case .longLength, .longLengthActive: @@ -212,7 +212,7 @@ enum CommentState { return nil } } - + var isHiddenNoticeLabel: Bool { switch self { case .longLength, .longLengthActive, .shortLength: @@ -221,7 +221,7 @@ enum CommentState { return true } } - + var isHiddenPlaceHolder: Bool { switch self { case .empty, .emptyActive: diff --git a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentTitleSection/AddCommentTitleSection.swift b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentTitleSection/AddCommentTitleSection.swift index 3a50e804..d8e99796 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentTitleSection/AddCommentTitleSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentTitleSection/AddCommentTitleSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct AddCommentTitleSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = AddCommentTitleSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -36,7 +36,7 @@ struct AddCommentTitleSection: Sectionable { // 섹션 생성 let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 0, leading: 20, bottom: 0, trailing: 20) - + return section } } diff --git a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentTitleSection/AddCommentTitleSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentTitleSection/AddCommentTitleSectionCell.swift index 8c7ad96d..c71411bf 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentTitleSection/AddCommentTitleSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentTitleSection/AddCommentTitleSectionCell.swift @@ -7,26 +7,25 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class AddCommentTitleSectionCell: UICollectionViewCell { - + // MARK: - Components let disposeBag = DisposeBag() - + private let titleLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 16) - return label + return PPLabel(style: .bold, fontSize: 16) }() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } @@ -46,7 +45,7 @@ extension AddCommentTitleSectionCell: Inputable { struct Input { var title: String? } - + func injection(with input: Input) { titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .bold, size: 16)) } diff --git a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/NormalCommentAddView.swift b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/NormalCommentAddView.swift index c5030a02..1913249a 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/NormalCommentAddView.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/NormalCommentAddView.swift @@ -10,33 +10,32 @@ import UIKit import SnapKit final class NormalCommentAddView: UIView { - + // MARK: - Components - + let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() view.headerLabel.setLineHeightText(text: "코멘트 작성하기", font: .KorFont(style: .regular, size: 15)) return view }() - + let contentCollectionView: UICollectionView = { let view = UICollectionView(frame: .zero, collectionViewLayout: .init()) view.backgroundColor = .g50 view.isScrollEnabled = false return view }() - + let saveButton: PPButton = { - let button = PPButton(style: .primary, text: "저장", disabledText: "저장") - return button + return PPButton(style: .primary, text: "저장", disabledText: "저장") }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -44,19 +43,19 @@ final class NormalCommentAddView: UIView { // MARK: - SetUp private extension NormalCommentAddView { - + func setUpConstraints() { self.addSubview(headerView) headerView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() } - + self.addSubview(saveButton) saveButton.snp.makeConstraints { make in make.leading.trailing.bottom.equalToSuperview().inset(20) make.height.equalTo(52) } - + self.addSubview(contentCollectionView) contentCollectionView.snp.makeConstraints { make in make.top.equalTo(headerView.snp.bottom) diff --git a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentEdit/NormalCommentEditController.swift b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentEdit/NormalCommentEditController.swift index 84732cc8..e451afc4 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentEdit/NormalCommentEditController.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentEdit/NormalCommentEditController.swift @@ -7,23 +7,23 @@ import UIKit -import SnapKit -import RxCocoa -import RxSwift import ReactorKit +import RxCocoa import RxKeyboard +import RxSwift +import SnapKit final class NormalCommentEditController: BaseViewController, View { - + typealias Reactor = NormalCommentEditReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var keyBoardDisposeBag = DisposeBag() - + private var mainView = NormalCommentEditView() - + private var sections: [any Sectionable] = [] } @@ -33,7 +33,7 @@ extension NormalCommentEditController { super.viewDidLoad() setUp() } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tabBarController?.tabBar.isHidden = true @@ -79,12 +79,12 @@ private extension NormalCommentEditController { // MARK: - Methods extension NormalCommentEditController { func bind(reactor: Reactor) { - + rx.viewWillAppear .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.headerView.backButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -92,7 +92,7 @@ extension NormalCommentEditController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + // RxKeyboard로 키보드 높이 감지 RxKeyboard.instance.visibleHeight .skip(1) @@ -103,7 +103,7 @@ extension NormalCommentEditController { UIView.animate(withDuration: 0.3) { self.mainView.transform = .identity } - + } else { UIView.animate(withDuration: 0.3) { self.mainView.transform = .init(translationX: 0, y: -100) @@ -111,7 +111,7 @@ extension NormalCommentEditController { } }) .disposed(by: keyBoardDisposeBag) - + mainView.saveButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -120,7 +120,7 @@ extension NormalCommentEditController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -136,17 +136,17 @@ extension NormalCommentEditController { } owner.sections = state.sections if state.isReloadView { owner.mainView.contentCollectionView.reloadData() } - + } .disposed(by: disposeBag) - + reactor.state .take(1) .withUnretained(self) .subscribe { (owner, state) in owner.sections = state.sections if state.isReloadView { owner.mainView.contentCollectionView.reloadData() } - + } .disposed(by: disposeBag) } @@ -157,11 +157,11 @@ extension NormalCommentEditController: UICollectionViewDelegate, UICollectionVie func numberOfSections(in collectionView: UICollectionView) -> Int { return sections.count } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sections[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath @@ -174,7 +174,7 @@ extension NormalCommentEditController: UICollectionViewDelegate, UICollectionVie .bind(to: reactor.action) .disposed(by: cell.disposeBag) } - + if let cell = cell as? AddCommentSectionCell { cell.commentTextView.rx.didChange .withUnretained(cell) diff --git a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentEdit/NormalCommentEditReactor.swift b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentEdit/NormalCommentEditReactor.swift index 437a7046..dee4c3cc 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentEdit/NormalCommentEditReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentEdit/NormalCommentEditReactor.swift @@ -5,15 +5,15 @@ // Created by SeoJunYoung on 2/1/25. // -import UIKit import PhotosUI +import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class NormalCommentEditReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear @@ -24,7 +24,7 @@ final class NormalCommentEditReactor: Reactor { case inputComment(text: String?) case saveButtonTapped(controller: BaseViewController) } - + enum Mutation { case loadView case showImagePicker(controller: BaseViewController) @@ -32,25 +32,25 @@ final class NormalCommentEditReactor: Reactor { case setComment(text: String?) case save(controller: BaseViewController) } - + struct State { var sections: [any Sectionable] = [] var text: String? var isReloadView: Bool = true var isSaving: Bool = false } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() private var popUpID: Int64 private var popUpName: String private var originComment: DetailCommentSection.CellType.Input - + private let commentAPIUseCase = CommentAPIUseCaseImpl(repository: CommentAPIRepository(provider: ProviderImpl())) private let imageService = PreSignedService() - + lazy var compositionalLayout: UICollectionViewCompositionalLayout = { UICollectionViewCompositionalLayout { [weak self] section, env in guard let self = self else { @@ -74,7 +74,7 @@ final class NormalCommentEditReactor: Reactor { private let spacing5Section = SpacingSection(inputDataList: [.init(spacing: 5)]) private let spacing16Section = SpacingSection(inputDataList: [.init(spacing: 16)]) private let spacing32Section = SpacingSection(inputDataList: [.init(spacing: 32)]) - + // MARK: - init init(popUpID: Int64, popUpName: String, comment: DetailCommentSection.CellType.Input) { self.initialState = State(text: comment.comment) @@ -86,7 +86,7 @@ final class NormalCommentEditReactor: Reactor { .init(image: nil, isFirstCell: false, isEditCase: true, imageURL: url, imageID: id) })) } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -108,7 +108,7 @@ final class NormalCommentEditReactor: Reactor { return Observable.just(.save(controller: controller)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state newState.isReloadView = false @@ -147,26 +147,26 @@ final class NormalCommentEditReactor: Reactor { commentSection.inputDataList[0].text = text case .save(let controller): newState.isSaving = true - + let addImages = imageSection.inputDataList.compactMap { $0.image }.enumerated().map { $0 } let uuid = UUID().uuidString let pathList = addImages.map { "PopUpComment/\(popUpName)/\(uuid)/\($0.offset).jpg" } - + let keepImages = imageSection.inputDataList.compactMap { $0.imageURL } - + let originImages = zip(originComment.imageList, originComment.imageIDList) var deleteImages: [(String?, Int64)] = [] - + for (imageURL, imageID) in originImages { if !keepImages.contains(imageURL!) { deleteImages.append((imageURL, imageID)) } } - + var convertAddImages: [PutCommentImageDataRequestDTO] = addImages.map { .init(imageId: nil, imageUrl: pathList[$0.offset], actionType: "ADD")} var convertKeepImages: [PutCommentImageDataRequestDTO] = keepImages.map { .init(imageId: nil, imageUrl: $0, actionType: "KEEP")} var convertDeleteImages: [PutCommentImageDataRequestDTO] = deleteImages.map { .init(imageId: $0.1, imageUrl: $0.0, actionType: "DELETE")} - + if !addImages.isEmpty { imageService.tryUpload(datas: addImages.map { .init(filePath: pathList[$0.offset], image: $0.element)}) .subscribe { [weak self] _ in @@ -208,7 +208,7 @@ final class NormalCommentEditReactor: Reactor { } return newState } - + func getSection() -> [any Sectionable] { return [ spacing25Section, @@ -233,19 +233,19 @@ final class NormalCommentEditReactor: Reactor { extension NormalCommentEditReactor: PHPickerViewControllerDelegate { func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { picker.dismiss(animated: true) - + // 이미지가 로드된 순서를 보장하기 위해 선택한 이미지 개수만큼의 nil 배열을 생성 var originImageList = [UIImage?](repeating: nil, count: results.count) let dispatchGroup = DispatchGroup() // 모든 이미지를 로드할 때까지 대기 - + // results에서 이미지를 비동기적으로 로드 for (index, result) in results.enumerated() { if result.itemProvider.canLoadObject(ofClass: UIImage.self) { dispatchGroup.enter() // 이미지 로드가 시작될 때 그룹에 등록 - + result.itemProvider.loadObject(ofClass: UIImage.self) { image, _ in defer { dispatchGroup.leave() } // 이미지 로드가 끝날 때 그룹에서 제거 - + if let image = image as? UIImage { originImageList[index] = image // 로드된 이미지를 해당 인덱스에 저장 } else { @@ -256,7 +256,7 @@ extension NormalCommentEditReactor: PHPickerViewControllerDelegate { Logger.log(message: "ItemProvider Can Not Load Object", category: .error) } } - + // 모든 이미지가 로드된 후에 한 번에 choiceImageList 업데이트 dispatchGroup.notify(queue: .main) { let filteredImages = originImageList.compactMap { $0 } diff --git a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentEdit/NormalCommentEditView.swift b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentEdit/NormalCommentEditView.swift index 544e47aa..b3d9d1b2 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentEdit/NormalCommentEditView.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentEdit/NormalCommentEditView.swift @@ -10,33 +10,32 @@ import UIKit import SnapKit final class NormalCommentEditView: UIView { - + // MARK: - Components - + let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() view.headerLabel.setLineHeightText(text: "코멘트 수정하기", font: .KorFont(style: .regular, size: 15)) return view }() - + let contentCollectionView: UICollectionView = { let view = UICollectionView(frame: .zero, collectionViewLayout: .init()) view.backgroundColor = .g50 view.isScrollEnabled = false return view }() - + let saveButton: PPButton = { - let button = PPButton(style: .primary, text: "저장", disabledText: "저장") - return button + return PPButton(style: .primary, text: "저장", disabledText: "저장") }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -44,19 +43,19 @@ final class NormalCommentEditView: UIView { // MARK: - SetUp private extension NormalCommentEditView { - + func setUpConstraints() { self.addSubview(headerView) headerView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() } - + self.addSubview(saveButton) saveButton.snp.makeConstraints { make in make.leading.trailing.bottom.equalToSuperview().inset(20) make.height.equalTo(52) } - + self.addSubview(contentCollectionView) contentCollectionView.snp.makeConstraints { make in make.top.equalTo(headerView.snp.bottom) diff --git a/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/OtherUserCommentController.swift b/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/OtherUserCommentController.swift index 2665a516..cbe375f7 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/OtherUserCommentController.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/OtherUserCommentController.swift @@ -7,22 +7,22 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit final class OtherUserCommentController: BaseViewController, View { - + typealias Reactor = OtherUserCommentReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = OtherUserCommentView() - + private var sections: [any Sectionable] = [] - + private let cellTapped: PublishSubject = .init() } @@ -31,9 +31,9 @@ extension OtherUserCommentController { override func viewDidLoad() { super.viewDidLoad() setUp() - + } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tabBarController?.tabBar.isHidden = true @@ -48,13 +48,13 @@ private extension OtherUserCommentController { mainView.snp.makeConstraints { make in make.edges.equalTo(view.safeAreaLayoutGuide) } - + if let layout = reactor?.compositionalLayout { mainView.contentCollectionView.collectionViewLayout = layout } mainView.contentCollectionView.delegate = self mainView.contentCollectionView.dataSource = self - + mainView.contentCollectionView.register( CommentListTitleSectionCell.self, forCellWithReuseIdentifier: CommentListTitleSectionCell.identifiers @@ -62,7 +62,7 @@ private extension OtherUserCommentController { mainView.contentCollectionView.register( SpacingSectionCell.self, forCellWithReuseIdentifier: SpacingSectionCell.identifiers - ) + ) mainView.contentCollectionView.register( MyCommentedPopUpGridSectionCell.self, forCellWithReuseIdentifier: MyCommentedPopUpGridSectionCell.identifiers @@ -77,7 +77,7 @@ extension OtherUserCommentController { .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.headerView.backButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -85,7 +85,7 @@ extension OtherUserCommentController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + cellTapped .withUnretained(self) .map { (owner, row) in @@ -93,7 +93,7 @@ extension OtherUserCommentController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -109,19 +109,18 @@ extension OtherUserCommentController: UICollectionViewDelegate, UICollectionView func numberOfSections(in collectionView: UICollectionView) -> Int { return sections.count } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sections[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath ) -> UICollectionViewCell { - let cell = sections[indexPath.section].getCell(collectionView: collectionView, indexPath: indexPath) - return cell + return sections[indexPath.section].getCell(collectionView: collectionView, indexPath: indexPath) } - + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if indexPath.section == 3 { cellTapped.onNext(indexPath.row) } } diff --git a/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/OtherUserCommentReactor.swift b/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/OtherUserCommentReactor.swift index af4281b6..bc2de562 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/OtherUserCommentReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/OtherUserCommentReactor.swift @@ -8,38 +8,38 @@ import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class OtherUserCommentReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear case backButtonTapped(controller: BaseViewController) case cellTapped(controller: BaseViewController, row: Int) } - + enum Mutation { case moveToRecentScene(controller: BaseViewController) case loadView case skip case moveToDetailScene(controller: BaseViewController, row: Int) } - + struct State { var sections: [any Sectionable] = [] var isReloadView: Bool = false } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - + private let commenterID: String? private let userAPIUseCase = UserAPIUseCaseImpl(repository: UserAPIRepositoryImpl(provider: ProviderImpl())) - + lazy var compositionalLayout: UICollectionViewCompositionalLayout = { UICollectionViewCompositionalLayout { [weak self] section, env in guard let self = self else { @@ -56,13 +56,13 @@ final class OtherUserCommentReactor: Reactor { private let spacing16Section = SpacingSection(inputDataList: [.init(spacing: 16)]) private var countTitleSection = CommentListTitleSection(inputDataList: []) private var popUpSection = MyCommentedPopUpGridSection(inputDataList: []) - + // MARK: - init init(commenterID: String?) { self.initialState = State() self.commenterID = commenterID } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -88,7 +88,7 @@ final class OtherUserCommentReactor: Reactor { return Observable.just(.moveToDetailScene(controller: controller, row: row)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state newState.isReloadView = false @@ -108,7 +108,7 @@ final class OtherUserCommentReactor: Reactor { } return newState } - + func getSection() -> [any Sectionable] { return [ spacing16Section, diff --git a/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/View/OtherUserCommentSection/OtherUserCommentSection.swift b/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/View/OtherUserCommentSection/OtherUserCommentSection.swift index 68cacc55..42954f36 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/View/OtherUserCommentSection/OtherUserCommentSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/View/OtherUserCommentSection/OtherUserCommentSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct OtherUserCommentSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = OtherUserCommentSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .absolute((UIScreen.main.bounds.width - 40 - 8) / 2), diff --git a/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/View/OtherUserCommentSection/OtherUserCommentSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/View/OtherUserCommentSection/OtherUserCommentSectionCell.swift index 2640bfa9..b086e771 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/View/OtherUserCommentSection/OtherUserCommentSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/View/OtherUserCommentSection/OtherUserCommentSectionCell.swift @@ -7,56 +7,55 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class OtherUserCommentSectionCell: UICollectionViewCell { - + // MARK: - Components private let imageView: UIImageView = { - let view = UIImageView() - return view + return UIImageView() }() - + private let titleLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 11) label.textColor = .blu500 return label }() - + private let contentLabel: PPLabel = { let label = PPLabel(style: .medium, fontSize: 11) label.lineBreakMode = . byTruncatingTail label.numberOfLines = 2 return label }() - + private let dateLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 11) label.textColor = .g400 return label }() - + private let likeImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_like_clear") return view }() - + private let likeCountLabel: PPLabel = { let label = PPLabel(style: .medium, fontSize: 11) label.textColor = .w100 return label }() - + let disposeBag = DisposeBag() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } @@ -77,31 +76,30 @@ private extension OtherUserCommentSectionCell { make.top.leading.trailing.equalToSuperview() make.height.equalTo(163.5) } - + contentView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.top.equalTo(imageView.snp.bottom).offset(16) make.leading.trailing.equalToSuperview().inset(12) } - + contentView.addSubview(contentLabel) contentLabel.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(8) make.leading.trailing.equalToSuperview().inset(12) } - + contentView.addSubview(dateLabel) dateLabel.snp.makeConstraints { make in make.leading.trailing.equalToSuperview().inset(12) make.bottom.equalToSuperview().inset(16) } - imageView.addSubview(likeCountLabel) likeCountLabel.snp.makeConstraints { make in make.trailing.bottom.equalToSuperview().inset(12) } - + imageView.addSubview(likeImageView) likeImageView.snp.makeConstraints { make in make.size.equalTo(18) @@ -120,7 +118,7 @@ extension OtherUserCommentSectionCell: Inputable { var date: String? var popUpID: Int64 } - + func injection(with input: Input) { imageView.setPPImage(path: input.imagePath) titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .bold, size: 11)) diff --git a/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/View/OtherUserCommentView.swift b/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/View/OtherUserCommentView.swift index e6d99298..89715fd4 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/View/OtherUserCommentView.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/View/OtherUserCommentView.swift @@ -10,26 +10,26 @@ import UIKit import SnapKit final class OtherUserCommentView: UIView { - + // MARK: - Components let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() view.headerLabel.setLineHeightText(text: "코멘트 작성 팝업", font: .KorFont(style: .regular, size: 15)) return view }() - + let contentCollectionView: UICollectionView = { let view = UICollectionView(frame: .zero, collectionViewLayout: .init()) view.backgroundColor = .g50 return view }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -37,13 +37,13 @@ final class OtherUserCommentView: UIView { // MARK: - SetUp private extension OtherUserCommentView { - + func setUpConstraints() { self.addSubview(headerView) headerView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() } - + self.addSubview(contentCollectionView) contentCollectionView.snp.makeConstraints { make in make.top.equalTo(headerView.snp.bottom) diff --git a/Poppool/Poppool/Presentation/Scene/Detail/DetailController.swift b/Poppool/Poppool/Presentation/Scene/Detail/DetailController.swift index 75cfd69c..d6f3535e 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/DetailController.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/DetailController.swift @@ -7,10 +7,10 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit final class DetailController: BaseViewController, View { @@ -52,7 +52,6 @@ extension DetailController { tabBarController?.tabBar.isHidden = false } - } // MARK: - SetUp @@ -266,7 +265,7 @@ extension DetailController: UICollectionViewDelegate, UICollectionViewDataSource .bind(to: reactor.action) .disposed(by: cell.disposeBag) } - + if let cell = cell as? DetailEmptyCommetSectionCell { cell.commentButton.rx.tap .withUnretained(self) diff --git a/Poppool/Poppool/Presentation/Scene/Detail/DetailReactor.swift b/Poppool/Poppool/Presentation/Scene/Detail/DetailReactor.swift index 61e5c3d8..74097657 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/DetailReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/DetailReactor.swift @@ -7,13 +7,13 @@ import UIKit +import LinkPresentation import ReactorKit -import RxSwift import RxCocoa -import LinkPresentation +import RxSwift final class DetailReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear @@ -31,7 +31,7 @@ final class DetailReactor: Reactor { case backButtonTapped(controller: BaseViewController) case loginButtonTapped(controller: BaseViewController) } - + enum Mutation { case loadView case moveToCommentTypeSelectedScene(controller: BaseViewController) @@ -46,24 +46,24 @@ final class DetailReactor: Reactor { case moveToLoginScene(controller: BaseViewController) case moveToImageDetailScene(controller: BaseViewController, cellRow: Int, ImageRow: Int) } - + private var commentButtonIsEnable: Bool = false - + struct State { var sections: [any Sectionable] = [] var barkGroundImagePath: String? var commentButtonIsEnable: Bool = false } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() private let popUpID: Int64 private var popUpName: String? private var isLogin: Bool = false private var isFirstRequest: Bool = true - + private var imageService = PreSignedService() private let popUpAPIUseCase = PopUpAPIUseCaseImpl(repository: PopUpAPIRepositoryImpl(provider: ProviderImpl())) private let userAPIUseCase = UserAPIUseCaseImpl(repository: UserAPIRepositoryImpl(provider: ProviderImpl())) @@ -81,7 +81,7 @@ final class DetailReactor: Reactor { return getSection()[section].getSection(section: section, env: env) } }() - + private var imageBannerSection = ImageBannerSection(inputDataList: []) private var titleSection = DetailTitleSection(inputDataList: []) private var contentSection = DetailContentSection(inputDataList: []) @@ -91,8 +91,7 @@ final class DetailReactor: Reactor { private var commentEmptySection = DetailEmptyCommetSection(inputDataList: [.init()]) private var similarTitleSecion = SearchTitleSection(inputDataList: [.init(title: "지금 보고있는 팝업과 비슷한 팝업")]) private var similarSection = DetailSimilarSection(inputDataList: []) - - + private var spacing70Section = SpacingSection(inputDataList: [.init(spacing: 70)]) private var spacing40Section = SpacingSection(inputDataList: [.init(spacing: 40)]) private var spacing36Section = SpacingSection(inputDataList: [.init(spacing: 36)]) @@ -106,7 +105,7 @@ final class DetailReactor: Reactor { self.popUpID = popUpID self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -140,7 +139,7 @@ final class DetailReactor: Reactor { return Observable.just(.moveToImageDetailScene(controller: controller, cellRow: cellRow, ImageRow: ImageRow)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { @@ -176,9 +175,6 @@ final class DetailReactor: Reactor { mapGuideController.modalTransitionStyle = .coverVertical controller.present(mapGuideController, animated: true) - - - case .moveToCommentTotalScene(let controller): if isLogin { let nextController = CommentListController() @@ -225,7 +221,7 @@ final class DetailReactor: Reactor { } return newState } - + func getSection() -> [any Sectionable] { if similarSection.inputDataList.isEmpty { if commentSection.inputDataList.isEmpty { @@ -308,9 +304,9 @@ final class DetailReactor: Reactor { ] } } - + } - + func setContent() -> Observable { return popUpAPIUseCase.getPopUpDetail(commentType: "NORMAL", popUpStoredId: popUpID, isViewCount: isFirstRequest) .withUnretained(self) @@ -325,7 +321,7 @@ final class DetailReactor: Reactor { // titleSection owner.titleSection.inputDataList = [.init(title: response.name, isBookMark: response.bookmarkYn, isLogin: response.loginYn)] owner.popUpName = response.name - + // contentSection owner.contentSection.inputDataList = [.init(content: response.desc)] owner.infoSection.inputDataList = [.init( @@ -362,7 +358,7 @@ final class DetailReactor: Reactor { return .loadView } } - + func bookMark() -> Observable { if let isBookMark = titleSection.inputDataList.first?.isBookMark { titleSection.inputDataList[0].isBookMark.toggle() @@ -378,32 +374,32 @@ final class DetailReactor: Reactor { return Observable.just(.loadView) } } - + func showSharedBoard(controller: BaseViewController) { let storeName = titleSection.inputDataList.first?.title ?? "" let imagePath = KeyPath.popPoolS3BaseURL + (imageBannerSection.inputDataList.first?.imagePaths.first ?? "") - + // URL 인코딩 후 생성 guard let encodedPath = imagePath.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let url = URL(string: encodedPath) else { Logger.log(message: "URL 생성 실패", category: .error) return } - + // 🔹 비동기적으로 이미지 다운로드 - URLSession.shared.dataTask(with: url) { data, response, error in + URLSession.shared.dataTask(with: url) { data, _, error in if let error = error { Logger.log(message: "다운로드 실패", category: .error) return } - + guard let data = data, let image = UIImage(data: data) else { Logger.log(message: "이미지 변환 실패", category: .error) return } - + Logger.log(message: "이미지 다운로드 성공", category: .info) - + let sharedText = "[팝풀] \(storeName) 팝업 어때요?\n지금 바로 팝풀에서 확인해보세요!" // UI 업데이트는 메인 스레드에서 실행 DispatchQueue.main.async { @@ -414,10 +410,10 @@ final class DetailReactor: Reactor { ) controller.present(activityViewController, animated: true, completion: nil) } - + }.resume() } - + func commentLike(indexPath: IndexPath) -> Observable { let isLike = commentSection.inputDataList[indexPath.row].isLike let commentID = commentSection.inputDataList[indexPath.row].commentID @@ -432,7 +428,7 @@ final class DetailReactor: Reactor { .andThen(Observable.just(.loadView)) } } - + func showOtherUserCommentMenu(controller: BaseViewController, indexPath: IndexPath, comment: DetailCommentSection.CellType.Input) { let nextController = CommentUserInfoController() nextController.reactor = CommentUserInfoReactor(nickName: comment.nickName) @@ -462,7 +458,7 @@ final class DetailReactor: Reactor { case .block: ToastMaker.createToast(message: "\(comment.nickName ?? "")을 차단했어요") self.userAPIUseCase.postUserBlock(blockedUserId: comment.creator) - .subscribe(onDisposed: { + .subscribe(onDisposed: { blockController.dismiss(animated: true) }) .disposed(by: self.disposeBag) @@ -480,13 +476,13 @@ final class DetailReactor: Reactor { }) .disposed(by: disposeBag) } - + func showMyCommentMenu(controller: BaseViewController, indexPath: IndexPath, comment: DetailCommentSection.CellType.Input) { let nextController = CommentMyMenuController() nextController.reactor = CommentMyMenuReactor(nickName: comment.nickName) imageService = PreSignedService() controller.presentPanModal(nextController) - + nextController.reactor?.state .withUnretained(nextController) .subscribe(onNext: { [weak self] (owner, state) in @@ -499,7 +495,7 @@ final class DetailReactor: Reactor { ToastMaker.createToast(message: "작성한 코멘트를 삭제했어요") }) .disposed(by: self.disposeBag) - + let commentList = comment.imageList.compactMap { $0 } self.imageService.tryDelete(targetPaths: .init(objectKeyList: commentList)) .subscribe { @@ -527,7 +523,7 @@ final class DetailReactor: Reactor { class ItemDetailSource: NSObject { let name: String let image: UIImage - + init(name: String, image: UIImage) { self.name = name self.image = image @@ -535,14 +531,14 @@ class ItemDetailSource: NSObject { } extension ItemDetailSource: UIActivityItemSource { - + func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any { image } func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? { image } - + func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? { let metaData = LPLinkMetadata() metaData.title = name diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentSection/DetailCommentImageCell.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentSection/DetailCommentImageCell.swift index b94dfefb..514b9bf9 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentSection/DetailCommentImageCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentSection/DetailCommentImageCell.swift @@ -7,11 +7,11 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class DetailCommentImageCell: UICollectionViewCell { - + // MARK: - Components private let imageView: UIImageView = { let view = UIImageView() @@ -19,14 +19,14 @@ final class DetailCommentImageCell: UICollectionViewCell { view.clipsToBounds = true return view }() - + // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } @@ -46,7 +46,7 @@ extension DetailCommentImageCell: Inputable { struct Input { var imagePath: String? } - + func injection(with input: Input) { imageView.setPPImage(path: input.imagePath) } diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentSection/DetailCommentProfileView.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentSection/DetailCommentProfileView.swift index fdbada94..44ec4a67 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentSection/DetailCommentProfileView.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentSection/DetailCommentProfileView.swift @@ -10,7 +10,7 @@ import UIKit import SnapKit final class DetailCommentProfileView: UIStackView { - + // MARK: - Components let profileImageView: UIImageView = { let view = UIImageView() @@ -19,47 +19,46 @@ final class DetailCommentProfileView: UIStackView { view.contentMode = .scaleAspectFill return view }() - + let contentStackView: UIStackView = { let view = UIStackView() view.axis = .vertical view.spacing = 3 return view }() - + let nickNameLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 13) - return label + return PPLabel(style: .bold, fontSize: 13) }() - + let dateLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 12) label.textColor = .g400 return label }() - + let button: UIButton = { let button = UIButton() button.setImage(UIImage(named: "icon_comment_button"), for: .normal) return button }() - + let spacingView: UIView = UIView() // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + } // MARK: - SetUp private extension DetailCommentProfileView { - + func setUpConstraints() { self.alignment = .center self.spacing = 12 diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentSection/DetailCommentSection.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentSection/DetailCommentSection.swift index ada32f6b..4f08bab2 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentSection/DetailCommentSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentSection/DetailCommentSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct DetailCommentSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = DetailCommentSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -37,7 +37,7 @@ struct DetailCommentSection: Sectionable { // 섹션 생성 let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0) - + return section } } diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentSection/DetailCommentSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentSection/DetailCommentSectionCell.swift index 6df1feb5..b7fceb6c 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentSection/DetailCommentSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentSection/DetailCommentSectionCell.swift @@ -7,11 +7,11 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class DetailCommentSectionCell: UICollectionViewCell { - + // MARK: - Components private let contentStackView: UIStackView = { let view = UIStackView() @@ -20,12 +20,11 @@ final class DetailCommentSectionCell: UICollectionViewCell { view.spacing = 16 return view }() - + let profileView: DetailCommentProfileView = { - let view = DetailCommentProfileView() - return view + return DetailCommentProfileView() }() - + let imageCollectionView: UICollectionView = { let layout = UICollectionViewFlowLayout() layout.itemSize = .init(width: 80, height: 80) @@ -36,53 +35,51 @@ final class DetailCommentSectionCell: UICollectionViewCell { view.showsHorizontalScrollIndicator = false return view }() - + private let contentLabel: PPLabel = { let label = PPLabel(style: .medium, fontSize: 13) label.numberOfLines = 3 return label }() - + let totalViewButton: UIButton = { - let button = UIButton() - return button + return UIButton() }() - + private let buttonTitleLabel: PPLabel = { let label = PPLabel(style: .medium, fontSize: 13, text: "코멘트 전체보기") label.textColor = .g600 return label }() - + private let buttonImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_right_black") return view }() - + let likeButton: UIButton = { - let button = UIButton() - return button + return UIButton() }() - + private let likeButtonTitleLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 13, text: "도움돼요") label.textColor = .g400 return label }() - + private let likeButtonImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_like_gray") return view }() - + private let borderView: UIView = { let view = UIView() view.backgroundColor = .g100 return view }() - + private let blurBackGroundView: UIView = { let view = UIView() view.backgroundColor = .white @@ -95,9 +92,9 @@ final class DetailCommentSectionCell: UICollectionViewCell { view.isUserInteractionEnabled = false return view }() - + var disposeBag = DisposeBag() - + private let loginStackView: UIStackView = { let view = UIStackView() view.axis = .vertical @@ -105,13 +102,13 @@ final class DetailCommentSectionCell: UICollectionViewCell { view.spacing = 16 return view }() - + private let loginNoticelabel: UILabel = { let label = UILabel() label.numberOfLines = 2 return label }() - + let loginButton: UIButton = { let button = UIButton() button.setTitle("로그인하고 후기보기", for: .normal) @@ -121,19 +118,19 @@ final class DetailCommentSectionCell: UICollectionViewCell { button.backgroundColor = .blu500 return button }() - + private var imagePathList: [String?] = [] // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() @@ -143,12 +140,11 @@ final class DetailCommentSectionCell: UICollectionViewCell { // MARK: - SetUp private extension DetailCommentSectionCell { func setUpConstraints() { - + imageCollectionView.delegate = self imageCollectionView.dataSource = self imageCollectionView.register(DetailCommentImageCell.self, forCellWithReuseIdentifier: DetailCommentImageCell.identifiers) - - + totalViewButton.addSubview(buttonTitleLabel) buttonTitleLabel.snp.makeConstraints { make in make.leading.equalToSuperview() @@ -163,7 +159,7 @@ private extension DetailCommentSectionCell { make.leading.equalTo(buttonTitleLabel.snp.trailing) make.centerY.equalToSuperview() } - + profileView.snp.makeConstraints { make in make.width.equalTo(contentView.bounds.width - 40).priority(.high) } @@ -178,32 +174,32 @@ private extension DetailCommentSectionCell { contentStackView.addArrangedSubview(imageCollectionView) contentStackView.addArrangedSubview(contentLabel) contentStackView.addArrangedSubview(totalViewButton) - + contentView.addSubview(contentStackView) contentStackView.snp.makeConstraints { make in make.top.equalToSuperview().inset(20) make.leading.trailing.equalToSuperview() } - + likeButton.addSubview(likeButtonTitleLabel) likeButtonTitleLabel.snp.makeConstraints { make in make.height.equalTo(20).priority(.high) make.top.bottom.trailing.equalToSuperview() } - + likeButton.addSubview(likeButtonImageView) likeButtonImageView.snp.makeConstraints { make in make.size.equalTo(20) make.leading.centerY.equalToSuperview() make.trailing.equalTo(likeButtonTitleLabel.snp.leading) } - + contentView.addSubview(likeButton) likeButton.snp.makeConstraints { make in make.top.equalTo(contentStackView.snp.bottom).offset(16) make.trailing.equalToSuperview().inset(20) } - + contentView.addSubview(borderView) borderView.snp.makeConstraints { make in make.top.equalTo(likeButton.snp.bottom).offset(16) @@ -211,7 +207,7 @@ private extension DetailCommentSectionCell { make.height.equalTo(1).priority(.high) make.bottom.equalToSuperview() } - + contentView.addSubview(blurBackGroundView) blurBackGroundView.snp.makeConstraints { make in make.edges.equalToSuperview() @@ -220,7 +216,7 @@ private extension DetailCommentSectionCell { blurView.snp.makeConstraints { make in make.edges.equalToSuperview() } - + contentView.addSubview(loginStackView) loginStackView.snp.makeConstraints { make in make.centerY.equalToSuperview() @@ -281,7 +277,7 @@ extension DetailCommentSectionCell: Inputable { var isMyComment: Bool var isLastCell: Bool = false } - + func injection(with input: Input) { let comment = input.comment ?? "" @@ -308,7 +304,7 @@ extension DetailCommentSectionCell: Inputable { imageCollectionView.isHidden = false imagePathList = input.imageList } - + imageCollectionView.reloadData() if input.isLogin { blurBackGroundView.isHidden = true @@ -327,7 +323,7 @@ extension DetailCommentSectionCell: Inputable { let reviewRange = (fullText as NSString).range(of: "생생한 후기") let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineHeightMultiple = 1.4 - + // 기본 스타일 (폰트, 색상 등) let normalAttributes: [NSAttributedString.Key: Any] = [ .font: UIFont.KorFont(style: .regular, size: 14)!, @@ -353,12 +349,11 @@ extension DetailCommentSectionCell: Inputable { attributedString.addAttributes(popupStoreAttributes, range: popupStoreRange) attributedString.addAttributes(reviewAttributes, range: reviewRange) - loginNoticelabel.attributedText = attributedString loginNoticelabel.textAlignment = .center loginNoticelabel.lineBreakStrategy = .hangulWordPriority loginNoticelabel.numberOfLines = 0 - + borderView.isHidden = input.isLastCell } } @@ -368,7 +363,7 @@ extension DetailCommentSectionCell: UICollectionViewDelegate, UICollectionViewDa func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return imagePathList.count } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentTitleSection/DetailCommentTitleSection.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentTitleSection/DetailCommentTitleSection.swift index d1a25558..d2979800 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentTitleSection/DetailCommentTitleSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentTitleSection/DetailCommentTitleSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct DetailCommentTitleSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = DetailCommentTitleSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -37,7 +37,7 @@ struct DetailCommentTitleSection: Sectionable { // 섹션 생성 let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 0, leading: 20, bottom: 0, trailing: 20) - + return section } } diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentTitleSection/DetailCommentTitleSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentTitleSection/DetailCommentTitleSectionCell.swift index a285a972..26024838 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentTitleSection/DetailCommentTitleSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentTitleSection/DetailCommentTitleSectionCell.swift @@ -7,23 +7,22 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class DetailCommentTitleSectionCell: UICollectionViewCell { - + // MARK: - Components private let titleLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 16, text: "이 팝업에 대한 코멘트") - return label + return PPLabel(style: .bold, fontSize: 16, text: "이 팝업에 대한 코멘트") }() - + private let countLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 13) label.textColor = .g600 return label }() - + let totalViewButton: UIButton = { let button = UIButton() let attributedTitle = NSAttributedString( @@ -38,16 +37,16 @@ final class DetailCommentTitleSectionCell: UICollectionViewCell { }() var disposeBag = DisposeBag() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() @@ -61,13 +60,13 @@ private extension DetailCommentTitleSectionCell { titleLabel.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() } - + contentView.addSubview(countLabel) countLabel.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(5) make.leading.bottom.equalToSuperview() } - + contentView.addSubview(totalViewButton) totalViewButton.snp.makeConstraints { make in make.centerY.equalToSuperview() @@ -81,7 +80,7 @@ extension DetailCommentTitleSectionCell: Inputable { var commentCount: Int64 var buttonIsHidden: Bool = false } - + func injection(with input: Input) { countLabel.setLineHeightText(text: "총 \(input.commentCount)개", font: .KorFont(style: .regular, size: 13)) totalViewButton.isHidden = input.buttonIsHidden diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailContentSection/DetailContentSection.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailContentSection/DetailContentSection.swift index 24a4e9d4..8d9c926c 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailContentSection/DetailContentSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailContentSection/DetailContentSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct DetailContentSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = DetailContentSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -37,7 +37,7 @@ struct DetailContentSection: Sectionable { // 섹션 생성 let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 0, leading: 20, bottom: 0, trailing: 20) - + return section } } diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailContentSection/DetailContentSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailContentSection/DetailContentSectionCell.swift index aa7c0f73..e319d2ab 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailContentSection/DetailContentSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailContentSection/DetailContentSectionCell.swift @@ -7,11 +7,11 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class DetailContentSectionCell: UICollectionViewCell { - + // MARK: - Components private let contentStackView: UIStackView = { @@ -26,38 +26,37 @@ final class DetailContentSectionCell: UICollectionViewCell { label.numberOfLines = 3 return label }() - + let dropDownButton: UIButton = { - let button = UIButton() - return button + return UIButton() }() - + let buttonTitleLabel: PPLabel = { let label = PPLabel(style: .medium, fontSize: 13, text: "더보기") label.textColor = .g600 return label }() - + let buttonImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_dropdown_bottom_gray") return view }() - + var isOpen: Bool = false - + var disposeBag = DisposeBag() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() @@ -97,7 +96,7 @@ extension DetailContentSectionCell: Inputable { struct Input { var content: String? } - + func injection(with input: Input) { let text = input.content ?? "" contentLabel.setLineHeightText(text: text, font: .KorFont(style: .regular, size: 13)) diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailEmptyCommetSection/DetailEmptyCommetSection.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailEmptyCommetSection/DetailEmptyCommetSection.swift index 45ae3913..3cda98e4 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailEmptyCommetSection/DetailEmptyCommetSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailEmptyCommetSection/DetailEmptyCommetSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct DetailEmptyCommetSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = DetailEmptyCommetSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -37,7 +37,7 @@ struct DetailEmptyCommetSection: Sectionable { // 섹션 생성 let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 20, leading: 20, bottom: 20, trailing: 20) - + return section } } diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailEmptyCommetSection/DetailEmptyCommetSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailEmptyCommetSection/DetailEmptyCommetSectionCell.swift index a5a1e031..aa27bcbc 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailEmptyCommetSection/DetailEmptyCommetSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailEmptyCommetSection/DetailEmptyCommetSectionCell.swift @@ -7,21 +7,21 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class DetailEmptyCommetSectionCell: UICollectionViewCell { - + // MARK: - Components private let noticeLabel: PPLabel = { - let label = PPLabel(style: .medium, fontSize: 14, text: "아직 작성된 코멘트가 없어요\n가장 먼저 후기를 남겨주시겠어요?" , lineHeight: 1.5) + let label = PPLabel(style: .medium, fontSize: 14, text: "아직 작성된 코멘트가 없어요\n가장 먼저 후기를 남겨주시겠어요?", lineHeight: 1.5) label.textAlignment = .center label.numberOfLines = 2 label.textColor = .g400 return label }() - + let commentButton: UIButton = { let button = UIButton() let attributedTitle = NSAttributedString( @@ -34,19 +34,19 @@ final class DetailEmptyCommetSectionCell: UICollectionViewCell { button.setAttributedTitle(attributedTitle, for: .normal) return button }() - + var disposeBag = DisposeBag() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() @@ -61,7 +61,7 @@ private extension DetailEmptyCommetSectionCell { make.top.equalToSuperview().inset(80) make.centerX.equalToSuperview() } - + contentView.addSubview(commentButton) commentButton.snp.makeConstraints { make in make.top.equalTo(noticeLabel.snp.bottom).offset(26) @@ -74,7 +74,7 @@ extension DetailEmptyCommetSectionCell: Inputable { struct Input { } - + func injection(with input: Input) { } diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailInfoSection/DetailInfoSection.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailInfoSection/DetailInfoSection.swift index d21f060a..f869c617 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailInfoSection/DetailInfoSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailInfoSection/DetailInfoSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct DetailInfoSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = DetailInfoSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -37,7 +37,7 @@ struct DetailInfoSection: Sectionable { // 섹션 생성 let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 0, leading: 20, bottom: 0, trailing: 20) - + return section } } diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailInfoSection/DetailInfoSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailInfoSection/DetailInfoSectionCell.swift index e509e3c4..e7029550 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailInfoSection/DetailInfoSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailInfoSection/DetailInfoSectionCell.swift @@ -7,79 +7,73 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class DetailInfoSectionCell: UICollectionViewCell { - + // MARK: - Components - + private let dateTitleLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 13, text: "기간") - return label + return PPLabel(style: .bold, fontSize: 13, text: "기간") }() - + private let dateLabel: PPLabel = { - let label = PPLabel(style: .regular, fontSize: 14) - return label + return PPLabel(style: .regular, fontSize: 14) }() - + private let timeTitleLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 13, text: "시간") - return label + return PPLabel(style: .bold, fontSize: 13, text: "시간") }() - + private let timeLabel: PPLabel = { - let label = PPLabel(style: .regular, fontSize: 14) - return label + return PPLabel(style: .regular, fontSize: 14) }() - + private let addressTitleLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 13, text: "주소") - return label + return PPLabel(style: .bold, fontSize: 13, text: "주소") }() - + private let addressLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 14) label.numberOfLines = 2 return label }() - + let copyButton: UIButton = { let button = UIButton() button.setImage(UIImage(named: "icon_copy_gray"), for: .normal) return button }() - + let mapButton: UIButton = { - let button = UIButton() - return button + return UIButton() }() - + let mapButtonTitle: PPLabel = { let label = PPLabel(style: .regular, fontSize: 13, text: "찾아가는 길") label.textColor = .blu500 return label }() - + let mapButtonImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_arrow_right_blue") return view }() - + var disposeBag = DisposeBag() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() @@ -94,36 +88,36 @@ private extension DetailInfoSectionCell { make.top.equalToSuperview() make.leading.equalToSuperview() } - + contentView.addSubview(dateLabel) dateLabel.snp.makeConstraints { make in make.centerY.equalTo(dateTitleLabel) make.leading.equalTo(dateTitleLabel.snp.trailing).offset(12) } - + contentView.addSubview(timeTitleLabel) timeTitleLabel.snp.makeConstraints { make in make.top.equalTo(dateTitleLabel.snp.bottom).offset(11) make.leading.equalToSuperview() } - + contentView.addSubview(timeLabel) timeLabel.snp.makeConstraints { make in make.centerY.equalTo(timeTitleLabel) make.leading.equalTo(timeTitleLabel.snp.trailing).offset(12) } - + contentView.addSubview(addressTitleLabel) addressTitleLabel.snp.makeConstraints { make in make.top.equalTo(timeTitleLabel.snp.bottom).offset(11) make.leading.equalToSuperview() } - + mapButton.addSubview(mapButtonTitle) mapButtonTitle.snp.makeConstraints { make in make.leading.centerY.equalToSuperview() } - + mapButton.addSubview(mapButtonImageView) mapButtonImageView.snp.makeConstraints { make in make.leading.equalTo(mapButtonTitle.snp.trailing) @@ -131,13 +125,13 @@ private extension DetailInfoSectionCell { make.trailing.equalToSuperview() make.centerY.equalToSuperview().offset(0.5) } - + contentView.addSubview(mapButton) mapButton.snp.makeConstraints { make in make.centerY.equalTo(addressTitleLabel) make.trailing.equalToSuperview() } - + contentView.addSubview(addressLabel) addressLabel.snp.makeConstraints { make in make.top.equalTo(addressTitleLabel).offset(-2) @@ -145,14 +139,13 @@ private extension DetailInfoSectionCell { make.width.lessThanOrEqualTo(188) make.bottom.equalToSuperview() } - + contentView.addSubview(copyButton) copyButton.snp.makeConstraints { make in make.size.equalTo(16) make.top.equalTo(addressLabel).offset(1) make.leading.equalTo(addressLabel.snp.trailing).offset(2) } - } } @@ -165,16 +158,16 @@ extension DetailInfoSectionCell: Inputable { var endTime: String? var address: String? } - + func injection(with input: Input) { let startDate = input.startDate ?? "?" let endDate = input.endDate ?? "?" let startTime = input.startTime ?? "?" let endTime = input.endTime ?? "?" - + dateLabel.setLineHeightText(text: startDate + " ~ " + endDate, font: .KorFont(style: .regular, size: 14)) timeLabel.setLineHeightText(text: startTime + " ~ " + endTime, font: .KorFont(style: .regular, size: 14)) addressLabel.setLineHeightText(text: input.address, font: .KorFont(style: .regular, size: 13)) } } - + diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailSimilarSection/DetailSimilarSection.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailSimilarSection/DetailSimilarSection.swift index 664f296e..c62203f8 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailSimilarSection/DetailSimilarSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailSimilarSection/DetailSimilarSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct DetailSimilarSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = DetailSimilarSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .absolute(163), @@ -39,7 +39,7 @@ struct DetailSimilarSection: Sectionable { section.contentInsets = .init(top: 0, leading: 20, bottom: 0, trailing: 20) section.orthogonalScrollingBehavior = .continuous section.interGroupSpacing = 12 - + return section } } diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailSimilarSection/DetailSimilarSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailSimilarSection/DetailSimilarSectionCell.swift index d746d9ae..e4b66e5f 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailSimilarSection/DetailSimilarSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailSimilarSection/DetailSimilarSectionCell.swift @@ -7,11 +7,11 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class DetailSimilarSectionCell: UICollectionViewCell { - + // MARK: - Components private let imageView: UIImageView = { let view = UIImageView() @@ -19,28 +19,26 @@ final class DetailSimilarSectionCell: UICollectionViewCell { view.clipsToBounds = true return view }() - + private let dateLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 11) label.font = .EngFont(style: .regular, size: 11) label.textColor = .g400 return label }() - + private let titleLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 12) - return label + return PPLabel(style: .bold, fontSize: 12) }() - + let bookMarkButton: UIButton = { - let button = UIButton() - return button + return UIButton() }() - + private let trailingView: UIView = UIView() - + var disposeBag = DisposeBag() - + // MARK: - init override init(frame: CGRect) { super.init(frame: frame) @@ -52,12 +50,11 @@ final class DetailSimilarSectionCell: UICollectionViewCell { addHolesToCell() } - + required init?(coder: NSCoder) { fatalError() } - - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() @@ -65,27 +62,27 @@ final class DetailSimilarSectionCell: UICollectionViewCell { private func addHolesToCell() { // 전체 영역 경로 let fullPath = UIBezierPath(roundedRect: contentView.bounds, cornerRadius: 4) - + // 왼쪽 아래와 오른쪽 아래 구멍을 뚫을 위치 설정 (이미지뷰의 frame 위치 고려) let leftHoleCenter = CGPoint(x: contentView.bounds.minX, y: 190) let rightHoleCenter = CGPoint(x: contentView.bounds.maxX, y: 190) - + // 구멍을 만드는 경로 생성 (반지름 6) let leftHolePath = UIBezierPath(arcCenter: leftHoleCenter, radius: 6, startAngle: -.pi / 2, endAngle: .pi / 2, clockwise: true) let rightHolePath = UIBezierPath(arcCenter: rightHoleCenter, radius: 6, startAngle: .pi / 2, endAngle: -.pi / 2, clockwise: true) - + // 구멍 경로를 전체 경로에서 빼기 fullPath.append(leftHolePath) fullPath.append(rightHolePath) fullPath.usesEvenOddFillRule = true - + // 기존에 구멍을 뚫을 경로를 추가하는 레이어 let holeLayer = CAShapeLayer() holeLayer.path = fullPath.cgPath holeLayer.fillRule = .evenOdd holeLayer.fillColor = UIColor.black.cgColor trailingView.layer.mask = holeLayer - + // 그림자 Layer let shadowLayer = CAShapeLayer() shadowLayer.path = fullPath.cgPath @@ -106,25 +103,25 @@ private extension DetailSimilarSectionCell { trailingView.snp.makeConstraints { make in make.edges.equalToSuperview() } - + trailingView.addSubview(imageView) imageView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() make.height.equalTo(190) } - + trailingView.addSubview(dateLabel) dateLabel.snp.makeConstraints { make in make.top.equalTo(imageView.snp.bottom).offset(10) make.leading.trailing.equalToSuperview().inset(12) } - + trailingView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.leading.trailing.equalToSuperview().inset(12) make.top.equalTo(dateLabel.snp.bottom).offset(5.5) } - + trailingView.addSubview(bookMarkButton) bookMarkButton.snp.makeConstraints { make in make.size.equalTo(20) @@ -141,7 +138,7 @@ extension DetailSimilarSectionCell: Inputable { var id: Int64 var isBookMark: Bool? } - + func injection(with input: Input) { let date = input.date ?? "" imageView.setPPImage(path: input.imagePath) diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailTitleSection/DetailTitleSection.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailTitleSection/DetailTitleSection.swift index f99f45b0..8b2b7ee2 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailTitleSection/DetailTitleSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailTitleSection/DetailTitleSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct DetailTitleSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = DetailTitleSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -37,7 +37,7 @@ struct DetailTitleSection: Sectionable { // 섹션 생성 let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 0, leading: 20, bottom: 0, trailing: 20) - + return section } } diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailTitleSection/DetailTitleSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailTitleSection/DetailTitleSectionCell.swift index b2a86cce..2f7f7767 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailTitleSection/DetailTitleSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailTitleSection/DetailTitleSectionCell.swift @@ -7,41 +7,40 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class DetailTitleSectionCell: UICollectionViewCell { - + // MARK: - Components private let titleLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 18) label.numberOfLines = 2 return label }() - + let bookMarkButton: UIButton = { - let button = UIButton() - return button + return UIButton() }() - + let sharedButton: UIButton = { let button = UIButton() button.setImage(UIImage(named: "icon_shared"), for: .normal) return button }() - + var disposeBag = DisposeBag() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() @@ -56,14 +55,14 @@ private extension DetailTitleSectionCell { make.size.equalTo(28) make.top.trailing.equalToSuperview() } - + contentView.addSubview(bookMarkButton) bookMarkButton.snp.makeConstraints { make in make.size.equalTo(28) make.top.equalToSuperview() make.trailing.equalTo(sharedButton.snp.leading).offset(-4) } - + contentView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.top.leading.equalToSuperview() @@ -79,7 +78,7 @@ extension DetailTitleSectionCell: Inputable { var isBookMark: Bool var isLogin: Bool } - + func injection(with input: Input) { let bookMarkImage = input.isBookMark ? UIImage(named: "icon_bookmark_blue") : UIImage(named: "icon_bookmark_gray") bookMarkButton.setImage(bookMarkImage, for: .normal) diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailView.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailView.swift index a79b9333..0e286f01 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailView.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailView.swift @@ -10,19 +10,18 @@ import UIKit import SnapKit final class DetailView: UIView { - + // MARK: - Components let contentCollectionView: UICollectionView = { let view = UICollectionView(frame: .zero, collectionViewLayout: .init()) view.contentInsetAdjustmentBehavior = .never return view }() - + let commentPostButton: PPButton = { - let button = PPButton(style: .primary, text: "코멘트 작성하기", disabledText: "코멘트 작성 완료") - return button + return PPButton(style: .primary, text: "코멘트 작성하기", disabledText: "코멘트 작성 완료") }() - + private let buttonTopView: UIView = { var view = UIView() view.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 32) @@ -39,13 +38,13 @@ final class DetailView: UIView { view.layer.addSublayer(gradientLayer) return view }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -53,21 +52,20 @@ final class DetailView: UIView { // MARK: - SetUp private extension DetailView { - + func setUpConstraints() { self.addSubview(commentPostButton) commentPostButton.snp.makeConstraints { make in make.bottom.leading.trailing.equalToSuperview().inset(20) make.height.equalTo(52) } - self.addSubview(contentCollectionView) contentCollectionView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() make.bottom.equalTo(commentPostButton.snp.top) } - + self.addSubview(buttonTopView) buttonTopView.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() diff --git a/Poppool/Poppool/Presentation/Scene/Home/List/HomeListController.swift b/Poppool/Poppool/Presentation/Scene/Home/List/HomeListController.swift index ce91a1f1..8fe382bf 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/List/HomeListController.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/List/HomeListController.swift @@ -7,24 +7,24 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit final class HomeListController: BaseViewController, View { - + typealias Reactor = HomeListReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = HomeListView() - + private var sections: [any Sectionable] = [] - + private let pageChange: PublishSubject = .init() - + private let cellTapped: PublishSubject = .init() } @@ -33,9 +33,9 @@ extension HomeListController { override func viewDidLoad() { super.viewDidLoad() setUp() - + } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tabBarController?.tabBar.isHidden = true @@ -49,13 +49,13 @@ private extension HomeListController { mainView.snp.makeConstraints { make in make.edges.equalTo(view.safeAreaLayoutGuide) } - + if let layout = reactor?.compositionalLayout { mainView.contentCollectionView.collectionViewLayout = layout } mainView.contentCollectionView.delegate = self mainView.contentCollectionView.dataSource = self - + mainView.contentCollectionView.register( HomeCardSectionCell.self, forCellWithReuseIdentifier: HomeCardSectionCell.identifiers @@ -74,7 +74,7 @@ extension HomeListController { .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.headerView.backButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -82,13 +82,13 @@ extension HomeListController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + pageChange .throttle(.milliseconds(1000), scheduler: MainScheduler.asyncInstance) .map { Reactor.Action.changePage } .bind(to: reactor.action) .disposed(by: disposeBag) - + cellTapped .withUnretained(self) .map { (owner, row) in @@ -96,7 +96,7 @@ extension HomeListController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -113,11 +113,11 @@ extension HomeListController: UICollectionViewDelegate, UICollectionViewDataSour func numberOfSections(in collectionView: UICollectionView) -> Int { return sections.count } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sections[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath @@ -130,10 +130,10 @@ extension HomeListController: UICollectionViewDelegate, UICollectionViewDataSour .bind(to: reactor.action) .disposed(by: cell.disposeBag) } - + return cell } - + func scrollViewDidScroll(_ scrollView: UIScrollView) { let contentHeight = scrollView.contentSize.height let scrollViewHeight = scrollView.frame.size.height @@ -142,7 +142,7 @@ extension HomeListController: UICollectionViewDelegate, UICollectionViewDataSour pageChange.onNext(()) } } - + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if indexPath.section == 1 { cellTapped.onNext(indexPath.row)} } diff --git a/Poppool/Poppool/Presentation/Scene/Home/List/HomeListReactor.swift b/Poppool/Poppool/Presentation/Scene/Home/List/HomeListReactor.swift index 56480168..23903e0b 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/List/HomeListReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/List/HomeListReactor.swift @@ -8,11 +8,11 @@ import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class HomeListReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear @@ -21,7 +21,7 @@ final class HomeListReactor: Reactor { case changePage case cellTapped(controller: BaseViewController, row: Int) } - + enum Mutation { case moveToRecentScene(controller: BaseViewController) case loadView @@ -30,28 +30,28 @@ final class HomeListReactor: Reactor { case appendData case moveToDetailScene(controller: BaseViewController, row: Int) } - + struct State { var popUpType: HomePopUpType var sections: [any Sectionable] = [] var isReloadView: Bool = false } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() var popUpType: HomePopUpType - + private let homeAPIUseCase = HomeAPIUseCaseImpl() private let userDefaultService = UserDefaultService() private let userAPIUseCase = UserAPIUseCaseImpl(repository: UserAPIRepositoryImpl(provider: ProviderImpl())) - + private var isLoading: Bool = false private var totalPage: Int32 = 0 private var currentPage: Int32 = 0 private var size: Int32 = 10 - + lazy var compositionalLayout: UICollectionViewCompositionalLayout = { UICollectionViewCompositionalLayout { [weak self] section, env in guard let self = self else { @@ -68,13 +68,13 @@ final class HomeListReactor: Reactor { private let spacing24Section = SpacingSection(inputDataList: [.init(spacing: 24)]) private let spacing64Section = SpacingSection(inputDataList: [.init(spacing: 64)]) private var cardSections = HomeCardGridSection(inputDataList: []) - + // MARK: - init init(popUpType: HomePopUpType) { self.initialState = State(popUpType: popUpType) self.popUpType = popUpType } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -122,7 +122,7 @@ final class HomeListReactor: Reactor { return Observable.just(.moveToDetailScene(controller: controller, row: row)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state newState.isReloadView = false @@ -149,11 +149,11 @@ final class HomeListReactor: Reactor { } return newState } - + func getSection() -> [any Sectionable] { - return [spacing24Section,cardSections,spacing64Section] + return [spacing24Section, cardSections, spacing64Section] } - + func setSection(response: GetHomeInfoResponse) { let isLogin = response.loginYn switch popUpType { @@ -206,7 +206,7 @@ final class HomeListReactor: Reactor { totalPage = response.popularPopUpStoreTotalPages } } - + func appendSectionData(response: GetHomeInfoResponse) { let isLogin = response.loginYn switch popUpType { diff --git a/Poppool/Poppool/Presentation/Scene/Home/List/HomePopUpType.swift b/Poppool/Poppool/Presentation/Scene/Home/List/HomePopUpType.swift index d257675a..2a2a6071 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/List/HomePopUpType.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/List/HomePopUpType.swift @@ -11,7 +11,7 @@ enum HomePopUpType { case curation case new case popular - + var title: String { switch self { case .curation: @@ -22,7 +22,7 @@ enum HomePopUpType { return "인기 팝업 전체보기" } } - + var path: String { switch self { case .curation: diff --git a/Poppool/Poppool/Presentation/Scene/Home/List/View/HomeCardGridSection.swift b/Poppool/Poppool/Presentation/Scene/Home/List/View/HomeCardGridSection.swift index 185069a7..92406df4 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/List/View/HomeCardGridSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/List/View/HomeCardGridSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct HomeCardGridSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = HomeCardSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .absolute((UIScreen.main.bounds.width - 40 - 16) / 2), diff --git a/Poppool/Poppool/Presentation/Scene/Home/List/View/HomeListView.swift b/Poppool/Poppool/Presentation/Scene/Home/List/View/HomeListView.swift index 7db0f713..e4769564 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/List/View/HomeListView.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/List/View/HomeListView.swift @@ -10,24 +10,22 @@ import UIKit import SnapKit final class HomeListView: UIView { - + // MARK: - Components let headerView: PPReturnHeaderView = { - let view = PPReturnHeaderView() - return view + return PPReturnHeaderView() }() - + let contentCollectionView: UICollectionView = { - let view = UICollectionView(frame: .zero, collectionViewLayout: .init()) - return view + return UICollectionView(frame: .zero, collectionViewLayout: .init()) }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -35,13 +33,13 @@ final class HomeListView: UIView { // MARK: - SetUp private extension HomeListView { - + func setUpConstraints() { self.addSubview(headerView) headerView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() } - + self.addSubview(contentCollectionView) contentCollectionView.snp.makeConstraints { make in make.top.equalTo(headerView.snp.bottom) diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/HomeController.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/HomeController.swift index 5a507a7d..f2a883d6 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/HomeController.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/HomeController.swift @@ -7,29 +7,28 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit final class HomeController: BaseViewController, View { - + typealias Reactor = HomeReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = HomeView() - + let homeHeaderView: HomeHeaderView = { - let view = HomeHeaderView() - return view + return HomeHeaderView() }() - + private let headerBackgroundView: UIView = UIView() let backGroundblurEffect = UIBlurEffect(style: .regular) lazy var backGroundblurView = UIVisualEffectView(effect: backGroundblurEffect) - + private var sections: [any Sectionable] = [] } @@ -39,7 +38,7 @@ extension HomeController { super.viewDidLoad() setUp() } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tabBarController?.tabBar.isHidden = false @@ -55,58 +54,57 @@ private extension HomeController { } mainView.contentCollectionView.delegate = self mainView.contentCollectionView.dataSource = self - + mainView.contentCollectionView.register( ImageBannerSectionCell.self, forCellWithReuseIdentifier: ImageBannerSectionCell.identifiers ) - + mainView.contentCollectionView.register( SpacingSectionCell.self, forCellWithReuseIdentifier: SpacingSectionCell.identifiers ) - + mainView.contentCollectionView.register( HomeTitleSectionCell.self, forCellWithReuseIdentifier: HomeTitleSectionCell.identifiers ) - + mainView.contentCollectionView.register( HomeCardSectionCell.self, forCellWithReuseIdentifier: HomeCardSectionCell.identifiers ) - + mainView.contentCollectionView.register( HomePopularCardSectionCell.self, forCellWithReuseIdentifier: HomePopularCardSectionCell.identifiers ) - + view.addSubview(mainView) mainView.snp.makeConstraints { make in make.top.equalToSuperview() make.leading.trailing.bottom.equalTo(view.safeAreaLayoutGuide) } - + view.addSubview(homeHeaderView) homeHeaderView.snp.makeConstraints { make in make.top.equalTo(view.safeAreaLayoutGuide).inset(7) make.leading.trailing.equalToSuperview() } - + headerBackgroundView.addSubview(backGroundblurView) backGroundblurView.snp.makeConstraints { make in make.edges.equalToSuperview() } backGroundblurView.isUserInteractionEnabled = false backGroundblurView.isHidden = true - + view.addSubview(headerBackgroundView) headerBackgroundView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() make.bottom.equalTo(homeHeaderView.snp.bottom).offset(7) } - view.bringSubviewToFront(homeHeaderView) } } @@ -118,7 +116,7 @@ extension HomeController { .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + homeHeaderView.searchBarButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -126,7 +124,7 @@ extension HomeController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -142,18 +140,18 @@ extension HomeController: UICollectionViewDelegate, UICollectionViewDataSource { func numberOfSections(in collectionView: UICollectionView) -> Int { return sections.count } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sections[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath ) -> UICollectionViewCell { let cell = sections[indexPath.section].getCell(collectionView: collectionView, indexPath: indexPath) guard let reactor = reactor else { return cell } - + if let cell = cell as? ImageBannerSectionCell { cell.bannerTapped .withUnretained(self) @@ -162,7 +160,7 @@ extension HomeController: UICollectionViewDelegate, UICollectionViewDataSource { }) .bind(to: reactor.action) .disposed(by: cell.disposeBag) - + cell.imageSection.currentPage .distinctUntilChanged() .withUnretained(self) @@ -182,17 +180,17 @@ extension HomeController: UICollectionViewDelegate, UICollectionViewDataSource { .bind(to: reactor.action) .disposed(by: cell.disposeBag) } - + if let cell = cell as? HomeCardSectionCell { cell.bookmarkButton.rx.tap .map { Reactor.Action.bookMarkButtonTapped(indexPath: indexPath)} .bind(to: reactor.action) .disposed(by: cell.disposeBag) } - + return cell } - + func scrollViewDidScroll(_ scrollView: UIScrollView) { if scrollView.contentOffset.y <= (307 - headerBackgroundView.frame.maxY) { backGroundblurView.isHidden = true @@ -201,7 +199,7 @@ extension HomeController: UICollectionViewDelegate, UICollectionViewDataSource { backGroundblurView.isHidden = false } } - + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { reactor?.action.onNext(.collectionViewCellTapped(controller: self, indexPath: indexPath)) } diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/HomeReactor.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/HomeReactor.swift index d7bdfb8a..4759ee2c 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/HomeReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/HomeReactor.swift @@ -8,11 +8,11 @@ import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class HomeReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear @@ -24,7 +24,7 @@ final class HomeReactor: Reactor { case bannerCellTapped(controller: BaseViewController, row: Int) case changeIndicatorColor(controller: BaseViewController, row: Int) } - + enum Mutation { case loadView case setHedaerState(isDarkMode: Bool) @@ -33,23 +33,23 @@ final class HomeReactor: Reactor { case moveToSearchScene(controller: BaseViewController) case skip } - + struct State { var sections: [any Sectionable] = [] var headerIsDarkMode: Bool = true var isReloadView: Bool = false } - + // MARK: - properties - + var initialState: State - + var disposeBag = DisposeBag() - + private let homeApiUseCase = HomeAPIUseCaseImpl() private let userAPIUseCase = UserAPIUseCaseImpl(repository: UserAPIRepositoryImpl(provider: ProviderImpl())) private let userDefaultService = UserDefaultService() - + lazy var compositionalLayout: UICollectionViewCompositionalLayout = { UICollectionViewCompositionalLayout { [weak self] section, env in guard let self = self else { @@ -80,12 +80,12 @@ final class HomeReactor: Reactor { private var spaceGray40Section = SpacingSection(inputDataList: [.init(spacing: 40, backgroundColor: .g700)]) private var spaceGray28Section = SpacingSection(inputDataList: [.init(spacing: 28, backgroundColor: .g700)]) private var spaceGray24Section = SpacingSection(inputDataList: [.init(spacing: 24, backgroundColor: .g700)]) - + // MARK: - init init() { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -125,7 +125,7 @@ final class HomeReactor: Reactor { return Observable.just(.moveToDetailScene(controller: controller, indexPath: IndexPath(row: row, section: 0))) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state newState.isReloadView = false @@ -162,9 +162,9 @@ final class HomeReactor: Reactor { } return newState } - + func getSection() -> [any Sectionable] { - + if isLoign { return [ loginImageBannerSection, @@ -193,7 +193,7 @@ final class HomeReactor: Reactor { } } - + func getNewSection() -> [any Sectionable] { if newSection.isEmpty { return [] @@ -206,17 +206,17 @@ final class HomeReactor: Reactor { ] } } - + func setBannerSection(response: GetHomeInfoResponse) { let imagePaths = response.bannerPopUpStoreList.map { $0.mainImageUrl } let idList = response.bannerPopUpStoreList.map { $0.id } loginImageBannerSection.inputDataList = imagePaths.isEmpty ? [] : [.init(imagePaths: imagePaths, idList: idList)] } - + func setCurationTitleSection(response: GetHomeInfoResponse) { curationTitleSection.inputDataList = [.init(blueText: response.nickname, topSubText: "님을 위한", bottomText: "맞춤 팝업 큐레이션")] } - + func setCurationSection(response: GetHomeInfoResponse) { let islogin = response.loginYn curationSection.inputDataList = response.customPopUpStoreList.map({ response in @@ -233,7 +233,7 @@ final class HomeReactor: Reactor { ) }) } - + func setPopularSection(response: GetHomeInfoResponse) { popularSection.inputDataList = response.popularPopUpStoreList.map({ response in return .init( @@ -246,7 +246,7 @@ final class HomeReactor: Reactor { ) }) } - + func setNewSection(response: GetHomeInfoResponse) { let islogin = response.loginYn newSection.inputDataList = response.newPopUpStoreList.map({ response in @@ -263,7 +263,7 @@ final class HomeReactor: Reactor { ) }) } - + func getDetailController(indexPath: IndexPath, currentController: BaseViewController) { if isLoign { switch indexPath.section { @@ -334,7 +334,7 @@ final class HomeReactor: Reactor { } } } - + func getPopUpData(indexPath: IndexPath) -> HomeCardSectionCell.Input { if isLoign { switch indexPath.section { diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeCardSection/HomeCardSection.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeCardSection/HomeCardSection.swift index 7d2e3c8e..23e570ee 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeCardSection/HomeCardSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeCardSection/HomeCardSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct HomeCardSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = HomeCardSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .absolute(158), diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeCardSection/HomeCardSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeCardSection/HomeCardSectionCell.swift index 5bc534e3..8ca3caf6 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeCardSection/HomeCardSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeCardSection/HomeCardSectionCell.swift @@ -7,35 +7,35 @@ import UIKit -import SnapKit -import RxSwift import Kingfisher +import RxSwift +import SnapKit final class HomeCardSectionCell: UICollectionViewCell { - + // MARK: - Components var disposeBag = DisposeBag() - + private let imageView: UIImageView = { let view = UIImageView() view.contentMode = .scaleAspectFill return view }() - + private let categoryLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 11) label.textColor = .blu500 return label }() - + private let titleLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 14) label.numberOfLines = 2 label.lineBreakMode = .byTruncatingTail return label }() - + private let addressLabel: PPLabel = { let label = PPLabel(style: .medium, fontSize: 11) label.numberOfLines = 1 @@ -43,19 +43,18 @@ final class HomeCardSectionCell: UICollectionViewCell { label.textColor = .g400 return label }() - + private let dateLabel: PPLabel = { let label = PPLabel(style: .medium, fontSize: 11) label.lineBreakMode = .byTruncatingTail label.textColor = .g400 return label }() - + let bookmarkButton: UIButton = { - let button = UIButton() - return button + return UIButton() }() - + private let rankLabel: UILabel = { let label = UILabel() label.backgroundColor = .w10 @@ -65,19 +64,19 @@ final class HomeCardSectionCell: UICollectionViewCell { label.textColor = .w100 return label }() - + private let imageService = PreSignedService() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() @@ -89,7 +88,7 @@ private extension HomeCardSectionCell { func setUpConstraints() { contentView.layer.cornerRadius = 4 contentView.clipsToBounds = true - + contentView.addSubview(imageView) imageView.snp.makeConstraints { make in make.width.equalTo(contentView.bounds.width) @@ -99,42 +98,40 @@ private extension HomeCardSectionCell { } imageView.layer.cornerRadius = 4 imageView.clipsToBounds = true - + contentView.addSubview(categoryLabel) categoryLabel.snp.makeConstraints { make in make.leading.equalToSuperview() make.top.equalTo(imageView.snp.bottom).offset(12) make.height.equalTo(15) } - + contentView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.top.equalTo(categoryLabel.snp.bottom).offset(4) make.leading.trailing.equalToSuperview() } - - contentView.addSubview(dateLabel) dateLabel.snp.makeConstraints { make in make.leading.equalToSuperview() make.height.equalTo(15).priority(.high) make.bottom.equalToSuperview() } - + contentView.addSubview(addressLabel) addressLabel.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() make.bottom.equalTo(dateLabel.snp.top) make.height.equalTo(17).priority(.high) } - + contentView.addSubview(bookmarkButton) bookmarkButton.snp.makeConstraints { make in make.size.equalTo(24) make.top.trailing.equalToSuperview().inset(8) } - + imageView.addSubview(rankLabel) rankLabel.snp.makeConstraints { make in make.height.equalTo(24) @@ -158,7 +155,7 @@ extension HomeCardSectionCell: Inputable { var isPopular: Bool = false var row: Int? } - + func injection(with input: Input) { categoryLabel.setLineHeightText(text: "#" + (input.category ?? ""), font: .KorFont(style: .bold, size: 11)) titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .bold, size: 14)) @@ -169,7 +166,7 @@ extension HomeCardSectionCell: Inputable { bookmarkButton.setImage(bookmarkImage, for: .normal) imageView.setPPImage(path: input.imagePath) bookmarkButton.isHidden = !input.isLogin - + rankLabel.isHidden = !input.isPopular let rank = input.row ?? 0 rankLabel.setLineHeightText(text: "\(rank + 1)위", font: .KorFont(style: .medium, size: 11), lineHeight: 1) diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeHeaderView.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeHeaderView.swift index 1a3a4fee..ed9aadf7 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeHeaderView.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeHeaderView.swift @@ -10,37 +10,37 @@ import UIKit import SnapKit final class HomeHeaderView: UIView { - + // MARK: - Components - + let searchBarButton: UIButton = { let button = UIButton() button.layer.cornerRadius = 4 return button }() - + let blurEffect = UIBlurEffect(style: .regular) lazy var blurView = UIVisualEffectView(effect: blurEffect) - + private let searchIconImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_search_black") return view }() - + private let searchLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 14, text: "팝업스토어명을 입력해보세요") label.textColor = .g1000 label.isUserInteractionEnabled = false return label }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -48,7 +48,7 @@ final class HomeHeaderView: UIView { // MARK: - SetUp private extension HomeHeaderView { - + func setUpConstraints() { blurView.isUserInteractionEnabled = false blurView.layer.cornerRadius = 4 @@ -57,21 +57,21 @@ private extension HomeHeaderView { blurView.snp.makeConstraints { make in make.edges.equalTo(searchBarButton) } - + self.addSubview(searchBarButton) searchBarButton.snp.makeConstraints { make in make.height.equalTo(37) make.leading.trailing.equalToSuperview().inset(20) make.top.bottom.equalToSuperview() } - + searchBarButton.addSubview(searchIconImageView) searchIconImageView.snp.makeConstraints { make in make.size.equalTo(20) make.centerY.equalToSuperview() make.leading.equalToSuperview().inset(12) } - + searchBarButton.addSubview(searchLabel) searchLabel.snp.makeConstraints { make in make.centerY.equalTo(searchIconImageView) diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSection.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSection.swift index e790e333..4196db68 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct HomePopularCardSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = HomePopularCardSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .absolute(232), @@ -33,13 +33,13 @@ struct HomePopularCardSection: Sectionable { heightDimension: .absolute(332) ) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) - + // 섹션 생성 let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 0, leading: 20, bottom: 0, trailing: 20) section.interGroupSpacing = 16 section.orthogonalScrollingBehavior = .continuous - + return section } } diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSectionCell.swift index aa395113..f613e678 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSectionCell.swift @@ -7,19 +7,19 @@ import UIKit -import SnapKit -import RxSwift import Kingfisher +import RxSwift +import SnapKit final class HomePopularCardSectionCell: UICollectionViewCell { - + // MARK: - Components private var backGroundImageView: UIImageView = { let view = UIImageView() view.contentMode = .scaleAspectFill return view }() - + private let blurView: UIView = { var view = UIView() view.frame = CGRect(x: 0, y: 0, width: 232, height: 332) @@ -36,43 +36,43 @@ final class HomePopularCardSectionCell: UICollectionViewCell { view.layer.addSublayer(gradientLayer) return view }() - + private let dateLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 16) label.textColor = .w100 return label }() - + private let categoryLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 16) label.textColor = .g1000 label.backgroundColor = .w100 return label }() - + private let titleLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 16) label.numberOfLines = 2 label.textColor = .w100 return label }() - + private let locationLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 16) label.textColor = .w100 return label }() - + let disposeBag = DisposeBag() - + private let imageService = PreSignedService() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } @@ -83,36 +83,36 @@ private extension HomePopularCardSectionCell { func setUpConstraints() { contentView.layer.cornerRadius = 4 contentView.clipsToBounds = true - + contentView.addSubview(backGroundImageView) backGroundImageView.snp.makeConstraints { make in make.edges.equalToSuperview() } - + backGroundImageView.addSubview(blurView) blurView.snp.makeConstraints { make in make.edges.equalToSuperview() } - + contentView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.bottom.leading.trailing.equalToSuperview().inset(20) make.height.equalTo(48) } - + contentView.addSubview(categoryLabel) categoryLabel.snp.makeConstraints { make in make.bottom.equalTo(titleLabel.snp.top).offset(-16) make.leading.equalToSuperview().inset(20) make.height.equalTo(24) } - + contentView.addSubview(dateLabel) dateLabel.snp.makeConstraints { make in make.bottom.equalTo(categoryLabel.snp.top).offset(-6) make.leading.trailing.equalToSuperview().inset(20) } - + contentView.addSubview(locationLabel) locationLabel.snp.makeConstraints { make in make.centerY.equalTo(categoryLabel) @@ -131,7 +131,7 @@ extension HomePopularCardSectionCell: Inputable { var id: Int64 var address: String? } - + func injection(with input: Input) { let date = "#\(input.endDate.toDate().toPPDateMonthString())까지 열리는" dateLabel.setLineHeightText(text: date, font: .KorFont(style: .regular, size: 16)) @@ -142,7 +142,7 @@ extension HomePopularCardSectionCell: Inputable { locationLabel.text = "#\(address)" } } - + categoryLabel.text = category titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .regular, size: 16)) backGroundImageView.setPPImage(path: input.imagePath) diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeTitleSection/HomeTitleSection.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeTitleSection/HomeTitleSection.swift index 32dc908a..3be6b834 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeTitleSection/HomeTitleSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeTitleSection/HomeTitleSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct HomeTitleSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = HomeTitleSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -35,9 +35,8 @@ struct HomeTitleSection: Sectionable { let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) // 섹션 생성 - let section = NSCollectionLayoutSection(group: group) // section.contentInsets = .init(top: 0, leading: 20, bottom: 0, trailing: 16) - - return section + + return NSCollectionLayoutSection(group: group) } } diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeTitleSection/HomeTitleSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeTitleSection/HomeTitleSectionCell.swift index e74a2fcd..d25f06bf 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeTitleSection/HomeTitleSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeTitleSection/HomeTitleSectionCell.swift @@ -7,47 +7,45 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class HomeTitleSectionCell: UICollectionViewCell { - + // MARK: - Components var disposeBag = DisposeBag() - + private var blueLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 16) label.textColor = .blu500 return label }() - + private let subLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 16) - return label + return PPLabel(style: .bold, fontSize: 16) }() - + private let bottomLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 16) - return label + return PPLabel(style: .bold, fontSize: 16) }() - + let detailButton: UIButton = { let button = UIButton() button.setImage(UIImage(named: "icon_right_gray"), for: .normal) return button }() - + // MARK: - init override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() @@ -72,7 +70,7 @@ private extension HomeTitleSectionCell { make.leading.equalToSuperview().inset(20) make.bottom.equalToSuperview() } - + contentView.addSubview(detailButton) detailButton.snp.makeConstraints { make in make.size.equalTo(24) @@ -90,7 +88,7 @@ extension HomeTitleSectionCell: Inputable { var backgroundColor: UIColor? = .clear var textColor: UIColor? = .g1000 } - + func injection(with input: Input) { blueLabel.setLineHeightText(text: input.blueText, font: .KorFont(style: .bold, size: 16)) subLabel.setLineHeightText(text: input.topSubText, font: .KorFont(style: .bold, size: 16)) diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeView.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeView.swift index cab79f80..3710e7e0 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeView.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeView.swift @@ -10,20 +10,20 @@ import UIKit import SnapKit final class HomeView: UIView { - + // MARK: - Components let contentCollectionView: UICollectionView = { let view = UICollectionView(frame: .zero, collectionViewLayout: .init()) view.contentInsetAdjustmentBehavior = .never return view }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -31,7 +31,7 @@ final class HomeView: UIView { // MARK: - SetUp private extension HomeView { - + func setUpConstraints() { self.addSubview(contentCollectionView) contentCollectionView.snp.makeConstraints { make in diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerChildSection/ImageBannerChildSection.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerChildSection/ImageBannerChildSection.swift index 14ddb820..8d323718 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerChildSection/ImageBannerChildSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerChildSection/ImageBannerChildSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct ImageBannerChildSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = ImageBannerChildSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerChildSection/ImageBannerChildSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerChildSection/ImageBannerChildSectionCell.swift index 43dcc0e9..173f05ed 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerChildSection/ImageBannerChildSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerChildSection/ImageBannerChildSectionCell.swift @@ -7,28 +7,28 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class ImageBannerChildSectionCell: UICollectionViewCell { - + // MARK: - Components let disposeBag = DisposeBag() - + private let imageView: UIImageView = { let view = UIImageView() view.contentMode = .scaleAspectFill return view }() - + // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } @@ -49,7 +49,7 @@ extension ImageBannerChildSectionCell: Inputable { var imagePath: String? var id: Int64 } - + func injection(with input: Input) { imageView.setPPImage(path: input.imagePath) } diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSection.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSection.swift index 8a03671c..e60a5a6d 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct ImageBannerSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = ImageBannerSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -35,8 +35,7 @@ struct ImageBannerSection: Sectionable { let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) // 섹션 생성 - let section = NSCollectionLayoutSection(group: group) - - return section + + return NSCollectionLayoutSection(group: group) } } diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift index 025ec4a9..5f65f77d 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift @@ -7,26 +7,25 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class ImageBannerSectionCell: UICollectionViewCell { - + // MARK: - Components var disposeBag = DisposeBag() - + private var autoScrollTimer: Timer? - + private lazy var contentCollectionView: UICollectionView = { let view = UICollectionView(frame: .zero, collectionViewLayout: compositionalLayout) view.contentInsetAdjustmentBehavior = .never return view }() - + var pageControl: CustomPageControl = { - let controller = CustomPageControl() - return controller + return CustomPageControl() }() let stopButton: UIButton = { @@ -34,18 +33,18 @@ final class ImageBannerSectionCell: UICollectionViewCell { button.setImage(UIImage(named: "icon_banner_stopButton"), for: .normal) return button }() - + let playButton: UIButton = { let button = UIButton() button.setImage(UIImage(named: "icon_banner_playButton"), for: .normal) return button }() - + private var isAutoBannerPlay: Bool = false private var isFirstResponseAutoScroll: Bool = true - + var imageSection = ImageBannerChildSection(inputDataList: []) - + lazy var compositionalLayout: UICollectionViewCompositionalLayout = { UICollectionViewCompositionalLayout { [weak self] section, env in guard let self = self else { @@ -59,30 +58,30 @@ final class ImageBannerSectionCell: UICollectionViewCell { return getSection()[section].getSection(section: section, env: env) } }() - + let bannerTapped: PublishSubject = .init() - + private var currentIndex: Int = 1 private var isHiddenPauseButton: Bool = true - + // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUp() setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() isFirstResponseAutoScroll = false } - + // 자동 스크롤 중지 함수 func stopAutoScroll() { stopButton.isHidden = true @@ -91,7 +90,7 @@ final class ImageBannerSectionCell: UICollectionViewCell { autoScrollTimer?.invalidate() autoScrollTimer = nil } - + func startAutoScroll(interval: TimeInterval = 3.0) { stopAutoScroll() // 기존 타이머를 중지 stopButton.isHidden = false @@ -112,13 +111,13 @@ private extension ImageBannerSectionCell { func setUp() { contentCollectionView.delegate = self contentCollectionView.dataSource = self - + contentCollectionView.register( ImageBannerChildSectionCell.self, forCellWithReuseIdentifier: ImageBannerChildSectionCell.identifiers ) } - + func setUpConstraints() { contentView.addSubview(contentCollectionView) contentCollectionView.snp.makeConstraints { make in @@ -135,7 +134,7 @@ private extension ImageBannerSectionCell { make.centerY.equalTo(pageControl) make.leading.equalTo(pageControl.snp.trailing).offset(6) } - + contentView.addSubview(playButton) playButton.snp.makeConstraints { make in make.size.equalTo(6) @@ -143,11 +142,11 @@ private extension ImageBannerSectionCell { make.leading.equalTo(pageControl.snp.trailing).offset(6) } } - + func getSection() -> [any Sectionable] { return [imageSection] } - + private func findViewController() -> BaseViewController? { var nextResponder = self.next while nextResponder != nil { @@ -158,7 +157,7 @@ private extension ImageBannerSectionCell { } return nil } - + func bind() { stopButton.rx.tap .withUnretained(self) @@ -170,7 +169,7 @@ private extension ImageBannerSectionCell { } } .disposed(by: disposeBag) - + playButton.rx.tap .withUnretained(self) .subscribe { (owner, _) in @@ -181,7 +180,7 @@ private extension ImageBannerSectionCell { } } .disposed(by: disposeBag) - + imageSection.currentPage .distinctUntilChanged() .withUnretained(self) @@ -202,7 +201,7 @@ extension ImageBannerSectionCell: Inputable { var idList: [Int64] var isHiddenPauseButton: Bool = false } - + func injection(with input: Input) { if imageSection.isEmpty { pageControl.setNumberOfPages(input.imagePaths.count) @@ -219,22 +218,22 @@ extension ImageBannerSectionCell: Inputable { ) } } - + contentCollectionView.reloadData() isHiddenPauseButton = input.isHiddenPauseButton if isFirstResponseAutoScroll { startAutoScroll() isFirstResponseAutoScroll = false } - + if input.isHiddenPauseButton { stopAutoScroll() stopButton.isHidden = true playButton.isHidden = true } - + bind() - + if input.imagePaths.count == 1 { playButton.isHidden = true stopButton.isHidden = true @@ -245,26 +244,25 @@ extension ImageBannerSectionCell: Inputable { // MARK: - UICollectionViewDelegate, UICollectionViewDataSource extension ImageBannerSectionCell: UICollectionViewDelegate, UICollectionViewDataSource { - + func numberOfSections(in collectionView: UICollectionView) -> Int { return getSection().count } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return getSection()[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath ) -> UICollectionViewCell { - let cell = getSection()[indexPath.section].getCell(collectionView: collectionView, indexPath: indexPath) - return cell + return getSection()[indexPath.section].getCell(collectionView: collectionView, indexPath: indexPath) } - + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { bannerTapped.onNext(indexPath.row) } - + func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { if currentIndex == 0 { contentCollectionView.scrollToItem( @@ -281,7 +279,7 @@ extension ImageBannerSectionCell: UICollectionViewDelegate, UICollectionViewData if !isHiddenPauseButton { startAutoScroll() } - + } } diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/SectionBackGroundDecorationView.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/SectionBackGroundDecorationView.swift index 6a2c2396..300f838a 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/SectionBackGroundDecorationView.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/SectionBackGroundDecorationView.swift @@ -13,7 +13,7 @@ class SectionBackGroundDecorationView: UICollectionReusableView { super.init(frame: frame) self.backgroundColor = .g700 } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -23,7 +23,7 @@ extension SectionBackGroundDecorationView: Inputable { struct Input { var backgroundColor: UIColor } - + func injection(with input: Input) { self.backgroundColor = input.backgroundColor } diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/SpacingSection/SpacingSection.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/SpacingSection/SpacingSection.swift index 02c2172b..0c7ae8ee 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/SpacingSection/SpacingSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/SpacingSection/SpacingSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct SpacingSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = SpacingSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -35,8 +35,7 @@ struct SpacingSection: Sectionable { let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) // 섹션 생성 - let section = NSCollectionLayoutSection(group: group) - - return section + + return NSCollectionLayoutSection(group: group) } } diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/SpacingSection/SpacingSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/SpacingSection/SpacingSectionCell.swift index db08e964..5cb66eb4 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/SpacingSection/SpacingSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/SpacingSection/SpacingSectionCell.swift @@ -7,24 +7,24 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class SpacingSectionCell: UICollectionViewCell { - + // MARK: - Components let disposeBag = DisposeBag() - + private let spaceView: UIView = UIView() - + // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } @@ -42,7 +42,7 @@ extension SpacingSectionCell: Inputable { var spacing: Float var backgroundColor: UIColor? = .clear } - + func injection(with input: Input) { spaceView.snp.removeConstraints() spaceView.snp.makeConstraints { make in diff --git a/Poppool/Poppool/Presentation/Scene/ImageDetail/ImageDetailController.swift b/Poppool/Poppool/Presentation/Scene/ImageDetail/ImageDetailController.swift index c2b104a5..49a2a231 100644 --- a/Poppool/Poppool/Presentation/Scene/ImageDetail/ImageDetailController.swift +++ b/Poppool/Poppool/Presentation/Scene/ImageDetail/ImageDetailController.swift @@ -7,20 +7,20 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit final class ImageDetailController: BaseViewController, View { - + typealias Reactor = ImageDetailReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = ImageDetailView() - + private let cancelButton: UIButton = { let view = UIButton() view.setImage(UIImage(named: "icon_xmark_white"), for: .normal) @@ -56,7 +56,7 @@ private extension ImageDetailController { // MARK: - Methods extension ImageDetailController { func bind(reactor: Reactor) { - + cancelButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -64,7 +64,7 @@ extension ImageDetailController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in diff --git a/Poppool/Poppool/Presentation/Scene/ImageDetail/ImageDetailReactor.swift b/Poppool/Poppool/Presentation/Scene/ImageDetail/ImageDetailReactor.swift index 826253be..f94bb7bc 100644 --- a/Poppool/Poppool/Presentation/Scene/ImageDetail/ImageDetailReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/ImageDetail/ImageDetailReactor.swift @@ -6,34 +6,34 @@ // import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class ImageDetailReactor: Reactor { - + // MARK: - Reactor enum Action { case backButtonTapped(controller: BaseViewController) } - + enum Mutation { case moveToRecentScene(controller: BaseViewController) } - + struct State { var imagePath: String? } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - + // MARK: - init init(imagePath: String?) { self.initialState = State(imagePath: imagePath) } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -41,7 +41,7 @@ final class ImageDetailReactor: Reactor { return Observable.just(.moveToRecentScene(controller: controller)) } } - + func reduce(state: State, mutation: Mutation) -> State { switch mutation { case .moveToRecentScene(let controller): diff --git a/Poppool/Poppool/Presentation/Scene/ImageDetail/ImageDetailView.swift b/Poppool/Poppool/Presentation/Scene/ImageDetail/ImageDetailView.swift index 512c88d1..6092969d 100644 --- a/Poppool/Poppool/Presentation/Scene/ImageDetail/ImageDetailView.swift +++ b/Poppool/Poppool/Presentation/Scene/ImageDetail/ImageDetailView.swift @@ -10,7 +10,7 @@ import UIKit import SnapKit final class ImageDetailView: UIView { - + // MARK: - Components let imageView: UIImageView = { let view = UIImageView() @@ -18,13 +18,13 @@ final class ImageDetailView: UIView { view.contentMode = .scaleAspectFit return view }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -32,7 +32,7 @@ final class ImageDetailView: UIView { // MARK: - SetUp private extension ImageDetailView { - + func setUpConstraints() { self.addSubview(imageView) imageView.snp.makeConstraints { make in diff --git a/Poppool/Poppool/Presentation/Scene/Login/LastLoginView.swift b/Poppool/Poppool/Presentation/Scene/Login/LastLoginView.swift index 28c0ff4b..fb04c749 100644 --- a/Poppool/Poppool/Presentation/Scene/Login/LastLoginView.swift +++ b/Poppool/Poppool/Presentation/Scene/Login/LastLoginView.swift @@ -10,26 +10,26 @@ import UIKit import SnapKit final class LastLoginView: UIView { - + /// 방향에 따라 툴팁을 다르게 표시합니다 enum TipDirection { case pointUp case pointDown } - + /// 툴팁의 색상을 지정하였습니다 /// 텍스트 컬러 또한 TipColor에 따라 수정됩니다 enum TipColor { case blu500 case w100 - + var color: UIColor { switch self { case .blu500: return UIColor.blu500 case .w100: return UIColor.w100 } } - + var textColor: UIColor { switch self { case .blu500: return UIColor.w100 @@ -37,34 +37,33 @@ final class LastLoginView: UIView { } } } - + // MARK: - Properties - + private let bgView: UIView = { - let view = UIView() - return view + return UIView() }() - + private let notificationLabel: UILabel = { let label = UILabel() label.font = .KorFont(style: .medium, size: 13) return label }() - + private var colorType: TipColor { didSet { self.setNeedsDisplay() } } - + private var midX: CGFloat { return self.bounds.midX } - + private var direction: TipDirection - + // MARK: - init - + /// 툴팁 뷰를 생성합니다 /// - Parameters: /// - colorType: 툴팁의 색상(UIColor)을 인자로 받습니다 - w100, blu500 @@ -77,29 +76,29 @@ final class LastLoginView: UIView { notificationLabel.textColor = colorType.textColor notificationLabel.text = text } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override func draw(_ rect: CGRect) { drawToolTip() } } extension LastLoginView { - + // MARK: - Methods - + private func setupLayer(color: TipColor) { self.backgroundColor = .clear addSubview(bgView) - + bgView.snp.makeConstraints { make in make.height.equalTo(45) make.edges.equalToSuperview() } - + bgView.addSubview(notificationLabel) switch direction { case .pointUp: @@ -107,7 +106,7 @@ extension LastLoginView { make.leading.trailing.equalToSuperview().inset(16) make.bottom.equalToSuperview().inset(11) } - + case .pointDown: notificationLabel.snp.makeConstraints { make in make.leading.trailing.equalToSuperview().inset(16) @@ -115,20 +114,20 @@ extension LastLoginView { } } } - + /// 툴팁을 방향에 맞춰 그리고 섀도우를 더하는 메서드 private func drawToolTip() { switch direction { case .pointUp: drawUpPointingToolTip() addShadow() - + case .pointDown: drawDownPointingTip() addShadow() } } - + /// 위를 가리키는 툴팁을 만듭니다 private func drawUpPointingToolTip() { let textLength = notificationLabel.frame.width @@ -137,10 +136,10 @@ extension LastLoginView { tip.addLine(to: CGPoint(x: midX, y: 0)) tip.addLine(to: CGPoint(x: midX + 8, y: 10)) tip.close() - + colorType.color.setFill() tip.fill() - + let message = UIBezierPath( roundedRect: CGRect( x: 0, y: 10, @@ -149,25 +148,25 @@ extension LastLoginView { ), cornerRadius: 6 ) - + colorType.color.setFill() message.fill() message.close() } - + /// 아래를 가리키는 툴팁을 만듭니다 private func drawDownPointingTip() { let textLength = notificationLabel.frame.width - + let tip = UIBezierPath() tip.move(to: CGPoint(x: midX - 8, y: 35)) tip.addLine(to: CGPoint(x: midX, y: 45)) tip.addLine(to: CGPoint(x: midX + 8, y: 35)) tip.close() - + colorType.color.setFill() tip.fill() - + let message = UIBezierPath( roundedRect: CGRect( x: 0, y: 0, @@ -176,19 +175,19 @@ extension LastLoginView { ), cornerRadius: 6 ) - + colorType.color.setFill() message.fill() message.close() } - + /// 툴팁의 섀도우를 더합니다 private func addShadow() { layer.shadowOffset = CGSize(width: 0, height: 5) layer.shadowColor = UIColor.black.cgColor layer.shadowOpacity = 0.2 layer.shadowRadius = 5 - + // 섀도우를 그릴 때 드는 리소스를 줄이기 위해 캐시를 적용하는 방식 // layer.shadowPath = UIBezierPath(rect: self.bounds).cgPath layer.shouldRasterize = true @@ -200,9 +199,9 @@ extension UIView { func showToolTip(color: LastLoginView.TipColor, direction: LastLoginView.TipDirection, text: String? = "최근에 이 방법으로 로그인했어요") { // 호출하는 컴포넌트 위 또는 아래에 생성되기 위해 superview를 구합니다 guard let superview = self.superview else { return } - + let toolTip = LastLoginView(colorType: color, direction: direction, text: text) - + superview.addSubview(toolTip) toolTip.snp.makeConstraints { make in if direction == .pointDown { diff --git a/Poppool/Poppool/Presentation/Scene/Login/Main/LoginController.swift b/Poppool/Poppool/Presentation/Scene/Login/Main/LoginController.swift index f0fd9f61..47912023 100644 --- a/Poppool/Poppool/Presentation/Scene/Login/Main/LoginController.swift +++ b/Poppool/Poppool/Presentation/Scene/Login/Main/LoginController.swift @@ -7,18 +7,18 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit final class LoginController: BaseViewController, View { - + typealias Reactor = LoginReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = LoginView() } @@ -28,7 +28,7 @@ extension LoginController { super.viewDidLoad() setUp() } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if let lastLogin = reactor?.userDefaultService.fetch(key: "lastLogin") { @@ -57,12 +57,12 @@ private extension LoginController { // MARK: - Methods extension LoginController { func bind(reactor: Reactor) { - + rx.viewWillAppear .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.guestButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -70,7 +70,7 @@ extension LoginController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.kakaoButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -78,7 +78,7 @@ extension LoginController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.inquiryButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -86,7 +86,7 @@ extension LoginController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.appleButton.rx.tap .withUnretained(self) .map { (owner, _) in diff --git a/Poppool/Poppool/Presentation/Scene/Login/Main/LoginReactor.swift b/Poppool/Poppool/Presentation/Scene/Login/Main/LoginReactor.swift index 74ef7572..1d3425a0 100644 --- a/Poppool/Poppool/Presentation/Scene/Login/Main/LoginReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Login/Main/LoginReactor.swift @@ -6,11 +6,11 @@ // import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class LoginReactor: Reactor { - + // MARK: - Reactor enum Action { case kakaoButtonTapped(controller: BaseViewController) @@ -19,7 +19,7 @@ final class LoginReactor: Reactor { case viewWillAppear case inquiryButtonTapped(controller: BaseViewController) } - + enum Mutation { case moveToSignUpScene(controller: BaseViewController) case moveToHomeScene(controller: BaseViewController) @@ -27,28 +27,28 @@ final class LoginReactor: Reactor { case resetService case moveToInquiryScene(controller: BaseViewController) } - + struct State { } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - + private var authrizationCode: String? - + private let kakaoLoginService = KakaoLoginService() private var appleLoginService = AppleLoginService() private let authApiUseCase = AuthAPIUseCaseImpl(repository: AuthAPIRepositoryImpl(provider: ProviderImpl())) private let keyChainService = KeyChainService() let userDefaultService = UserDefaultService() - + // MARK: - init init() { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -57,8 +57,8 @@ final class LoginReactor: Reactor { case .appleButtonTapped(let controller): return loginWithApple(controller: controller) case .guestButtonTapped(let controller): - let _ = keyChainService.deleteToken(type: .accessToken) - let _ = keyChainService.deleteToken(type: .refreshToken) + _ = keyChainService.deleteToken(type: .accessToken) + _ = keyChainService.deleteToken(type: .refreshToken) return Observable.just(.moveToHomeScene(controller: controller)) case .viewWillAppear: return Observable.just(.resetService) @@ -66,7 +66,7 @@ final class LoginReactor: Reactor { return Observable.just(.moveToInquiryScene(controller: controller)) } } - + func reduce(state: State, mutation: Mutation) -> State { switch mutation { case .moveToSignUpScene(let controller): @@ -88,7 +88,7 @@ final class LoginReactor: Reactor { } return state } - + func loginWithKakao(controller: BaseViewController) -> Observable { return kakaoLoginService.fetchUserCredential() .withUnretained(self) @@ -115,7 +115,7 @@ final class LoginReactor: Reactor { } } } - + func loginWithApple(controller: BaseViewController) -> Observable { return appleLoginService.fetchUserCredential() .withUnretained(self) diff --git a/Poppool/Poppool/Presentation/Scene/Login/Main/LoginView.swift b/Poppool/Poppool/Presentation/Scene/Login/Main/LoginView.swift index ed3830da..8333b589 100644 --- a/Poppool/Poppool/Presentation/Scene/Login/Main/LoginView.swift +++ b/Poppool/Poppool/Presentation/Scene/Login/Main/LoginView.swift @@ -10,7 +10,7 @@ import UIKit import SnapKit final class LoginView: UIView { - + // MARK: - Components let guestButton: UIButton = { let button = UIButton(type: .system) @@ -19,43 +19,41 @@ final class LoginView: UIView { button.setTitleColor(.g1000, for: .normal) return button }() - + private let logoImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "image_login_logo") view.contentMode = .scaleAspectFit return view }() - + private let titleLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 16, text: "간편하게 SNS 로그인하고\n팝풀 서비스를 이용해보세요") label.numberOfLines = 0 label.textAlignment = .center return label }() - + let kakaoButton: PPButton = { - let button = PPButton(style: .kakao, text: "카카오톡으로 로그인") - return button + return PPButton(style: .kakao, text: "카카오톡으로 로그인") }() - + private let kakaoImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_login_kakao") return view }() - + let appleButton: PPButton = { - let button = PPButton(style: .apple, text: "Apple로 로그인") - return button + return PPButton(style: .apple, text: "Apple로 로그인") }() - + private let appleImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_login_apple") return view }() - + let inquiryButton: UIButton = { let button = UIButton(type: .system) button.setTitle("로그인이 어려우신가요?", for: .normal) @@ -63,13 +61,13 @@ final class LoginView: UIView { button.setTitleColor(.g1000, for: .normal) return button }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -77,14 +75,14 @@ final class LoginView: UIView { // MARK: - SetUp private extension LoginView { - + func setUpConstraints() { self.addSubview(guestButton) guestButton.snp.makeConstraints { make in make.top.equalToSuperview().inset(11) make.trailing.equalToSuperview().inset(20) } - + self.addSubview(logoImageView) logoImageView.snp.makeConstraints { make in make.height.equalTo(90) @@ -92,41 +90,41 @@ private extension LoginView { make.top.equalTo(guestButton.snp.bottom).offset(75) make.centerX.equalToSuperview() } - + self.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.centerX.equalToSuperview() make.top.equalTo(logoImageView.snp.bottom).offset(28) } - + self.addSubview(kakaoButton) kakaoButton.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(156) make.leading.trailing.equalToSuperview().inset(20) make.height.equalTo(50) } - + kakaoButton.addSubview(kakaoImageView) kakaoImageView.snp.makeConstraints { make in make.centerY.equalToSuperview() make.leading.equalToSuperview().inset(20) make.size.equalTo(22) } - + self.addSubview(appleButton) appleButton.snp.makeConstraints { make in make.top.equalTo(kakaoButton.snp.bottom).offset(16) make.leading.trailing.equalToSuperview().inset(20) make.height.equalTo(50) } - + appleButton.addSubview(appleImageView) appleImageView.snp.makeConstraints { make in make.centerY.equalToSuperview() make.leading.equalToSuperview().inset(20) make.size.equalTo(22) } - + self.addSubview(inquiryButton) inquiryButton.snp.makeConstraints { make in make.bottom.equalToSuperview().inset(56) diff --git a/Poppool/Poppool/Presentation/Scene/Login/Sub/SubLoginController.swift b/Poppool/Poppool/Presentation/Scene/Login/Sub/SubLoginController.swift index b35da1bb..a377e56b 100644 --- a/Poppool/Poppool/Presentation/Scene/Login/Sub/SubLoginController.swift +++ b/Poppool/Poppool/Presentation/Scene/Login/Sub/SubLoginController.swift @@ -7,18 +7,18 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit final class SubLoginController: BaseViewController, View { - + typealias Reactor = SubLoginReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = SubLoginView() } @@ -28,7 +28,7 @@ extension SubLoginController { super.viewDidLoad() setUp() } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if let lastLogin = reactor?.userDefaultService.fetch(key: "lastLogin") { @@ -62,7 +62,7 @@ extension SubLoginController { .map { Reactor.Action.viewWillAppear} .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.xmarkButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -70,7 +70,7 @@ extension SubLoginController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.kakaoButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -78,7 +78,7 @@ extension SubLoginController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.inquiryButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -86,7 +86,7 @@ extension SubLoginController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.appleButton.rx.tap .withUnretained(self) .map { (owner, _) in diff --git a/Poppool/Poppool/Presentation/Scene/Login/Sub/SubLoginReactor.swift b/Poppool/Poppool/Presentation/Scene/Login/Sub/SubLoginReactor.swift index c9254e87..24b6d961 100644 --- a/Poppool/Poppool/Presentation/Scene/Login/Sub/SubLoginReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Login/Sub/SubLoginReactor.swift @@ -6,11 +6,11 @@ // import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class SubLoginReactor: Reactor { - + // MARK: - Reactor enum Action { case kakaoButtonTapped(controller: BaseViewController) @@ -19,7 +19,7 @@ final class SubLoginReactor: Reactor { case viewWillAppear case inquiryButtonTapped(controller: BaseViewController) } - + enum Mutation { case moveToSignUpScene(controller: BaseViewController) case dismissScene(controller: BaseViewController) @@ -27,28 +27,28 @@ final class SubLoginReactor: Reactor { case resetService case moveToInquiryScene(controller: BaseViewController) } - + struct State { } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - + private var authrizationCode: String? - + private let kakaoLoginService = KakaoLoginService() private var appleLoginService = AppleLoginService() private let authApiUseCase = AuthAPIUseCaseImpl(repository: AuthAPIRepositoryImpl(provider: ProviderImpl())) private let keyChainService = KeyChainService() let userDefaultService = UserDefaultService() - + // MARK: - init init() { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -64,7 +64,7 @@ final class SubLoginReactor: Reactor { return Observable.just(.moveToInquiryScene(controller: controller)) } } - + func reduce(state: State, mutation: Mutation) -> State { switch mutation { case .moveToSignUpScene(let controller): @@ -85,7 +85,7 @@ final class SubLoginReactor: Reactor { } return state } - + func loginWithKakao(controller: BaseViewController) -> Observable { return kakaoLoginService.fetchUserCredential() .withUnretained(self) @@ -112,7 +112,7 @@ final class SubLoginReactor: Reactor { } } } - + func loginWithApple(controller: BaseViewController) -> Observable { return appleLoginService.fetchUserCredential() .withUnretained(self) diff --git a/Poppool/Poppool/Presentation/Scene/Login/Sub/SubLoginView.swift b/Poppool/Poppool/Presentation/Scene/Login/Sub/SubLoginView.swift index bcbe83c5..00e54ac7 100644 --- a/Poppool/Poppool/Presentation/Scene/Login/Sub/SubLoginView.swift +++ b/Poppool/Poppool/Presentation/Scene/Login/Sub/SubLoginView.swift @@ -10,7 +10,7 @@ import UIKit import SnapKit final class SubLoginView: UIView { - + // MARK: - Components let xmarkButton: UIButton = { let button = UIButton(type: .system) @@ -18,14 +18,14 @@ final class SubLoginView: UIView { button.tintColor = .g1000 return button }() - + private let logoImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "image_login_logo") view.contentMode = .scaleAspectFit return view }() - + private let titleLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 16, text: "간편하게 SNS 로그인하고\n공감가는 코멘트에 반응해볼까요?\n다른 코멘트를 확인해볼까요?") label.setLineHeightText(text: "간편하게 SNS 로그인하고\n공감가는 코멘트에 반응해볼까요?\n다른 코멘트를 확인해볼까요?", font: .KorFont(style: .bold, size: 16), lineHeight: 1.3) @@ -33,29 +33,27 @@ final class SubLoginView: UIView { label.textAlignment = .center return label }() - + let kakaoButton: PPButton = { - let button = PPButton(style: .kakao, text: "카카오톡으로 로그인") - return button + return PPButton(style: .kakao, text: "카카오톡으로 로그인") }() - + private let kakaoImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_login_kakao") return view }() - + let appleButton: PPButton = { - let button = PPButton(style: .apple, text: "Apple로 로그인") - return button + return PPButton(style: .apple, text: "Apple로 로그인") }() - + private let appleImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_login_apple") return view }() - + let inquiryButton: UIButton = { let button = UIButton(type: .system) button.setTitle("로그인이 어려우신가요?", for: .normal) @@ -63,13 +61,13 @@ final class SubLoginView: UIView { button.setTitleColor(.g1000, for: .normal) return button }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -77,7 +75,7 @@ final class SubLoginView: UIView { // MARK: - SetUp private extension SubLoginView { - + func setUpConstraints() { self.addSubview(xmarkButton) xmarkButton.snp.makeConstraints { make in @@ -85,7 +83,7 @@ private extension SubLoginView { make.trailing.equalToSuperview().inset(20) make.size.equalTo(32) } - + self.addSubview(logoImageView) logoImageView.snp.makeConstraints { make in make.height.equalTo(90) @@ -93,41 +91,41 @@ private extension SubLoginView { make.top.equalTo(xmarkButton.snp.bottom).offset(75) make.centerX.equalToSuperview() } - + self.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.centerX.equalToSuperview() make.top.equalTo(logoImageView.snp.bottom).offset(28) } - + self.addSubview(kakaoButton) kakaoButton.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(156) make.leading.trailing.equalToSuperview().inset(20) make.height.equalTo(50) } - + kakaoButton.addSubview(kakaoImageView) kakaoImageView.snp.makeConstraints { make in make.centerY.equalToSuperview() make.leading.equalToSuperview().inset(20) make.size.equalTo(22) } - + self.addSubview(appleButton) appleButton.snp.makeConstraints { make in make.top.equalTo(kakaoButton.snp.bottom).offset(16) make.leading.trailing.equalToSuperview().inset(20) make.height.equalTo(50) } - + appleButton.addSubview(appleImageView) appleImageView.snp.makeConstraints { make in make.centerY.equalToSuperview() make.leading.equalToSuperview().inset(20) make.size.equalTo(22) } - + self.addSubview(inquiryButton) inquiryButton.snp.makeConstraints { make in make.bottom.equalToSuperview().inset(56) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Block/BlockUserManageController.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Block/BlockUserManageController.swift index 9e52be99..ac172875 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Block/BlockUserManageController.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Block/BlockUserManageController.swift @@ -7,20 +7,20 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit final class BlockUserManageController: BaseViewController, View { - + typealias Reactor = BlockUserManageReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = BlockUserManageView() - + private var sections: [any Sectionable] = [] } @@ -30,7 +30,7 @@ extension BlockUserManageController { super.viewDidLoad() setUp() } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tabBarController?.tabBar.isHidden = true @@ -52,12 +52,12 @@ private extension BlockUserManageController { mainView.contentCollectionView.register( CommentListTitleSectionCell.self, forCellWithReuseIdentifier: CommentListTitleSectionCell.identifiers - ) + ) mainView.contentCollectionView.register( BlockUserListSectionCell.self, forCellWithReuseIdentifier: BlockUserListSectionCell.identifiers ) - + view.addSubview(mainView) mainView.snp.makeConstraints { make in make.edges.equalTo(view.safeAreaLayoutGuide) @@ -75,12 +75,12 @@ extension BlockUserManageController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + rx.viewWillAppear .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -98,11 +98,11 @@ extension BlockUserManageController: UICollectionViewDelegate, UICollectionViewD func numberOfSections(in collectionView: UICollectionView) -> Int { return sections.count } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sections[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Block/BlockUserManageReactor.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Block/BlockUserManageReactor.swift index f714226b..99c16ac2 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Block/BlockUserManageReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Block/BlockUserManageReactor.swift @@ -8,35 +8,35 @@ import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class BlockUserManageReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear case blockButtonTapped(row: Int) case backButtonTapped(controller: BaseViewController) } - + enum Mutation { case loadView case moveToRecentScene(controller: BaseViewController) } - + struct State { var sections: [any Sectionable] = [] var isEmptyList: Bool = true } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - + private let userAPIUseCase = UserAPIUseCaseImpl(repository: UserAPIRepositoryImpl(provider: ProviderImpl())) - + lazy var compositionalLayout: UICollectionViewCompositionalLayout = { UICollectionViewCompositionalLayout { [weak self] section, env in guard let self = self else { @@ -50,16 +50,16 @@ final class BlockUserManageReactor: Reactor { return getSection()[section].getSection(section: section, env: env) } }() - + private var countSection = CommentListTitleSection(inputDataList: []) private var listSection = BlockUserListSection(inputDataList: []) private var spcing16Section = SpacingSection(inputDataList: [.init(spacing: 16)]) - + // MARK: - init init() { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -87,7 +87,7 @@ final class BlockUserManageReactor: Reactor { return Observable.just(.moveToRecentScene(controller: controller)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { @@ -99,7 +99,7 @@ final class BlockUserManageReactor: Reactor { newState.isEmptyList = listSection.isEmpty return newState } - + func getSection() -> [any Sectionable] { return [ spcing16Section, diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Block/View/BlockUserListSection/BlockUserListSection.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Block/View/BlockUserListSection/BlockUserListSection.swift index 28e01604..43dd9f31 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Block/View/BlockUserListSection/BlockUserListSection.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Block/View/BlockUserListSection/BlockUserListSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct BlockUserListSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = BlockUserListSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Block/View/BlockUserListSection/BlockUserListSectionCell.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Block/View/BlockUserListSection/BlockUserListSectionCell.swift index f784f85b..e7380373 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Block/View/BlockUserListSection/BlockUserListSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Block/View/BlockUserListSection/BlockUserListSectionCell.swift @@ -7,15 +7,15 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class BlockUserListSectionCell: UICollectionViewCell { - + // MARK: - Components var disposeBag = DisposeBag() - + private let profileImageView: UIImageView = { let view = UIImageView() view.layer.cornerRadius = 18 @@ -23,29 +23,28 @@ final class BlockUserListSectionCell: UICollectionViewCell { view.contentMode = .scaleAspectFill return view }() - + private let nickNameLabel: UILabel = { - let label = UILabel() - return label + return UILabel() }() - + let blockButton: UIButton = { let button = UIButton() button.layer.cornerRadius = 4 return button }() - + // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() @@ -60,18 +59,18 @@ private extension BlockUserListSectionCell { make.size.equalTo(36) make.leading.centerY.equalToSuperview() } - + contentView.addSubview(nickNameLabel) nickNameLabel.snp.makeConstraints { make in make.centerY.equalToSuperview() make.leading.equalTo(profileImageView.snp.trailing).offset(12) } - + contentView.addSubview(blockButton) blockButton.snp.makeConstraints { make in make.width.equalTo(75) make.height.equalTo(32) - + make.centerY.trailing.equalToSuperview() } } @@ -84,7 +83,7 @@ extension BlockUserListSectionCell: Inputable { var userID: String? var isBlocked: Bool } - + func injection(with input: Input) { profileImageView.setPPImage(path: input.profileImagePath) nickNameLabel.setLineHeightText(text: input.nickName, font: .KorFont(style: .bold, size: 14)) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Block/View/BlockUserManageView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Block/View/BlockUserManageView.swift index f217bc90..f3fb7ef3 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Block/View/BlockUserManageView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Block/View/BlockUserManageView.swift @@ -10,19 +10,18 @@ import UIKit import SnapKit final class BlockUserManageView: UIView { - + // MARK: - Components let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() view.headerLabel.setLineHeightText(text: "차단한 사용자 관리", font: .KorFont(style: .regular, size: 15)) return view }() - + let contentCollectionView: UICollectionView = { - let view = UICollectionView(frame: .zero, collectionViewLayout: .init()) - return view + return UICollectionView(frame: .zero, collectionViewLayout: .init()) }() - + let emptyLabel: PPLabel = { let label = PPLabel(style: .medium, fontSize: 14, text: "차단한 사용자가 없어요") label.textColor = .g400 @@ -33,7 +32,7 @@ final class BlockUserManageView: UIView { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -41,19 +40,19 @@ final class BlockUserManageView: UIView { // MARK: - SetUp private extension BlockUserManageView { - + func setUpConstraints() { self.addSubview(headerView) headerView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() } - + self.addSubview(contentCollectionView) contentCollectionView.snp.makeConstraints { make in make.top.equalTo(headerView.snp.bottom) make.leading.trailing.bottom.equalToSuperview() } - + self.addSubview(emptyLabel) emptyLabel.snp.makeConstraints { make in make.top.equalTo(headerView.snp.bottom).offset(137) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/MyPageBookmarkController.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/MyPageBookmarkController.swift index 413df27f..5b24ba1c 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/MyPageBookmarkController.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/MyPageBookmarkController.swift @@ -7,28 +7,28 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit final class MyPageBookmarkController: BaseViewController, View { - + typealias Reactor = MyPageBookmarkReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = MyPageBookmarkView() - + private var sections: [any Sectionable] = [] - + private var cellTapped: PublishSubject = .init() - + private var currentPageIndex: Int = 0 - + private var maxPageIndex: Int = 0 - + private var viewType: String? } @@ -38,7 +38,7 @@ extension MyPageBookmarkController { super.viewDidLoad() setUp() } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tabBarController?.tabBar.isHidden = true @@ -64,7 +64,7 @@ private extension MyPageBookmarkController { mainView.contentCollectionView.register( ListCountButtonSectionCell.self, forCellWithReuseIdentifier: ListCountButtonSectionCell.identifiers - ) + ) mainView.contentCollectionView.register( PopUpCardSectionCell.self, forCellWithReuseIdentifier: PopUpCardSectionCell.identifiers @@ -84,7 +84,7 @@ extension MyPageBookmarkController { .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.headerView.backButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -92,7 +92,7 @@ extension MyPageBookmarkController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + cellTapped .withUnretained(self) .map { (owner, row) in @@ -100,7 +100,7 @@ extension MyPageBookmarkController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.emptyButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -108,7 +108,7 @@ extension MyPageBookmarkController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.countButtonView.dropdownButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -116,7 +116,7 @@ extension MyPageBookmarkController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.contentCollectionView.rx.gesture(.swipe(direction: .up)) .skip(1) .withUnretained(self) @@ -139,12 +139,12 @@ extension MyPageBookmarkController { } owner.currentPageIndex += 1 owner.mainView.contentCollectionView.scrollToItem(at: .init(row: owner.currentPageIndex, section: 1), at: .top, animated: true) - + } } } .disposed(by: disposeBag) - + mainView.contentCollectionView.rx.gesture(.swipe(direction: .down)) .skip(1) .withUnretained(self) @@ -157,7 +157,7 @@ extension MyPageBookmarkController { } } .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -168,11 +168,11 @@ extension MyPageBookmarkController { owner.mainView.emptyButton.isHidden = !state.isEmptyCase owner.mainView.countButtonView.buttonTitleLabel.setLineHeightText(text: state.buttonTitle, font: .KorFont(style: .regular, size: 13)) owner.mainView.countButtonView.countLabel.setLineHeightText(text: "총 \(state.count)개", font: .KorFont(style: .regular, size: 13)) - + if state.buttonTitle != owner.viewType { owner.mainView.contentCollectionView.scrollsToTop = true } - + if state.buttonTitle == "크게보기" { owner.mainView.contentCollectionView.isScrollEnabled = false if owner.viewType == "모아서보기" { @@ -182,7 +182,7 @@ extension MyPageBookmarkController { } else { owner.mainView.contentCollectionView.isScrollEnabled = true } - + owner.maxPageIndex = Int(state.count) owner.viewType = state.buttonTitle } @@ -195,11 +195,11 @@ extension MyPageBookmarkController: UICollectionViewDelegate, UICollectionViewDa func numberOfSections(in collectionView: UICollectionView) -> Int { return sections.count } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sections[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath @@ -212,7 +212,7 @@ extension MyPageBookmarkController: UICollectionViewDelegate, UICollectionViewDa .bind(to: reactor.action) .disposed(by: cell.disposeBag) } - + if let cell = cell as? DetailSimilarSectionCell { cell.bookMarkButton.rx.tap .map { Reactor.Action.bookMarkButtonTapped(row: indexPath.row) } @@ -221,7 +221,7 @@ extension MyPageBookmarkController: UICollectionViewDelegate, UICollectionViewDa } return cell } - + func scrollViewDidScroll(_ scrollView: UIScrollView) { let contentHeight = scrollView.contentSize.height let scrollViewHeight = scrollView.frame.size.height @@ -230,7 +230,7 @@ extension MyPageBookmarkController: UICollectionViewDelegate, UICollectionViewDa reactor?.action.onNext(.changePage) } } - + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if indexPath.section == 1 { cellTapped.onNext(indexPath.row) } } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/MyPageBookmarkReactor.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/MyPageBookmarkReactor.swift index a9ffdfee..077b4506 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/MyPageBookmarkReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/MyPageBookmarkReactor.swift @@ -8,11 +8,11 @@ import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class MyPageBookmarkReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear @@ -23,7 +23,7 @@ final class MyPageBookmarkReactor: Reactor { case emptyButtonTapped(controller: BaseViewController) case bookMarkButtonTapped(row: Int) } - + enum Mutation { case loadView case skip @@ -32,7 +32,7 @@ final class MyPageBookmarkReactor: Reactor { case presentModal(controller: BaseViewController) case moveToSuggestScene(controller: BaseViewController) } - + struct State { var sections: [any Sectionable] = [] var isReloadView: Bool = false @@ -40,9 +40,9 @@ final class MyPageBookmarkReactor: Reactor { var count: Int32 = 0 var buttonTitle: String? } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() private var isLoading: Bool = false @@ -51,9 +51,9 @@ final class MyPageBookmarkReactor: Reactor { private var currentPage: Int32 = 0 private var size: Int32 = 10 private var viewType: String = "크게보기" - + private let userAPIUseCase = UserAPIUseCaseImpl(repository: UserAPIRepositoryImpl(provider: ProviderImpl())) - + lazy var compositionalLayout: UICollectionViewCompositionalLayout = { UICollectionViewCompositionalLayout { [weak self] section, env in guard let self = self else { @@ -67,17 +67,17 @@ final class MyPageBookmarkReactor: Reactor { return getSection()[section].getSection(section: section, env: env) } }() - + private var listSection = RecentPopUpSection(inputDataList: []) private var cardListSection = PopUpCardSection(inputDataList: []) private var spacing12Section = SpacingSection(inputDataList: [.init(spacing: 12)]) private var spacing150Section = SpacingSection(inputDataList: [.init(spacing: 150)]) - + // MARK: - init init() { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -165,7 +165,7 @@ final class MyPageBookmarkReactor: Reactor { } } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state newState.isReloadView = false @@ -204,7 +204,7 @@ final class MyPageBookmarkReactor: Reactor { newState.buttonTitle = viewType return newState } - + func getSection() -> [any Sectionable] { return [ spacing12Section, diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/MyPageBookmarkView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/MyPageBookmarkView.swift index e6cb99f7..8c5a7f1d 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/MyPageBookmarkView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/MyPageBookmarkView.swift @@ -10,14 +10,14 @@ import UIKit import SnapKit final class MyPageBookmarkView: UIView { - + // MARK: - Components let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() view.headerLabel.setLineHeightText(text: "찜한 팝업", font: .KorFont(style: .regular, size: 15)) return view }() - + let contentCollectionView: UICollectionView = { let view = UICollectionView(frame: .zero, collectionViewLayout: .init()) view.backgroundColor = .g50 @@ -25,39 +25,38 @@ final class MyPageBookmarkView: UIView { view.isPrefetchingEnabled = true return view }() - + let emptyLabel: PPLabel = { let label = PPLabel(style: .medium, fontSize: 14, text: "앗! 아직 찜해둔 팝업이 없어요") label.textColor = .g400 label.isHidden = true return label }() - + let countButtonView: CountButtonView = { - let view = CountButtonView() - return view + return CountButtonView() }() - + let emptyButton: UIButton = { let button = UIButton() let buttonTitle = NSAttributedString( string: "추천 팝업 보러가기", attributes: [ - .font : UIFont.KorFont(style: .regular, size: 13)!, - .underlineStyle : NSUnderlineStyle.single.rawValue, - .foregroundColor : UIColor.g1000 + .font: UIFont.KorFont(style: .regular, size: 13)!, + .underlineStyle: NSUnderlineStyle.single.rawValue, + .foregroundColor: UIColor.g1000 ] ) button.setAttributedTitle(buttonTitle, for: .normal) return button }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -65,7 +64,7 @@ final class MyPageBookmarkView: UIView { // MARK: - SetUp private extension MyPageBookmarkView { - + func setUpConstraints() { self.addSubview(headerView) headerView.snp.makeConstraints { make in @@ -82,13 +81,13 @@ private extension MyPageBookmarkView { make.top.equalTo(countButtonView.snp.bottom).offset(16) make.leading.trailing.bottom.equalToSuperview() } - + self.addSubview(emptyLabel) emptyLabel.snp.makeConstraints { make in make.centerX.equalToSuperview() make.top.equalToSuperview().inset(245) } - + self.addSubview(emptyButton) emptyButton.snp.makeConstraints { make in make.top.equalTo(emptyLabel.snp.bottom).offset(26) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/CountButtonView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/CountButtonView.swift index b36c7257..7d2168e8 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/CountButtonView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/CountButtonView.swift @@ -10,35 +10,33 @@ import UIKit import SnapKit final class CountButtonView: UIView { - + // MARK: - Components let countLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 13) label.textColor = .g400 return label }() - + private let dropDownImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_dropdown") return view }() - + let buttonTitleLabel: UILabel = { - let label = UILabel() - return label + return UILabel() }() - + let dropdownButton: UIButton = { - let button = UIButton() - return button + return UIButton() }() // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -46,27 +44,27 @@ final class CountButtonView: UIView { // MARK: - SetUp private extension CountButtonView { - + func setUpConstraints() { self.addSubview(countLabel) countLabel.snp.makeConstraints { make in make.leading.equalToSuperview() make.centerY.equalToSuperview() } - + dropdownButton.addSubview(dropDownImageView) dropDownImageView.snp.makeConstraints { make in make.size.equalTo(22) make.top.trailing.bottom.equalToSuperview() } - + dropdownButton.addSubview(buttonTitleLabel) buttonTitleLabel.snp.makeConstraints { make in make.leading.equalToSuperview() make.trailing.equalTo(dropDownImageView.snp.leading).offset(-6) make.centerY.equalToSuperview() } - + self.addSubview(dropdownButton) dropdownButton.snp.makeConstraints { make in make.trailing.equalToSuperview() diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/PopUpCardSection/PopUpCardSection.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/PopUpCardSection/PopUpCardSection.swift index d71ea811..bb6347c8 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/PopUpCardSection/PopUpCardSection.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/PopUpCardSection/PopUpCardSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct PopUpCardSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = PopUpCardSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/PopUpCardSection/PopUpCardSectionCell.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/PopUpCardSection/PopUpCardSectionCell.swift index 8812d5b0..a957011f 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/PopUpCardSection/PopUpCardSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/PopUpCardSection/PopUpCardSectionCell.swift @@ -7,12 +7,12 @@ import UIKit -import SnapKit -import RxSwift import RxCocoa +import RxSwift +import SnapKit final class PopUpCardSectionCell: UICollectionViewCell { - + // MARK: - Components let imageView: UIImageView = { let view = UIImageView() @@ -20,35 +20,34 @@ final class PopUpCardSectionCell: UICollectionViewCell { view.clipsToBounds = true return view }() - + private let dateLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 11) label.font = .EngFont(style: .regular, size: 11) label.textColor = .g1000 return label }() - + private let titleLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 12) label.numberOfLines = 2 return label }() - + private let addressLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 12) label.textColor = .g400 return label }() - + let bookMarkButton: UIButton = { - let button = UIButton() - return button + return UIButton() }() - + private let trailingView: UIView = UIView() - + var disposeBag = DisposeBag() - + // MARK: - init override init(frame: CGRect) { super.init(frame: frame) @@ -59,40 +58,40 @@ final class PopUpCardSectionCell: UICollectionViewCell { addHolesToCell() setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() } - + private func addHolesToCell() { // 전체 영역 경로 let fullPath = UIBezierPath(roundedRect: contentView.bounds, cornerRadius: 4) - + // 왼쪽 아래와 오른쪽 아래 구멍을 뚫을 위치 설정 (이미지뷰의 frame 위치 고려) let leftHoleCenter = CGPoint(x: contentView.bounds.minX, y: 423) let rightHoleCenter = CGPoint(x: contentView.bounds.maxX, y: 423) - + // 구멍을 만드는 경로 생성 (반지름 6) let leftHolePath = UIBezierPath(arcCenter: leftHoleCenter, radius: 12, startAngle: -.pi / 2, endAngle: .pi / 2, clockwise: true) let rightHolePath = UIBezierPath(arcCenter: rightHoleCenter, radius: 12, startAngle: .pi / 2, endAngle: -.pi / 2, clockwise: true) - + // 구멍 경로를 전체 경로에서 빼기 fullPath.append(leftHolePath) fullPath.append(rightHolePath) fullPath.usesEvenOddFillRule = true - + // 기존에 구멍을 뚫을 경로를 추가하는 레이어 let holeLayer = CAShapeLayer() holeLayer.path = fullPath.cgPath holeLayer.fillRule = .evenOdd holeLayer.fillColor = UIColor.black.cgColor trailingView.layer.mask = holeLayer - + // 그림자 Layer let shadowLayer = CAShapeLayer() shadowLayer.path = fullPath.cgPath @@ -109,7 +108,7 @@ final class PopUpCardSectionCell: UICollectionViewCell { // MARK: - SetUp private extension PopUpCardSectionCell { func setUpConstraints() { - + contentView.addSubview(trailingView) trailingView.snp.makeConstraints { make in make.edges.equalToSuperview() @@ -119,26 +118,26 @@ private extension PopUpCardSectionCell { make.top.leading.trailing.equalToSuperview() make.height.equalTo(423) } - + trailingView.addSubview(bookMarkButton) bookMarkButton.snp.makeConstraints { make in make.size.equalTo(36) make.top.trailing.equalToSuperview().inset(20) } - + trailingView.addSubview(dateLabel) dateLabel.snp.makeConstraints { make in make.top.equalTo(imageView.snp.bottom).offset(24) make.centerX.equalToSuperview() } - + trailingView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.top.equalTo(dateLabel.snp.bottom).offset(20) make.leading.trailing.equalToSuperview().inset(20) make.height.equalTo(44) } - + trailingView.addSubview(addressLabel) addressLabel.snp.makeConstraints { make in make.leading.trailing.equalToSuperview().inset(20) @@ -156,7 +155,7 @@ extension PopUpCardSectionCell: Inputable { var address: String? var isBookMark: Bool } - + func injection(with input: Input) { let date = input.date ?? "" imageView.setPPImage(path: input.imagePath) @@ -165,7 +164,7 @@ extension PopUpCardSectionCell: Inputable { titleLabel.textAlignment = .center addressLabel.setLineHeightText(text: input.address, font: .KorFont(style: .regular, size: 14)) addressLabel.textAlignment = .center - + if input.isBookMark { bookMarkButton.setImage(UIImage(named: "icon_bookmark_fill"), for: .normal) } else { diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/PopUpCardSection/PopUpCardView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/PopUpCardSection/PopUpCardView.swift index 7b4378b6..41ffc78c 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/PopUpCardSection/PopUpCardView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/PopUpCardSection/PopUpCardView.swift @@ -7,12 +7,12 @@ import UIKit -import SnapKit -import RxSwift import RxCocoa +import RxSwift +import SnapKit final class PopUpCardView: UIView { - + // MARK: - Components let imageView: UIImageView = { let view = UIImageView() @@ -20,36 +20,34 @@ final class PopUpCardView: UIView { view.clipsToBounds = true return view }() - + let contentView: UIView = UIView() - + private let dateLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 11) label.font = .EngFont(style: .regular, size: 11) label.textColor = .g1000 return label }() - + private let titleLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 12) - return label + return PPLabel(style: .bold, fontSize: 12) }() - + private let addressLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 12) label.textColor = .g400 return label }() - + let bookMarkButton: UIButton = { - let button = UIButton() - return button + return UIButton() }() - + private let trailingView: UIView = UIView() - + var disposeBag = DisposeBag() - + // MARK: - init init() { super.init(frame: .zero) @@ -59,40 +57,40 @@ final class PopUpCardView: UIView { trailingView.layer.cornerRadius = 4 setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } - + override func layoutSubviews() { super.layoutSubviews() addHolesToCell() } - + private func addHolesToCell() { // 전체 영역 경로 let fullPath = UIBezierPath(roundedRect: bounds, cornerRadius: 4) - + // 왼쪽 아래와 오른쪽 아래 구멍을 뚫을 위치 설정 (이미지뷰의 frame 위치 고려) let leftHoleCenter = CGPoint(x: bounds.minX, y: 423) let rightHoleCenter = CGPoint(x: bounds.maxX, y: 423) - + // 구멍을 만드는 경로 생성 (반지름 6) let leftHolePath = UIBezierPath(arcCenter: leftHoleCenter, radius: 12, startAngle: -.pi / 2, endAngle: .pi / 2, clockwise: true) let rightHolePath = UIBezierPath(arcCenter: rightHoleCenter, radius: 12, startAngle: .pi / 2, endAngle: -.pi / 2, clockwise: true) - + // 구멍 경로를 전체 경로에서 빼기 fullPath.append(leftHolePath) fullPath.append(rightHolePath) fullPath.usesEvenOddFillRule = true - + // 기존에 구멍을 뚫을 경로를 추가하는 레이어 let holeLayer = CAShapeLayer() holeLayer.path = fullPath.cgPath holeLayer.fillRule = .evenOdd holeLayer.fillColor = UIColor.black.cgColor trailingView.layer.mask = holeLayer - + // 그림자 Layer let shadowLayer = CAShapeLayer() shadowLayer.path = fullPath.cgPath @@ -109,7 +107,7 @@ final class PopUpCardView: UIView { // MARK: - SetUp private extension PopUpCardView { func setUpConstraints() { - + self.addSubview(trailingView) trailingView.snp.makeConstraints { make in make.edges.equalToSuperview() @@ -119,25 +117,25 @@ private extension PopUpCardView { make.top.leading.trailing.equalToSuperview() make.height.equalTo(423) } - + trailingView.addSubview(bookMarkButton) bookMarkButton.snp.makeConstraints { make in make.size.equalTo(36) make.top.trailing.equalToSuperview().inset(20) } - + trailingView.addSubview(dateLabel) dateLabel.snp.makeConstraints { make in make.top.equalTo(imageView.snp.bottom).offset(24) make.centerX.equalToSuperview() } - + trailingView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.centerX.equalToSuperview() make.top.equalTo(dateLabel.snp.bottom).offset(20) } - + trailingView.addSubview(addressLabel) addressLabel.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(16) @@ -156,14 +154,14 @@ extension PopUpCardView: Inputable { var address: String? var isBookMark: Bool } - + func injection(with input: Input) { let date = input.date ?? "" imageView.setPPImage(path: input.imagePath) dateLabel.setLineHeightText(text: date, font: .EngFont(style: .regular, size: 13)) titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .bold, size: 16)) addressLabel.setLineHeightText(text: input.address, font: .KorFont(style: .regular, size: 14)) - + if input.isBookMark { bookMarkButton.setImage(UIImage(named: "icon_bookmark_fill"), for: .normal) } else { diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/ViewTypeModal/BookMarkPopUpViewTypeModalController.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/ViewTypeModal/BookMarkPopUpViewTypeModalController.swift index 9616ae96..67a61a01 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/ViewTypeModal/BookMarkPopUpViewTypeModalController.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/ViewTypeModal/BookMarkPopUpViewTypeModalController.swift @@ -7,19 +7,19 @@ import UIKit -import SnapKit +import PanModal +import ReactorKit import RxCocoa import RxSwift -import ReactorKit -import PanModal +import SnapKit final class BookMarkPopUpViewTypeModalController: BaseViewController, View { - + typealias Reactor = BookMarkPopUpViewTypeModalReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = BookMarkPopUpViewTypeModalView() } @@ -48,13 +48,13 @@ extension BookMarkPopUpViewTypeModalController { .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.sortedSegmentControl.rx.selectedSegmentIndex .skip(1) .map { Reactor.Action.selectedSegmentControl(row: $0) } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.saveButton.rx.tap .withUnretained(self) .compactMap { (owner, _) in @@ -62,7 +62,7 @@ extension BookMarkPopUpViewTypeModalController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.xmarkButton.rx.tap .withUnretained(self) .compactMap { (owner, _) in @@ -70,7 +70,7 @@ extension BookMarkPopUpViewTypeModalController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -92,7 +92,7 @@ extension BookMarkPopUpViewTypeModalController: PanModalPresentable { var panScrollable: UIScrollView? { return nil } - + var longFormHeight: PanModalHeight { return .contentHeight(250) } @@ -106,4 +106,3 @@ extension BookMarkPopUpViewTypeModalController: PanModalPresentable { return 20 } } - diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/ViewTypeModal/BookMarkPopUpViewTypeModalReactor.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/ViewTypeModal/BookMarkPopUpViewTypeModalReactor.swift index 8bd33888..8d879bdd 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/ViewTypeModal/BookMarkPopUpViewTypeModalReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/ViewTypeModal/BookMarkPopUpViewTypeModalReactor.swift @@ -6,11 +6,11 @@ // import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class BookMarkPopUpViewTypeModalReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear @@ -18,13 +18,13 @@ final class BookMarkPopUpViewTypeModalReactor: Reactor { case saveButtonTapped(controller: BaseViewController) case xmarkButtonTapped(controller: BaseViewController) } - + enum Mutation { case loadView case setSelectedIndex(row: Int) case dismissScene(controller: BaseViewController, isSave: Bool) } - + struct State { var isSetView: Bool = false var originSortedCode: String @@ -32,18 +32,17 @@ final class BookMarkPopUpViewTypeModalReactor: Reactor { var saveButtonIsEnabled: Bool = false var isSave: Bool = false } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - - + // MARK: - init init(sortedCode: String) { self.initialState = State(originSortedCode: sortedCode) } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -57,7 +56,7 @@ final class BookMarkPopUpViewTypeModalReactor: Reactor { return Observable.just(.dismissScene(controller: controller, isSave: false)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state newState.isSetView = false diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/ViewTypeModal/BookMarkPopUpViewTypeModalView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/ViewTypeModal/BookMarkPopUpViewTypeModalView.swift index 8604d089..f57b0988 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/ViewTypeModal/BookMarkPopUpViewTypeModalView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/ViewTypeModal/BookMarkPopUpViewTypeModalView.swift @@ -10,35 +10,32 @@ import UIKit import SnapKit final class BookMarkPopUpViewTypeModalView: UIView { - + // MARK: - Components private let titleLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 18, text: "보기 옵션을 선택해주세요") - return label + return PPLabel(style: .bold, fontSize: 18, text: "보기 옵션을 선택해주세요") }() - + let xmarkButton: UIButton = { let button = UIButton() button.setImage(UIImage(named: "icon_xmark"), for: .normal) return button }() - + let sortedSegmentControl: PPSegmentedControl = { - let control = PPSegmentedControl(type: .base, segments: ["크게보기","모아서보기"]) - return control + return PPSegmentedControl(type: .base, segments: ["크게보기", "모아서보기"]) }() - + let saveButton: PPButton = { - let button = PPButton(style: .primary, text: "저장", disabledText: "저장") - return button + return PPButton(style: .primary, text: "저장", disabledText: "저장") }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -46,33 +43,33 @@ final class BookMarkPopUpViewTypeModalView: UIView { // MARK: - SetUp private extension BookMarkPopUpViewTypeModalView { - + func setUpConstraints() { self.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.leading.equalToSuperview().inset(20) make.top.equalToSuperview().inset(32) } - + self.addSubview(xmarkButton) xmarkButton.snp.makeConstraints { make in make.size.equalTo(24) make.trailing.equalToSuperview().inset(20) make.centerY.equalTo(titleLabel) } - + self.addSubview(sortedSegmentControl) sortedSegmentControl.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(36) make.leading.trailing.equalToSuperview().inset(20) make.height.equalTo(48) } - + self.addSubview(saveButton) saveButton.snp.makeConstraints { make in make.leading.trailing.bottom.equalToSuperview().inset(20) make.height.equalTo(50) } - + } } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/FAQController.swift b/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/FAQController.swift index dbdb455d..3057e6d8 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/FAQController.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/FAQController.swift @@ -7,18 +7,18 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit final class FAQController: BaseViewController, View { - + typealias Reactor = FAQReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = FAQView() private var sections: [any Sectionable] = [] } @@ -29,7 +29,7 @@ extension FAQController { super.viewDidLoad() setUp() } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tabBarController?.tabBar.isHidden = true @@ -51,11 +51,11 @@ private extension FAQController { mainView.contentCollectionView.register( MyPageMyCommentTitleSectionCell.self, forCellWithReuseIdentifier: MyPageMyCommentTitleSectionCell.identifiers - ) + ) mainView.contentCollectionView.register( MyPageListSectionCell.self, forCellWithReuseIdentifier: MyPageListSectionCell.identifiers - ) + ) mainView.contentCollectionView.register( FAQDropdownSectionCell.self, forCellWithReuseIdentifier: FAQDropdownSectionCell.identifiers @@ -78,12 +78,12 @@ extension FAQController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + rx.viewWillAppear .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -99,11 +99,11 @@ extension FAQController: UICollectionViewDelegate, UICollectionViewDataSource { func numberOfSections(in collectionView: UICollectionView) -> Int { return sections.count } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sections[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath @@ -118,7 +118,7 @@ extension FAQController: UICollectionViewDelegate, UICollectionViewDataSource { } return cell } - + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if let cell = collectionView.cellForItem(at: indexPath) as? MyPageListSectionCell { reactor?.action.onNext(.mailInquiryCellTapped(controller: self)) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/FAQReactor.swift b/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/FAQReactor.swift index 1f32d7e2..ba9947a4 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/FAQReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/FAQReactor.swift @@ -8,11 +8,11 @@ import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class FAQReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear @@ -20,22 +20,22 @@ final class FAQReactor: Reactor { case backButtonTapped(controller: BaseViewController) case mailInquiryCellTapped(controller: BaseViewController) } - + enum Mutation { case loadView case moveToRecentScene(controller: BaseViewController) case moveToMailApp(controller: BaseViewController) } - + struct State { var sections: [any Sectionable] = [] } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - + lazy var compositionalLayout: UICollectionViewCompositionalLayout = { UICollectionViewCompositionalLayout { [weak self] section, env in guard let self = self else { @@ -80,7 +80,7 @@ final class FAQReactor: Reactor { title: "고객센터 상담은 어디서 할 수 있나요?", content: "[마이페이지 > 고객문의 > 메일로 문의]에서 할 수 있으며, 주말, 공휴일을 제외한 평일 오전 9시부터 오후 6시까지 운영해요.", isOpen: false - ), + ) ]) private let qnaTitleSection = MyPageMyCommentTitleSection(inputDataList: [.init(title: "직접 문의하기")]) @@ -94,7 +94,7 @@ final class FAQReactor: Reactor { init() { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -109,7 +109,7 @@ final class FAQReactor: Reactor { return Observable.just(.moveToMailApp(controller: controller)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { @@ -128,27 +128,27 @@ final class FAQReactor: Reactor { } return newState } - + func showMailAppRecoveryAlert(controller: BaseViewController) { - + let alert = UIAlertController( title: "'Mail' 앱을 복원하겠습니까?", message: "계속하려면 App Store에서 'Mail' 앱을\n다운로드하십시오", preferredStyle: .alert ) - + alert.addAction(UIAlertAction(title: "App Store로 이동", style: .default, handler: { _ in // 📌 App Store의 메일 앱 복구 페이지 열기 if let mailAppURL = URL(string: "itms-apps://itunes.apple.com/app/id1108187098") { UIApplication.shared.open(mailAppURL, options: [:], completionHandler: nil) } })) - + alert.addAction(UIAlertAction(title: "취소", style: .cancel, handler: nil)) - + controller.present(alert, animated: true, completion: nil) } - + func getSection() -> [any Sectionable] { return [ spacing24Section, diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/View/FAQDropdownSection/FAQDropdownSection.swift b/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/View/FAQDropdownSection/FAQDropdownSection.swift index 715c815e..2ba04ea5 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/View/FAQDropdownSection/FAQDropdownSection.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/View/FAQDropdownSection/FAQDropdownSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct FAQDropdownSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = FAQDropdownSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -37,7 +37,7 @@ struct FAQDropdownSection: Sectionable { // 섹션 생성 let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0) - + return section } } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/View/FAQDropdownSection/FAQDropdownSectionCell.swift b/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/View/FAQDropdownSection/FAQDropdownSectionCell.swift index b8910b60..57a7e833 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/View/FAQDropdownSection/FAQDropdownSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/View/FAQDropdownSection/FAQDropdownSectionCell.swift @@ -7,43 +7,41 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class FAQDropdownSectionCell: UICollectionViewCell { - + // MARK: - Components let contentStackView: UIStackView = { let view = UIStackView() view.axis = .vertical return view }() - + let listContentButton = UIButton() - + let qLabel: UILabel = { let label = UILabel() label.setLineHeightText(text: "Q", font: .EngFont(style: .bold, size: 16), lineHeight: 1) label.textColor = .blu500 return label }() - + let titleLabel: UILabel = { - let label = UILabel() - return label + return UILabel() }() - + let dropDownImageView: UIImageView = { - let view = UIImageView() - return view + return UIImageView() }() - + let dropContentView: UIView = { let view = UIView() view.backgroundColor = .pb4 return view }() - + let dropContentLabel: UILabel = { let label = UILabel() label.numberOfLines = 0 @@ -51,16 +49,16 @@ final class FAQDropdownSectionCell: UICollectionViewCell { }() var disposeBag = DisposeBag() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() @@ -74,7 +72,7 @@ private extension FAQDropdownSectionCell { contentStackView.snp.makeConstraints { make in make.edges.equalToSuperview() } - + listContentButton.snp.makeConstraints { make in make.height.equalTo(59).priority(.high) } @@ -94,7 +92,7 @@ private extension FAQDropdownSectionCell { make.centerY.equalToSuperview() make.trailing.equalToSuperview().inset(20) } - + dropContentView.addSubview(dropContentLabel) dropContentLabel.snp.makeConstraints { make in make.top.bottom.equalToSuperview().inset(16) @@ -112,7 +110,7 @@ extension FAQDropdownSectionCell: Inputable { var content: String? var isOpen: Bool } - + func injection(with input: Input) { titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .medium, size: 14)) dropContentLabel.setLineHeightText(text: input.content, font: .KorFont(style: .regular, size: 14), lineHeight: 1.5) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/View/FAQView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/View/FAQView.swift index 0ecc6ab2..93a270ec 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/View/FAQView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/View/FAQView.swift @@ -10,26 +10,26 @@ import UIKit import SnapKit final class FAQView: UIView { - + // MARK: - Components let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() view.headerLabel.setLineHeightText(text: "고객문의", font: .KorFont(style: .regular, size: 15)) return view }() - + let contentCollectionView: UICollectionView = { let view = UICollectionView(frame: .zero, collectionViewLayout: .init()) view.backgroundColor = .g50 return view }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -37,13 +37,13 @@ final class FAQView: UIView { // MARK: - SetUp private extension FAQView { - + func setUpConstraints() { self.addSubview(headerView) headerView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() } - + self.addSubview(contentCollectionView) contentCollectionView.snp.makeConstraints { make in make.top.equalTo(headerView.snp.bottom) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Main/MyPageController.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Main/MyPageController.swift index 14b45244..e699c2d3 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Main/MyPageController.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Main/MyPageController.swift @@ -7,42 +7,42 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit final class MyPageController: BaseViewController, View { - + typealias Reactor = MyPageReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = MyPageView() - + private let headerView: UIView = { let view = UIView() view.backgroundColor = .w100 view.alpha = 0 return view }() - + private let settingButton: UIButton = { let button = UIButton(type: .system) button.setImage(UIImage(named: "icon_gear_white"), for: .normal) - button.tintColor = .g1000 + button.tintColor = .g1000 return button }() - + private var sections: [any Sectionable] = [] private var commentCellTapped: PublishSubject = .init() private var listCellTapped: PublishSubject = .init() - + private var isBrightImage: Bool = false - + private var scrollAlpha: CGFloat = 0 - + } // MARK: - Life Cycle @@ -51,7 +51,7 @@ extension MyPageController { super.viewDidLoad() setUp() } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tabBarController?.tabBar.isHidden = false @@ -61,7 +61,7 @@ extension MyPageController { // MARK: - SetUp private extension MyPageController { func setUp() { - + if let layout = reactor?.compositionalLayout { mainView.contentCollectionView.collectionViewLayout = layout } @@ -74,19 +74,19 @@ private extension MyPageController { mainView.contentCollectionView.register( MyPageProfileSectionCell.self, forCellWithReuseIdentifier: MyPageProfileSectionCell.identifiers - ) + ) mainView.contentCollectionView.register( MyPageMyCommentTitleSectionCell.self, forCellWithReuseIdentifier: MyPageMyCommentTitleSectionCell.identifiers - ) + ) mainView.contentCollectionView.register( MyPageCommentSectionCell.self, forCellWithReuseIdentifier: MyPageCommentSectionCell.identifiers - ) + ) mainView.contentCollectionView.register( MyPageListSectionCell.self, forCellWithReuseIdentifier: MyPageListSectionCell.identifiers - ) + ) mainView.contentCollectionView.register( MyPageLogoutSectionCell.self, forCellWithReuseIdentifier: MyPageLogoutSectionCell.identifiers @@ -95,13 +95,13 @@ private extension MyPageController { mainView.snp.makeConstraints { make in make.edges.equalToSuperview() } - + view.addSubview(headerView) headerView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() make.bottom.equalTo(view.safeAreaLayoutGuide.snp.top).offset(44) } - + view.addSubview(settingButton) settingButton.snp.makeConstraints { make in make.trailing.equalTo(headerView.snp.trailing).inset(16) @@ -118,7 +118,7 @@ extension MyPageController { .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + commentCellTapped .withUnretained(self) .map { (owner, index) in @@ -126,7 +126,7 @@ extension MyPageController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + listCellTapped .withUnretained(self) .map { (owner, title) in @@ -134,7 +134,7 @@ extension MyPageController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + settingButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -142,18 +142,17 @@ extension MyPageController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in owner.settingButton.isHidden = !state.isLogin owner.sections = state.sections owner.mainView.contentCollectionView.reloadData() - } .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -171,7 +170,7 @@ extension MyPageController { owner.settingButton.tintColor = .w100 owner.systemStatusBarIsDark.accept(false) } - + } } } @@ -185,18 +184,18 @@ extension MyPageController: UICollectionViewDelegate, UICollectionViewDataSource func numberOfSections(in collectionView: UICollectionView) -> Int { return sections.count } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sections[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath ) -> UICollectionViewCell { let cell = sections[indexPath.section].getCell(collectionView: collectionView, indexPath: indexPath) guard let reactor = reactor else { return cell } - + if let cell = cell as? MyPageProfileSectionCell { let originHeight = 162 + 49 + 44 + view.safeAreaInsets.top cell.updateHeight(height: originHeight) @@ -209,7 +208,7 @@ extension MyPageController: UICollectionViewDelegate, UICollectionViewDataSource .bind(to: reactor.action) .disposed(by: cell.disposeBag) } - + if let cell = cell as? MyPageMyCommentTitleSectionCell { cell.button.rx.tap .withUnretained(self) @@ -219,7 +218,7 @@ extension MyPageController: UICollectionViewDelegate, UICollectionViewDataSource .bind(to: reactor.action) .disposed(by: cell.disposeBag) } - + if let cell = cell as? MyPageLogoutSectionCell { cell.logoutButton.rx.tap .map { Reactor.Action.logoutButtonTapped } @@ -228,7 +227,7 @@ extension MyPageController: UICollectionViewDelegate, UICollectionViewDataSource } return cell } - + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if let cell = collectionView.cellForItem(at: indexPath) as? MyPageCommentSectionCell { commentCellTapped.onNext(indexPath.row) @@ -238,7 +237,7 @@ extension MyPageController: UICollectionViewDelegate, UICollectionViewDataSource listCellTapped.onNext(title) } } - + func scrollViewDidScroll(_ scrollView: UIScrollView) { if let cell = mainView.contentCollectionView.cellForItem(at: IndexPath(row: 0, section: 0)) as? MyPageProfileSectionCell { let contentOffsetY = scrollView.contentOffset.y diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Main/MyPageReactor.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Main/MyPageReactor.swift index b15851c9..7ac24f0b 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Main/MyPageReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Main/MyPageReactor.swift @@ -5,10 +5,10 @@ // Created by SeoJunYoung on 12/30/24. // -import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift +import UIKit final class MyPageReactor: Reactor { @@ -129,7 +129,7 @@ final class MyPageReactor: Reactor { ) ] // 내가 댓글 단 팝업 리스트 - owner.commentSection.inputDataList = response.myCommentedPopUpList.map { + owner.commentSection.inputDataList = response.myCommentedPopUpList.map { .init(popUpImagePath: $0.mainImageUrl, title: $0.popUpStoreName, popUpID: $0.popUpStoreId) } if !owner.commentSection.inputDataList.isEmpty { @@ -185,8 +185,8 @@ final class MyPageReactor: Reactor { case .logout: let service = KeyChainService() - let _ = service.deleteToken(type: .accessToken) - let _ = service.deleteToken(type: .refreshToken) + _ = service.deleteToken(type: .accessToken) + _ = service.deleteToken(type: .refreshToken) ToastMaker.createToast(message: "로그아웃 되었어요") DispatchQueue.main.async { [weak self] in self?.action.onNext(.viewWillAppear) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageCommentSection/MyPageCommentSection.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageCommentSection/MyPageCommentSection.swift index 274f90fb..f92f51fc 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageCommentSection/MyPageCommentSection.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageCommentSection/MyPageCommentSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct MyPageCommentSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = MyPageCommentSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .absolute(68), diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageCommentSection/MyPageCommentSectionCell.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageCommentSection/MyPageCommentSectionCell.swift index 2f17bf4c..72b73d0a 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageCommentSection/MyPageCommentSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageCommentSection/MyPageCommentSectionCell.swift @@ -7,11 +7,11 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class MyPageCommentSectionCell: UICollectionViewCell { - + // MARK: - Components private let firstBackgroundView: UIView = { let view = UIView() @@ -20,20 +20,20 @@ final class MyPageCommentSectionCell: UICollectionViewCell { view.clipsToBounds = true return view }() - + private let imageBackgroundView: UIView = { let view = UIView() view.backgroundColor = .w100 view.layer.cornerRadius = 31 return view }() - + private let gradientView: AnimatedGradientView = { let view = AnimatedGradientView() view.isHidden = true return view }() - + private let imageView: UIImageView = { let view = UIImageView() view.layer.cornerRadius = 28 @@ -41,20 +41,19 @@ final class MyPageCommentSectionCell: UICollectionViewCell { view.contentMode = .scaleAspectFill return view }() - + private let titleLabel: PPLabel = { - let label = PPLabel(style: .regular, fontSize: 11) - return label + return PPLabel(style: .regular, fontSize: 11) }() - + let disposeBag = DisposeBag() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } @@ -68,24 +67,24 @@ private extension MyPageCommentSectionCell { make.leading.trailing.top.equalToSuperview() make.height.equalTo(68) } - + firstBackgroundView.addSubview(gradientView) gradientView.snp.makeConstraints { make in make.edges.equalToSuperview() } - + firstBackgroundView.addSubview(imageBackgroundView) imageBackgroundView.snp.makeConstraints { make in make.size.equalTo(62) make.center.equalToSuperview() } - + imageBackgroundView.addSubview(imageView) imageView.snp.makeConstraints { make in make.size.equalTo(56) make.center.equalToSuperview() } - + contentView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.leading.trailing.bottom.equalToSuperview() @@ -100,12 +99,12 @@ extension MyPageCommentSectionCell: Inputable { var popUpID: Int64 var isFirstCell: Bool = false } - + func injection(with input: Input) { imageView.setPPImage(path: input.popUpImagePath) titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .regular, size: 11)) titleLabel.textAlignment = .center - + if input.isFirstCell { gradientView.isHidden = false } else { @@ -115,39 +114,39 @@ extension MyPageCommentSectionCell: Inputable { } class AnimatedGradientView: UIView { - + private let gradientLayer = CAGradientLayer() - + override init(frame: CGRect) { super.init(frame: frame) setupGradient() } - + required init?(coder: NSCoder) { super.init(coder: coder) setupGradient() } - + private func setupGradient() { // 초기 그라디언트 색상 설정 gradientLayer.colors = [ UIColor.init(hexCode: "#1570FC").cgColor, UIColor.init(hexCode: "#00E6BD").cgColor ] - + gradientLayer.startPoint = CGPoint(x: 0, y: 0) gradientLayer.endPoint = CGPoint(x: 1, y: 1) gradientLayer.frame = bounds layer.insertSublayer(gradientLayer, at: 0) - + animateGradient() } - + override func layoutSubviews() { super.layoutSubviews() gradientLayer.frame = bounds // 레이아웃 변경 시 반영 } - + private func animateGradient() { let animation = CABasicAnimation(keyPath: "colors") animation.fromValue = gradientLayer.colors @@ -158,7 +157,7 @@ class AnimatedGradientView: UIView { animation.duration = 1 // 색이 부드럽게 바뀌는 시간 animation.autoreverses = true // 원래 색으로 돌아가게 함 animation.repeatCount = .infinity // 무한 반복 - + gradientLayer.add(animation, forKey: "colorChange") } } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageListSection/MyPageListSection.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageListSection/MyPageListSection.swift index b31885e8..441344c6 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageListSection/MyPageListSection.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageListSection/MyPageListSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct MyPageListSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = MyPageListSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -37,7 +37,7 @@ struct MyPageListSection: Sectionable { // 섹션 생성 let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 0, leading: 20, bottom: 0, trailing: 20) - + return section } } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageListSection/MyPageListSectionCell.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageListSection/MyPageListSectionCell.swift index faa94ae6..31d71249 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageListSection/MyPageListSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageListSection/MyPageListSectionCell.swift @@ -7,33 +7,32 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class MyPageListSectionCell: UICollectionViewCell { - + // MARK: - Components let titleLabel = UILabel() - + private let rightImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_right_gray") return view }() - + private let subTitleLabel: UILabel = { - let label = UILabel() - return label + return UILabel() }() - + let disposeBag = DisposeBag() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } @@ -47,13 +46,13 @@ private extension MyPageListSectionCell { make.centerY.equalToSuperview() make.leading.equalToSuperview() } - + contentView.addSubview(rightImageView) rightImageView.snp.makeConstraints { make in make.trailing.centerY.equalToSuperview() make.size.equalTo(22) } - + contentView.addSubview(subTitleLabel) subTitleLabel.snp.makeConstraints { make in make.centerY.trailing.equalToSuperview() @@ -66,10 +65,10 @@ extension MyPageListSectionCell: Inputable { var title: String? var subTitle: String? } - + func injection(with input: Input) { titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .regular, size: 15)) - + if input.subTitle == nil { rightImageView.isHidden = false subTitleLabel.isHidden = true diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageLogoutSection/MyPageLogoutSection.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageLogoutSection/MyPageLogoutSection.swift index be5246e8..89795be7 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageLogoutSection/MyPageLogoutSection.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageLogoutSection/MyPageLogoutSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct MyPageLogoutSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = MyPageLogoutSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -37,7 +37,7 @@ struct MyPageLogoutSection: Sectionable { // 섹션 생성 let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 0, leading: 20, bottom: 0, trailing: 20) - + return section } } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageLogoutSection/MyPageLogoutSectionCell.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageLogoutSection/MyPageLogoutSectionCell.swift index dc46e212..3c737be7 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageLogoutSection/MyPageLogoutSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageLogoutSection/MyPageLogoutSectionCell.swift @@ -7,30 +7,29 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class MyPageLogoutSectionCell: UICollectionViewCell { - + // MARK: - Components let logoutButton: PPButton = { - let button = PPButton(style: .secondary, text: "로그아웃") - return button + return PPButton(style: .secondary, text: "로그아웃") }() - + var disposeBag = DisposeBag() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() @@ -51,7 +50,7 @@ extension MyPageLogoutSectionCell: Inputable { struct Input { } - + func injection(with input: Input) { } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageMyCommentTitleSection/MyPageMyCommentTitleSection.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageMyCommentTitleSection/MyPageMyCommentTitleSection.swift index e87b6626..20d2a2b2 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageMyCommentTitleSection/MyPageMyCommentTitleSection.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageMyCommentTitleSection/MyPageMyCommentTitleSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct MyPageMyCommentTitleSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = MyPageMyCommentTitleSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -37,7 +37,7 @@ struct MyPageMyCommentTitleSection: Sectionable { // 섹션 생성 let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 0, leading: 20, bottom: 0, trailing: 20) - + return section } } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageMyCommentTitleSection/MyPageMyCommentTitleSectionCell.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageMyCommentTitleSection/MyPageMyCommentTitleSectionCell.swift index f9820f1c..e6ccae2a 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageMyCommentTitleSection/MyPageMyCommentTitleSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageMyCommentTitleSection/MyPageMyCommentTitleSectionCell.swift @@ -7,34 +7,32 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class MyPageMyCommentTitleSectionCell: UICollectionViewCell { - + // MARK: - Components private let titleLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 16) - return label + return PPLabel(style: .bold, fontSize: 16) }() - + let button: UIButton = { - let button = UIButton() - return button + return UIButton() }() - + var disposeBag = DisposeBag() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() @@ -48,7 +46,7 @@ private extension MyPageMyCommentTitleSectionCell { titleLabel.snp.makeConstraints { make in make.centerY.leading.equalToSuperview() } - + contentView.addSubview(button) button.snp.makeConstraints { make in make.centerY.trailing.equalToSuperview() @@ -61,17 +59,17 @@ extension MyPageMyCommentTitleSectionCell: Inputable { var title: String? var buttonTitle: String? } - + func injection(with input: Input) { titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .bold, size: 16)) - + if input.buttonTitle != nil { let buttonTitle = NSAttributedString( string: input.buttonTitle ?? "", attributes: [ - .font : UIFont.KorFont(style: .regular, size: 13)!, - .underlineStyle : NSUnderlineStyle.single.rawValue, - .foregroundColor : UIColor.g600 + .font: UIFont.KorFont(style: .regular, size: 13)!, + .underlineStyle: NSUnderlineStyle.single.rawValue, + .foregroundColor: UIColor.g600 ] ) button.setAttributedTitle(buttonTitle, for: .normal) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageProfileSection/MyPageProfileSection.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageProfileSection/MyPageProfileSection.swift index 8276831c..430fae01 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageProfileSection/MyPageProfileSection.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageProfileSection/MyPageProfileSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct MyPageProfileSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = MyPageProfileSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -35,8 +35,7 @@ struct MyPageProfileSection: Sectionable { let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) // 섹션 생성 - let section = NSCollectionLayoutSection(group: group) - - return section + + return NSCollectionLayoutSection(group: group) } } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageProfileSection/MyPageProfileSectionCell.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageProfileSection/MyPageProfileSectionCell.swift index c7edecae..642118cf 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageProfileSection/MyPageProfileSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageProfileSection/MyPageProfileSectionCell.swift @@ -7,34 +7,30 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class MyPageProfileSectionCell: UICollectionViewCell { - + // MARK: - Components private let backGroundTrailingView: UIView = { - let view = UIView() - return view + return UIView() }() - + private let backGroundImageView: UIImageView = { - let view = UIImageView() - return view + return UIImageView() }() - + lazy var blurView: UIVisualEffectView = { let blurEffect = UIBlurEffect(style: .regular) - let view = UIVisualEffectView(effect: blurEffect) - return view + return UIVisualEffectView(effect: blurEffect) }() - + private let profileView: UIView = { - let view = UIView() - return view + return UIView() }() - + private let profileImageView: UIImageView = { let view = UIImageView() view.layer.cornerRadius = 32 @@ -42,42 +38,39 @@ final class MyPageProfileSectionCell: UICollectionViewCell { view.clipsToBounds = true return view }() - + private let bottomView: UIView = { let view = UIView() view.backgroundColor = .w100 return view }() - + let nickNameLabel: UILabel = { - let label = UILabel() - return label + return UILabel() }() - + private let descriptionLabel: UILabel = { - let label = UILabel() - return label + return UILabel() }() - + private let bottomHoleView: UIView = { let view = UIView() view.backgroundColor = .w100 view.alpha = 0 return view }() - + private let loginView: UIView = { - let view = UIView() - return view + return UIView() }() - + private let loginLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 18, text: "나에게 딱 맞는\n팝업스토어 만나러 가기") label.textColor = .w100 label.numberOfLines = 2 return label }() - + let loginButton: UIButton = { let button = UIButton() button.setTitle("로그인/회원가입", for: .normal) @@ -87,28 +80,28 @@ final class MyPageProfileSectionCell: UICollectionViewCell { button.layer.cornerRadius = 4 return button }() - + var disposeBag = DisposeBag() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() addHolesToCell() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() } - + var cellHeight: Constraint? var containerTopInset: Constraint? - + var isBright: PublishSubject = .init() } @@ -121,62 +114,62 @@ private extension MyPageProfileSectionCell { make.edges.equalToSuperview() cellHeight = make.height.equalTo(162 + 49 + 44).priority(.high).constraint } - + backGroundTrailingView.addSubview(backGroundImageView) backGroundImageView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() make.size.equalTo(UIScreen.main.bounds.width) } - + backGroundTrailingView.addSubview(blurView) blurView.snp.makeConstraints { make in make.edges.equalToSuperview() } - + backGroundTrailingView.addSubview(profileView) profileView.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() make.height.equalTo(162) containerTopInset = make.top.equalToSuperview().constraint } - + backGroundTrailingView.addSubview(bottomView) bottomView.snp.makeConstraints { make in make.bottom.leading.trailing.equalToSuperview() make.height.equalTo(49) } - + backGroundTrailingView.addSubview(loginView) loginView.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() make.height.equalTo(162) make.bottom.equalTo(bottomView.snp.top) } - + profileView.addSubview(profileImageView) profileImageView.snp.makeConstraints { make in make.size.equalTo(64) make.top.equalToSuperview().inset(17) make.leading.equalToSuperview().inset(20) } - + profileView.addSubview(nickNameLabel) nickNameLabel.snp.makeConstraints { make in make.centerY.equalTo(profileImageView) make.leading.equalTo(profileImageView.snp.trailing).offset(10) } - + profileView.addSubview(descriptionLabel) descriptionLabel.snp.makeConstraints { make in make.top.equalTo(profileImageView.snp.bottom).offset(25) make.leading.equalToSuperview().inset(20) } - + contentView.addSubview(bottomHoleView) bottomHoleView.snp.makeConstraints { make in make.edges.equalTo(bottomView) } - + contentView.addSubview(loginButton) loginButton.snp.makeConstraints { make in make.width.equalTo(120) @@ -184,32 +177,32 @@ private extension MyPageProfileSectionCell { make.bottom.equalToSuperview().inset(97) make.leading.equalToSuperview().inset(20) } - + contentView.addSubview(loginLabel) loginLabel.snp.makeConstraints { make in make.bottom.equalTo(loginButton.snp.top).offset(-16) make.leading.equalToSuperview().inset(20) } } - + private func addHolesToCell() { // 전체 영역 경로 let fullPath = UIBezierPath(rect: bounds) - + // 왼쪽 아래와 오른쪽 아래 구멍을 뚫을 위치 설정 (이미지뷰의 frame 위치 고려) let holeCenter = CGPoint(x: bounds.maxX / 2, y: bounds.minY) - + // 구멍을 만드는 경로 생성 (반지름 6) let holePath = UIBezierPath(arcCenter: holeCenter, radius: 12, startAngle: 0, endAngle: .pi, clockwise: true) - + // 구멍 경로를 전체 경로에서 빼기 fullPath.append(holePath) - + // 기존에 구멍을 뚫을 경로를 추가하는 레이어 let holeLayer = CAShapeLayer() holeLayer.path = fullPath.cgPath holeLayer.fillRule = .evenOdd - + // 구멍을 추가하는 서브 레이어로 삽입 bottomView.layer.mask = holeLayer } @@ -222,7 +215,7 @@ extension MyPageProfileSectionCell: Inputable { var nickName: String? var description: String? } - + func injection(with input: Input) { if input.isLogin { profileView.isHidden = false @@ -255,19 +248,19 @@ extension MyPageProfileSectionCell: Inputable { loginLabel.isHidden = false loginButton.isHidden = false blurView.isHidden = true - + } } - + func updateHeight(height: CGFloat) { cellHeight?.update(offset: height) layoutIfNeeded() } - + func updateAlpha(alpha: CGFloat) { bottomHoleView.alpha = alpha } - + func updateContentTopInset(inset: CGFloat) { containerTopInset?.update(offset: inset) } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageView.swift index 967bc557..832979d9 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageView.swift @@ -10,7 +10,7 @@ import UIKit import SnapKit final class MyPageView: UIView { - + // MARK: - Components let contentCollectionView: UICollectionView = { let view = UICollectionView(frame: .zero, collectionViewLayout: .init()) @@ -22,7 +22,7 @@ final class MyPageView: UIView { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -30,7 +30,7 @@ final class MyPageView: UIView { // MARK: - SetUp private extension MyPageView { - + func setUpConstraints() { self.addSubview(contentCollectionView) contentCollectionView.snp.makeConstraints { make in diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/MyCommentController.swift b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/MyCommentController.swift index d886c21b..1673c99e 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/MyCommentController.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/MyCommentController.swift @@ -7,22 +7,22 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit final class MyCommentController: BaseViewController, View { - + typealias Reactor = MyCommentReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = MyCommentView() - + private var sections: [any Sectionable] = [] - + private var cellTapped: PublishSubject = .init() } @@ -46,7 +46,7 @@ private extension MyCommentController { } mainView.contentCollectionView.delegate = self mainView.contentCollectionView.dataSource = self - + mainView.contentCollectionView.register( CommentListTitleSectionCell.self, forCellWithReuseIdentifier: CommentListTitleSectionCell.identifiers @@ -59,7 +59,7 @@ private extension MyCommentController { MyCommentedPopUpGridSectionCell.self, forCellWithReuseIdentifier: MyCommentedPopUpGridSectionCell.identifiers ) - + view.backgroundColor = .g50 view.addSubview(mainView) mainView.snp.makeConstraints { make in @@ -75,7 +75,7 @@ extension MyCommentController { .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.headerView.backButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -83,7 +83,7 @@ extension MyCommentController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + cellTapped .withUnretained(self) .map { (owner, row) in @@ -91,7 +91,7 @@ extension MyCommentController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -107,19 +107,18 @@ extension MyCommentController: UICollectionViewDelegate, UICollectionViewDataSou func numberOfSections(in collectionView: UICollectionView) -> Int { return sections.count } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sections[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath ) -> UICollectionViewCell { - let cell = sections[indexPath.section].getCell(collectionView: collectionView, indexPath: indexPath) - return cell + return sections[indexPath.section].getCell(collectionView: collectionView, indexPath: indexPath) } - + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if indexPath.section == 3 { cellTapped.onNext(indexPath.row) } } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/MyCommentReactor.swift b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/MyCommentReactor.swift index bc297806..83445aac 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/MyCommentReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/MyCommentReactor.swift @@ -8,37 +8,37 @@ import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class MyCommentReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear case listTapped(controller: BaseViewController, row: Int) case backButtonTapped(controller: BaseViewController) } - + enum Mutation { case loadView case skip case moveToDetailScene(controller: BaseViewController, row: Int) case moveToRecentScene(controller: BaseViewController) } - + struct State { var sections: [any Sectionable] = [] var isReloadView: Bool = false } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - + private let userAPIUseCase = UserAPIUseCaseImpl(repository: UserAPIRepositoryImpl(provider: ProviderImpl())) - + lazy var compositionalLayout: UICollectionViewCompositionalLayout = { UICollectionViewCompositionalLayout { [weak self] section, env in guard let self = self else { @@ -52,17 +52,17 @@ final class MyCommentReactor: Reactor { return getSection()[section].getSection(section: section, env: env) } }() - + private var listCountSection = CommentListTitleSection(inputDataList: []) private var listSection = MyCommentedPopUpGridSection(inputDataList: []) private var spacing16Section = SpacingSection(inputDataList: [.init(spacing: 16)]) private var spacing64Section = SpacingSection(inputDataList: [.init(spacing: 64)]) - + // MARK: - init init() { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -93,7 +93,7 @@ final class MyCommentReactor: Reactor { return Observable.just(.moveToRecentScene(controller: controller)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state newState.isReloadView = false @@ -113,7 +113,7 @@ final class MyCommentReactor: Reactor { } return newState } - + func getSection() -> [any Sectionable] { return [ spacing16Section, diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/ListCountButtonSection/ListCountButtonSection.swift b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/ListCountButtonSection/ListCountButtonSection.swift index 2b287e57..bbe9f9af 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/ListCountButtonSection/ListCountButtonSection.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/ListCountButtonSection/ListCountButtonSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct ListCountButtonSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = ListCountButtonSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -37,7 +37,7 @@ struct ListCountButtonSection: Sectionable { // 섹션 생성 let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 0, leading: 20, bottom: 0, trailing: 20) - + return section } } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/ListCountButtonSection/ListCountButtonSectionCell.swift b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/ListCountButtonSection/ListCountButtonSectionCell.swift index de479782..41fb7421 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/ListCountButtonSection/ListCountButtonSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/ListCountButtonSection/ListCountButtonSectionCell.swift @@ -7,47 +7,45 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class ListCountButtonSectionCell: UICollectionViewCell { - + // MARK: - Components - + private let countLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 13) label.textColor = .g400 return label }() - + private let dropDownImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_dropdown") return view }() - + private let buttonTitleLabel: UILabel = { - let label = UILabel() - return label + return UILabel() }() - + let dropdownButton: UIButton = { - let button = UIButton() - return button + return UIButton() }() - + var disposeBag = DisposeBag() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() @@ -62,20 +60,20 @@ private extension ListCountButtonSectionCell { make.leading.equalToSuperview() make.centerY.equalToSuperview() } - + dropdownButton.addSubview(dropDownImageView) dropDownImageView.snp.makeConstraints { make in make.size.equalTo(22) make.top.trailing.bottom.equalToSuperview() } - + dropdownButton.addSubview(buttonTitleLabel) buttonTitleLabel.snp.makeConstraints { make in make.leading.equalToSuperview() make.trailing.equalTo(dropDownImageView.snp.leading).offset(-6) make.centerY.equalToSuperview() } - + self.addSubview(dropdownButton) dropdownButton.snp.makeConstraints { make in make.trailing.equalToSuperview() @@ -89,7 +87,7 @@ extension ListCountButtonSectionCell: Inputable { var count: Int64 var buttonTitle: String? } - + func injection(with input: Input) { countLabel.setLineHeightText(text: "총 \(input.count)개", font: .KorFont(style: .regular, size: 13)) buttonTitleLabel.setLineHeightText(text: input.buttonTitle, font: .KorFont(style: .regular, size: 13)) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/MyCommentView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/MyCommentView.swift index 25bc49e9..8d0b9c95 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/MyCommentView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/MyCommentView.swift @@ -10,26 +10,26 @@ import UIKit import SnapKit final class MyCommentView: UIView { - + // MARK: - Components let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() view.headerLabel.setLineHeightText(text: "내가 코멘트한 팝업", font: .KorFont(style: .regular, size: 15)) return view }() - + let contentCollectionView: UICollectionView = { let view = UICollectionView(frame: .zero, collectionViewLayout: .init()) view.backgroundColor = .g50 return view }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -37,13 +37,13 @@ final class MyCommentView: UIView { // MARK: - SetUp private extension MyCommentView { - + func setUpConstraints() { self.addSubview(headerView) headerView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() } - + self.addSubview(contentCollectionView) contentCollectionView.snp.makeConstraints { make in make.top.equalTo(headerView.snp.bottom) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/MyCommentedPopUpGridSection/MyCommentedPopUpGridSection.swift b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/MyCommentedPopUpGridSection/MyCommentedPopUpGridSection.swift index 3b41bf85..96612580 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/MyCommentedPopUpGridSection/MyCommentedPopUpGridSection.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/MyCommentedPopUpGridSection/MyCommentedPopUpGridSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct MyCommentedPopUpGridSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = MyCommentedPopUpGridSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .absolute((UIScreen.main.bounds.width - 40 - 8) / 2), @@ -34,7 +34,7 @@ struct MyCommentedPopUpGridSection: Sectionable { ) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) group.interItemSpacing = .fixed(8) - + // 섹션 생성 let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 0, leading: 20, bottom: 0, trailing: 20) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/MyCommentedPopUpGridSection/MyCommentedPopUpGridSectionCell.swift b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/MyCommentedPopUpGridSection/MyCommentedPopUpGridSectionCell.swift index 29a04bdf..0c88ef82 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/MyCommentedPopUpGridSection/MyCommentedPopUpGridSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/MyCommentedPopUpGridSection/MyCommentedPopUpGridSectionCell.swift @@ -7,11 +7,11 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class MyCommentedPopUpGridSectionCell: UICollectionViewCell { - + // MARK: - Components private let contentImageView: UIImageView = { let view = UIImageView() @@ -19,32 +19,31 @@ final class MyCommentedPopUpGridSectionCell: UICollectionViewCell { view.clipsToBounds = true return view }() - + private let titleLabel: UILabel = { let label = UILabel() label.textColor = .blu500 return label }() - + private let contentLabel: UILabel = { - let label = UILabel() - return label + return UILabel() }() - + private let dateLabel: UILabel = { let label = UILabel() label.textColor = .g400 return label }() - + let disposeBag = DisposeBag() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } @@ -60,25 +59,25 @@ private extension MyCommentedPopUpGridSectionCell { contentView.layer.shadowOpacity = 1 contentView.layer.shadowRadius = 8 contentView.layer.shadowOffset = CGSize(width: 0, height: 2) - + contentView.addSubview(contentImageView) contentImageView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() make.height.equalTo(contentView.bounds.width) } - + contentView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.top.equalTo(contentImageView.snp.bottom).offset(16) make.leading.trailing.equalToSuperview().inset(12) } - + contentView.addSubview(contentLabel) contentLabel.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(8) make.leading.trailing.equalToSuperview().inset(12) } - + contentView.addSubview(dateLabel) dateLabel.snp.makeConstraints { make in make.top.equalTo(contentLabel.snp.bottom).offset(8) @@ -97,7 +96,7 @@ extension MyCommentedPopUpGridSectionCell: Inputable { var startDate: String? var endDate: String? } - + func injection(with input: Input) { contentImageView.setPPImage(path: input.imageURL) titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .bold, size: 11)) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/SortedModal/MyCommentSortedModalController.swift b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/SortedModal/MyCommentSortedModalController.swift index b107a650..04030d12 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/SortedModal/MyCommentSortedModalController.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/SortedModal/MyCommentSortedModalController.swift @@ -7,19 +7,19 @@ import UIKit -import SnapKit +import PanModal +import ReactorKit import RxCocoa import RxSwift -import ReactorKit -import PanModal +import SnapKit final class MyCommentSortedModalController: BaseViewController, View { - + typealias Reactor = MyCommentSortedModalReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = MyCommentSortedModalView() } @@ -48,13 +48,13 @@ extension MyCommentSortedModalController { .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.sortedSegmentControl.rx.selectedSegmentIndex .skip(1) .map { Reactor.Action.selectedSegmentControl(row: $0) } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.saveButton.rx.tap .withUnretained(self) .compactMap { (owner, _) in @@ -62,7 +62,7 @@ extension MyCommentSortedModalController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.xmarkButton.rx.tap .withUnretained(self) .compactMap { (owner, _) in @@ -70,7 +70,7 @@ extension MyCommentSortedModalController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -92,7 +92,7 @@ extension MyCommentSortedModalController: PanModalPresentable { var panScrollable: UIScrollView? { return nil } - + var longFormHeight: PanModalHeight { return .contentHeight(250) } @@ -106,4 +106,3 @@ extension MyCommentSortedModalController: PanModalPresentable { return 20 } } - diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/SortedModal/MyCommentSortedModalReactor.swift b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/SortedModal/MyCommentSortedModalReactor.swift index ec940c78..60106273 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/SortedModal/MyCommentSortedModalReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/SortedModal/MyCommentSortedModalReactor.swift @@ -6,11 +6,11 @@ // import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class MyCommentSortedModalReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear @@ -18,13 +18,13 @@ final class MyCommentSortedModalReactor: Reactor { case saveButtonTapped(controller: BaseViewController) case xmarkButtonTapped(controller: BaseViewController) } - + enum Mutation { case loadView case setSelectedIndex(row: Int) case dismissScene(controller: BaseViewController, isSave: Bool) } - + struct State { var isSetView: Bool = false var originSortedCode: String @@ -32,18 +32,17 @@ final class MyCommentSortedModalReactor: Reactor { var saveButtonIsEnabled: Bool = false var isSave: Bool = false } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - - + // MARK: - init init(sortedCode: String) { self.initialState = State(originSortedCode: sortedCode) } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -57,7 +56,7 @@ final class MyCommentSortedModalReactor: Reactor { return Observable.just(.dismissScene(controller: controller, isSave: false)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state newState.isSetView = false diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/SortedModal/MyCommentSortedModalView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/SortedModal/MyCommentSortedModalView.swift index 625953d9..f5bf99ee 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/SortedModal/MyCommentSortedModalView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/SortedModal/MyCommentSortedModalView.swift @@ -10,35 +10,32 @@ import UIKit import SnapKit final class MyCommentSortedModalView: UIView { - + // MARK: - Components private let titleLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 18, text: "보기 옵션을 선택해주세요") - return label + return PPLabel(style: .bold, fontSize: 18, text: "보기 옵션을 선택해주세요") }() - + let xmarkButton: UIButton = { let button = UIButton() button.setImage(UIImage(named: "icon_xmark"), for: .normal) return button }() - + let sortedSegmentControl: PPSegmentedControl = { - let control = PPSegmentedControl(type: .base, segments: ["최신순","반응순"]) - return control + return PPSegmentedControl(type: .base, segments: ["최신순", "반응순"]) }() - + let saveButton: PPButton = { - let button = PPButton(style: .primary, text: "저장", disabledText: "저장") - return button + return PPButton(style: .primary, text: "저장", disabledText: "저장") }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -46,33 +43,33 @@ final class MyCommentSortedModalView: UIView { // MARK: - SetUp private extension MyCommentSortedModalView { - + func setUpConstraints() { self.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.leading.equalToSuperview().inset(20) make.top.equalToSuperview().inset(32) } - + self.addSubview(xmarkButton) xmarkButton.snp.makeConstraints { make in make.size.equalTo(24) make.trailing.equalToSuperview().inset(20) make.centerY.equalTo(titleLabel) } - + self.addSubview(sortedSegmentControl) sortedSegmentControl.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(36) make.leading.trailing.equalToSuperview().inset(20) make.height.equalTo(48) } - + self.addSubview(saveButton) saveButton.snp.makeConstraints { make in make.leading.trailing.bottom.equalToSuperview().inset(20) make.height.equalTo(50) } - + } } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/Detail/MyPageNoticeDetailController.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/Detail/MyPageNoticeDetailController.swift index abcfb3d0..535b6b12 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/Detail/MyPageNoticeDetailController.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/Detail/MyPageNoticeDetailController.swift @@ -7,18 +7,18 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit final class MyPageNoticeDetailController: BaseViewController, View { - + typealias Reactor = MyPageNoticeDetailReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = MyPageNoticeDetailView() } @@ -48,7 +48,7 @@ extension MyPageNoticeDetailController { .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.headerView.backButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -56,7 +56,7 @@ extension MyPageNoticeDetailController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/Detail/MyPageNoticeDetailReactor.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/Detail/MyPageNoticeDetailReactor.swift index 7fc3d1c1..02bb5176 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/Detail/MyPageNoticeDetailReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/Detail/MyPageNoticeDetailReactor.swift @@ -6,44 +6,44 @@ // import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class MyPageNoticeDetailReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear case backButtonTapped(controller: BaseViewController) } - + enum Mutation { case loadView case moveToRecentScene(controller: BaseViewController) } - + struct State { var title: String? var date: String? var content: String? } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() var title: String? var date: String? var content: String? var noticeID: Int64 - + let userAPIUseCase = UserAPIUseCaseImpl(repository: UserAPIRepositoryImpl(provider: ProviderImpl())) // MARK: - init init(noticeID: Int64) { self.noticeID = noticeID self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -60,7 +60,7 @@ final class MyPageNoticeDetailReactor: Reactor { return Observable.just(.moveToRecentScene(controller: controller)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/Detail/MyPageNoticeDetailView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/Detail/MyPageNoticeDetailView.swift index 2ae6a1d2..a4ae7b9d 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/Detail/MyPageNoticeDetailView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/Detail/MyPageNoticeDetailView.swift @@ -10,17 +10,16 @@ import UIKit import SnapKit final class MyPageNoticeDetailView: UIView { - + // MARK: - Components let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() view.headerLabel.setLineHeightText(text: "공지사항", font: .KorFont(style: .regular, size: 15)) return view }() - + let titleLabel: UILabel = { - let label = UILabel() - return label + return UILabel() }() let dateLabel: UILabel = { let label = UILabel() @@ -32,16 +31,16 @@ final class MyPageNoticeDetailView: UIView { label.numberOfLines = 0 return label }() - + private let scrollView: UIScrollView = UIScrollView() private let contentView: UIView = UIView() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -49,26 +48,26 @@ final class MyPageNoticeDetailView: UIView { // MARK: - SetUp private extension MyPageNoticeDetailView { - + func setUpConstraints() { self.backgroundColor = .g50 self.addSubview(headerView) headerView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() } - + self.addSubview(scrollView) scrollView.snp.makeConstraints { make in make.top.equalTo(headerView.snp.bottom) make.leading.trailing.bottom.equalToSuperview() } - + scrollView.addSubview(contentView) contentView.snp.makeConstraints { make in make.edges.equalToSuperview() make.width.equalToSuperview() } - + contentView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.top.equalToSuperview().inset(24) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/MyPageNoticeController.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/MyPageNoticeController.swift index f09947a0..10a2ffd7 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/MyPageNoticeController.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/MyPageNoticeController.swift @@ -7,22 +7,22 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit final class MyPageNoticeController: BaseViewController, View { - + typealias Reactor = MyPageNoticeReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = MyPageNoticeView() - + private var sections: [any Sectionable] = [] - + private var cellTapped: PublishSubject = .init() } @@ -32,7 +32,7 @@ extension MyPageNoticeController { super.viewDidLoad() setUp() } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tabBarController?.tabBar.isHidden = true @@ -50,11 +50,11 @@ private extension MyPageNoticeController { mainView.contentCollectionView.register( SpacingSectionCell.self, forCellWithReuseIdentifier: SpacingSectionCell.identifiers - ) + ) mainView.contentCollectionView.register( CommentListTitleSectionCell.self, forCellWithReuseIdentifier: CommentListTitleSectionCell.identifiers - ) + ) mainView.contentCollectionView.register( NoticeListSectionCell.self, forCellWithReuseIdentifier: NoticeListSectionCell.identifiers @@ -70,7 +70,7 @@ private extension MyPageNoticeController { // MARK: - Methods extension MyPageNoticeController { func bind(reactor: Reactor) { - + mainView.headerView.backButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -78,12 +78,12 @@ extension MyPageNoticeController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + rx.viewWillAppear .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + cellTapped .withUnretained(self) .map { (owner, row) in @@ -91,7 +91,7 @@ extension MyPageNoticeController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -107,11 +107,11 @@ extension MyPageNoticeController: UICollectionViewDelegate, UICollectionViewData func numberOfSections(in collectionView: UICollectionView) -> Int { return sections.count } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sections[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath @@ -120,7 +120,7 @@ extension MyPageNoticeController: UICollectionViewDelegate, UICollectionViewData guard let reactor = reactor else { return cell } return cell } - + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if indexPath.section == 3 { cellTapped.onNext(indexPath.row) } } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/MyPageNoticeReactor.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/MyPageNoticeReactor.swift index 5b24879d..ab4da90c 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/MyPageNoticeReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/MyPageNoticeReactor.swift @@ -8,34 +8,34 @@ import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class MyPageNoticeReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear case listCellTapped(controller: BaseViewController, row: Int) case backButtonTapped(controller: BaseViewController) } - + enum Mutation { case loadView case moveToDetailScene(controller: BaseViewController, row: Int) case moveToRecentScene(controller: BaseViewController) } - + struct State { var sections: [any Sectionable] = [] } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() private let userAPIUseCase = UserAPIUseCaseImpl(repository: UserAPIRepositoryImpl(provider: ProviderImpl())) - + lazy var compositionalLayout: UICollectionViewCompositionalLayout = { UICollectionViewCompositionalLayout { [weak self] section, env in guard let self = self else { @@ -53,12 +53,11 @@ final class MyPageNoticeReactor: Reactor { private var listSection = NoticeListSection(inputDataList: []) private let spacing16Section = SpacingSection(inputDataList: [.init(spacing: 16)]) - // MARK: - init init() { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -78,7 +77,7 @@ final class MyPageNoticeReactor: Reactor { return Observable.just(.moveToRecentScene(controller: controller)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { @@ -93,7 +92,7 @@ final class MyPageNoticeReactor: Reactor { } return newState } - + func getSection() -> [any Sectionable] { return [ spacing16Section, diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/View/MyPageNoticeView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/View/MyPageNoticeView.swift index cb08ae4f..bb0af74b 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/View/MyPageNoticeView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/View/MyPageNoticeView.swift @@ -10,26 +10,26 @@ import UIKit import SnapKit final class MyPageNoticeView: UIView { - + // MARK: - Components let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() view.headerLabel.setLineHeightText(text: "공지사항", font: .KorFont(style: .regular, size: 15)) return view }() - + let contentCollectionView: UICollectionView = { let view = UICollectionView(frame: .zero, collectionViewLayout: .init()) view.backgroundColor = .g50 return view }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -37,18 +37,18 @@ final class MyPageNoticeView: UIView { // MARK: - SetUp private extension MyPageNoticeView { - + func setUpConstraints() { self.addSubview(headerView) headerView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() } - + self.addSubview(contentCollectionView) contentCollectionView.snp.makeConstraints { make in make.top.equalTo(headerView.snp.bottom) make.leading.trailing.bottom.equalToSuperview() } - + } } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/View/NoticeListSection/NoticeListSection.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/View/NoticeListSection/NoticeListSection.swift index c95268f7..c1c6f4a9 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/View/NoticeListSection/NoticeListSection.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/View/NoticeListSection/NoticeListSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct NoticeListSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = NoticeListSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -37,7 +37,7 @@ struct NoticeListSection: Sectionable { // 섹션 생성 let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 0, leading: 20, bottom: 0, trailing: 20) - + return section } } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/View/NoticeListSection/NoticeListSectionCell.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/View/NoticeListSection/NoticeListSectionCell.swift index 7f6eede7..c06f9e4a 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/View/NoticeListSection/NoticeListSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/View/NoticeListSection/NoticeListSectionCell.swift @@ -7,23 +7,22 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class NoticeListSectionCell: UICollectionViewCell { - + // MARK: - Components private let titleLabel: PPLabel = { - let label = PPLabel(style: .medium, fontSize: 14) - return label + return PPLabel(style: .medium, fontSize: 14) }() - + private let dateLabel: UILabel = { let label = UILabel() label.textColor = .g400 return label }() - + private let arrowImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_right_gray") @@ -31,12 +30,12 @@ final class NoticeListSectionCell: UICollectionViewCell { }() let disposeBag = DisposeBag() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } @@ -50,14 +49,13 @@ private extension NoticeListSectionCell { make.leading.equalToSuperview() make.top.equalToSuperview().inset(20) } - + contentView.addSubview(dateLabel) dateLabel.snp.makeConstraints { make in make.leading.equalToSuperview() make.bottom.equalToSuperview().inset(20) } - - + contentView.addSubview(arrowImageView) arrowImageView.snp.makeConstraints { make in make.size.equalTo(22) @@ -73,7 +71,7 @@ extension NoticeListSectionCell: Inputable { var date: String? var noticeID: Int64 } - + func injection(with input: Input) { titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .medium, size: 14)) dateLabel.setLineHeightText(text: input.date, font: .EngFont(style: .regular, size: 12)) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/CategoryEditModal/CategoryEditModalController.swift b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/CategoryEditModal/CategoryEditModalController.swift index a17e4108..1ecd1e02 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/CategoryEditModal/CategoryEditModalController.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/CategoryEditModal/CategoryEditModalController.swift @@ -7,21 +7,21 @@ import UIKit -import SnapKit +import PanModal +import ReactorKit import RxCocoa import RxSwift -import ReactorKit -import PanModal +import SnapKit final class CategoryEditModalController: BaseViewController, View { - + typealias Reactor = CategoryEditModalReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = CategoryEditModalView() - + private var sections: [any Sectionable] = [] } @@ -42,7 +42,7 @@ private extension CategoryEditModalController { mainView.contentCollectionView.delegate = self mainView.contentCollectionView.dataSource = self mainView.contentCollectionView.register(TagSectionCell.self, forCellWithReuseIdentifier: TagSectionCell.identifiers) - + view.addSubview(mainView) mainView.snp.makeConstraints { make in make.edges.equalTo(view.safeAreaLayoutGuide) @@ -57,7 +57,7 @@ extension CategoryEditModalController { .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.xmarkButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -65,7 +65,7 @@ extension CategoryEditModalController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.saveButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -73,7 +73,7 @@ extension CategoryEditModalController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -90,19 +90,18 @@ extension CategoryEditModalController: UICollectionViewDelegate, UICollectionVie func numberOfSections(in collectionView: UICollectionView) -> Int { return sections.count } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sections[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath ) -> UICollectionViewCell { - let cell = sections[indexPath.section].getCell(collectionView: collectionView, indexPath: indexPath) - return cell + return sections[indexPath.section].getCell(collectionView: collectionView, indexPath: indexPath) } - + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { reactor?.action.onNext(.cellTapped(row: indexPath.row)) } @@ -113,7 +112,7 @@ extension CategoryEditModalController: PanModalPresentable { var panScrollable: UIScrollView? { return nil } - + var longFormHeight: PanModalHeight { return .contentHeight(360) } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/CategoryEditModal/CategoryEditModalReactor.swift b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/CategoryEditModal/CategoryEditModalReactor.swift index f4b209f7..d02975cd 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/CategoryEditModal/CategoryEditModalReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/CategoryEditModal/CategoryEditModalReactor.swift @@ -8,11 +8,11 @@ import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class CategoryEditModalReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear @@ -20,24 +20,24 @@ final class CategoryEditModalReactor: Reactor { case cellTapped(row: Int) case saveButtonTapped(controller: BaseViewController) } - + enum Mutation { case loadView case moveToRecentScene(controller: BaseViewController, isEdit: Bool) } - + struct State { var sections: [any Sectionable] = [] var originSelectedID: [Int64] var saveButtonIsEnable: Bool = false } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() private let originSelectedID: [Int64] - + lazy var compositionalLayout: UICollectionViewCompositionalLayout = { UICollectionViewCompositionalLayout { [weak self] section, env in guard let self = self else { @@ -51,17 +51,17 @@ final class CategoryEditModalReactor: Reactor { return getSection()[section].getSection(section: section, env: env) } }() - + private var signUpUseCase = SignUpAPIUseCaseImpl(repository: SignUpRepositoryImpl(provider: ProviderImpl())) private var userAPIUseCase = UserAPIUseCaseImpl(repository: UserAPIRepositoryImpl(provider: ProviderImpl())) private var tagSection = TagSection(inputDataList: []) - + // MARK: - init init(selectedID: [Int64]) { self.originSelectedID = selectedID self.initialState = State(originSelectedID: selectedID) } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -106,7 +106,7 @@ final class CategoryEditModalReactor: Reactor { .andThen(Observable.just(.moveToRecentScene(controller: controller, isEdit: true))) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state let originArray = originSelectedID.sorted(by: <) @@ -118,7 +118,7 @@ final class CategoryEditModalReactor: Reactor { if isEdit { ToastMaker.createToast(message: "수정사항을 반영했어요")} controller.dismiss(animated: true) } - + if currentArray.isEmpty { newState.saveButtonIsEnable = false } else { @@ -126,7 +126,7 @@ final class CategoryEditModalReactor: Reactor { } return newState } - + func getSection() -> [any Sectionable] { return [tagSection] } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/CategoryEditModal/CategoryEditModalView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/CategoryEditModal/CategoryEditModalView.swift index 13be5401..537ab835 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/CategoryEditModal/CategoryEditModalView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/CategoryEditModal/CategoryEditModalView.swift @@ -10,36 +10,34 @@ import UIKit import SnapKit final class CategoryEditModalView: UIView { - + // MARK: - Components private let titleLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 18, text: "관심 카테고리를 선택해주세요") - return label + return PPLabel(style: .bold, fontSize: 18, text: "관심 카테고리를 선택해주세요") }() - + let xmarkButton: UIButton = { let button = UIButton() button.setImage(UIImage(named: "icon_xmark"), for: .normal) return button }() - + let contentCollectionView: UICollectionView = { let view = UICollectionView(frame: .zero, collectionViewLayout: .init()) view.isScrollEnabled = false return view }() - + let saveButton: PPButton = { - let button = PPButton(style: .primary, text: "저장", disabledText: "저장") - return button + return PPButton(style: .primary, text: "저장", disabledText: "저장") }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -47,28 +45,28 @@ final class CategoryEditModalView: UIView { // MARK: - SetUp private extension CategoryEditModalView { - + func setUpConstraints() { self.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.leading.equalToSuperview().inset(20) make.top.equalToSuperview().inset(32) } - + self.addSubview(xmarkButton) xmarkButton.snp.makeConstraints { make in make.size.equalTo(24) make.trailing.equalToSuperview().inset(20) make.centerY.equalTo(titleLabel) } - + self.addSubview(contentCollectionView) contentCollectionView.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(32) make.leading.trailing.equalToSuperview() make.height.equalTo(195) } - + self.addSubview(saveButton) saveButton.snp.makeConstraints { make in make.leading.trailing.bottom.equalToSuperview().inset(20) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/InfoEditModal/InfoEditModalController.swift b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/InfoEditModal/InfoEditModalController.swift index e4467054..92b2c880 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/InfoEditModal/InfoEditModalController.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/InfoEditModal/InfoEditModalController.swift @@ -7,19 +7,19 @@ import UIKit -import SnapKit +import PanModal +import ReactorKit import RxCocoa import RxSwift -import ReactorKit -import PanModal +import SnapKit final class InfoEditModalController: BaseViewController, View { - + typealias Reactor = InfoEditModalReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = InfoEditModalView() } @@ -51,12 +51,12 @@ extension InfoEditModalController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.genderSegmentControl.rx.selectedSegmentIndex .map { Reactor.Action.changeGender(index: $0)} .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.ageButton.button.rx.tap .withUnretained(self) .map { (owner, _) in @@ -64,7 +64,7 @@ extension InfoEditModalController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.saveButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -72,7 +72,7 @@ extension InfoEditModalController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -92,7 +92,7 @@ extension InfoEditModalController: PanModalPresentable { var panScrollable: UIScrollView? { return nil } - + var longFormHeight: PanModalHeight { return .contentHeight(390) } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/InfoEditModal/InfoEditModalReactor.swift b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/InfoEditModal/InfoEditModalReactor.swift index 252ca754..df0ae566 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/InfoEditModal/InfoEditModalReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/InfoEditModal/InfoEditModalReactor.swift @@ -8,11 +8,11 @@ import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class InfoEditModalReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear @@ -22,7 +22,7 @@ final class InfoEditModalReactor: Reactor { case changeAge(age: Int32) case saveButtonTapped(controller: BaseViewController) } - + enum Mutation { case loadView case setGender(index: Int) @@ -30,24 +30,24 @@ final class InfoEditModalReactor: Reactor { case moveToAgeSelectedScene(controller: BaseViewController) case moveToRecentScene(controller: BaseViewController, isEdit: Bool) } - + struct State { var age: Int32 var gender: String? var isLoadView: Bool = true var saveButtonEnable: Bool = false } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - + var originAge: Int32 var originGender: String? var currentAge: Int32 = 0 var currentGender: String? - + private let userAPIUseCase = UserAPIUseCaseImpl(repository: UserAPIRepositoryImpl(provider: ProviderImpl())) // MARK: - init init(age: Int32, gender: String?) { @@ -55,7 +55,7 @@ final class InfoEditModalReactor: Reactor { self.originGender = gender self.initialState = State(age: age, gender: gender) } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -74,7 +74,7 @@ final class InfoEditModalReactor: Reactor { .andThen(Observable.just(.moveToRecentScene(controller: controller, isEdit: true))) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state newState.isLoadView = false @@ -96,7 +96,6 @@ final class InfoEditModalReactor: Reactor { .disposed(by: nextController.disposeBag) } - case .moveToRecentScene(let controller, let isEdit): if isEdit { ToastMaker.createToast(message: "수정사항을 반영했어요") } controller.dismiss(animated: true) @@ -106,7 +105,7 @@ final class InfoEditModalReactor: Reactor { case .setAge(let age): newState.age = age } - + if newState.gender == originGender && newState.age == originAge { newState.saveButtonEnable = false } else { diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/InfoEditModal/InfoEditModalView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/InfoEditModal/InfoEditModalView.swift index 0d48161e..71d05ee9 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/InfoEditModal/InfoEditModalView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/InfoEditModal/InfoEditModalView.swift @@ -10,50 +10,44 @@ import UIKit import SnapKit final class InfoEditModalView: UIView { - + // MARK: - Components private let titleLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 18, text: "사용자 정보를 설정해주세요.") - return label + return PPLabel(style: .bold, fontSize: 18, text: "사용자 정보를 설정해주세요.") }() - + let xmarkButton: UIButton = { let button = UIButton() button.setImage(UIImage(named: "icon_xmark"), for: .normal) return button }() - + private let genderTitleLabel: PPLabel = { - let label = PPLabel(style: .regular, fontSize: 13, text: "성별") - return label + return PPLabel(style: .regular, fontSize: 13, text: "성별") }() - + let genderSegmentControl: PPSegmentedControl = { - let control = PPSegmentedControl(type: .base, segments: ["남성", "여성", "선택안함"]) - return control + return PPSegmentedControl(type: .base, segments: ["남성", "여성", "선택안함"]) }() - + private let ageTitleLabel: PPLabel = { - let label = PPLabel(style: .regular, fontSize: 13, text: "나이") - return label + return PPLabel(style: .regular, fontSize: 13, text: "나이") }() - + let ageButton: AgeSelectedButton = { - let label = AgeSelectedButton() - return label + return AgeSelectedButton() }() - + let saveButton: PPButton = { - let button = PPButton(style: .primary, text: "저장", disabledText: "저장") - return button + return PPButton(style: .primary, text: "저장", disabledText: "저장") }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -61,46 +55,46 @@ final class InfoEditModalView: UIView { // MARK: - SetUp private extension InfoEditModalView { - + func setUpConstraints() { self.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.leading.equalToSuperview().inset(20) make.top.equalToSuperview().inset(32) } - + self.addSubview(xmarkButton) xmarkButton.snp.makeConstraints { make in make.size.equalTo(24) make.trailing.equalToSuperview().inset(20) make.centerY.equalTo(titleLabel) } - + self.addSubview(genderTitleLabel) genderTitleLabel.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(36) make.leading.equalToSuperview().inset(20) } - + self.addSubview(genderSegmentControl) genderSegmentControl.snp.makeConstraints { make in make.top.equalTo(genderTitleLabel.snp.bottom).offset(8) make.leading.trailing.equalToSuperview().inset(20) } - + self.addSubview(ageTitleLabel) ageTitleLabel.snp.makeConstraints { make in make.top.equalTo(genderSegmentControl.snp.bottom).offset(36) make.leading.equalToSuperview().inset(20) } - + self.addSubview(ageButton) ageButton.snp.makeConstraints { make in make.top.equalTo(ageTitleLabel.snp.bottom).offset(8) make.leading.trailing.equalToSuperview().inset(20) make.height.equalTo(72) } - + self.addSubview(saveButton) saveButton.snp.makeConstraints { make in make.leading.trailing.bottom.equalToSuperview().inset(20) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/ProfileEditController.swift b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/ProfileEditController.swift index 3b71886f..a6cee295 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/ProfileEditController.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/ProfileEditController.swift @@ -5,24 +5,24 @@ // Created by SeoJunYoung on 1/4/25. // -import UIKit import PhotosUI +import UIKit -import SnapKit -import RxCocoa -import RxSwift import ReactorKit +import RxCocoa import RxGesture +import RxSwift +import SnapKit final class ProfileEditController: BaseViewController, View { - + typealias Reactor = ProfileEditReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = ProfileEditView() - + var isFirstResponse: Bool = true } @@ -32,7 +32,7 @@ extension ProfileEditController { super.viewDidLoad() setUp() } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tabBarController?.tabBar.isHidden = true @@ -53,60 +53,60 @@ private extension ProfileEditController { // MARK: - Methods extension ProfileEditController { func bind(reactor: Reactor) { - + mainView.rx.tapGesture() .withUnretained(self) .subscribe { (owner, _) in owner.mainView.endEditing(true) } .disposed(by: disposeBag) - + rx.viewWillAppear .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.nickNameTextField.rx.text .skip(1) .debounce(.milliseconds(300), scheduler: MainScheduler.asyncInstance) .map { Reactor.Action.changeNickName(nickName: $0)} .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.nickNameTextField.rx.controlEvent(.editingDidBegin) .map { Reactor.Action.beginNickName } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.nickNameTextField.rx.controlEvent(.editingDidEnd) .map { Reactor.Action.endNickName } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.introTextView.rx.text .distinctUntilChanged() .skip(1) .map { Reactor.Action.changeIntro(intro: $0)} .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.introTextView.rx.didBeginEditing .map { Reactor.Action.beginIntro } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.introTextView.rx.didEndEditing .map { Reactor.Action.endIntro } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.profileImageButton.rx.tap .withUnretained(self) .subscribe(onNext: { (owner, _) in owner.showActionSheet() }) .disposed(by: disposeBag) - + mainView.categoryButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -114,7 +114,7 @@ extension ProfileEditController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.infoButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -122,18 +122,17 @@ extension ProfileEditController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.saveButton.rx.tap .map { Reactor.Action.saveButtonTapped } .bind(to: reactor.action) .disposed(by: disposeBag) - - + mainView.nickNameDuplicatedCheckButton.rx.tap .map { Reactor.Action.nickNameCheckButtonTapped } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.headerView.backButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -141,18 +140,18 @@ extension ProfileEditController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in guard let originProfileData = state.originProfileData else { return } - + if owner.isFirstResponse { owner.mainView.profileImageView.setPPImage(path: originProfileData.profileImageUrl) owner.mainView.nickNameTextField.text = originProfileData.nickname owner.mainView.introTextView.text = originProfileData.intro } - + let categoryTitleList = originProfileData.interestCategoryList.map { $0.category } let categoryTitle: String if let firstTitle = categoryTitleList.first { @@ -166,9 +165,8 @@ extension ProfileEditController { .setLineHeightText(text: categoryTitle, font: .KorFont(style: .regular, size: 13), lineHeight: 1) let userInfoTitle = "\(originProfileData.gender ?? "")・\(originProfileData.age)세" owner.mainView.infoButton.subTitleLabel - .setLineHeightText(text: userInfoTitle, font: .KorFont(style: .regular, size: 13) ,lineHeight: 1) - - + .setLineHeightText(text: userInfoTitle, font: .KorFont(style: .regular, size: 13), lineHeight: 1) + // NickName TextField 설정 owner.mainView.nickNameTextFieldTrailingView.layer.borderColor = state.nickNameState.borderColor?.cgColor owner.mainView.nickNameClearButton.isHidden = state.nickNameState.isHiddenClearButton @@ -178,7 +176,7 @@ extension ProfileEditController { owner.mainView.nickNameTextField.textColor = state.nickNameState.textFieldTextColor owner.mainView.nickNameTextCountLabel.text = "\(owner.mainView.nickNameTextField.text?.count ?? 0) / 10자" owner.mainView.nickNameDuplicatedCheckButton.isEnabled = state.nickNameState.duplicatedCheckButtonIsEnabled - + // Intro TextView 설정 owner.mainView.introTextCountLabel.text = "\(owner.mainView.introTextView.text?.count ?? 0) / 30자" owner.mainView.introTextTrailingView.layer.borderColor = state.introState.borderColor?.cgColor @@ -187,7 +185,7 @@ extension ProfileEditController { owner.mainView.introTextCountLabel.textColor = state.introState.textColor owner.mainView.introTextView.textColor = state.introState.textFieldTextColor owner.mainView.introPlaceHolderLabel.isHidden = state.introState.placeHolderIsHidden - + owner.mainView.saveButton.isEnabled = state.saveButtonIsEnable owner.isFirstResponse = false } @@ -199,7 +197,7 @@ extension ProfileEditController: PHPickerViewControllerDelegate, UIImagePickerCo func showActionSheet() { // ActionSheet 생성 let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) - + // 버튼 추가 let takePhotoAction = UIAlertAction(title: "촬영하기", style: .default) { [weak self] _ in self?.showCamera() @@ -212,32 +210,32 @@ extension ProfileEditController: PHPickerViewControllerDelegate, UIImagePickerCo self?.reactor?.action.onNext(.changeDefaultImage) } let cancelAction = UIAlertAction(title: "취소", style: .cancel) - + // 버튼 스타일 변경 (기본 이미지는 빨간색으로 표시) changeToDefaultImageAction.setValue(UIColor.red, forKey: "titleTextColor") - + // 버튼 추가 actionSheet.addAction(takePhotoAction) actionSheet.addAction(selectFromAlbumAction) actionSheet.addAction(changeToDefaultImageAction) actionSheet.addAction(cancelAction) - + present(actionSheet, animated: true) } - + func showCamera() { guard UIImagePickerController.isSourceTypeAvailable(.camera) else { Logger.log(message: "카메라를 사용할 수 없습니다.", category: .error) return } - + let imagePicker = UIImagePickerController() imagePicker.sourceType = .camera - + imagePicker.delegate = self present(imagePicker, animated: true) } - + // MARK: - PHPicker 실행 func showPHPicker() { var configuration = PHPickerConfiguration() @@ -247,26 +245,26 @@ extension ProfileEditController: PHPickerViewControllerDelegate, UIImagePickerCo picker.delegate = self present(picker, animated: true) } - + // MARK: - UIImagePickerControllerDelegate - func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { if let selectedImage = info[.originalImage] as? UIImage { handleSelectedImage(selectedImage) } picker.dismiss(animated: true) } - + func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { picker.dismiss(animated: true) } - + // MARK: - PHPickerViewControllerDelegate func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { picker.dismiss(animated: true) - + for result in results { if result.itemProvider.canLoadObject(ofClass: UIImage.self) { - result.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] (image, error) in + result.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] (image, _) in if let selectedImage = image as? UIImage { DispatchQueue.main.async { self?.handleSelectedImage(selectedImage) @@ -276,7 +274,7 @@ extension ProfileEditController: PHPickerViewControllerDelegate, UIImagePickerCo } } } - + // MARK: - 이미지 처리 func handleSelectedImage(_ image: UIImage) { // 선택한 이미지 처리 diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/ProfileEditReactor.swift b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/ProfileEditReactor.swift index 80f0740e..fef97600 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/ProfileEditReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/ProfileEditReactor.swift @@ -5,15 +5,15 @@ // Created by SeoJunYoung on 1/4/25. // -import UIKit import PhotosUI +import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class ProfileEditReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear @@ -31,7 +31,7 @@ final class ProfileEditReactor: Reactor { case saveButtonTapped case backButtonTapped(controller: BaseViewController) } - + enum Mutation { case loadView case moveToInfoEditScene(controller: BaseViewController) @@ -40,39 +40,39 @@ final class ProfileEditReactor: Reactor { case moveToRecentScene(controller: BaseViewController) case changeNickNameState } - + struct State { var originProfileData: GetMyProfileResponse? var saveButtonIsEnable: Bool = false var nickNameState: NickNameState = .myNickName var introState: IntroState = .validate } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() var originProfileData: GetMyProfileResponse? - + var currentImage: UIImage? var isChangeImage: Bool = false var currentImagePath: String? - + var currentNickName: String? var nickNameIsActive: Bool = false - + var currentIntro: String? var introIsActive: Bool = false - + private let userAPIUseCase = UserAPIUseCaseImpl(repository: UserAPIRepositoryImpl(provider: ProviderImpl())) private let signUpAPIUseCase = SignUpAPIUseCaseImpl(repository: SignUpRepositoryImpl(provider: ProviderImpl())) private let imageService = PreSignedService() - + // MARK: - init init() { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -124,7 +124,7 @@ final class ProfileEditReactor: Reactor { return Observable.just(.moveToRecentScene(controller: controller)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { @@ -150,12 +150,12 @@ final class ProfileEditReactor: Reactor { case .changeNickNameState: newState.nickNameState = checkNickNameState(text: currentNickName, isActive: nickNameIsActive) } - + let originNickName = originProfileData?.nickname ?? "" let currentNickName = currentNickName ?? "" let originIntro = originProfileData?.intro ?? "" let currentIntro = currentIntro ?? "" - + if isChangeImage || originNickName != currentNickName || originIntro != currentIntro { if newState.nickNameState == .validate || newState.nickNameState == .validateActive || newState.nickNameState == .myNickName || newState.nickNameState == .myNickNameActive { if newState.introState == .validate || newState.introState == .validateActive || newState.introState == .empty || newState.introState == .emptyActive { @@ -169,10 +169,10 @@ final class ProfileEditReactor: Reactor { } else { newState.saveButtonIsEnable = false } - + return newState } - + func uploadS3() -> Observable { if let changeImage = currentImage { let newPath = "ProfileImage/\(UUID().uuidString).jpg" @@ -206,7 +206,7 @@ final class ProfileEditReactor: Reactor { } } } - + func editProfile() -> Observable { isChangeImage = false ToastMaker.createToast(message: "내용을 저장했어요") @@ -219,7 +219,7 @@ final class ProfileEditReactor: Reactor { ) .andThen(Observable.just(.loadView)) } - + func loadProfileData() -> Observable { return userAPIUseCase.getMyProfile() .withUnretained(self) @@ -231,27 +231,27 @@ final class ProfileEditReactor: Reactor { return .loadView } } - + func checkNickNameState(text: String?, isActive: Bool) -> NickNameState { guard let text = text, let originNickName = originProfileData?.nickname else { return isActive ? .emptyActive : .empty } if originNickName == text { return isActive ? .myNickNameActive : .myNickName } - + // textEmpty Check if text.isEmpty { return isActive ? .emptyActive : .empty } - + // kor and end Check let pattern = "^[가-힣a-zA-Z\\s]+$" // 허용하는 문자만 검사 let regex = try! NSRegularExpression(pattern: pattern) let range = NSRange(location: 0, length: text.utf16.count) if regex.firstMatch(in: text, options: [], range: range) == nil { return isActive ? .korAndEngActive : .korAndEng } - + // textLength Check - + if text.count < 2 { return isActive ? .shortLengthActive : .shortLength } if text.count > 10 { return isActive ? .longLengthActive : .longLength } return isActive ? .checkActive : .check } - + func checkIntroState(text: String?, isActive: Bool) -> IntroState { guard let text = text else { return isActive ? .emptyActive : .empty } if text.isEmpty { return isActive ? .emptyActive : .empty } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/View/ProfileEditListButton.swift b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/View/ProfileEditListButton.swift index aa83db1a..844b3d7c 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/View/ProfileEditListButton.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/View/ProfileEditListButton.swift @@ -10,31 +10,30 @@ import UIKit import SnapKit final class ProfileEditListButton: UIButton { - + // MARK: - Components let mainTitleLabel: UILabel = { - let label = UILabel() - return label + return UILabel() }() - + let subTitleLabel: UILabel = { let label = UILabel() label.textColor = .g400 return label }() - + let iconImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_right_gray") return view }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -42,19 +41,19 @@ final class ProfileEditListButton: UIButton { // MARK: - SetUp private extension ProfileEditListButton { - + func setUpConstraints() { self.addSubview(mainTitleLabel) mainTitleLabel.snp.makeConstraints { make in make.leading.centerY.equalToSuperview() } - + self.addSubview(iconImageView) iconImageView.snp.makeConstraints { make in make.size.equalTo(22) make.top.bottom.trailing.equalToSuperview() } - + self.addSubview(subTitleLabel) subTitleLabel.snp.makeConstraints { make in make.centerY.equalToSuperview() diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/View/ProfileEditView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/View/ProfileEditView.swift index c1faa431..c2adb140 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/View/ProfileEditView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/View/ProfileEditView.swift @@ -10,7 +10,7 @@ import UIKit import SnapKit final class ProfileEditView: UIView { - + // MARK: - Components let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() @@ -18,13 +18,12 @@ final class ProfileEditView: UIView { return view }() let saveButton: PPButton = { - let button = PPButton(style: .primary, text: "저장", disabledText: "저장") - return button + return PPButton(style: .primary, text: "저장", disabledText: "저장") }() - + private let scrollView: UIScrollView = UIScrollView() private let contentView: UIView = UIView() - + let profileImageView: UIImageView = { let view = UIImageView() view.layer.cornerRadius = 48 @@ -51,10 +50,9 @@ final class ProfileEditView: UIView { view.contentMode = .scaleAspectFit return view }() - + private let nickNameTitleLabel: PPLabel = { - let label = PPLabel(style: .regular, fontSize: 13, text: "별명") - return label + return PPLabel(style: .regular, fontSize: 13, text: "별명") }() let nickNameTextFieldTrailingView: UIStackView = { let view = UIStackView() @@ -116,10 +114,9 @@ final class ProfileEditView: UIView { button.setAttributedTitle(disabledAttributedTitle, for: .disabled) return button }() - + private let introTitleLabel: PPLabel = { - let label = PPLabel(style: .regular, fontSize: 13, text: "자기소개") - return label + return PPLabel(style: .regular, fontSize: 13, text: "자기소개") }() let introTextTrailingView: UIView = { let view = UIView() @@ -143,38 +140,36 @@ final class ProfileEditView: UIView { return label }() let introDescriptionLabel: PPLabel = { - let label = PPLabel(style: .medium, fontSize: 12) - return label + return PPLabel(style: .medium, fontSize: 12) }() let introPlaceHolderLabel: PPLabel = { let label = PPLabel(style: .medium, fontSize: 14, text: "자기소개를 입력해주세요") label.textColor = .g200 return label }() - + private let customInfoTitlelabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 16, text: "맞춤정보") - return label + return PPLabel(style: .bold, fontSize: 16, text: "맞춤정보") }() - + let categoryButton: ProfileEditListButton = { let button = ProfileEditListButton() button.mainTitleLabel.setLineHeightText(text: "관심 카테고리", font: .KorFont(style: .regular, size: 15)) return button }() - + let infoButton: ProfileEditListButton = { let button = ProfileEditListButton() button.mainTitleLabel.setLineHeightText(text: "사용자 정보", font: .KorFont(style: .regular, size: 15)) return button }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -182,7 +177,7 @@ final class ProfileEditView: UIView { // MARK: - SetUp private extension ProfileEditView { - + func setUpConstraints() { self.addSubview(headerView) headerView.snp.makeConstraints { make in @@ -209,7 +204,7 @@ private extension ProfileEditView { setUpIntroView() setUpCustomInfoView() } - + func setUpProfileImageView() { contentView.addSubview(profileImageView) profileImageView.snp.makeConstraints { make in @@ -228,7 +223,7 @@ private extension ProfileEditView { make.center.equalToSuperview() } } - + func setUpNickNameView() { contentView.addSubview(nickNameTitleLabel) nickNameTitleLabel.snp.makeConstraints { make in @@ -261,7 +256,7 @@ private extension ProfileEditView { make.trailing.equalToSuperview().inset(24) } } - + func setUpIntroView() { contentView.addSubview(introTitleLabel) introTitleLabel.snp.makeConstraints { make in @@ -294,21 +289,21 @@ private extension ProfileEditView { make.leading.equalToSuperview().inset(20) } } - + func setUpCustomInfoView() { contentView.addSubview(customInfoTitlelabel) customInfoTitlelabel.snp.makeConstraints { make in make.top.equalTo(introTextTrailingView.snp.bottom).offset(27) make.leading.equalToSuperview().inset(20) } - + contentView.addSubview(categoryButton) categoryButton.snp.makeConstraints { make in make.top.equalTo(customInfoTitlelabel.snp.bottom).offset(32) make.leading.equalToSuperview().inset(22) make.trailing.equalToSuperview().inset(20) } - + contentView.addSubview(infoButton) infoButton.snp.makeConstraints { make in make.top.equalTo(categoryButton.snp.bottom).offset(32) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Recent/MyPageRecentController.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Recent/MyPageRecentController.swift index a21e7549..12bb9237 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Recent/MyPageRecentController.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Recent/MyPageRecentController.swift @@ -7,22 +7,22 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit final class MyPageRecentController: BaseViewController, View { - + typealias Reactor = MyPageRecentReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = MyPageRecentView() - + private var sections: [any Sectionable] = [] - + private var cellTapped: PublishSubject = .init() } @@ -32,7 +32,7 @@ extension MyPageRecentController { super.viewDidLoad() setUp() } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tabBarController?.tabBar.isHidden = true @@ -50,11 +50,11 @@ private extension MyPageRecentController { mainView.contentCollectionView.register( SpacingSectionCell.self, forCellWithReuseIdentifier: SpacingSectionCell.identifiers - ) + ) mainView.contentCollectionView.register( DetailSimilarSectionCell.self, forCellWithReuseIdentifier: DetailSimilarSectionCell.identifiers - ) + ) mainView.contentCollectionView.register( CommentListTitleSectionCell.self, forCellWithReuseIdentifier: CommentListTitleSectionCell.identifiers @@ -74,7 +74,7 @@ extension MyPageRecentController { .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.headerView.backButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -82,7 +82,7 @@ extension MyPageRecentController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + cellTapped .withUnretained(self) .map { (owner, row) in @@ -90,13 +90,13 @@ extension MyPageRecentController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in owner.sections = state.sections if state.isReloadView { owner.mainView.contentCollectionView.reloadData() } - + } .disposed(by: disposeBag) } @@ -107,11 +107,11 @@ extension MyPageRecentController: UICollectionViewDelegate, UICollectionViewData func numberOfSections(in collectionView: UICollectionView) -> Int { return sections.count } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sections[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath @@ -120,7 +120,7 @@ extension MyPageRecentController: UICollectionViewDelegate, UICollectionViewData guard let reactor = reactor else { return cell } return cell } - + func scrollViewDidScroll(_ scrollView: UIScrollView) { let contentHeight = scrollView.contentSize.height let scrollViewHeight = scrollView.frame.size.height @@ -129,7 +129,7 @@ extension MyPageRecentController: UICollectionViewDelegate, UICollectionViewData reactor?.action.onNext(.changePage) } } - + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if indexPath.section == 3 { cellTapped.onNext(indexPath.row) } } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Recent/MyPageRecentReactor.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Recent/MyPageRecentReactor.swift index d7d85365..300b6bcb 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Recent/MyPageRecentReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Recent/MyPageRecentReactor.swift @@ -8,11 +8,11 @@ import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class MyPageRecentReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear @@ -20,30 +20,30 @@ final class MyPageRecentReactor: Reactor { case backButtonTapped(controller: BaseViewController) case cellTapped(controller: BaseViewController, row: Int) } - + enum Mutation { case loadView case skip case moveToRecentScene(controller: BaseViewController) case moveToDetailScene(controller: BaseViewController, row: Int) } - + struct State { var sections: [any Sectionable] = [] var isReloadView: Bool = false } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() private var isLoading: Bool = false private var totalPage: Int32 = 0 private var currentPage: Int32 = 0 private var size: Int32 = 100 - + private let userAPIUseCase = UserAPIUseCaseImpl(repository: UserAPIRepositoryImpl(provider: ProviderImpl())) - + lazy var compositionalLayout: UICollectionViewCompositionalLayout = { UICollectionViewCompositionalLayout { [weak self] section, env in guard let self = self else { @@ -57,16 +57,16 @@ final class MyPageRecentReactor: Reactor { return getSection()[section].getSection(section: section, env: env) } }() - + private var countSection = CommentListTitleSection(inputDataList: []) private var listSection = RecentPopUpSection(inputDataList: []) private var spacing16Section = SpacingSection(inputDataList: [.init(spacing: 16)]) - + // MARK: - init init() { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -111,7 +111,7 @@ final class MyPageRecentReactor: Reactor { return Observable.just(.moveToDetailScene(controller: controller, row: row)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state newState.isReloadView = false @@ -130,7 +130,7 @@ final class MyPageRecentReactor: Reactor { } return newState } - + func getSection() -> [any Sectionable] { return [ spacing16Section, diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Recent/View/MyPageRecentView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Recent/View/MyPageRecentView.swift index 0f585389..a608b4e9 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Recent/View/MyPageRecentView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Recent/View/MyPageRecentView.swift @@ -10,14 +10,14 @@ import UIKit import SnapKit final class MyPageRecentView: UIView { - + // MARK: - Components let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() view.headerLabel.setLineHeightText(text: "최근 본 팝업", font: .KorFont(style: .regular, size: 15)) return view }() - + let contentCollectionView: UICollectionView = { let view = UICollectionView(frame: .zero, collectionViewLayout: .init()) view.backgroundColor = .g50 @@ -28,7 +28,7 @@ final class MyPageRecentView: UIView { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -36,13 +36,13 @@ final class MyPageRecentView: UIView { // MARK: - SetUp private extension MyPageRecentView { - + func setUpConstraints() { self.addSubview(headerView) headerView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() } - + self.addSubview(contentCollectionView) contentCollectionView.snp.makeConstraints { make in make.top.equalTo(headerView.snp.bottom) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Recent/View/RecentPopUpSection/RecentPopUpSection.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Recent/View/RecentPopUpSection/RecentPopUpSection.swift index 70afc695..a744e56e 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Recent/View/RecentPopUpSection/RecentPopUpSection.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Recent/View/RecentPopUpSection/RecentPopUpSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct RecentPopUpSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = DetailSimilarSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .absolute((UIScreen.main.bounds.width - 40 - 8) / 2), @@ -38,8 +38,7 @@ struct RecentPopUpSection: Sectionable { let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 0, leading: 20, bottom: 0, trailing: 20) section.interGroupSpacing = 12 - + return section } } - diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Terms/MyPageTermsController.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Terms/MyPageTermsController.swift index fbea50cf..68883315 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Terms/MyPageTermsController.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Terms/MyPageTermsController.swift @@ -7,24 +7,24 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit final class MyPageTermsController: BaseViewController, View { - + typealias Reactor = MyPageTermsReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() view.headerLabel.setLineHeightText(text: "약관", font: .KorFont(style: .regular, size: 15)) return view }() - + lazy var compositionalLayout: UICollectionViewCompositionalLayout = { UICollectionViewCompositionalLayout { [weak self] section, env in guard let self = self else { @@ -38,15 +38,15 @@ final class MyPageTermsController: BaseViewController, View { return sections[section].getSection(section: section, env: env) } }() - + private lazy var contentCollectionView: UICollectionView = { let view = UICollectionView(frame: .zero, collectionViewLayout: self.compositionalLayout) view.backgroundColor = .g50 return view }() - + private var sections: [any Sectionable] = [] - + private let cellTapped: PublishSubject = .init() } @@ -56,7 +56,7 @@ extension MyPageTermsController { super.viewDidLoad() setUp() } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tabBarController?.tabBar.isHidden = true @@ -76,7 +76,7 @@ private extension MyPageTermsController { make.top.equalTo(headerView.snp.bottom) make.leading.trailing.bottom.equalTo(view.safeAreaLayoutGuide) } - + contentCollectionView.delegate = self contentCollectionView.dataSource = self contentCollectionView.register(CommentListTitleSectionCell.self, forCellWithReuseIdentifier: CommentListTitleSectionCell.identifiers) @@ -92,7 +92,7 @@ extension MyPageTermsController { .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + headerView.backButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -100,7 +100,7 @@ extension MyPageTermsController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + cellTapped .withUnretained(self) .map { (owner, indexPath) in @@ -108,7 +108,7 @@ extension MyPageTermsController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -124,19 +124,18 @@ extension MyPageTermsController: UICollectionViewDelegate, UICollectionViewDataS func numberOfSections(in collectionView: UICollectionView) -> Int { return sections.count } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sections[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath ) -> UICollectionViewCell { - let cell = sections[indexPath.section].getCell(collectionView: collectionView, indexPath: indexPath) - return cell + return sections[indexPath.section].getCell(collectionView: collectionView, indexPath: indexPath) } - + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { cellTapped.onNext(indexPath) } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Terms/MyPageTermsReactor.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Terms/MyPageTermsReactor.swift index bc4fffc3..18bdf23e 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Terms/MyPageTermsReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Terms/MyPageTermsReactor.swift @@ -7,47 +7,47 @@ import Foundation import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class MyPageTermsReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear - case cellTapped(indexPath: IndexPath, controller : BaseViewController) + case cellTapped(indexPath: IndexPath, controller: BaseViewController) case backButtonTapped(controller: BaseViewController) } - + enum Mutation { case loadView case moveToDetailScene(controller: BaseViewController, indexPath: IndexPath) case moveToRecentScene(controller: BaseViewController) } - + struct State { var sections: [any Sectionable] = [] } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - + private lazy var countSection = CommentListTitleSection(inputDataList: [.init(count: termsSection.dataCount)]) private var termsSection = MyPageListSection(inputDataList: [ .init(title: "서비스이용약관"), .init(title: "개인정보처리방침"), .init(title: "위치정보 이용약관") ]) - + private let spacing16Section = SpacingSection(inputDataList: [.init(spacing: 16)]) - + // MARK: - init init() { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -57,10 +57,10 @@ final class MyPageTermsReactor: Reactor { return Observable.just(.moveToRecentScene(controller: controller)) case .cellTapped(let indexPath, let controller): return Observable.just(.moveToDetailScene(controller: controller, indexPath: indexPath)) - + } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { @@ -75,7 +75,7 @@ final class MyPageTermsReactor: Reactor { } return newState } - + func getSections() -> [any Sectionable] { return [ spacing16Section, @@ -84,7 +84,7 @@ final class MyPageTermsReactor: Reactor { termsSection ] } - + func getContent(index: Int) -> String { if let path = Bundle.main.path(forResource: "Terms", ofType: "plist"), let dict = NSDictionary(contentsOfFile: path) as? [String: String], diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/CheckModal/WithdrawlCheckModalController.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/CheckModal/WithdrawlCheckModalController.swift index cf7f2d28..b8fbe817 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/CheckModal/WithdrawlCheckModalController.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/CheckModal/WithdrawlCheckModalController.swift @@ -6,28 +6,28 @@ // import UIKit -import SnapKit +import PanModal +import ReactorKit import RxCocoa import RxSwift -import ReactorKit -import PanModal +import SnapKit final class WithdrawlCheckModalController: BaseViewController, View { - + typealias Reactor = WithdrawlCheckModalReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = WithdrawlCheckModalView() - + init(nickName: String?) { super.init() let title = "\(nickName ?? "")님, 팝풀 서비스를\n정말 탈퇴하시겠어요?" mainView.titleLabel.setLineHeightText(text: title, font: .KorFont(style: .bold, size: 18), lineHeight: 1.312) mainView.titleLabel.numberOfLines = 2 } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -58,7 +58,7 @@ extension WithdrawlCheckModalController { .map { Reactor.Action.cancelButtonTapped } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.agreeButton.rx.tap .map { Reactor.Action.appleyButtonTapped } .bind(to: reactor.action) @@ -71,7 +71,7 @@ extension WithdrawlCheckModalController: PanModalPresentable { var panScrollable: UIScrollView? { return nil } - + var longFormHeight: PanModalHeight { return .contentHeight(370) } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/CheckModal/WithdrawlCheckModalReactor.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/CheckModal/WithdrawlCheckModalReactor.swift index 81094df7..51b0c6ad 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/CheckModal/WithdrawlCheckModalReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/CheckModal/WithdrawlCheckModalReactor.swift @@ -6,41 +6,41 @@ // import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class WithdrawlCheckModalReactor: Reactor { - + enum ModalState { case none case cancel case apply } - + // MARK: - Reactor enum Action { case cancelButtonTapped case appleyButtonTapped } - + enum Mutation { case setModalState(state: ModalState) } - + struct State { var state: ModalState = .none } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - + // MARK: - init init() { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -50,7 +50,7 @@ final class WithdrawlCheckModalReactor: Reactor { return Observable.just(.setModalState(state: .cancel)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/CheckModal/WithdrawlCheckModalView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/CheckModal/WithdrawlCheckModalView.swift index 860c1739..5c72ed50 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/CheckModal/WithdrawlCheckModalView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/CheckModal/WithdrawlCheckModalView.swift @@ -10,30 +10,27 @@ import UIKit import SnapKit final class WithdrawlCheckModalView: UIView { - + // MARK: - Components let titleLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 18) - return label + return PPLabel(style: .bold, fontSize: 18) }() - + private let trailingView: UIView = { let view = UIView() view.backgroundColor = .g50 view.layer.cornerRadius = 4 return view }() - + let cancelButton: PPButton = { - let button = PPButton(style: .secondary, text: "취소") - return button + return PPButton(style: .secondary, text: "취소") }() - + let agreeButton: PPButton = { - let button = PPButton(style: .primary, text: "동의 후 탈퇴하기") - return button + return PPButton(style: .primary, text: "동의 후 탈퇴하기") }() - + let firstLabel: UILabel = { let text = "서비스 탈퇴 시 회원 전용 서비스 이용이 불가하며 회원 데이터는 일괄 삭제 처리돼요." let label = UILabel() @@ -42,7 +39,7 @@ final class WithdrawlCheckModalView: UIView { label.textColor = .g600 return label }() - + let secondLabel: UILabel = { let text = "탈퇴 후에는 계정을 다시 살리거나 복구할 수 없어요." let label = UILabel() @@ -51,7 +48,7 @@ final class WithdrawlCheckModalView: UIView { label.textColor = .g600 return label }() - + let thirdLabel: PPLabel = { let text = "작성하신 코멘트나 댓글 등의 일부 정보는 계속 남아있을 수 있어요. " let label = PPLabel(style: .regular, fontSize: 13, text: text) @@ -59,20 +56,20 @@ final class WithdrawlCheckModalView: UIView { label.textColor = .g600 return label }() - + let textStackView: UIStackView = { let view = UIStackView() view.axis = .vertical view.spacing = 12 return view }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -80,20 +77,20 @@ final class WithdrawlCheckModalView: UIView { // MARK: - SetUp private extension WithdrawlCheckModalView { - + func setUpConstraints() { self.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.top.equalToSuperview().inset(32) make.leading.trailing.equalToSuperview().inset(20) } - + self.addSubview(trailingView) trailingView.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(24) make.leading.trailing.equalToSuperview().inset(20) } - + self.addSubview(cancelButton) cancelButton.snp.makeConstraints { make in make.leading.equalToSuperview().inset(20) @@ -101,7 +98,7 @@ private extension WithdrawlCheckModalView { make.height.equalTo(50) make.width.equalTo(108) } - + self.addSubview(agreeButton) agreeButton.snp.makeConstraints { make in make.bottom.equalToSuperview() @@ -109,13 +106,13 @@ private extension WithdrawlCheckModalView { make.trailing.equalToSuperview().inset(20) make.height.equalTo(50) } - + trailingView.addSubview(textStackView) textStackView.snp.makeConstraints { make in // make.top.leading.trailing.equalToSuperview().inset(20) make.edges.equalToSuperview().inset(20) } - + textStackView.addArrangedSubview(firstLabel) textStackView.addArrangedSubview(secondLabel) textStackView.addArrangedSubview(thirdLabel) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/Complete/WithdrawlCompleteController.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/Complete/WithdrawlCompleteController.swift index 75be91ad..8bc6c8c8 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/Complete/WithdrawlCompleteController.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/Complete/WithdrawlCompleteController.swift @@ -7,16 +7,16 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit final class WithdrawlCompleteController: BaseViewController { - + // MARK: - Properties var disposeBag = DisposeBag() - + var mainView = WithdrawlCompleteView() } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/Complete/WithdrawlCompleteView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/Complete/WithdrawlCompleteView.swift index 3538fc0a..e85f5a9a 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/Complete/WithdrawlCompleteView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/Complete/WithdrawlCompleteView.swift @@ -10,14 +10,14 @@ import UIKit import SnapKit final class WithdrawlCompleteView: UIView { - + // MARK: - Components private let imageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_check_fill_blue") return view }() - + private let titleLabel: PPLabel = { let text = "탈퇴 완료\n다음에 또 만나요" let label = PPLabel(style: .bold, fontSize: 20, text: text) @@ -25,7 +25,7 @@ final class WithdrawlCompleteView: UIView { label.textAlignment = .center return label }() - + private let descriptionLabel: PPLabel = { let text = "고객님이 만족하실 수 있는\n팝풀이 되도록 앞으로도 노력할게요 :)" let label = PPLabel(style: .regular, fontSize: 15, text: text) @@ -35,18 +35,17 @@ final class WithdrawlCompleteView: UIView { label.textColor = .g600 return label }() - + let checkButton: PPButton = { - let button = PPButton(style: .primary, text: "확인") - return button + return PPButton(style: .primary, text: "확인") }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -54,7 +53,7 @@ final class WithdrawlCompleteView: UIView { // MARK: - SetUp private extension WithdrawlCompleteView { - + func setUpConstraints() { self.addSubview(imageView) imageView.snp.makeConstraints { make in @@ -62,19 +61,19 @@ private extension WithdrawlCompleteView { make.top.equalToSuperview().inset(84) make.centerX.equalToSuperview() } - + self.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.top.equalTo(imageView.snp.bottom).offset(64) make.centerX.equalToSuperview() } - + self.addSubview(descriptionLabel) descriptionLabel.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(16) make.centerX.equalToSuperview() } - + self.addSubview(checkButton) checkButton.snp.makeConstraints { make in make.leading.bottom.trailing.equalToSuperview().inset(20) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/View/WithdrawlCheckSection.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/View/WithdrawlCheckSection.swift index 862397ec..de4f14c0 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/View/WithdrawlCheckSection.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/View/WithdrawlCheckSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct WithdrawlCheckSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = WithdrawlCheckSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/View/WithdrawlCheckSectionCell.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/View/WithdrawlCheckSectionCell.swift index 9d308a9d..2e0f1eb5 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/View/WithdrawlCheckSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/View/WithdrawlCheckSectionCell.swift @@ -7,23 +7,21 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class WithdrawlCheckSectionCell: UICollectionViewCell { - + // MARK: - Components private let checkImageView: UIImageView = { - let view = UIImageView() - return view + return UIImageView() }() - + private let titleLabel: UILabel = { - let label = UILabel() - return label + return UILabel() }() - + let textView: UITextView = { let view = UITextView() view.textContainerInset = .zero @@ -32,14 +30,13 @@ final class WithdrawlCheckSectionCell: UICollectionViewCell { view.backgroundColor = .clear return view }() - + let cellButton: UIButton = UIButton() - + private let trailingView: UIView = { - let view = UIView() - return view + return UIView() }() - + private let textTrailingView: UIView = { let view = UIView() view.layer.cornerRadius = 4 @@ -47,36 +44,36 @@ final class WithdrawlCheckSectionCell: UICollectionViewCell { view.layer.borderColor = UIColor.g100.cgColor view.backgroundColor = .w100 view.clipsToBounds = true - + return view }() - + private let contentStackView: UIStackView = { let view = UIStackView() view.axis = .vertical view.spacing = 10 return view }() - + private let placeHolderLabel: PPLabel = { let label = PPLabel(style: .medium, fontSize: 14, text: "탈퇴 이유를 입력해주세요") label.textColor = .g200 return label }() - + var disposeBag = DisposeBag() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() bind() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() @@ -92,7 +89,7 @@ private extension WithdrawlCheckSectionCell { make.centerY.equalToSuperview() make.leading.equalToSuperview() } - + cellButton.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.leading.equalTo(checkImageView.snp.trailing).offset(8) @@ -100,12 +97,12 @@ private extension WithdrawlCheckSectionCell { make.top.bottom.equalToSuperview() make.trailing.equalToSuperview() } - + contentView.addSubview(cellButton) cellButton.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() } - + contentView.addSubview(contentStackView) contentStackView.addArrangedSubview(cellButton) contentStackView.addArrangedSubview(trailingView) @@ -113,31 +110,30 @@ private extension WithdrawlCheckSectionCell { make.edges.equalToSuperview() } - trailingView.snp.makeConstraints { make in make.height.equalTo(120) } - + trailingView.addSubview(textTrailingView) textTrailingView.snp.makeConstraints { make in make.leading.equalToSuperview().inset(20) make.trailing.equalToSuperview() make.top.bottom.equalToSuperview() } - + textTrailingView.addSubview(textView) textView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview().inset(16) make.height.equalTo(70) } - + textTrailingView.addSubview(placeHolderLabel) placeHolderLabel.snp.makeConstraints { make in make.top.equalToSuperview().inset(16) make.leading.equalToSuperview().inset(20) } } - + func bind() { textView.rx.text .withUnretained(self) @@ -156,7 +152,7 @@ extension WithdrawlCheckSectionCell: Inputable { var id: Int64 var text: String? } - + func injection(with input: Input) { let image = input.isSelected ? UIImage(named: "icon_check_fill") : UIImage(named: "icon_check") checkImageView.image = image diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/View/WithdrawlReasonView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/View/WithdrawlReasonView.swift index 7692c604..de4f289b 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/View/WithdrawlReasonView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/View/WithdrawlReasonView.swift @@ -10,14 +10,14 @@ import UIKit import SnapKit final class WithdrawlReasonView: UIView { - + // MARK: - Components let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() view.headerLabel.setLineHeightText(text: "회원 탈퇴", font: .KorFont(style: .regular, size: 15)) return view }() - + private let titleLabel: UILabel = { let text = "탈퇴하려는 이유가\n무엇인가요?" let label = UILabel() @@ -25,7 +25,7 @@ final class WithdrawlReasonView: UIView { label.numberOfLines = 2 return label }() - + private let descriptionLabel: PPLabel = { let text = "알려주시는 내용을 참고해 더 나은 팝풀을\n만들어볼게요." let label = PPLabel(style: .regular, fontSize: 15, text: text) @@ -34,25 +34,24 @@ final class WithdrawlReasonView: UIView { label.setLineHeightText(text: text, font: .KorFont(style: .regular, size: 15), lineHeight: 1.4) return label }() - + let skipButton: PPButton = { let button = PPButton(style: .secondary, text: "건너뛰기") button.setBackgroundColor(.w100, for: .normal) return button }() - + let checkButton: PPButton = { - let button = PPButton(style: .primary, text: "확인", disabledText: "확인") - return button + return PPButton(style: .primary, text: "확인", disabledText: "확인") }() - + private let buttonStackView: UIStackView = { let view = UIStackView() view.distribution = .fillEqually view.spacing = 12 return view }() - + let contentCollectionView: UICollectionView = { let view = UICollectionView(frame: .zero, collectionViewLayout: .init()) view.backgroundColor = .g50 @@ -63,7 +62,7 @@ final class WithdrawlReasonView: UIView { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -71,34 +70,34 @@ final class WithdrawlReasonView: UIView { // MARK: - SetUp private extension WithdrawlReasonView { - + func setUpConstraints() { self.addSubview(headerView) headerView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() } - + self.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.top.equalTo(headerView.snp.bottom).offset(64) make.leading.trailing.equalToSuperview().inset(20) } - + self.addSubview(descriptionLabel) descriptionLabel.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(16) make.leading.trailing.equalToSuperview().inset(20) } - + self.addSubview(buttonStackView) buttonStackView.snp.makeConstraints { make in make.leading.trailing.bottom.equalToSuperview().inset(20) make.height.equalTo(50) } - + buttonStackView.addArrangedSubview(skipButton) buttonStackView.addArrangedSubview(checkButton) - + self.addSubview(contentCollectionView) contentCollectionView.snp.makeConstraints { make in make.top.equalTo(descriptionLabel.snp.bottom).offset(48) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/WithdrawlReasonController.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/WithdrawlReasonController.swift index b584ab6b..c3ecb0f2 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/WithdrawlReasonController.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/WithdrawlReasonController.swift @@ -7,22 +7,22 @@ import UIKit -import SnapKit -import RxCocoa -import RxSwift import ReactorKit -import RxKeyboard +import RxCocoa import RxGesture +import RxKeyboard +import RxSwift +import SnapKit final class WithdrawlReasonController: BaseViewController, View { - + typealias Reactor = WithdrawlReasonReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = WithdrawlReasonView() - + private var sections: [any Sectionable] = [] } @@ -32,7 +32,7 @@ extension WithdrawlReasonController { super.viewDidLoad() setUp() } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tabBarController?.tabBar.isHidden = true @@ -50,12 +50,12 @@ private extension WithdrawlReasonController { mainView.contentCollectionView.register( WithdrawlCheckSectionCell.self, forCellWithReuseIdentifier: WithdrawlCheckSectionCell.identifiers - ) + ) mainView.contentCollectionView.register( SpacingSectionCell.self, forCellWithReuseIdentifier: SpacingSectionCell.identifiers ) - + view.backgroundColor = .g50 view.addSubview(mainView) mainView.snp.makeConstraints { make in @@ -73,7 +73,7 @@ extension WithdrawlReasonController { owner.view.endEditing(true) } .disposed(by: disposeBag) - + RxKeyboard.instance.visibleHeight .drive { [weak self] height in if height > 0 { @@ -93,7 +93,7 @@ extension WithdrawlReasonController { .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.headerView.backButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -101,7 +101,7 @@ extension WithdrawlReasonController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.checkButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -109,7 +109,7 @@ extension WithdrawlReasonController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.skipButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -117,7 +117,7 @@ extension WithdrawlReasonController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -136,24 +136,24 @@ extension WithdrawlReasonController: UICollectionViewDelegate, UICollectionViewD func numberOfSections(in collectionView: UICollectionView) -> Int { return sections.count } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sections[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath ) -> UICollectionViewCell { let cell = sections[indexPath.section].getCell(collectionView: collectionView, indexPath: indexPath) guard let reactor = reactor else { return cell } - + if let cell = cell as? WithdrawlCheckSectionCell { cell.cellButton.rx.tap .map { Reactor.Action.cellTapped(row: indexPath.row) } .bind(to: reactor.action) .disposed(by: cell.disposeBag) - + cell.textView.rx.text .map { Reactor.Action.etcTextInput(text: $0)} .bind(to: reactor.action) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/WithdrawlReasonReactor.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/WithdrawlReasonReactor.swift index 29943ce2..ca08cbf5 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/WithdrawlReasonReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/WithdrawlReasonReactor.swift @@ -8,11 +8,11 @@ import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class WithdrawlReasonReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear @@ -22,25 +22,25 @@ final class WithdrawlReasonReactor: Reactor { case skipButtonTapped(controller: BaseViewController) case checkButtonTapped(controller: BaseViewController) } - + enum Mutation { case loadView case moveToRecentScene(controller: BaseViewController) case none case moveToCompleteScene(controller: BaseViewController) } - + struct State { var sections: [any Sectionable] = [] var buttonIsEnabled: Bool = false var isReloadView: Bool = false } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - + lazy var compositionalLayout: UICollectionViewCompositionalLayout = { UICollectionViewCompositionalLayout { [weak self] section, env in guard let self = self else { @@ -54,7 +54,7 @@ final class WithdrawlReasonReactor: Reactor { return getSection()[section].getSection(section: section, env: env) } }() - + private var reasonSection = WithdrawlCheckSection(inputDataList: []) private var spacing156Section = SpacingSection(inputDataList: [.init(spacing: 156)]) private let userAPIUseCase = UserAPIUseCaseImpl(repository: UserAPIRepositoryImpl(provider: ProviderImpl())) @@ -64,7 +64,7 @@ final class WithdrawlReasonReactor: Reactor { init() { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -96,7 +96,7 @@ final class WithdrawlReasonReactor: Reactor { .andThen(Observable.just(.moveToCompleteScene(controller: controller))) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state newState.isReloadView = false @@ -125,9 +125,9 @@ final class WithdrawlReasonReactor: Reactor { .disposed(by: disposeBag) controller.navigationController?.pushViewController(nextController, animated: true) } - + let isEmpty = reasonSection.inputDataList.filter { $0.isSelected == true }.isEmpty - + if let etc = reasonSection.inputDataList.filter({ $0.title == "기타" }).first { if etc.isSelected { if etc.text?.isEmpty ?? true { @@ -143,7 +143,7 @@ final class WithdrawlReasonReactor: Reactor { } return newState } - + func getSection() -> [any Sectionable] { return [ reasonSection, diff --git a/Poppool/Poppool/Presentation/Scene/Search/AfterSearch/SearchResultController.swift b/Poppool/Poppool/Presentation/Scene/Search/AfterSearch/SearchResultController.swift index 6db3bb92..92b01182 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/AfterSearch/SearchResultController.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/AfterSearch/SearchResultController.swift @@ -7,18 +7,18 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit final class SearchResultController: BaseViewController, View { - + typealias Reactor = SearchResultReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = SearchResultView() private var sections: [any Sectionable] = [] private let cellTapped: PublishSubject = .init() @@ -30,7 +30,7 @@ extension SearchResultController { super.viewDidLoad() setUp() } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tabBarController?.tabBar.isHidden = true @@ -45,7 +45,7 @@ private extension SearchResultController { } mainView.contentCollectionView.delegate = self mainView.contentCollectionView.dataSource = self - + mainView.contentCollectionView.register( SearchTitleSectionCell.self, forCellWithReuseIdentifier: SearchTitleSectionCell.identifiers @@ -72,7 +72,7 @@ private extension SearchResultController { // MARK: - Methods extension SearchResultController { func bind(reactor: Reactor) { - + cellTapped .withUnretained(self) .map({ (owner, indexPath) in @@ -80,7 +80,7 @@ extension SearchResultController { }) .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -103,11 +103,11 @@ extension SearchResultController: UICollectionViewDelegate, UICollectionViewData func numberOfSections(in collectionView: UICollectionView) -> Int { return sections.count } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sections[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath @@ -122,7 +122,7 @@ extension SearchResultController: UICollectionViewDelegate, UICollectionViewData } return cell } - + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { cellTapped.onNext(indexPath) } diff --git a/Poppool/Poppool/Presentation/Scene/Search/AfterSearch/SearchResultReactor.swift b/Poppool/Poppool/Presentation/Scene/Search/AfterSearch/SearchResultReactor.swift index 4dcde029..f4601985 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/AfterSearch/SearchResultReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/AfterSearch/SearchResultReactor.swift @@ -8,31 +8,31 @@ import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class SearchResultReactor: Reactor { - + // MARK: - Reactor enum Action { case returnSearch(text: String) case bookmarkButtonTapped(indexPath: IndexPath) case cellTapped(controller: BaseViewController, indexPath: IndexPath) } - + enum Mutation { case loadView case emptyView case moveToDetailScene(controller: BaseViewController, indexPath: IndexPath) } - + struct State { var sections: [any Sectionable] = [] var isEmptyResult: Bool = false } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() private var popUpAPIUseCase = PopUpAPIUseCaseImpl(repository: PopUpAPIRepositoryImpl(provider: ProviderImpl())) @@ -50,19 +50,19 @@ final class SearchResultReactor: Reactor { return getSection()[section].getSection(section: section, env: env) } }() - + private var titleSection = SearchTitleSection(inputDataList: [.init(title: "포함된 팝업", buttonTitle: nil)]) private var searchCountSection = SearchResultCountSection(inputDataList: [.init(count: 65)]) private var searchListSection = HomeCardGridSection(inputDataList: []) private let spacing24Section = SpacingSection(inputDataList: [.init(spacing: 24)]) private let spacing16Section = SpacingSection(inputDataList: [.init(spacing: 16)]) private let spacing64Section = SpacingSection(inputDataList: [.init(spacing: 64)]) - + // MARK: - init init() { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -112,7 +112,7 @@ final class SearchResultReactor: Reactor { } } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { @@ -128,8 +128,7 @@ final class SearchResultReactor: Reactor { } return newState } - - + func getSection() -> [any Sectionable] { return [ spacing24Section, @@ -142,14 +141,14 @@ final class SearchResultReactor: Reactor { } func hasFinalConsonant(_ text: String) -> Bool { guard let lastCharacter = text.last else { return false } - + let unicodeValue = Int(lastCharacter.unicodeScalars.first!.value) - + // 한글 유니코드 범위 체크 let base = 0xAC00 let last = 0xD7A3 guard base...last ~= unicodeValue else { return false } - + // 종성 인덱스 계산 (받침이 있으면 1 이상) let finalConsonantIndex = (unicodeValue - base) % 28 return finalConsonantIndex != 0 diff --git a/Poppool/Poppool/Presentation/Scene/Search/AfterSearch/View/SearchResultCountSection/SearchResultCountSection.swift b/Poppool/Poppool/Presentation/Scene/Search/AfterSearch/View/SearchResultCountSection/SearchResultCountSection.swift index 24ac0177..e3555ab5 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/AfterSearch/View/SearchResultCountSection/SearchResultCountSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/AfterSearch/View/SearchResultCountSection/SearchResultCountSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct SearchResultCountSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = SearchResultCountSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -38,7 +38,7 @@ struct SearchResultCountSection: Sectionable { // 섹션 생성 let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 5, leading: 20, bottom: 0, trailing: 20) - + return section } } diff --git a/Poppool/Poppool/Presentation/Scene/Search/AfterSearch/View/SearchResultCountSection/SearchResultCountSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Search/AfterSearch/View/SearchResultCountSection/SearchResultCountSectionCell.swift index ffcf0156..d7c5d211 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/AfterSearch/View/SearchResultCountSection/SearchResultCountSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/AfterSearch/View/SearchResultCountSection/SearchResultCountSectionCell.swift @@ -7,32 +7,32 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class SearchResultCountSectionCell: UICollectionViewCell { - + // MARK: - Components var disposeBag = DisposeBag() - + private let countLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 13) label.textColor = .g600 return label }() - + // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() @@ -54,7 +54,7 @@ extension SearchResultCountSectionCell: Inputable { struct Input { var count: Int } - + func injection(with input: Input) { countLabel.text = "총 \(input.count)개를 찾았어요." } diff --git a/Poppool/Poppool/Presentation/Scene/Search/AfterSearch/View/SearchResultView.swift b/Poppool/Poppool/Presentation/Scene/Search/AfterSearch/View/SearchResultView.swift index 74352be4..43bbd685 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/AfterSearch/View/SearchResultView.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/AfterSearch/View/SearchResultView.swift @@ -10,13 +10,12 @@ import UIKit import SnapKit final class SearchResultView: UIView { - + // MARK: - Components let contentCollectionView: UICollectionView = { - let view = UICollectionView(frame: .zero, collectionViewLayout: .init()) - return view + return UICollectionView(frame: .zero, collectionViewLayout: .init()) }() - + let emptyLabel: PPLabel = { let label = PPLabel(style: .medium, fontSize: 14, text: "검색 결과가 없어요:(\n다른 키워드로 검색해주세요") label.textAlignment = .center @@ -24,13 +23,13 @@ final class SearchResultView: UIView { label.textColor = .g400 return label }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -38,14 +37,14 @@ final class SearchResultView: UIView { // MARK: - SetUp private extension SearchResultView { - + func setUpConstraints() { self.addSubview(contentCollectionView) contentCollectionView.snp.makeConstraints { make in make.top.equalToSuperview().offset(56) make.leading.trailing.bottom.equalToSuperview() } - + self.addSubview(emptyLabel) emptyLabel.snp.makeConstraints { make in make.top.equalTo(contentCollectionView.snp.top).inset(193) diff --git a/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/SearchController.swift b/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/SearchController.swift index 7b00a9b1..9cac2f2b 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/SearchController.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/SearchController.swift @@ -7,19 +7,19 @@ import UIKit -import SnapKit -import RxCocoa -import RxSwift import ReactorKit +import RxCocoa import RxGesture +import RxSwift +import SnapKit final class SearchController: BaseViewController, View { - + typealias Reactor = SearchReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = SearchView() private var sections: [any Sectionable] = [] private let cellTapped: PublishSubject = .init() @@ -32,7 +32,7 @@ extension SearchController { super.viewDidLoad() setUp() } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tabBarController?.tabBar.isHidden = true @@ -81,7 +81,7 @@ extension SearchController { .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + cellTapped .withUnretained(self) .map({ (owner, indexPath) in @@ -89,13 +89,13 @@ extension SearchController { }) .bind(to: reactor.action) .disposed(by: disposeBag) - + pageChange .throttle(.milliseconds(1000), scheduler: MainScheduler.asyncInstance) .map { Reactor.Action.changePage } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -111,11 +111,11 @@ extension SearchController: UICollectionViewDelegate, UICollectionViewDataSource func numberOfSections(in collectionView: UICollectionView) -> Int { return sections.count } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sections[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath @@ -130,7 +130,7 @@ extension SearchController: UICollectionViewDelegate, UICollectionViewDataSource .bind(to: reactor.action) .disposed(by: cell.disposeBag) } - + if let cell = cell as? CancelableTagSectionCell { if searchList.isEmpty { cell.cancelButton.rx.tap @@ -151,7 +151,7 @@ extension SearchController: UICollectionViewDelegate, UICollectionViewDataSource } } } - + if let cell = cell as? SearchCountTitleSectionCell { cell.sortedButton.rx.tap .withUnretained(self) @@ -161,7 +161,7 @@ extension SearchController: UICollectionViewDelegate, UICollectionViewDataSource .bind(to: reactor.action) .disposed(by: cell.disposeBag) } - + if let cell = cell as? HomeCardSectionCell { cell.bookmarkButton.rx.tap .map { Reactor.Action.bookmarkButtonTapped(indexPath: indexPath)} @@ -170,7 +170,7 @@ extension SearchController: UICollectionViewDelegate, UICollectionViewDataSource } return cell } - + func scrollViewDidScroll(_ scrollView: UIScrollView) { mainView.endEditing(true) let contentHeight = scrollView.contentSize.height @@ -180,7 +180,7 @@ extension SearchController: UICollectionViewDelegate, UICollectionViewDataSource pageChange.onNext(()) } } - + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { cellTapped.onNext(indexPath) } diff --git a/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/SearchReactor.swift b/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/SearchReactor.swift index 396090d3..a8fa3122 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/SearchReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/SearchReactor.swift @@ -8,11 +8,11 @@ import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class SearchReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear @@ -29,7 +29,7 @@ final class SearchReactor: Reactor { case bookmarkButtonTapped(indexPath: IndexPath) case resetSearchKeyWord } - + enum Mutation { case loadView case moveToCategoryScene(controller: BaseViewController) @@ -38,29 +38,29 @@ final class SearchReactor: Reactor { case setSearchKeyWord(text: String?) case resetSearchKeyWord } - + struct State { var sections: [any Sectionable] = [] var searchKeyWord: String? } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - + private var sortedIndex: Int = 1 private var filterIndex: Int = 0 - + private var currentPage: Int32 = 0 private var lastAppendPage: Int32 = 0 private var lastPage: Int32 = 0 private var isLoading: Bool = false - + let userDefaultService = UserDefaultService() private let popUpAPIUseCase = PopUpAPIUseCaseImpl(repository: PopUpAPIRepositoryImpl(provider: ProviderImpl())) private let userAPIUseCase = UserAPIUseCaseImpl(repository: UserAPIRepositoryImpl(provider: ProviderImpl())) - + lazy var compositionalLayout: UICollectionViewCompositionalLayout = { UICollectionViewCompositionalLayout { [weak self] section, env in guard let self = self else { @@ -74,10 +74,10 @@ final class SearchReactor: Reactor { return getSection()[section].getSection(section: section, env: env) } }() - + private let recentKeywordTitleSection = SearchTitleSection(inputDataList: [.init(title: "최근 검색어", buttonTitle: "모두삭제")]) private var recentKeywordSection = CancelableTagSection(inputDataList: []) - + private let searchTitleSection = SearchTitleSection(inputDataList: [.init(title: "팝업스토어 찾기")]) private var searchCategorySection = CancelableTagSection(inputDataList: [ .init(title: "카테고리", isSelected: false, isCancelAble: false) @@ -89,12 +89,12 @@ final class SearchReactor: Reactor { private let spacing18Section = SpacingSection(inputDataList: [.init(spacing: 18)]) private let spacing48Section = SpacingSection(inputDataList: [.init(spacing: 48)]) private let spacing64Section = SpacingSection(inputDataList: [.init(spacing: 64)]) - + // MARK: - init init() { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { let sort = sortedIndex == 0 ? "NEWEST" : "MOST_VIEWED,MOST_COMMENTED,MOST_BOOKMARKED" @@ -179,7 +179,7 @@ final class SearchReactor: Reactor { } } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { @@ -199,7 +199,7 @@ final class SearchReactor: Reactor { } else { owner.action.onNext(.changeCategory(categoryList: state.categoryIDList, categoryTitleList: state.categoryTitleList)) } - + } if state.isReset { owner.action.onNext(.resetCategory)} }) @@ -229,7 +229,7 @@ final class SearchReactor: Reactor { } return newState } - + func getSection() -> [any Sectionable] { let searchList = userDefaultService.fetchArray(key: "searchList") ?? [] if searchList.isEmpty { @@ -262,19 +262,19 @@ final class SearchReactor: Reactor { ] } } - + func setSearchList() { let searchList = userDefaultService.fetchArray(key: "searchList") ?? [] recentKeywordSection.inputDataList = searchList.map { return .init(title: $0) } } - + func appendSearchList(text: String?) { if let text = text { if !text.isEmpty { var searchList = userDefaultService.fetchArray(key: "searchList") ?? [] if searchList.contains(text) { let targetIndex = searchList.firstIndex(of: text)! - searchList.remove(at: targetIndex) + searchList.remove(at: targetIndex) } searchList = [text] + searchList userDefaultService.save(key: "searchList", value: searchList) @@ -282,19 +282,19 @@ final class SearchReactor: Reactor { } } } - + func removeSearchList(indexPath: IndexPath) { var searchList = userDefaultService.fetchArray(key: "searchList") ?? [] searchList.remove(at: indexPath.row) userDefaultService.save(key: "searchList", value: searchList) recentKeywordSection.inputDataList = searchList.map { return .init(title: $0) } } - + func resetSearchList() { userDefaultService.save(key: "searchList", value: []) recentKeywordSection.inputDataList = [] } - + func setBottomSearchList(sort: String?) -> Observable { let isOpen = filterIndex == 0 ? true : false let categorys = searchCategorySection.inputDataList.compactMap { $0.id } diff --git a/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/CancelableTagSection/CancelableTagSection.swift b/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/CancelableTagSection/CancelableTagSection.swift index fb80227f..7a26b80f 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/CancelableTagSection/CancelableTagSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/CancelableTagSection/CancelableTagSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct CancelableTagSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = CancelableTagSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .estimated(100), diff --git a/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/CancelableTagSection/CancelableTagSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/CancelableTagSection/CancelableTagSectionCell.swift index 7a3afb15..e89783de 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/CancelableTagSection/CancelableTagSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/CancelableTagSection/CancelableTagSectionCell.swift @@ -7,43 +7,41 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class CancelableTagSectionCell: UICollectionViewCell { - + // MARK: - Components var disposeBag = DisposeBag() - + private let titleLabel: PPLabel = { - let label = PPLabel(style: .medium, fontSize: 11) - return label + return PPLabel(style: .medium, fontSize: 11) }() - + let cancelButton: UIButton = { - let button = UIButton() - return button + return UIButton() }() - + private let contentStackView: UIStackView = { let view = UIStackView() view.alignment = .center view.spacing = 2 return view }() - + // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() @@ -56,7 +54,7 @@ private extension CancelableTagSectionCell { contentView.layer.cornerRadius = 15.5 contentView.clipsToBounds = true contentView.layer.borderWidth = 1 - + contentView.addSubview(contentStackView) contentStackView.snp.makeConstraints { make in make.top.bottom.equalToSuperview() @@ -65,7 +63,7 @@ private extension CancelableTagSectionCell { } contentStackView.addArrangedSubview(titleLabel) contentStackView.addArrangedSubview(cancelButton) - + titleLabel.snp.makeConstraints { make in make.height.equalTo(18) } @@ -82,7 +80,7 @@ extension CancelableTagSectionCell: Inputable { var isSelected: Bool = false var isCancelAble: Bool = true } - + func injection(with input: Input) { let xmarkImage = input.isSelected ? UIImage(named: "icon_xmark_white") : UIImage(named: "icon_xmark_gray") cancelButton.setImage(xmarkImage, for: .normal) @@ -98,7 +96,7 @@ extension CancelableTagSectionCell: Inputable { contentView.layer.borderColor = UIColor.g200.cgColor } cancelButton.isHidden = !input.isCancelAble - + if input.isCancelAble { contentStackView.snp.updateConstraints { make in make.trailing.equalToSuperview().inset(8) diff --git a/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchCountTitleSection/SearchCountTitleSection.swift b/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchCountTitleSection/SearchCountTitleSection.swift index 6feaa53b..41617011 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchCountTitleSection/SearchCountTitleSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchCountTitleSection/SearchCountTitleSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct SearchCountTitleSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = SearchCountTitleSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -38,7 +38,7 @@ struct SearchCountTitleSection: Sectionable { // 섹션 생성 let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 0, leading: 20, bottom: 0, trailing: 20) - + return section } } diff --git a/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchCountTitleSection/SearchCountTitleSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchCountTitleSection/SearchCountTitleSectionCell.swift index 7ae94994..bc765fe4 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchCountTitleSection/SearchCountTitleSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchCountTitleSection/SearchCountTitleSectionCell.swift @@ -7,49 +7,47 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class SearchCountTitleSectionCell: UICollectionViewCell { - + // MARK: - Components var disposeBag = DisposeBag() - + private let countLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 13) label.textColor = .g400 return label }() - + private let sortedTitleLabel: PPLabel = { - let label = PPLabel(style: .regular, fontSize: 13) - return label + return PPLabel(style: .regular, fontSize: 13) }() - + private let downImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_dropdown") view.isUserInteractionEnabled = false return view }() - + let sortedButton: UIButton = { - let button = UIButton() - return button + return UIButton() }() - + // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() @@ -64,19 +62,19 @@ private extension SearchCountTitleSectionCell { make.leading.equalToSuperview() make.centerY.equalToSuperview() } - + contentView.addSubview(sortedButton) sortedButton.snp.makeConstraints { make in make.trailing.equalToSuperview() make.centerY.equalToSuperview() } - + sortedButton.addSubview(sortedTitleLabel) sortedTitleLabel.snp.makeConstraints { make in make.leading.equalToSuperview() make.centerY.equalToSuperview() } - + sortedButton.addSubview(downImageView) downImageView.snp.makeConstraints { make in make.size.equalTo(22) @@ -92,7 +90,7 @@ extension SearchCountTitleSectionCell: Inputable { var count: Int64 var sortedTitle: String? } - + func injection(with input: Input) { sortedTitleLabel.text = input.sortedTitle countLabel.text = "총 \(input.count)개" diff --git a/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchTitleSection/SearchTitleSection.swift b/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchTitleSection/SearchTitleSection.swift index aa09c76a..3731bbaa 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchTitleSection/SearchTitleSection.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchTitleSection/SearchTitleSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct SearchTitleSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = SearchTitleSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), @@ -37,7 +37,7 @@ struct SearchTitleSection: Sectionable { // 섹션 생성 let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 0, leading: 20, bottom: 0, trailing: 20) - + return section } } diff --git a/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchTitleSection/SearchTitleSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchTitleSection/SearchTitleSectionCell.swift index adf7d9da..97d074a2 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchTitleSection/SearchTitleSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchTitleSection/SearchTitleSectionCell.swift @@ -7,36 +7,35 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class SearchTitleSectionCell: UICollectionViewCell { - + // MARK: - Components var disposeBag = DisposeBag() - + private let sectionTitleLabel: UILabel = { let label = UILabel() label.font = .KorFont(style: .bold, size: 16) return label }() - + let titleButton: UIButton = { - let button = UIButton() - return button + return UIButton() }() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } - + override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() @@ -52,7 +51,7 @@ private extension SearchTitleSectionCell { make.centerY.equalToSuperview() make.height.equalTo(22) } - + self.addSubview(titleButton) titleButton.snp.makeConstraints { make in make.centerY.equalToSuperview() @@ -67,7 +66,7 @@ extension SearchTitleSectionCell: Inputable { var title: String? var buttonTitle: String? } - + func injection(with input: Input) { sectionTitleLabel.text = input.title if let buttonTitle = input.buttonTitle { diff --git a/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchView.swift b/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchView.swift index f7925427..874e3b1a 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchView.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchView.swift @@ -10,19 +10,18 @@ import UIKit import SnapKit final class SearchView: UIView { - + // MARK: - Components let contentCollectionView: UICollectionView = { - let view = UICollectionView(frame: .zero, collectionViewLayout: .init()) - return view + return UICollectionView(frame: .zero, collectionViewLayout: .init()) }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -30,7 +29,7 @@ final class SearchView: UIView { // MARK: - SetUp private extension SearchView { - + func setUpConstraints() { self.addSubview(contentCollectionView) contentCollectionView.snp.makeConstraints { make in diff --git a/Poppool/Poppool/Presentation/Scene/Search/CategoryController/SearchCategoryController.swift b/Poppool/Poppool/Presentation/Scene/Search/CategoryController/SearchCategoryController.swift index da326158..c7ab66a9 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/CategoryController/SearchCategoryController.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/CategoryController/SearchCategoryController.swift @@ -7,19 +7,19 @@ import UIKit -import SnapKit +import PanModal +import ReactorKit import RxCocoa import RxSwift -import ReactorKit -import PanModal +import SnapKit final class SearchCategoryController: BaseViewController, View { - + typealias Reactor = SearchCategoryReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = SearchCategoryView() private var sections: [any Sectionable] = [] private let cellTapped: PublishSubject = .init() @@ -59,12 +59,12 @@ extension SearchCategoryController { .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + cellTapped .map { Reactor.Action.cellTapped(indexPath: $0)} .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.resetButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -72,7 +72,7 @@ extension SearchCategoryController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.closeButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -80,7 +80,7 @@ extension SearchCategoryController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.saveButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -88,7 +88,7 @@ extension SearchCategoryController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -105,11 +105,11 @@ extension SearchCategoryController: UICollectionViewDelegate, UICollectionViewDa func numberOfSections(in collectionView: UICollectionView) -> Int { return sections.count } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sections[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath @@ -118,7 +118,7 @@ extension SearchCategoryController: UICollectionViewDelegate, UICollectionViewDa guard let reactor = reactor else { return cell } return cell } - + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { cellTapped.onNext(indexPath) } diff --git a/Poppool/Poppool/Presentation/Scene/Search/CategoryController/SearchCategoryReactor.swift b/Poppool/Poppool/Presentation/Scene/Search/CategoryController/SearchCategoryReactor.swift index 8f5e4461..383d676f 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/CategoryController/SearchCategoryReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/CategoryController/SearchCategoryReactor.swift @@ -8,11 +8,11 @@ import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class SearchCategoryReactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear @@ -21,14 +21,14 @@ final class SearchCategoryReactor: Reactor { case resetButtonTapped(controller: BaseViewController) case cellTapped(indexPath: IndexPath) } - + enum Mutation { case moveToRecentScene(controller: BaseViewController) case loadView case save(controller: BaseViewController) case reset(controller: BaseViewController) } - + struct State { var sections: [any Sectionable] = [] var categoryIDList: [Int64] = [] @@ -37,9 +37,9 @@ final class SearchCategoryReactor: Reactor { var isSave: Bool = false var isReset: Bool = false } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() var originCategoryList: [Int64] @@ -58,13 +58,13 @@ final class SearchCategoryReactor: Reactor { return getSection()[section].getSection(section: section, env: env) } }() - + // MARK: - init init(originCategoryList: [Int64]) { self.initialState = State() self.originCategoryList = originCategoryList } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -89,7 +89,7 @@ final class SearchCategoryReactor: Reactor { return Observable.just(.reset(controller: controller)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { @@ -111,7 +111,7 @@ final class SearchCategoryReactor: Reactor { } return newState } - + func getSection() -> [any Sectionable] { return [ tagSection diff --git a/Poppool/Poppool/Presentation/Scene/Search/CategoryController/SearchCategoryView.swift b/Poppool/Poppool/Presentation/Scene/Search/CategoryController/SearchCategoryView.swift index 42ebb089..f8a452ca 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/CategoryController/SearchCategoryView.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/CategoryController/SearchCategoryView.swift @@ -10,48 +10,45 @@ import UIKit import SnapKit final class SearchCategoryView: UIView { - + // MARK: - Components private let titleLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 18, text: "카테고리를 선택해주세요") - return label + return PPLabel(style: .bold, fontSize: 18, text: "카테고리를 선택해주세요") }() - + let closeButton: UIButton = { let button = UIButton() button.setImage(UIImage(named: "icon_xmark"), for: .normal) return button }() - + let contentCollectionView: UICollectionView = { let view = UICollectionView(frame: .zero, collectionViewLayout: .init()) view.isScrollEnabled = false return view }() - + let buttonStackView: UIStackView = { let view = UIStackView() view.distribution = .fillEqually view.spacing = 12 return view }() - + let resetButton: PPButton = { - let button = PPButton(style: .secondary, text: "초기화") - return button + return PPButton(style: .secondary, text: "초기화") }() - + let saveButton: PPButton = { - let button = PPButton(style: .primary, text: "옵션저장", disabledText: "옵션저장") - return button + return PPButton(style: .primary, text: "옵션저장", disabledText: "옵션저장") }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -59,14 +56,14 @@ final class SearchCategoryView: UIView { // MARK: - SetUp private extension SearchCategoryView { - + func setUpConstraints() { self.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.leading.equalToSuperview().inset(20) make.top.equalToSuperview().inset(12) } - + self.addSubview(closeButton) closeButton.snp.makeConstraints { make in make.size.equalTo(24) diff --git a/Poppool/Poppool/Presentation/Scene/Search/Main/SearchMainController.swift b/Poppool/Poppool/Presentation/Scene/Search/Main/SearchMainController.swift index 9a16aa29..c9b9fe6f 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/Main/SearchMainController.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/Main/SearchMainController.swift @@ -7,39 +7,39 @@ import UIKit -import SnapKit +import Pageboy +import ReactorKit import RxCocoa import RxSwift -import ReactorKit -import Pageboy +import SnapKit import Tabman final class SearchMainController: BaseTabmanController, View { - + typealias Reactor = SearchMainReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = SearchMainView() - + var beforeController: SearchController = { let controller = SearchController() controller.reactor = SearchReactor() return controller }() - + var afterController: SearchResultController = { let controller = SearchResultController() controller.reactor = SearchResultReactor() return controller }() - + lazy var controllers = [ beforeController, afterController ] - + var isResponseTextField: Bool = false } @@ -49,7 +49,7 @@ extension SearchMainController { super.viewDidLoad() setUp() } - + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) if !isResponseTextField { @@ -57,7 +57,7 @@ extension SearchMainController { isResponseTextField = true } } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tabBarController?.tabBar.isHidden = true @@ -95,7 +95,7 @@ extension SearchMainController { } }) .disposed(by: disposeBag) - + // mainView.searchTextField.rx.controlEvent(.editingDidEndOnExit) // .withUnretained(self) // .map { (owner, _) in @@ -129,7 +129,6 @@ extension SearchMainController { }) .disposed(by: disposeBag) - mainView.searchTextField.rx.text .withUnretained(self) .subscribe(onNext: { (owner, text) in @@ -140,7 +139,7 @@ extension SearchMainController { } }) .disposed(by: disposeBag) - + mainView.cancelButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -156,7 +155,7 @@ extension SearchMainController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.clearButton.rx.tap .withUnretained(self) .subscribe { (owner, _) in @@ -164,7 +163,7 @@ extension SearchMainController { owner.mainView.clearButton.isHidden = true } .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -180,18 +179,18 @@ extension SearchMainController: PageboyViewControllerDataSource, TMBarDataSource func barItem(for bar: any Tabman.TMBar, at index: Int) -> any Tabman.TMBarItemable { return TMBarItem(title: "") } - + func numberOfViewControllers(in pageboyViewController: Pageboy.PageboyViewController) -> Int { return controllers.count } - + func viewController( for pageboyViewController: Pageboy.PageboyViewController, at index: Pageboy.PageboyViewController.PageIndex ) -> UIViewController? { return controllers[index] } - + func defaultPage( for pageboyViewController: Pageboy.PageboyViewController ) -> Pageboy.PageboyViewController.Page? { diff --git a/Poppool/Poppool/Presentation/Scene/Search/Main/SearchMainReactor.swift b/Poppool/Poppool/Presentation/Scene/Search/Main/SearchMainReactor.swift index 29446caa..25c83527 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/Main/SearchMainReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/Main/SearchMainReactor.swift @@ -6,33 +6,33 @@ // import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class SearchMainReactor: Reactor { - + // MARK: - Reactor enum Action { case returnSearchKeyWord(text: String?) } - + enum Mutation { case setSearchKeyWord(text: String?) } - + struct State { var searchKeyword: String? } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() // MARK: - init init() { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -40,7 +40,7 @@ final class SearchMainReactor: Reactor { return Observable.just(.setSearchKeyWord(text: text)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { diff --git a/Poppool/Poppool/Presentation/Scene/Search/Main/SearchMainView.swift b/Poppool/Poppool/Presentation/Scene/Search/Main/SearchMainView.swift index d04eb79d..89364b85 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/Main/SearchMainView.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/Main/SearchMainView.swift @@ -10,7 +10,7 @@ import UIKit import SnapKit final class SearchMainView: UIView { - + // MARK: - Components private let searchTrailingView: UIView = { let view = UIView() @@ -19,20 +19,20 @@ final class SearchMainView: UIView { view.clipsToBounds = true return view }() - + private let searchIconImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_search_gray") return view }() - + private let searchStackView: UIStackView = { let view = UIStackView() view.spacing = 4 view.alignment = .center return view }() - + let cancelButton: UIButton = { let button = UIButton(type: .system) button.setTitle("취소", for: .normal) @@ -41,33 +41,33 @@ final class SearchMainView: UIView { button.imageView?.contentMode = .scaleAspectFit return button }() - + let searchTextField: UITextField = { let view = UITextField() view.font = .KorFont(style: .regular, size: 14) view.setPlaceholder(text: "팝업스토어명을 입력해보세요", color: .g400, font: .KorFont(style: .regular, size: 14)!) return view }() - + let clearButton: UIButton = { let button = UIButton() button.setImage(UIImage(named: "icon_clearButton"), for: .normal) return button }() - + private var headerStackView: UIStackView = { let view = UIStackView() view.alignment = .center view.spacing = 16 return view }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -75,7 +75,7 @@ final class SearchMainView: UIView { // MARK: - SetUp private extension SearchMainView { - + func setUpConstraints() { searchTrailingView.snp.makeConstraints { make in make.height.equalTo(37) @@ -96,15 +96,15 @@ private extension SearchMainView { searchStackView.addArrangedSubview(searchIconImageView) searchStackView.addArrangedSubview(searchTextField) searchStackView.addArrangedSubview(clearButton) - + searchIconImageView.snp.makeConstraints { make in make.size.equalTo(20) } - + searchTextField.snp.makeConstraints { make in make.height.equalTo(21) } - + clearButton.snp.makeConstraints { make in make.size.equalTo(16) } diff --git a/Poppool/Poppool/Presentation/Scene/Search/SortedController/SearchSortedController.swift b/Poppool/Poppool/Presentation/Scene/Search/SortedController/SearchSortedController.swift index 12c816d6..4fb31f8a 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/SortedController/SearchSortedController.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/SortedController/SearchSortedController.swift @@ -7,19 +7,19 @@ import UIKit -import SnapKit +import PanModal +import ReactorKit import RxCocoa import RxSwift -import ReactorKit -import PanModal +import SnapKit final class SearchSortedController: BaseViewController, View { - + typealias Reactor = SearchSortedReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = SearchSortedView() } @@ -51,7 +51,7 @@ extension SearchSortedController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.filterSegmentControl.rx.controlEvent(.valueChanged) .withUnretained(self) .map({ (owner, _) in @@ -59,7 +59,7 @@ extension SearchSortedController { }) .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.sortedSegmentControl.rx.controlEvent(.valueChanged) .withUnretained(self) .map({ (owner, _) in @@ -67,7 +67,7 @@ extension SearchSortedController { }) .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.saveButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -75,7 +75,7 @@ extension SearchSortedController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in diff --git a/Poppool/Poppool/Presentation/Scene/Search/SortedController/SearchSortedReactor.swift b/Poppool/Poppool/Presentation/Scene/Search/SortedController/SearchSortedReactor.swift index 747b9bfa..12dfc2d5 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/SortedController/SearchSortedReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/SortedController/SearchSortedReactor.swift @@ -6,11 +6,11 @@ // import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class SearchSortedReactor: Reactor { - + // MARK: - Reactor enum Action { case closeButtonTapped(controller: BaseViewController) @@ -18,29 +18,29 @@ final class SearchSortedReactor: Reactor { case changeSortedIndex(index: Int) case saveButtonTapped(controller: BaseViewController) } - + enum Mutation { case moveToRecentScene(controller: BaseViewController) case loadView case save(controller: BaseViewController) } - + struct State { var filterIndex: Int var sortedIndex: Int var saveButtonIsEnable: Bool = false var isSave: Bool = false } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() var originFilterIndex: Int var originSortedIndex: Int private var selectedFilterIndex: Int private var selectedSortedIndex: Int - + // MARK: - init init(filterIndex: Int, sortedIndex: Int) { self.initialState = State(filterIndex: filterIndex, sortedIndex: sortedIndex) @@ -49,7 +49,7 @@ final class SearchSortedReactor: Reactor { self.selectedFilterIndex = filterIndex self.selectedSortedIndex = sortedIndex } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -65,7 +65,7 @@ final class SearchSortedReactor: Reactor { return Observable.just(.save(controller: controller)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { @@ -74,7 +74,7 @@ final class SearchSortedReactor: Reactor { case .loadView: newState.filterIndex = selectedFilterIndex newState.sortedIndex = selectedSortedIndex - + if selectedFilterIndex != originFilterIndex || selectedSortedIndex != originSortedIndex { newState.saveButtonIsEnable = true } else { diff --git a/Poppool/Poppool/Presentation/Scene/Search/SortedController/SearchSortedView.swift b/Poppool/Poppool/Presentation/Scene/Search/SortedController/SearchSortedView.swift index d528513c..243560f1 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/SortedController/SearchSortedView.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/SortedController/SearchSortedView.swift @@ -10,50 +10,44 @@ import UIKit import SnapKit final class SearchSortedView: UIView { - + // MARK: - Components private let titleLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 18, text: "노출 순서를 선택해주세요") - return label + return PPLabel(style: .bold, fontSize: 18, text: "노출 순서를 선택해주세요") }() - + let closeButton: UIButton = { let button = UIButton() button.setImage(UIImage(named: "icon_xmark"), for: .normal) return button }() - + private let filterTitleLabel: PPLabel = { - let label = PPLabel(style: .regular, fontSize: 13, text: "노출 조건") - return label + return PPLabel(style: .regular, fontSize: 13, text: "노출 조건") }() - + let filterSegmentControl: PPSegmentedControl = { - let control = PPSegmentedControl(type: .base, segments: ["오픈", "종료"], selectedSegmentIndex: 0) - return control + return PPSegmentedControl(type: .base, segments: ["오픈", "종료"], selectedSegmentIndex: 0) }() - + private let sortedTitleLabel: PPLabel = { - let label = PPLabel(style: .regular, fontSize: 13, text: "팝업순서") - return label + return PPLabel(style: .regular, fontSize: 13, text: "팝업순서") }() - + let sortedSegmentControl: PPSegmentedControl = { - let control = PPSegmentedControl(type: .base, segments: ["신규순", "인기순"], selectedSegmentIndex: 0) - return control + return PPSegmentedControl(type: .base, segments: ["신규순", "인기순"], selectedSegmentIndex: 0) }() - + let saveButton: PPButton = { - let button = PPButton(style: .primary, text: "저장", disabledText: "저장") - return button + return PPButton(style: .primary, text: "저장", disabledText: "저장") }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -61,45 +55,45 @@ final class SearchSortedView: UIView { // MARK: - SetUp private extension SearchSortedView { - + func setUpConstraints() { self.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.leading.equalToSuperview().inset(20) make.top.equalToSuperview().inset(32) } - + self.addSubview(closeButton) closeButton.snp.makeConstraints { make in make.size.equalTo(24) make.trailing.equalToSuperview().inset(20) make.centerY.equalTo(titleLabel) } - + self.addSubview(filterTitleLabel) filterTitleLabel.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(36) make.leading.equalToSuperview().inset(20) } - + self.addSubview(filterSegmentControl) filterSegmentControl.snp.makeConstraints { make in make.top.equalTo(filterTitleLabel.snp.bottom).offset(8) make.leading.trailing.equalToSuperview().inset(20) } - + self.addSubview(sortedTitleLabel) sortedTitleLabel.snp.makeConstraints { make in make.top.equalTo(filterSegmentControl.snp.bottom).offset(20) make.leading.equalToSuperview().inset(20) } - + self.addSubview(sortedSegmentControl) sortedSegmentControl.snp.makeConstraints { make in make.top.equalTo(sortedTitleLabel.snp.bottom).offset(8) make.leading.trailing.equalToSuperview().inset(20) } - + self.addSubview(saveButton) saveButton.snp.makeConstraints { make in make.top.equalTo(sortedSegmentControl.snp.bottom).offset(32) diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Main/SignUpMainController.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Main/SignUpMainController.swift index 6580a573..6fd26f5c 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Main/SignUpMainController.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Main/SignUpMainController.swift @@ -7,46 +7,46 @@ import UIKit -import SnapKit +import Pageboy +import ReactorKit import RxCocoa import RxSwift -import ReactorKit -import Pageboy +import SnapKit import Tabman final class SignUpMainController: BaseTabmanController, View { - + typealias Reactor = SignUpMainReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = SignUpMainView() - + var step1Controller: SignUpStep1Controller = { let controller = SignUpStep1Controller() controller.reactor = SignUpStep1Reactor() return controller }() - + var step2Controller: SignUpStep2Controller = { let controller = SignUpStep2Controller() controller.reactor = SignUpStep2Reactor() return controller }() - + var step3Controller: SignUpStep3Controller = { let controller = SignUpStep3Controller() controller.reactor = SignUpStep3Reactor() return controller }() - + var step4Controller: SignUpStep4Controller = { let controller = SignUpStep4Controller() controller.reactor = SignUpStep4Reactor() return controller }() - + lazy var controllers = [ step1Controller, step2Controller, @@ -78,7 +78,7 @@ private extension SignUpMainController { // MARK: - Methods extension SignUpMainController { func bind(reactor: Reactor) { - + // 취소버튼 이벤트 mainView.headerView.cancelButton.rx.tap .withUnretained(self) @@ -87,7 +87,7 @@ extension SignUpMainController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + // 뒤로가기 버튼 mainView.headerView.backButton.rx.tap .withUnretained(self) @@ -97,7 +97,7 @@ extension SignUpMainController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + // step1 button tap 이벤트 step1Controller.mainView.completeButton.rx.tap .withUnretained(self) @@ -107,7 +107,7 @@ extension SignUpMainController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + // step2 button tap 이벤트 step2Controller.mainView.completeButton.rx.tap .withUnretained(self) @@ -117,7 +117,7 @@ extension SignUpMainController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + // step3 button tap 이벤트 step3Controller.mainView.completeButton.rx.tap .withUnretained(self) @@ -127,7 +127,7 @@ extension SignUpMainController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + // step3 Skip button tap 이벤트 step3Controller.mainView.skipButton.rx.tap .withUnretained(self) @@ -137,7 +137,7 @@ extension SignUpMainController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + // step4 button tap 이벤트 step4Controller.mainView.completeButton.rx.tap .withUnretained(self) @@ -148,7 +148,7 @@ extension SignUpMainController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + // step4 Skip button tap 이벤트 step4Controller.mainView.skipButton.rx.tap .withUnretained(self) @@ -160,7 +160,6 @@ extension SignUpMainController { .bind(to: reactor.action) .disposed(by: disposeBag) - // step1 Terms 이벤트 step1Controller.reactor?.state .map({ (state) in @@ -169,7 +168,7 @@ extension SignUpMainController { }) .bind(to: reactor.action) .disposed(by: disposeBag) - + // step2 nickName 이벤트 step2Controller.reactor?.state .map({ state in @@ -177,7 +176,7 @@ extension SignUpMainController { }) .bind(to: reactor.action) .disposed(by: disposeBag) - + // step3 category 이벤트 step3Controller.reactor?.state .map({ state in @@ -185,7 +184,7 @@ extension SignUpMainController { }) .bind(to: reactor.action) .disposed(by: disposeBag) - + // step4 gender 이벤트 step4Controller.reactor?.state .map({ state in @@ -205,7 +204,6 @@ extension SignUpMainController { .bind(to: reactor.action) .disposed(by: disposeBag) - reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -225,18 +223,18 @@ extension SignUpMainController: PageboyViewControllerDataSource, TMBarDataSource func barItem(for bar: any Tabman.TMBar, at index: Int) -> any Tabman.TMBarItemable { return TMBarItem(title: "") } - + func numberOfViewControllers(in pageboyViewController: Pageboy.PageboyViewController) -> Int { return controllers.count } - + func viewController( for pageboyViewController: Pageboy.PageboyViewController, at index: Pageboy.PageboyViewController.PageIndex ) -> UIViewController? { return controllers[index] } - + func defaultPage( for pageboyViewController: Pageboy.PageboyViewController ) -> Pageboy.PageboyViewController.Page? { diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Main/SignUpMainReactor.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Main/SignUpMainReactor.swift index 238105b7..363a7ec0 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Main/SignUpMainReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Main/SignUpMainReactor.swift @@ -6,11 +6,11 @@ // import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class SignUpMainReactor: Reactor { - + // MARK: - Reactor enum Action { case cancelButtonTapped(controller: BaseTabmanController) @@ -27,7 +27,7 @@ final class SignUpMainReactor: Reactor { case changeGender(gender: String?) case changeAge(age: Int?) } - + enum Mutation { case moveToLoginScene(controller: BaseTabmanController) case increasePageIndex(controller: BaseTabmanController, currentIndex: Int) @@ -41,7 +41,7 @@ final class SignUpMainReactor: Reactor { case setGender(gender: String?) case setAge(age: Int?) } - + struct State { var currentIndex: Int = 0 var isMarketingAgree: Bool = false @@ -52,25 +52,25 @@ final class SignUpMainReactor: Reactor { var categoryIDList: [Int64] = [] var age: Int? } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - + private var authrizationCode: String? - + private var signUpAPIUseCase = SignUpAPIUseCaseImpl(repository: SignUpRepositoryImpl(provider: ProviderImpl())) private let userDefaultService = UserDefaultService() var isFirstResponderCase: Bool - + // MARK: - init init(isFirstResponderCase: Bool, authrizationCode: String?) { self.initialState = State() self.authrizationCode = authrizationCode self.isFirstResponderCase = isFirstResponderCase } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -105,7 +105,7 @@ final class SignUpMainReactor: Reactor { ]) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { @@ -121,9 +121,9 @@ final class SignUpMainReactor: Reactor { guard let socialType = userDefaultService.fetch(key: "socialType"), let nickName = newState.nickName, let gender = newState.gender else { return newState } - + signUpAPIUseCase = SignUpAPIUseCaseImpl(repository: SignUpRepositoryImpl(provider: ProviderImpl())) - + signUpAPIUseCase.trySignUp( nickName: nickName, gender: gender, @@ -146,7 +146,7 @@ final class SignUpMainReactor: Reactor { ToastMaker.createToast(message: "회원가입 실패:\(error.localizedDescription)") } .disposed(by: disposeBag) - + case .skipStep3(let controller, let currentIndex): if newState.categoryIDList.count >= 5 { newState.categorys = Array(newState.categoryIDList.shuffled().prefix(5)) diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Main/View/SignUpMainView.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Main/View/SignUpMainView.swift index e3b9d5c4..a4928637 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Main/View/SignUpMainView.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Main/View/SignUpMainView.swift @@ -10,18 +10,18 @@ import UIKit import SnapKit final class SignUpMainView: UIView { - + // MARK: - Components let headerView: PPCancelHeaderView = PPCancelHeaderView() - + let progressIndicator: PPProgressIndicator = PPProgressIndicator(totalStep: 4, startPoint: 1) - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -29,7 +29,7 @@ final class SignUpMainView: UIView { // MARK: - SetUp private extension SignUpMainView { - + func setUpConstraints() { self.addSubview(headerView) headerView.snp.makeConstraints { make in diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteController.swift b/Poppool/Poppool/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteController.swift index f0ab226f..b7f26876 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteController.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteController.swift @@ -7,18 +7,18 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit final class SignUpCompleteController: BaseViewController, View { - + typealias Reactor = SignUpCompleteReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = SignUpCompleteView() } @@ -43,7 +43,7 @@ private extension SignUpCompleteController { // MARK: - Methods extension SignUpCompleteController { func bind(reactor: Reactor) { - + mainView.bottomButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -51,7 +51,7 @@ extension SignUpCompleteController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -65,7 +65,7 @@ extension SignUpCompleteController { let categoryString = state.categoryTitles.enumerated() .map { "#\($0.element)" } .joined(separator: ", ") - + let attributedText = NSMutableAttributedString( string: categoryString, attributes: [ diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteReactor.swift b/Poppool/Poppool/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteReactor.swift index 7c5e14a1..9269d52a 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteReactor.swift @@ -6,27 +6,27 @@ // import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class SignUpCompleteReactor: Reactor { - + // MARK: - Reactor enum Action { case completeButtonTapped(controller: BaseViewController) } - + enum Mutation { case moveToHomeScene(controller: BaseViewController) } - + struct State { var nickName: String var categoryTitles: [String] } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() var isFirstResponderCase: Bool @@ -35,7 +35,7 @@ final class SignUpCompleteReactor: Reactor { self.initialState = State(nickName: nickName, categoryTitles: categoryTitles) self.isFirstResponderCase = isFirstResponderCase } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -43,7 +43,7 @@ final class SignUpCompleteReactor: Reactor { return Observable.just(.moveToHomeScene(controller: controller)) } } - + func reduce(state: State, mutation: Mutation) -> State { switch mutation { case .moveToHomeScene(let controller): diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteView.swift b/Poppool/Poppool/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteView.swift index 73b5739c..b0ee7a2a 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteView.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteView.swift @@ -10,47 +10,43 @@ import UIKit import SnapKit final class SignUpCompleteView: UIView { - + // MARK: - Components private let imageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "image_signUp_complete") return view }() - + private let titleTopLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 20, text: "가입완료") - return label + return PPLabel(style: .bold, fontSize: 20, text: "가입완료") }() - + private let titleMiddleStackView: UIStackView = { - let view = UIStackView() - return view + return UIStackView() }() - + let nickNameLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 20) label.textColor = .blu500 return label }() - + private let nickNameSubLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 20, text: "님의") - return label + return PPLabel(style: .bold, fontSize: 20, text: "님의") }() - + private let titleBottomLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 20, text: "피드를 확인해보세요") - return label + return PPLabel(style: .bold, fontSize: 20, text: "피드를 확인해보세요") }() - + private let titleStackView: UIStackView = { let view = UIStackView() view.axis = .vertical view.alignment = .center return view }() - + let descriptionLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 15) label.textColor = .g600 @@ -59,18 +55,17 @@ final class SignUpCompleteView: UIView { label.textAlignment = .center return label }() - + let bottomButton: PPButton = { - let button = PPButton(style: .primary, text: "바로가기") - return button + return PPButton(style: .primary, text: "바로가기") }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -78,7 +73,7 @@ final class SignUpCompleteView: UIView { // MARK: - SetUp private extension SignUpCompleteView { - + func setUpConstraints() { self.addSubview(imageView) imageView.snp.makeConstraints { make in @@ -86,27 +81,27 @@ private extension SignUpCompleteView { make.size.equalTo(80) make.top.equalToSuperview().inset(124) } - + titleMiddleStackView.addArrangedSubview(nickNameLabel) titleMiddleStackView.addArrangedSubview(nickNameSubLabel) - + titleStackView.addArrangedSubview(titleTopLabel) titleStackView.addArrangedSubview(titleMiddleStackView) titleStackView.addArrangedSubview(titleBottomLabel) - + self.addSubview(titleStackView) titleStackView.snp.makeConstraints { make in make.centerX.equalToSuperview() make.top.equalTo(imageView.snp.bottom).offset(32) } - + self.addSubview(descriptionLabel) descriptionLabel.snp.makeConstraints { make in make.top.equalTo(titleStackView.snp.bottom).offset(16) make.leading.trailing.equalToSuperview().inset(20) - + } - + self.addSubview(bottomButton) bottomButton.snp.makeConstraints { make in make.leading.trailing.bottom.equalToSuperview().inset(20) diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step1/SignUpStep1Controller.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step1/SignUpStep1Controller.swift index 80cc81d2..1fd6c671 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step1/SignUpStep1Controller.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step1/SignUpStep1Controller.swift @@ -7,18 +7,18 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit final class SignUpStep1Controller: BaseViewController, View { - + typealias Reactor = SignUpStep1Reactor - + // MARK: - Properties var disposeBag = DisposeBag() - + var mainView = SignUpStep1View() } @@ -44,13 +44,13 @@ private extension SignUpStep1Controller { // MARK: - Methods extension SignUpStep1Controller { func bind(reactor: Reactor) { - + // totalButton Tap 이벤트 mainView.totalButton.button.rx.tap .map { Reactor.Action.totalButtonTapped } .bind(to: reactor.action) .disposed(by: disposeBag) - + // terms Tap 이벤트 mainView.terms1Button.button.rx.tap .map { Reactor.Action.termsButtonTapped(index: 1)} @@ -68,7 +68,7 @@ extension SignUpStep1Controller { .map { Reactor.Action.termsButtonTapped(index: 4)} .bind(to: reactor.action) .disposed(by: disposeBag) - + // terms Detail Button 이벤트 mainView.terms1Button.righticonButton.rx.tap .withUnretained(self) @@ -98,18 +98,18 @@ extension SignUpStep1Controller { }) .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in - + // selectedIndex가 4일 경우 전체 선택 버튼 활성화 및 비활성화 if state.selectedIndex.count == 4 { owner.mainView.totalButton.isSelected.accept(true) } else { owner.mainView.totalButton.isSelected.accept(false) } - + // 현 selectedIndex에 따라 view 변경 let termsViews = [ owner.mainView.terms1Button, @@ -121,7 +121,7 @@ extension SignUpStep1Controller { let isSelected = state.selectedIndex.contains(index + 1) view.isSelected.accept(isSelected) } - + // selectedIndex가 1,2,3을 포함할 시 completeButton 활성화 if state.selectedIndex.contains(1) && state.selectedIndex.contains(2) diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step1/SignUpStep1Reactor.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step1/SignUpStep1Reactor.swift index 467e416f..b4f838db 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step1/SignUpStep1Reactor.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step1/SignUpStep1Reactor.swift @@ -7,38 +7,38 @@ import Foundation import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class SignUpStep1Reactor: Reactor { - + // MARK: - Reactor enum Action { case totalButtonTapped case termsButtonTapped(index: Int) case termsRightButtonTapped(index: Int, controller: BaseViewController) } - + enum Mutation { case setTotalSelected case setSelectedIndex(index: Int) case moveToTermsDetailScene(index: Int, controller: BaseViewController) } - + struct State { var selectedIndex: [Int] = [] } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - + // MARK: - init init() { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -50,7 +50,7 @@ final class SignUpStep1Reactor: Reactor { return Observable.just(.moveToTermsDetailScene(index: index, controller: controller)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { @@ -75,7 +75,7 @@ final class SignUpStep1Reactor: Reactor { } return newState } - + func getTitle(index: Int) -> String { if index == 1 { return "[필수] 이용약관" @@ -85,7 +85,7 @@ final class SignUpStep1Reactor: Reactor { return "[선택] 위치정보 이용약관" } } - + func getContent(index: Int) -> String { if let path = Bundle.main.path(forResource: "Terms", ofType: "plist"), let dict = NSDictionary(contentsOfFile: path) as? [String: String], diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step1/View/SignUpCheckBoxButton.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step1/View/SignUpCheckBoxButton.swift index 0fead2c6..3a5e5a4b 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step1/View/SignUpCheckBoxButton.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step1/View/SignUpCheckBoxButton.swift @@ -7,33 +7,31 @@ import UIKit -import SnapKit -import RxSwift import RxCocoa +import RxSwift +import SnapKit final class SignUpCheckBoxButton: UIView { - + // MARK: - Components private let checkImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_checkBox") return view }() - + private let buttonLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 15, text: "약관에 모두 동의할게요") - return label + return PPLabel(style: .bold, fontSize: 15, text: "약관에 모두 동의할게요") }() - + let button: UIButton = { - let button = UIButton() - return button + return UIButton() }() - + let disposeBag = DisposeBag() - + let isSelected: BehaviorRelay = .init(value: false) - + // MARK: - init init() { super.init(frame: .zero) @@ -43,7 +41,7 @@ final class SignUpCheckBoxButton: UIView { self.layer.cornerRadius = 4 self.clipsToBounds = true } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -51,7 +49,7 @@ final class SignUpCheckBoxButton: UIView { // MARK: - SetUp private extension SignUpCheckBoxButton { - + func setUpConstraints() { self.addSubview(checkImageView) checkImageView.snp.makeConstraints { make in @@ -59,20 +57,20 @@ private extension SignUpCheckBoxButton { make.centerY.equalToSuperview() make.leading.equalTo(20) } - + self.addSubview(buttonLabel) buttonLabel.snp.makeConstraints { make in make.centerY.equalToSuperview() make.leading.equalTo(checkImageView.snp.trailing).offset(8) make.trailing.equalToSuperview().inset(20) } - + self.addSubview(button) button.snp.makeConstraints { make in make.edges.equalToSuperview() } } - + func bind() { button.rx.tap .withUnretained(self) @@ -80,7 +78,7 @@ private extension SignUpCheckBoxButton { owner.isSelected.accept(!owner.isSelected.value) } .disposed(by: disposeBag) - + isSelected .withUnretained(self) .subscribe { (owner, isSelected) in diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step1/View/SignUpStep1View.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step1/View/SignUpStep1View.swift index abd7ef42..5bbab051 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step1/View/SignUpStep1View.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step1/View/SignUpStep1View.swift @@ -10,42 +10,40 @@ import UIKit import SnapKit final class SignUpStep1View: UIView { - + // MARK: - Components private let titleLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 20, text: "서비스 이용을 위한\n약관을 확인해주세요") label.numberOfLines = 0 return label }() - + let totalButton: SignUpCheckBoxButton = { - let button = SignUpCheckBoxButton() - return button + return SignUpCheckBoxButton() }() - + let terms1Button: SignUpTermsView = SignUpTermsView(title: "[필수] 이용약관") let terms2Button: SignUpTermsView = SignUpTermsView(title: "[필수] 개인정보 수집 및 이용") let terms3Button: SignUpTermsView = SignUpTermsView(title: "[필수] 만 14세 이상") let terms4Button: SignUpTermsView = SignUpTermsView(title: "[선택] 위치정보 이용약관") - + let termsStackView: UIStackView = { let view = UIStackView() view.axis = .vertical view.spacing = 16 return view }() - + let completeButton: PPButton = { - let button = PPButton(style: .primary, text: "확인", disabledText: "확인") - return button + return PPButton(style: .primary, text: "확인", disabledText: "확인") }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -53,7 +51,7 @@ final class SignUpStep1View: UIView { // MARK: - SetUp private extension SignUpStep1View { - + func setUpConstraints() { self.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in @@ -61,14 +59,14 @@ private extension SignUpStep1View { make.leading.trailing.equalToSuperview().inset(20) make.height.equalTo(56) } - + self.addSubview(totalButton) totalButton.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(48) make.leading.trailing.equalToSuperview().inset(20) make.height.equalTo(49) } - + self.addSubview(termsStackView) termsStackView.snp.makeConstraints { make in make.top.equalTo(totalButton.snp.bottom).offset(36) @@ -78,14 +76,14 @@ private extension SignUpStep1View { termsStackView.addArrangedSubview(terms2Button) termsStackView.addArrangedSubview(terms3Button) termsStackView.addArrangedSubview(terms4Button) - + self.addSubview(completeButton) completeButton.snp.makeConstraints { make in make.leading.trailing.equalToSuperview().inset(20) make.bottom.equalToSuperview() make.height.equalTo(52) } - + terms3Button.righticonButton.isHidden = true } } diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step1/View/SignUpTermsView.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step1/View/SignUpTermsView.swift index 8230c466..cdfd3a56 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step1/View/SignUpTermsView.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step1/View/SignUpTermsView.swift @@ -7,40 +7,39 @@ import UIKit -import SnapKit -import RxSwift import RxCocoa +import RxSwift +import SnapKit final class SignUpTermsView: UIView { - + // MARK: - Components private let imageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_check") return view }() - + private let titleLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 14) label.text = "some" return label }() - + let righticonButton: UIButton = { let view = UIButton() view.setImage(UIImage(named: "icon_right_gray"), for: .normal) return view }() - + let button: UIButton = { - let button = UIButton() - return button + return UIButton() }() - + let isSelected: BehaviorRelay = .init(value: false) - + let disposeBag = DisposeBag() - + // MARK: - init init(title: String?) { super.init(frame: .zero) @@ -48,7 +47,7 @@ final class SignUpTermsView: UIView { bind() titleLabel.text = title } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -56,7 +55,7 @@ final class SignUpTermsView: UIView { // MARK: - SetUp private extension SignUpTermsView { - + func setUpConstraints() { self.addSubview(imageView) imageView.snp.makeConstraints { make in @@ -64,27 +63,27 @@ private extension SignUpTermsView { make.centerY.equalToSuperview() make.leading.equalToSuperview() } - + self.addSubview(righticonButton) righticonButton.snp.makeConstraints { make in make.size.equalTo(22) make.top.bottom.trailing.equalToSuperview() } - + self.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.centerY.equalToSuperview() make.leading.equalTo(imageView.snp.trailing).offset(8) make.trailing.equalTo(righticonButton.snp.leading) } - + self.addSubview(button) button.snp.makeConstraints { make in make.leading.top.bottom.equalToSuperview() make.trailing.equalTo(righticonButton.snp.leading) } } - + func bind() { button.rx.tap .withUnretained(self) @@ -92,7 +91,7 @@ private extension SignUpTermsView { owner.isSelected.accept(!owner.isSelected.value) } .disposed(by: disposeBag) - + isSelected .withUnretained(self) .subscribe { (owner, isSelected) in diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step2/IntroState.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step2/IntroState.swift index 1828b8a6..ce9e6152 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step2/IntroState.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step2/IntroState.swift @@ -14,7 +14,7 @@ enum IntroState { case validateActive case longLength case longLengthActive - + var borderColor: UIColor? { switch self { case .empty, .validate: @@ -25,7 +25,7 @@ enum IntroState { return .re500 } } - + var description: String? { switch self { case .empty, .emptyActive, .validate, .validateActive: @@ -34,7 +34,7 @@ enum IntroState { return "최대 30글자까지 입력해주세요" } } - + var textColor: UIColor? { switch self { case .empty, .emptyActive, .validate, .validateActive: @@ -43,7 +43,7 @@ enum IntroState { return .re500 } } - + var textFieldTextColor: UIColor? { switch self { case .longLength, .longLengthActive: @@ -52,7 +52,7 @@ enum IntroState { return .g1000 } } - + var placeHolderIsHidden: Bool { switch self { case .empty, .emptyActive: diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step2/NickNameState.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step2/NickNameState.swift index 964d5894..7693833f 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step2/NickNameState.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step2/NickNameState.swift @@ -26,10 +26,10 @@ enum NickNameState { case shortLengthActive case longLength case longLengthActive - + var borderColor: UIColor? { switch self { - case .empty, .duplicated, .validate, .check , .myNickName: + case .empty, .duplicated, .validate, .check, .myNickName: return .g200 case .emptyActive, .duplicatedActive, .validateActive, .checkActive, .myNickNameActive: return .g1000 @@ -37,7 +37,7 @@ enum NickNameState { return .re500 } } - + var description: String? { switch self { case .empty, .emptyActive: @@ -62,18 +62,18 @@ enum NickNameState { return nil } } - + var textColor: UIColor? { switch self { case .empty, .emptyActive, .check, .checkActive: return .g500 case .length, .lengthActive, .korAndEng, .korAndEngActive, .duplicated, .duplicatedActive, .shortLength, .shortLengthActive, .longLength, .longLengthActive: return .re500 - case .validate, .validateActive , .myNickName ,.myNickNameActive: + case .validate, .validateActive, .myNickName, .myNickNameActive: return .blu500 } } - + var textFieldTextColor: UIColor? { switch self { case .length, .lengthActive, .korAndEng, .korAndEngActive, .duplicated, .duplicatedActive, .shortLength, .shortLengthActive, .longLength, .longLengthActive: @@ -82,25 +82,25 @@ enum NickNameState { return .g1000 } } - + var isHiddenClearButton: Bool { switch self { - case .lengthActive , .korAndEngActive, .duplicatedActive, .validateActive, .checkActive, .myNickNameActive, .shortLengthActive, .longLengthActive: + case .lengthActive, .korAndEngActive, .duplicatedActive, .validateActive, .checkActive, .myNickNameActive, .shortLengthActive, .longLengthActive: return false default: return true } } - + var isHiddenCheckButton: Bool { switch self { - case .length , .korAndEng, .duplicated, .validate, .check, .shortLength, .longLength ,.myNickName: + case .length, .korAndEng, .duplicated, .validate, .check, .shortLength, .longLength, .myNickName: return false default: return true } } - + var isShakeAnimation: Bool { switch self { case .lengthActive, .korAndEngActive, .duplicatedActive: @@ -109,7 +109,7 @@ enum NickNameState { return false } } - + var duplicatedCheckButtonIsEnabled: Bool { switch self { case .check, .checkActive: diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step2/SignUpStep2Controller.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step2/SignUpStep2Controller.swift index 3b588360..42293868 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step2/SignUpStep2Controller.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step2/SignUpStep2Controller.swift @@ -7,19 +7,19 @@ import UIKit -import SnapKit -import RxCocoa -import RxSwift import ReactorKit +import RxCocoa import RxGesture +import RxSwift +import SnapKit final class SignUpStep2Controller: BaseViewController, View { - + typealias Reactor = SignUpStep2Reactor - + // MARK: - Properties var disposeBag = DisposeBag() - + var mainView = SignUpStep2View() } @@ -45,29 +45,29 @@ private extension SignUpStep2Controller { // MARK: - Methods extension SignUpStep2Controller { func bind(reactor: Reactor) { - + mainView.rx.tapGesture() .withUnretained(self) .subscribe { (owner, _) in owner.view.endEditing(true) } .disposed(by: disposeBag) - + mainView.textField.rx.text .map { Reactor.Action.inputNickName(text: $0)} .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.textField.rx.controlEvent(.editingDidBegin) .map { Reactor.Action.beginNickNameInput } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.textField.rx.controlEvent(.editingDidEnd) .map { Reactor.Action.endNickNameInput } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.clearButton.rx.tap .withUnretained(self) .map({ (owner, _) in @@ -76,16 +76,16 @@ extension SignUpStep2Controller { }) .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.duplicatedCheckButton.rx.tap .map { Reactor.Action.duplicatedButtonTapped } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in - + // duplicatedButton Active set switch state.nickNameState { case .check, .checkActive: @@ -98,11 +98,11 @@ extension SignUpStep2Controller { owner.mainView.textFieldTrailingView.layer.borderColor = state.nickNameState.borderColor?.cgColor owner.mainView.textDescriptionLabel.text = state.nickNameState.description owner.mainView.textDescriptionLabel.textColor = state.nickNameState.textColor - + // clearButton, Duplicated Button set owner.mainView.duplicatedCheckButton.isHidden = state.nickNameState.isHiddenCheckButton owner.mainView.clearButton.isHidden = state.nickNameState.isHiddenClearButton - + // count Label set if let nickName = state.nickName { owner.mainView.textCountLabel.text = "\(nickName.count) / 10자" @@ -114,8 +114,7 @@ extension SignUpStep2Controller { default: owner.mainView.textCountLabel.textColor = .g500 } - - + // completeButton isActive set switch state.nickNameState { case .validate, .validateActive: diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step2/SignUpStep2Reactor.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step2/SignUpStep2Reactor.swift index c46fd041..13b7df79 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step2/SignUpStep2Reactor.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step2/SignUpStep2Reactor.swift @@ -8,11 +8,11 @@ import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class SignUpStep2Reactor: Reactor { - + // MARK: - Reactor enum Action { case inputNickName(text: String?) @@ -21,32 +21,32 @@ final class SignUpStep2Reactor: Reactor { case clearButtonTapped case duplicatedButtonTapped } - + enum Mutation { case setNickNameState(text: String?) case setActiveState(isActive: Bool) case setDuplicatedSet(isDuplicated: Bool) case resetNickName } - + struct State { var nickNameState: NickNameState = .empty var isActiveInput: Bool = false var nickName: String? = nil } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() private let signUpAPIUseCase = SignUpAPIUseCaseImpl(repository: SignUpRepositoryImpl(provider: ProviderImpl())) private var nickName: String? - + // MARK: - init init() { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -66,7 +66,7 @@ final class SignUpStep2Reactor: Reactor { return Observable.just(.resetNickName) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { @@ -78,7 +78,7 @@ final class SignUpStep2Reactor: Reactor { newState.isActiveInput = isActive newState.nickNameState = checkNickNameState(text: newState.nickName, isActive: newState.isActiveInput) case .setDuplicatedSet(let isDuplicated): - newState.nickNameState = isDuplicated + newState.nickNameState = isDuplicated ? newState.isActiveInput ? .duplicatedActive : .duplicated : newState.isActiveInput ? .validateActive : .validate case .resetNickName: @@ -87,24 +87,22 @@ final class SignUpStep2Reactor: Reactor { } return newState } - + func checkNickNameState(text: String?, isActive: Bool) -> NickNameState { guard let text = text else { return isActive ? .emptyActive : .empty } // textEmpty Check if text.isEmpty { return isActive ? .emptyActive : .empty } - - + // textLength Check if text.count < 2 { return isActive ? .shortLengthActive : .shortLength } if text.count > 10 { return isActive ? .longLengthActive : .longLength } - + // kor and end Check let pattern = "^[가-힣a-zA-Z\\s]+$" // 허용하는 문자만 검사 let regex = try! NSRegularExpression(pattern: pattern) let range = NSRange(location: 0, length: text.utf16.count) if regex.firstMatch(in: text, options: [], range: range) == nil { return isActive ? .korAndEngActive : .korAndEng } - return isActive ? .checkActive : .check } } diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step2/SignUpStep2View.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step2/SignUpStep2View.swift index 1cb8caed..b40c3bc2 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step2/SignUpStep2View.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step2/SignUpStep2View.swift @@ -10,26 +10,26 @@ import UIKit import SnapKit final class SignUpStep2View: UIView { - + // MARK: - Components private let titleLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 20, text: "팝풀에서 사용할\n별명을 설정해볼까요?") label.numberOfLines = 0 return label }() - + private let descriptionLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 15, text: "이후 이 별명으로 팝풀에서 활동할 예정이에요.") label.textColor = .g600 return label }() - + let completeButton: PPButton = { let button = PPButton(style: .primary, text: "확인", disabledText: "다음") button.isEnabled = false return button }() - + let textFieldTrailingView: UIStackView = { let view = UIStackView() view.layoutMargins = .init(top: 0, left: 20, bottom: 0, right: 20) @@ -41,33 +41,33 @@ final class SignUpStep2View: UIView { view.layer.borderWidth = 1 return view }() - + let textField: UITextField = { let textField = UITextField() textField.placeholder = "별명을 입력해주세요" textField.font = .KorFont(style: .medium, size: 14) return textField }() - + let clearButton: UIButton = { let button = UIButton() button.setImage(UIImage(named: "icon_clearButton"), for: .normal) return button }() - + let textDescriptionLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 12) label.text = "temptemp" return label }() - + let textCountLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 12) label.text = "0/10자" label.textColor = .g500 return label }() - + let duplicatedCheckButton: UIButton = { let button = UIButton() let title = "중복체크" @@ -93,13 +93,13 @@ final class SignUpStep2View: UIView { button.setAttributedTitle(disabledAttributedTitle, for: .disabled) return button }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -107,7 +107,7 @@ final class SignUpStep2View: UIView { // MARK: - SetUp private extension SignUpStep2View { - + func setUpConstraints() { self.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in @@ -115,13 +115,13 @@ private extension SignUpStep2View { make.leading.trailing.equalToSuperview().inset(20) make.height.equalTo(56) } - + self.addSubview(descriptionLabel) descriptionLabel.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(16) make.leading.trailing.equalToSuperview().inset(20) } - + self.addSubview(textFieldTrailingView) textFieldTrailingView.snp.makeConstraints { make in make.top.equalTo(descriptionLabel.snp.bottom).offset(48) @@ -134,26 +134,26 @@ private extension SignUpStep2View { clearButton.snp.makeConstraints { make in make.size.equalTo(16) } - + self.addSubview(textDescriptionLabel) textDescriptionLabel.snp.makeConstraints { make in make.top.equalTo(textFieldTrailingView.snp.bottom).offset(6) make.leading.equalToSuperview().inset(24) } - + self.addSubview(textCountLabel) textCountLabel.snp.makeConstraints { make in make.centerY.equalTo(textDescriptionLabel) make.trailing.equalToSuperview().inset(24) } - + self.addSubview(completeButton) completeButton.snp.makeConstraints { make in make.leading.trailing.equalToSuperview().inset(20) make.bottom.equalToSuperview() make.height.equalTo(52) } - + textField.snp.makeConstraints { make in make.height.equalTo(52) } diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step3/SignUpStep3Controller.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step3/SignUpStep3Controller.swift index 0260f4d3..4b85d7c3 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step3/SignUpStep3Controller.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step3/SignUpStep3Controller.swift @@ -7,23 +7,23 @@ import UIKit -import SnapKit -import RxCocoa -import RxSwift import ReactorKit +import RxCocoa import RxGesture +import RxSwift +import SnapKit final class SignUpStep3Controller: BaseViewController, View { - + typealias Reactor = SignUpStep3Reactor - + // MARK: - Properties var disposeBag = DisposeBag() - + var mainView = SignUpStep3View() - + private var sections: [any Sectionable] = [] - + private let selectedTag: PublishSubject = .init() } @@ -63,12 +63,12 @@ extension SignUpStep3Controller { .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + selectedTag .map { Reactor.Action.selectedTag(indexPath: $0) } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in @@ -85,19 +85,18 @@ extension SignUpStep3Controller: UICollectionViewDelegate, UICollectionViewDataS func numberOfSections(in collectionView: UICollectionView) -> Int { return sections.count } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sections[section].dataCount } - + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath ) -> UICollectionViewCell { - let cell = sections[indexPath.section].getCell(collectionView: collectionView, indexPath: indexPath) - return cell + return sections[indexPath.section].getCell(collectionView: collectionView, indexPath: indexPath) } - + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { selectedTag.onNext(indexPath) } diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step3/SignUpStep3Reactor.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step3/SignUpStep3Reactor.swift index 50aff05f..30fbc30b 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step3/SignUpStep3Reactor.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step3/SignUpStep3Reactor.swift @@ -8,35 +8,35 @@ import UIKit import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class SignUpStep3Reactor: Reactor { - + // MARK: - Reactor enum Action { case viewWillAppear case selectedTag(indexPath: IndexPath) } - + enum Mutation { case loadView } - + struct State { var sections: [any Sectionable] = [] var selectedCategory: [Int64] = [] var selectedCategoryTitle: [String] = [] var categoryIDList: [Int64] = [] } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() private let signUpAPIUseCase = SignUpAPIUseCaseImpl(repository: SignUpRepositoryImpl(provider: ProviderImpl())) private var cetegoryIDList: [Int64] = [] - + lazy var compositionalLayout: UICollectionViewCompositionalLayout = { UICollectionViewCompositionalLayout { [weak self] section, env in guard let self = self else { @@ -50,14 +50,14 @@ final class SignUpStep3Reactor: Reactor { return getSection()[section].getSection(section: section, env: env) } }() - + private var categorySection: TagSection = TagSection(inputDataList: []) - + // MARK: - init init() { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -79,14 +79,14 @@ final class SignUpStep3Reactor: Reactor { } else { ToastMaker.createToast(message: "최대 5개까지 선택할 수 있어요") } - + } else { categorySection.inputDataList[indexPath.row].isSelected.toggle() } return Observable.just(.loadView) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { @@ -98,7 +98,7 @@ final class SignUpStep3Reactor: Reactor { } return newState } - + func getSection() -> [any Sectionable] { return [ categorySection diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step3/View/SignUpStep3View.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step3/View/SignUpStep3View.swift index 5de5d4b5..04c18968 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step3/View/SignUpStep3View.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step3/View/SignUpStep3View.swift @@ -10,7 +10,7 @@ import UIKit import SnapKit final class SignUpStep3View: UIView { - + // MARK: - Components let nickNameLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 20) @@ -18,64 +18,62 @@ final class SignUpStep3View: UIView { label.text = "하이" return label }() - + private let titleTopLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 20) label.text = "님에 대해" return label }() - + private let titleBottomLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 20) label.text = "조금 더 알려주시겠어요?" return label }() - + private let subTitleLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 16) label.text = "관심이 있는 카테고리를 선택해주세요" return label }() - + private let subTitleDescriptionLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 12) label.text = "최대 5개까지 선택할 수 있어요." return label }() - + let categoryCollectionView: UICollectionView = { let view = UICollectionView(frame: .zero, collectionViewLayout: .init()) view.isScrollEnabled = false return view }() - + let skipButton: PPButton = { - let button = PPButton(style: .secondary, text: "건너뛰기") - return button + return PPButton(style: .secondary, text: "건너뛰기") }() - + let completeButton: PPButton = { - let button = PPButton(style: .primary, text: "다음", disabledText: "다음") - return button + return PPButton(style: .primary, text: "다음", disabledText: "다음") }() - + private let buttonStackView: UIStackView = { let view = UIStackView() view.distribution = .fillEqually view.spacing = 12 return view }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + func setNickName(nickName: String?) { nickNameLabel.text = nickName } @@ -83,7 +81,7 @@ final class SignUpStep3View: UIView { // MARK: - SetUp private extension SignUpStep3View { - + func setUpConstraints() { self.addSubview(nickNameLabel) nickNameLabel.snp.makeConstraints { make in @@ -91,35 +89,35 @@ private extension SignUpStep3View { make.leading.equalToSuperview().inset(20) make.height.equalTo(28) } - + self.addSubview(titleTopLabel) titleTopLabel.snp.makeConstraints { make in make.top.equalTo(nickNameLabel) make.leading.equalTo(nickNameLabel.snp.trailing) make.height.equalTo(28) } - + self.addSubview(titleBottomLabel) titleBottomLabel.snp.makeConstraints { make in make.top.equalTo(titleTopLabel.snp.bottom) make.leading.equalToSuperview().inset(20) make.height.equalTo(28) } - + self.addSubview(subTitleLabel) subTitleLabel.snp.makeConstraints { make in make.top.equalTo(titleBottomLabel.snp.bottom).offset(48) make.leading.trailing.equalToSuperview().inset(20) make.height.equalTo(22) } - + self.addSubview(subTitleDescriptionLabel) subTitleDescriptionLabel.snp.makeConstraints { make in make.top.equalTo(subTitleLabel.snp.bottom) make.leading.equalToSuperview().inset(20) make.height.equalTo(18) } - + self.addSubview(buttonStackView) buttonStackView.snp.makeConstraints { make in make.leading.trailing.bottom.equalToSuperview().inset(20) @@ -127,7 +125,7 @@ private extension SignUpStep3View { } buttonStackView.addArrangedSubview(skipButton) buttonStackView.addArrangedSubview(completeButton) - + self.addSubview(categoryCollectionView) categoryCollectionView.snp.makeConstraints { make in make.top.equalTo(subTitleDescriptionLabel.snp.bottom).offset(36) diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step3/View/TagSection/TagSection.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step3/View/TagSection/TagSection.swift index 58f00723..2f508831 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step3/View/TagSection/TagSection.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step3/View/TagSection/TagSection.swift @@ -10,17 +10,17 @@ import UIKit import RxSwift struct TagSection: Sectionable { - + var currentPage: PublishSubject = .init() - + typealias CellType = TagSectionCell - + var inputDataList: [CellType.Input] - + var supplementaryItems: [any SectionSupplementaryItemable]? - + var decorationItems: [any SectionDecorationItemable]? - + func setSection(section: Int, env: any NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize( widthDimension: .estimated(26), @@ -38,7 +38,7 @@ struct TagSection: Sectionable { let section = NSCollectionLayoutSection(group: group) section.contentInsets = .init(top: 0, leading: 20, bottom: 0, trailing: 20) section.interGroupSpacing = 16 - + return section } } diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step3/View/TagSection/TagSectionCell.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step3/View/TagSection/TagSectionCell.swift index 6712e0d5..ed6bb5a6 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step3/View/TagSection/TagSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step3/View/TagSection/TagSectionCell.swift @@ -7,25 +7,24 @@ import UIKit -import SnapKit import RxSwift +import SnapKit final class TagSectionCell: UICollectionViewCell { - + // MARK: - Components private let titleLabel: PPLabel = { - let label = PPLabel(style: .medium, fontSize: 13) - return label + return PPLabel(style: .medium, fontSize: 13) }() - + let disposeBag = DisposeBag() // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError() } @@ -37,7 +36,7 @@ private extension TagSectionCell { contentView.clipsToBounds = true contentView.layer.cornerRadius = 18 contentView.layer.borderColor = UIColor.g200.cgColor - + contentView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.leading.trailing.equalToSuperview().inset(14).priority(.high) @@ -52,7 +51,7 @@ extension TagSectionCell: Inputable { var isSelected: Bool var id: Int64? } - + func injection(with input: Input) { titleLabel.text = input.title if input.isSelected { diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step4/AgeSelectedModal/AgeSelectedController.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step4/AgeSelectedModal/AgeSelectedController.swift index b3bbf5b0..2a2d3d8b 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step4/AgeSelectedModal/AgeSelectedController.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step4/AgeSelectedModal/AgeSelectedController.swift @@ -7,19 +7,19 @@ import UIKit -import SnapKit +import PanModal +import ReactorKit import RxCocoa import RxSwift -import ReactorKit -import PanModal +import SnapKit final class AgeSelectedController: BaseViewController, View { - + typealias Reactor = AgeSelectedReactor - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = AgeSelectedView() } @@ -51,7 +51,7 @@ extension AgeSelectedController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.completeButton.rx.tap .withUnretained(self) .map { (owner, _) in @@ -60,7 +60,7 @@ extension AgeSelectedController { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step4/AgeSelectedModal/AgeSelectedReactor.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step4/AgeSelectedModal/AgeSelectedReactor.swift index 8e1e2aef..08a1c326 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step4/AgeSelectedModal/AgeSelectedReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step4/AgeSelectedModal/AgeSelectedReactor.swift @@ -6,36 +6,36 @@ // import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class AgeSelectedReactor: Reactor { - + // MARK: - Reactor enum Action { case cancelButtonTapped(controller: BaseViewController) case completeButtonTapped(selectedAge: Int, controller: BaseViewController) } - + enum Mutation { case setSelectedAge(selectedAge: Int, controller: BaseViewController) case moveToRecentScene(controller: BaseViewController) } - + struct State { var selectedAge: Int? } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - + // MARK: - init init(age: Int?) { self.initialState = State(selectedAge: age) } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -45,7 +45,7 @@ final class AgeSelectedReactor: Reactor { return Observable.just(.setSelectedAge(selectedAge: selectedAge, controller: controller)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step4/AgeSelectedModal/AgeSelectedView.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step4/AgeSelectedModal/AgeSelectedView.swift index 6732f881..6322c58c 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step4/AgeSelectedModal/AgeSelectedView.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step4/AgeSelectedModal/AgeSelectedView.swift @@ -10,29 +10,25 @@ import UIKit import SnapKit final class AgeSelectedView: UIView { - + // MARK: - Components private let titleLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 18, text: "나이를 선택해주세요") - return label + return PPLabel(style: .bold, fontSize: 18, text: "나이를 선택해주세요") }() - + let picker: PPPicker = { let ageRange = (0...100).map { "\($0)세"} - let picker = PPPicker(components: ageRange) - return picker + return PPPicker(components: ageRange) }() - + let cancelButton: PPButton = { - let button = PPButton(style: .secondary, text: "취소") - return button + return PPButton(style: .secondary, text: "취소") }() - + let completeButton: PPButton = { - let button = PPButton(style: .primary, text: "확인", disabledText: "확인") - return button + return PPButton(style: .primary, text: "확인", disabledText: "확인") }() - + private let buttonStackView: UIStackView = { let view = UIStackView() view.distribution = .fillEqually @@ -44,7 +40,7 @@ final class AgeSelectedView: UIView { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -52,21 +48,21 @@ final class AgeSelectedView: UIView { // MARK: - SetUp private extension AgeSelectedView { - + func setUpConstraints() { self.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.top.equalToSuperview().inset(24) make.leading.trailing.equalToSuperview().inset(20) } - + self.addSubview(picker) picker.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(24) make.leading.trailing.equalToSuperview().inset(20) make.height.equalTo(208) } - + self.addSubview(buttonStackView) buttonStackView.snp.makeConstraints { make in make.top.equalTo(picker.snp.bottom).offset(24) diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step4/Main/SignUpStep4Controller.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step4/Main/SignUpStep4Controller.swift index 17d64901..772bf195 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step4/Main/SignUpStep4Controller.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step4/Main/SignUpStep4Controller.swift @@ -7,18 +7,18 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit final class SignUpStep4Controller: BaseViewController, View { - + typealias Reactor = SignUpStep4Reactor - + // MARK: - Properties var disposeBag = DisposeBag() - + var mainView = SignUpStep4View() } @@ -50,7 +50,7 @@ extension SignUpStep4Controller { }) .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.ageSelectedButton.button.rx.tap .withUnretained(self) .map { (owner, _) in @@ -58,7 +58,7 @@ extension SignUpStep4Controller { } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .withUnretained(self) .subscribe { (owner, state) in diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step4/Main/SignUpStep4Reactor.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step4/Main/SignUpStep4Reactor.swift index 50d87d2f..02d72a7f 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step4/Main/SignUpStep4Reactor.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step4/Main/SignUpStep4Reactor.swift @@ -6,39 +6,39 @@ // import ReactorKit -import RxSwift import RxCocoa +import RxSwift final class SignUpStep4Reactor: Reactor { - + // MARK: - Reactor enum Action { case selectedGender(index: Int) case ageSelectedButtonTapped(controller: BaseViewController) case ageSelected(age: Int?) } - + enum Mutation { case setGender(index: Int) case setAge(age: Int?) case moveToAgeSelectedScene(controller: BaseViewController) } - + struct State { var selectedGenderIndex: Int = 2 var age: Int? } - + // MARK: - properties - + var initialState: State var disposeBag = DisposeBag() - + // MARK: - init init() { self.initialState = State() } - + // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { @@ -50,7 +50,7 @@ final class SignUpStep4Reactor: Reactor { return Observable.just(.moveToAgeSelectedScene(controller: controller)) } } - + func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step4/Main/View/AgeSelectedButton.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step4/Main/View/AgeSelectedButton.swift index 55ec1d8a..43774bdf 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step4/Main/View/AgeSelectedButton.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step4/Main/View/AgeSelectedButton.swift @@ -10,38 +10,38 @@ import UIKit import SnapKit final class AgeSelectedButton: UIView { - + // MARK: - Components private let contentStackView: UIStackView = { let view = UIStackView() view.alignment = .center return view }() - + private let defaultLabel: PPLabel = { let label = PPLabel(style: .medium, fontSize: 14, text: "나이를 선택해주세요") label.textColor = .g400 return label }() - + private let rightImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "icon_dropdown") return view }() - + private let ageTitleLabel: PPLabel = { let label = PPLabel(style: .medium, fontSize: 11, text: "나이") label.textColor = .g400 return label }() - + private let ageLabel: PPLabel = { let label = PPLabel(style: .medium, fontSize: 14, text: "") label.textColor = .g1000 return label }() - + private let verticalStackView: UIStackView = { let view = UIStackView() view.axis = .vertical @@ -50,18 +50,17 @@ final class AgeSelectedButton: UIView { view.spacing = 4 return view }() - + let button: UIButton = { - let button = UIButton() - return button + return UIButton() }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -69,12 +68,12 @@ final class AgeSelectedButton: UIView { // MARK: - SetUp private extension AgeSelectedButton { - + func setUpConstraints() { self.layer.borderWidth = 1 self.layer.cornerRadius = 4 self.layer.borderColor = UIColor.g200.cgColor - + self.addSubview(contentStackView) contentStackView.snp.makeConstraints { make in make.leading.trailing.equalToSuperview().inset(20) @@ -85,11 +84,11 @@ private extension AgeSelectedButton { } verticalStackView.addArrangedSubview(ageTitleLabel) verticalStackView.addArrangedSubview(ageLabel) - + contentStackView.addArrangedSubview(defaultLabel) contentStackView.addArrangedSubview(verticalStackView) contentStackView.addArrangedSubview(rightImageView) - + self.addSubview(button) button.snp.makeConstraints { make in make.edges.equalToSuperview() @@ -101,7 +100,7 @@ extension AgeSelectedButton: Inputable { struct Input { var age: Int? } - + func injection(with input: Input) { if let age = input.age { verticalStackView.isHidden = false diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step4/Main/View/SignUpStep4View.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step4/Main/View/SignUpStep4View.swift index c9e19f9c..52453a36 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step4/Main/View/SignUpStep4View.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step4/Main/View/SignUpStep4View.swift @@ -10,7 +10,7 @@ import UIKit import SnapKit final class SignUpStep4View: UIView { - + // MARK: - Components let nickNameLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 20) @@ -18,84 +18,80 @@ final class SignUpStep4View: UIView { label.text = "하이" return label }() - + private let titleTopLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 20) label.text = "님에 대해" return label }() - + private let titleBottomLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 20) label.text = "조금 더 알려주시겠어요?" return label }() - + private let subTitleLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 16) label.text = "해당되시는 성별 / 나이대를 알려주세요" return label }() - + private let subTitleDescriptionLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 12) label.text = "가장 잘 맞는 팝업스토어를 소개해드릴게요." return label }() - + private let genderTitleLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 13) label.text = "성별" return label }() - + let genderSegmentControl: PPSegmentedControl = { - let control = PPSegmentedControl( + return PPSegmentedControl( type: .base, segments: ["남성", "여성", "선택안함"], selectedSegmentIndex: 2 ) - return control }() - + private let ageTitleLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 13) label.text = "나이" return label }() - + let ageSelectedButton: AgeSelectedButton = { - let button = AgeSelectedButton() - return button + return AgeSelectedButton() }() - + let skipButton: PPButton = { - let button = PPButton(style: .secondary, text: "건너뛰기") - return button + return PPButton(style: .secondary, text: "건너뛰기") }() - + let completeButton: PPButton = { - let button = PPButton(style: .primary, text: "확인", disabledText: "확인") - return button + return PPButton(style: .primary, text: "확인", disabledText: "확인") }() - + private let buttonStackView: UIStackView = { let view = UIStackView() view.distribution = .fillEqually view.spacing = 12 return view }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + func setNickName(nickName: String?) { nickNameLabel.text = nickName } @@ -103,7 +99,7 @@ final class SignUpStep4View: UIView { // MARK: - SetUp private extension SignUpStep4View { - + func setUpConstraints() { self.addSubview(nickNameLabel) nickNameLabel.snp.makeConstraints { make in @@ -111,62 +107,62 @@ private extension SignUpStep4View { make.leading.equalToSuperview().inset(20) make.height.equalTo(28) } - + self.addSubview(titleTopLabel) titleTopLabel.snp.makeConstraints { make in make.top.equalTo(nickNameLabel) make.leading.equalTo(nickNameLabel.snp.trailing) make.height.equalTo(28) } - + self.addSubview(titleBottomLabel) titleBottomLabel.snp.makeConstraints { make in make.top.equalTo(titleTopLabel.snp.bottom) make.leading.equalToSuperview().inset(20) make.height.equalTo(28) } - + self.addSubview(subTitleLabel) subTitleLabel.snp.makeConstraints { make in make.top.equalTo(titleBottomLabel.snp.bottom).offset(48) make.leading.trailing.equalToSuperview().inset(20) make.height.equalTo(22) } - + self.addSubview(subTitleDescriptionLabel) subTitleDescriptionLabel.snp.makeConstraints { make in make.top.equalTo(subTitleLabel.snp.bottom) make.leading.equalToSuperview().inset(20) make.height.equalTo(18) } - + self.addSubview(genderTitleLabel) genderTitleLabel.snp.makeConstraints { make in make.top.equalTo(subTitleDescriptionLabel.snp.bottom).offset(36) make.leading.equalToSuperview().inset(20) make.height.equalTo(20) } - + self.addSubview(genderSegmentControl) genderSegmentControl.snp.makeConstraints { make in make.top.equalTo(genderTitleLabel.snp.bottom).offset(8) make.leading.trailing.equalToSuperview().inset(20) } - + self.addSubview(ageTitleLabel) ageTitleLabel.snp.makeConstraints { make in make.top.equalTo(genderSegmentControl.snp.bottom).offset(36) make.leading.equalToSuperview().inset(20) make.height.equalTo(20) } - + self.addSubview(ageSelectedButton) ageSelectedButton.snp.makeConstraints { make in make.top.equalTo(ageTitleLabel.snp.bottom).offset(8) make.leading.trailing.equalToSuperview().inset(20) make.height.equalTo(72) } - + self.addSubview(buttonStackView) buttonStackView.snp.makeConstraints { make in make.leading.trailing.bottom.equalToSuperview().inset(20) diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/TermsDetail/TermsDetailController.swift b/Poppool/Poppool/Presentation/Scene/SignUp/TermsDetail/TermsDetailController.swift index 987ddee1..fdfa8afe 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/TermsDetail/TermsDetailController.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/TermsDetail/TermsDetailController.swift @@ -7,17 +7,17 @@ import UIKit -import SnapKit import RxCocoa import RxSwift +import SnapKit final class TermsDetailController: BaseViewController { - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = TermsDetailView() - + init(title: String?, content: String?) { super.init() let paragraphStyle = NSMutableParagraphStyle() @@ -30,9 +30,9 @@ final class TermsDetailController: BaseViewController { mainView.contentTextView.attributedText = NSAttributedString(string: content ?? "", attributes: attributes) mainView.titleLabel.text = title - + } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -55,7 +55,7 @@ private extension TermsDetailController { make.edges.equalTo(view.safeAreaLayoutGuide) } } - + func bind() { mainView.xmarkButton.rx.tap .withUnretained(self) diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/TermsDetail/TermsDetailView.swift b/Poppool/Poppool/Presentation/Scene/SignUp/TermsDetail/TermsDetailView.swift index 9a12dc81..6c5b7d7c 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/TermsDetail/TermsDetailView.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/TermsDetail/TermsDetailView.swift @@ -10,13 +10,12 @@ import UIKit import SnapKit final class TermsDetailView: UIView { - + // MARK: - Components let titleLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 15) - return label + return PPLabel(style: .bold, fontSize: 15) }() - + let contentTextView: UITextView = { let view = UITextView() view.isSelectable = false @@ -25,19 +24,18 @@ final class TermsDetailView: UIView { return view }() - let xmarkButton: UIButton = { let button = UIButton() button.setImage(UIImage(named: "icon_xmark"), for: .normal) return button }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -45,7 +43,7 @@ final class TermsDetailView: UIView { // MARK: - SetUp private extension TermsDetailView { - + func setUpConstraints() { self.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in @@ -53,14 +51,14 @@ private extension TermsDetailView { make.top.equalToSuperview().inset(25) make.height.equalTo(21) } - + self.addSubview(xmarkButton) xmarkButton.snp.makeConstraints { make in make.size.equalTo(24) make.trailing.equalToSuperview().inset(16) make.centerY.equalTo(titleLabel) } - + self.addSubview(contentTextView) contentTextView.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(43) diff --git a/Poppool/Poppool/Presentation/Scene/Splash/SplashController.swift b/Poppool/Poppool/Presentation/Scene/Splash/SplashController.swift index f0eb9ac8..c4fb9af4 100644 --- a/Poppool/Poppool/Presentation/Scene/Splash/SplashController.swift +++ b/Poppool/Poppool/Presentation/Scene/Splash/SplashController.swift @@ -7,20 +7,20 @@ import UIKit -import SnapKit +import ReactorKit import RxCocoa import RxSwift -import ReactorKit +import SnapKit final class SplashController: BaseViewController { - + // MARK: - Properties var disposeBag = DisposeBag() - + private var mainView = SplashView() private let authAPIUseCase = AuthAPIUseCaseImpl(repository: AuthAPIRepositoryImpl(provider: ProviderImpl())) private let keyChainService = KeyChainService() - + private var rootViewController: UIViewController? } @@ -43,7 +43,7 @@ private extension SplashController { make.edges.equalTo(view.safeAreaLayoutGuide) } } - + func playAnimation() { mainView.animationView.play { [weak self] _ in DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { @@ -51,15 +51,15 @@ private extension SplashController { } } } - + func setRootview() { authAPIUseCase.postTokenReissue() .withUnretained(self) .subscribe(onNext: { (owner, response) in let newAccessToken = response.accessToken ?? "" let newRefreshToken = response.refreshToken ?? "" - let _ = owner.keyChainService.saveToken(type: .accessToken, value: newAccessToken) - let _ = owner.keyChainService.saveToken(type: .refreshToken, value: newRefreshToken) + _ = owner.keyChainService.saveToken(type: .accessToken, value: newAccessToken) + _ = owner.keyChainService.saveToken(type: .refreshToken, value: newRefreshToken) let navigationController = WaveTabBarController() owner.rootViewController = navigationController }, onError: { [weak self] _ in @@ -71,7 +71,7 @@ private extension SplashController { }) .disposed(by: disposeBag) } - + func changeRootView() { view.window?.rootViewController = rootViewController view.window?.makeKeyAndVisible() diff --git a/Poppool/Poppool/Presentation/Scene/Splash/View/SplashView.swift b/Poppool/Poppool/Presentation/Scene/Splash/View/SplashView.swift index 08b21aee..db657eb4 100644 --- a/Poppool/Poppool/Presentation/Scene/Splash/View/SplashView.swift +++ b/Poppool/Poppool/Presentation/Scene/Splash/View/SplashView.swift @@ -7,25 +7,25 @@ import UIKit -import SnapKit import Lottie +import SnapKit final class SplashView: UIView { - + // MARK: - Components - + let animationView: LottieAnimationView = { let view = LottieAnimationView(name: "PP_splash") view.contentMode = .scaleAspectFit return view }() - + // MARK: - init init() { super.init(frame: .zero) setUpConstraints() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -33,7 +33,7 @@ final class SplashView: UIView { // MARK: - SetUp private extension SplashView { - + func setUpConstraints() { addSubview(animationView) animationView.snp.makeConstraints { make in diff --git a/Poppool/Poppool/Presentation/Scene/TabbarController/TabbarController.swift b/Poppool/Poppool/Presentation/Scene/TabbarController/TabbarController.swift index 35d94f0c..14c76db0 100644 --- a/Poppool/Poppool/Presentation/Scene/TabbarController/TabbarController.swift +++ b/Poppool/Poppool/Presentation/Scene/TabbarController/TabbarController.swift @@ -8,9 +8,9 @@ import UIKit class WaveTabBarController: UITabBarController, UITabBarControllerDelegate { - + private let waveLayer = CAShapeLayer() - + private let dotView: UIView = { let view = UIView() view.layer.borderWidth = 0.6 @@ -18,7 +18,7 @@ class WaveTabBarController: UITabBarController, UITabBarControllerDelegate { view.backgroundColor = .blu500 return view }() - + override func viewDidLoad() { super.viewDidLoad() addSomeTabItems() @@ -26,36 +26,36 @@ class WaveTabBarController: UITabBarController, UITabBarControllerDelegate { setupWaveTabBar() delegate = self } - + private func setupWaveTabBar() { // TabBar의 배경 투명 설정 tabBar.backgroundImage = UIImage() tabBar.shadowImage = UIImage() tabBar.isTranslucent = true - + // Wave Layer 설정 waveLayer.fillColor = UIColor.white.cgColor tabBar.layer.insertSublayer(waveLayer, at: 0) - + // Dot 설정 dotView.frame.size = CGSize(width: 12, height: 12) dotView.layer.cornerRadius = 6 tabBar.addSubview(dotView) } - + override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() updateWavePath() updateDotPosition(animated: false) updateItem() } - + private func updateItem() { let tabBarItemViews = tabBar.subviews.filter { $0.isUserInteractionEnabled } - + if selectedIndex < tabBarItemViews.count { let selectedView = tabBarItemViews[selectedIndex] - + // 애니메이션 적용 UIView.animate( withDuration: 0.3, @@ -71,7 +71,7 @@ class WaveTabBarController: UITabBarController, UITabBarControllerDelegate { ) } } - + private func updateWavePath(animated: Bool = false) { guard let items = tabBar.items else { return } let tabWidth = tabBar.bounds.width / CGFloat(items.count) @@ -139,12 +139,12 @@ class WaveTabBarController: UITabBarController, UITabBarControllerDelegate { guard let items = tabBar.items else { return } let tabWidth = tabBar.bounds.width / CGFloat(items.count) let selectedTabX = CGFloat(selectedIndex) * tabWidth - + let targetCenter = CGPoint( x: selectedTabX + tabWidth / 2, y: -12 ) - + if animated { UIView.animate(withDuration: 1, delay: 0, @@ -158,20 +158,20 @@ class WaveTabBarController: UITabBarController, UITabBarControllerDelegate { dotView.center = targetCenter } } - + // 탭 선택 시 애니메이션 적용 func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) { updateWavePath(animated: true) updateDotPosition(animated: true) updateItem() } - + func setUp() { self.selectedIndex = 1 self.tabBar.barTintColor = .g200 self.tabBar.tintColor = .blu500 } - + func resizeImage(image: UIImage?, targetSize: CGSize) -> UIImage? { guard let image = image else { return nil } let size = image.size @@ -180,32 +180,32 @@ class WaveTabBarController: UITabBarController, UITabBarControllerDelegate { let heightRatio = targetSize.height / size.height let scaleFactor = min(widthRatio, heightRatio) - + let scaledImageSize = CGSize(width: size.width * scaleFactor, height: size.height * scaleFactor) UIGraphicsBeginImageContextWithOptions(scaledImageSize, false, 0.0) image.draw(in: CGRect(origin: .zero, size: scaledImageSize)) - + let resizedImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return resizedImage } - + func addSomeTabItems() { let provider = ProviderImpl() let mapController = MapViewController() let mapUseCase = DefaultMapUseCase(repository: DefaultMapRepository(provider: provider)) - let directionRepository = DefaultMapDirectionRepository(provider: provider) + let directionRepository = DefaultMapDirectionRepository(provider: provider) mapController.reactor = MapReactor(useCase: mapUseCase, directionRepository: directionRepository) let homeController = HomeController() homeController.reactor = HomeReactor() - + let myPageController = MyPageController() myPageController.reactor = MyPageReactor() - + let iconSize = CGSize(width: 32, height: 32) // 탭바 아이템 생성 mapController.tabBarItem = UITabBarItem( @@ -223,17 +223,17 @@ class WaveTabBarController: UITabBarController, UITabBarControllerDelegate { image: resizeImage(image: UIImage(named: "icon_tabbar_menu"), targetSize: iconSize), selectedImage: resizeImage(image: UIImage(named: "icon_tabbar_menu"), targetSize: iconSize) ) - + // 네비게이션 컨트롤러 설정 let map = UINavigationController(rootViewController: mapController) let home = UINavigationController(rootViewController: homeController) let myPage = UINavigationController(rootViewController: myPageController) - + viewControllers = [map, home, myPage] - + let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineHeightMultiple = 1.2 // 기본 값보다 높은 라인 간격을 설정 - + // 폰트 설정 let appearance = UITabBarAppearance() appearance.stackedLayoutAppearance.normal.titleTextAttributes = [ @@ -244,12 +244,12 @@ class WaveTabBarController: UITabBarController, UITabBarControllerDelegate { .font: UIFont.KorFont(style: .bold, size: 11)!, .paragraphStyle: paragraphStyle ] - + let verticalOffset: CGFloat = 4 // 원하는 간격 (양수: 아래로 이동, 음수: 위로 이동) appearance.stackedLayoutAppearance.normal.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: verticalOffset) appearance.stackedLayoutAppearance.selected.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: verticalOffset) - + tabBar.standardAppearance = appearance } - + } diff --git a/Poppool/Poppool/Presentation/Utills/Controllers/BaseTabmanController.swift b/Poppool/Poppool/Presentation/Utills/Controllers/BaseTabmanController.swift index 330cad1e..639a66e5 100644 --- a/Poppool/Poppool/Presentation/Utills/Controllers/BaseTabmanController.swift +++ b/Poppool/Poppool/Presentation/Utills/Controllers/BaseTabmanController.swift @@ -7,8 +7,8 @@ import UIKit -import Tabman import Pageboy +import Tabman class BaseTabmanController: TabmanViewController { init() { @@ -20,17 +20,17 @@ class BaseTabmanController: TabmanViewController { line: #line ) } - + required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = .systemBackground self.navigationController?.navigationBar.isHidden = true } - + deinit { Logger.log( message: "\(self) deinit", diff --git a/Poppool/Poppool/Presentation/Utills/Controllers/BaseViewController.swift b/Poppool/Poppool/Presentation/Utills/Controllers/BaseViewController.swift index 8c2b573f..2184b672 100644 --- a/Poppool/Poppool/Presentation/Utills/Controllers/BaseViewController.swift +++ b/Poppool/Poppool/Presentation/Utills/Controllers/BaseViewController.swift @@ -7,14 +7,14 @@ import UIKit -import RxSwift import RxCocoa +import RxSwift class BaseViewController: UIViewController { - + var systemStatusBarIsDark: BehaviorRelay = .init(value: true) var systemStatusBarDisposeBag = DisposeBag() - + init() { super.init(nibName: nil, bundle: nil) Logger.log( @@ -24,23 +24,23 @@ class BaseViewController: UIViewController { line: #line ) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = .white self.navigationController?.navigationBar.isHidden = true systemStatusBarIsDarkBind() } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) systemStatusBarIsDark.accept(systemStatusBarIsDark.value) } - + deinit { Logger.log( message: "\(self) deinit", @@ -49,7 +49,7 @@ class BaseViewController: UIViewController { line: #line ) } - + func systemStatusBarIsDarkBind() { systemStatusBarIsDark .withUnretained(self) diff --git a/Poppool/Poppool/Presentation/Utills/Interfaces/Sectionable/SectionDecorationItem.swift b/Poppool/Poppool/Presentation/Utills/Interfaces/Sectionable/SectionDecorationItem.swift index 6a2abf42..5b37a557 100644 --- a/Poppool/Poppool/Presentation/Utills/Interfaces/Sectionable/SectionDecorationItem.swift +++ b/Poppool/Poppool/Presentation/Utills/Interfaces/Sectionable/SectionDecorationItem.swift @@ -11,13 +11,13 @@ import UIKit /// 제네릭 타입 `View`는 `UICollectionReusableView`와 `Inputable` 프로토콜을 준수해야 합니다. struct SectionDecorationItem: SectionDecorationItemable { typealias ReusableView = View - + /// 데코레이션 뷰의 종류를 나타내는 문자열입니다. var elementKind: String - + /// 데코레이션 뷰의 인스턴스입니다. var reusableView: ReusableView - + /// 데코레이션 뷰에 주입될 데이터입니다. var viewInput: ReusableView.Input } @@ -25,16 +25,16 @@ struct SectionDecorationItem: Sectio /// `SectionDecorationItemable` 프로토콜은 데코레이션 뷰에 대한 인터페이스를 정의합니다. /// 이 프로토콜을 준수하는 타입은 데코레이션 뷰를 설정하고 반환하는 기능을 가져야 합니다. protocol SectionDecorationItemable { - + /// 데코레이션 뷰의 타입을 정의합니다. 이 뷰는 `UICollectionReusableView`와 `Inputable` 프로토콜을 준수해야 합니다. associatedtype ReusableView: UICollectionReusableView & Inputable - + /// 데코레이션 뷰의 종류를 나타내는 문자열입니다. var elementKind: String { get set } - + /// 데코레이션 뷰의 인스턴스입니다. var reusableView: ReusableView { get set } - + /// 데코레이션 뷰에 주입될 데이터입니다. var viewInput: ReusableView.Input { get set } } diff --git a/Poppool/Poppool/Presentation/Utills/Interfaces/Sectionable/SectionSupplementaryItem.swift b/Poppool/Poppool/Presentation/Utills/Interfaces/Sectionable/SectionSupplementaryItem.swift index a8c41655..2837db0c 100644 --- a/Poppool/Poppool/Presentation/Utills/Interfaces/Sectionable/SectionSupplementaryItem.swift +++ b/Poppool/Poppool/Presentation/Utills/Interfaces/Sectionable/SectionSupplementaryItem.swift @@ -22,31 +22,31 @@ struct SectionSupplementaryItem: Sec /// `SectionSupplementaryItemable` 프로토콜은 Supplementary View에 대한 인터페이스를 정의합니다. /// 해당 프로토콜을 준수하는 타입은 Supplementary View를 설정하고 반환하는 기능을 가져야 합니다. protocol SectionSupplementaryItemable { - + /// Supplementary View의 타입을 정의합니다. 이 뷰는 `UICollectionReusableView`와 `Inputable` 프로토콜을 준수해야 합니다. associatedtype ReusableView: UICollectionReusableView, Inputable - + /// Supplementary View의 너비를 정의합니다. var widthDimension: NSCollectionLayoutDimension { get set } - + /// Supplementary View의 높이를 정의합니다. var heightDimension: NSCollectionLayoutDimension { get set } - + /// Supplementary View의 종류를 나타내는 문자열입니다. var elementKind: String { get set } - + /// Supplementary View의 정렬 방법을 정의합니다. var alignment: NSRectAlignment { get set } - + /// Supplementary View의 인스턴스입니다. var reusableView: ReusableView { get set } - + /// Supplementary View에 주입될 데이터입니다. var viewInput: ReusableView.Input { get set } } extension SectionSupplementaryItemable { - + /// 주어진 인덱스 경로에 해당하는 Supplementary View를 생성하고 반환합니다. /// /// - Parameters: @@ -59,13 +59,13 @@ extension SectionSupplementaryItemable { viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath ) -> UICollectionReusableView { - + collectionView.register( ReusableView.self, forSupplementaryViewOfKind: kind, withReuseIdentifier: reusableView.identifiers ) - + guard let view = collectionView.dequeueReusableSupplementaryView( ofKind: kind, withReuseIdentifier: reusableView.identifiers, diff --git a/Poppool/Poppool/Presentation/Utills/Interfaces/Sectionable/Sectionable.swift b/Poppool/Poppool/Presentation/Utills/Interfaces/Sectionable/Sectionable.swift index dd23e99e..158f9092 100644 --- a/Poppool/Poppool/Presentation/Utills/Interfaces/Sectionable/Sectionable.swift +++ b/Poppool/Poppool/Presentation/Utills/Interfaces/Sectionable/Sectionable.swift @@ -13,22 +13,22 @@ import SnapKit /// `Sectionable` 프로토콜은 컬렉션 뷰의 섹션 및 셀을 설정하기 위한 인터페이스를 정의합니다. /// 해당 프로토콜은 제네릭 타입 `CellType`을 사용하여 유연하게 컬렉션 뷰 셀을 처리할 수 있습니다. protocol Sectionable { - + /// 컬렉션 뷰 셀의 타입을 정의합니다. 이 셀은 `UICollectionViewCell`과 `Inputable` 프로토콜을 준수해야 합니다. associatedtype CellType: UICollectionViewCell, Inputable - + /// 셀에 입력될 데이터의 리스트를 정의합니다. var inputDataList: [CellType.Input] { get set } - + /// 섹션에 추가될 Supplementary View의 리스트를 정의합니다. var supplementaryItems: [any SectionSupplementaryItemable]? { get set } - + /// 섹션에 추가될 Decoration View의 리스트를 정의합니다. var decorationItems: [any SectionDecorationItemable]? { get set } - + /// 섹션의 페이지 var currentPage: PublishSubject { get set } - + /// 주어진 섹션 인덱스와 레이아웃 환경을 바탕으로 `NSCollectionLayoutSection`을 반환합니다. /// /// - Parameters: @@ -36,7 +36,7 @@ protocol Sectionable { /// - env: 레이아웃 환경 /// - Returns: 섹션 레이아웃을 반환합니다. func getSection(section: Int, env: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection - + /// 섹션의 레이아웃을 설정합니다. /// /// - Parameters: @@ -44,7 +44,7 @@ protocol Sectionable { /// - env: 레이아웃 환경 /// - Returns: 설정된 섹션 레이아웃을 반환합니다. func setSection(section: Int, env: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection - + /// 주어진 인덱스 경로에 해당하는 셀을 반환합니다. /// /// - Parameters: @@ -52,7 +52,7 @@ protocol Sectionable { /// - indexPath: 셀의 인덱스 경로 /// - Returns: 셀을 반환합니다. func getCell(collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell - + /// Supplementary View를 반환합니다. /// /// - Parameters: @@ -68,7 +68,7 @@ protocol Sectionable { } extension Sectionable { - + /// `setSection` 메서드를 호출하여 섹션 레이아웃을 설정하고, Supplementary View를 추가합니다. /// /// - Parameters: @@ -76,55 +76,53 @@ extension Sectionable { /// - env: 레이아웃 환경 /// - Returns: 설정된 섹션 레이아웃을 반환합니다. func getSection(section: Int, env: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { - + let section = setSection(section: section, env: env) section.visibleItemsInvalidationHandler = { _, contentOffset, environment in let bannerIndex = Int(max(0, round(contentOffset.x / environment.container.contentSize.width))) // 음수가 되는 것을 방지하기 위해 max 사용 currentPage.onNext(bannerIndex) } - + if let supplementaryItems = supplementaryItems { - + let items = supplementaryItems.map { - + let size = NSCollectionLayoutSize( widthDimension: $0.widthDimension, heightDimension: $0.heightDimension ) - let sectionItem = NSCollectionLayoutBoundarySupplementaryItem( + + return NSCollectionLayoutBoundarySupplementaryItem( layoutSize: size, elementKind: $0.elementKind, alignment: $0.alignment ) - - return sectionItem } section.boundarySupplementaryItems = items } - + if let decorationItems = decorationItems { - + let items = decorationItems.map { - - let sectionItem = NSCollectionLayoutDecorationItem.background( + + return NSCollectionLayoutDecorationItem.background( elementKind: $0.elementKind ) - return sectionItem } section.decorationItems = items } return section } - + /// `inputDataList`의 비어 있지 않은지를 반환합니다. var isEmpty: Bool { return inputDataList.isEmpty } - + /// `inputDataList`의 아이템 수를 반환합니다. var dataCount: Int { return inputDataList.count - + } /// 주어진 인덱스 경로에 해당하는 셀을 생성하고 반환합니다. /// @@ -149,7 +147,7 @@ extension Sectionable { cell.injection(with: input) return cell } - + /// 주어진 Supplementary View를 생성하고 반환합니다. /// /// - Parameters: @@ -162,7 +160,7 @@ extension Sectionable { viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath ) -> UICollectionReusableView { - + guard let item = supplementaryItems?.filter({ $0.elementKind == kind }).first else { Logger.log( message: "ReusableView Not Register", @@ -172,8 +170,7 @@ extension Sectionable { ) fatalError() } - - let view = item.getItem(collectionView: collectionView, viewForSupplementaryElementOfKind: kind, at: indexPath) - return view + + return item.getItem(collectionView: collectionView, viewForSupplementaryElementOfKind: kind, at: indexPath) } } diff --git a/Poppool/Poppool/Presentation/Utills/ToastMaker/BookMarkToastView.swift b/Poppool/Poppool/Presentation/Utills/ToastMaker/BookMarkToastView.swift index a23d52c3..8ec3d045 100644 --- a/Poppool/Poppool/Presentation/Utills/ToastMaker/BookMarkToastView.swift +++ b/Poppool/Poppool/Presentation/Utills/ToastMaker/BookMarkToastView.swift @@ -10,9 +10,9 @@ import UIKit import SnapKit final class BookMarkToastView: UIView { - + // MARK: - Components - + private let bgView: UIView = { let view = UIView() view.backgroundColor = .pb70 @@ -20,14 +20,14 @@ final class BookMarkToastView: UIView { view.clipsToBounds = true return view }() - + private let bookMarkLabel: UILabel = { let label = UILabel() label.setLineHeightText(text: "찜한 팝업에 저장했어요", font: .KorFont(style: .regular, size: 15), lineHeight: 1) label.textColor = .w100 return label }() - + private let unbookMarkLabel: UILabel = { let label = PPLabel(style: .regular, fontSize: 15, text: "찜한 팝업을 해제했어요") label.setLineHeightText(text: "찜한 팝업을 해제했어요", font: .KorFont(style: .regular, size: 15), lineHeight: 1) @@ -45,7 +45,7 @@ final class BookMarkToastView: UIView { button.titleLabel?.font = .KorFont(style: .medium, size: 12) return button }() - + // MARK: - init init(isBookMark: Bool) { super.init(frame: .zero) @@ -59,7 +59,7 @@ final class BookMarkToastView: UIView { setUpUnBookMarkConstraints() } } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -67,7 +67,7 @@ final class BookMarkToastView: UIView { // MARK: - SetUp private extension BookMarkToastView { - + func setUpBookMarkConstraints() { bgView.addSubview(moveButton) moveButton.snp.makeConstraints { make in @@ -83,7 +83,7 @@ private extension BookMarkToastView { make.centerY.equalToSuperview() } } - + func setUpUnBookMarkConstraints() { bgView.addSubview(unbookMarkLabel) unbookMarkLabel.snp.makeConstraints { make in diff --git a/Poppool/Poppool/Presentation/Utills/ToastMaker/ToastMaker.swift b/Poppool/Poppool/Presentation/Utills/ToastMaker/ToastMaker.swift index a52b772c..54f6fdf7 100644 --- a/Poppool/Poppool/Presentation/Utills/ToastMaker/ToastMaker.swift +++ b/Poppool/Poppool/Presentation/Utills/ToastMaker/ToastMaker.swift @@ -7,14 +7,14 @@ import UIKit -import SnapKit -import RxSwift import RxCocoa +import RxSwift +import SnapKit final class ToastMaker { - + // MARK: - Properties - + /// 현재 디바이스 최상단 Window를 지정 static var window: UIWindow? { return UIApplication @@ -23,7 +23,7 @@ final class ToastMaker { .flatMap { ($0 as? UIWindowScene)?.windows ?? [] } .first { $0.isKeyWindow } } - + /// 최상단의 ViewController를 가져오는 메서드 private static func topViewController( _ rootViewController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController @@ -39,32 +39,32 @@ final class ToastMaker { } return rootViewController } - + private static var currentToast: ToastView? private static var currentBookMarkToast: BookMarkToastView? private static var disposeBag = DisposeBag() } extension ToastMaker { - + // MARK: - Method - + /// 토스트 메시지를 생성하는 메서드 /// - Parameter message: 토스트 메세지에 담길 String 타입 static func createToast(message: String) { - + currentToast?.removeFromSuperview() currentToast = nil let toastMSG = ToastView(message: message) guard let window = window else { return } window.addSubview(toastMSG) currentToast = toastMSG - + toastMSG.snp.makeConstraints { make in make.bottom.equalTo(window.snp.bottom).inset(120) make.centerX.equalTo(window.snp.centerX) } - + UIView.animate( withDuration: 0.3, delay: 4, @@ -76,11 +76,11 @@ extension ToastMaker { if currentToast == toastMSG { currentToast = nil } } } - + /// 토스트 메시지를 생성하는 메서드 /// - Parameter message: 토스트 메세지에 담길 String 타입 static func createBookMarkToast(isBookMark: Bool) { - + currentBookMarkToast?.removeFromSuperview() currentBookMarkToast = nil disposeBag = DisposeBag() @@ -96,7 +96,7 @@ extension ToastMaker { owner.navigationController?.pushViewController(nextController, animated: true) }) .disposed(by: disposeBag) - + if isBookMark { toastMSG.snp.makeConstraints { make in make.bottom.equalTo(currentVC.view.snp.bottom).inset(120) @@ -108,8 +108,7 @@ extension ToastMaker { make.centerX.equalTo(currentVC.view.snp.centerX) } } - - + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { UIView.animate( withDuration: 0.3, delay: 0, @@ -122,6 +121,6 @@ extension ToastMaker { disposeBag = DisposeBag() } } - + } } diff --git a/Poppool/Poppool/Presentation/Utills/ToastMaker/ToastView.swift b/Poppool/Poppool/Presentation/Utills/ToastMaker/ToastView.swift index e20c3c72..bd04535e 100644 --- a/Poppool/Poppool/Presentation/Utills/ToastMaker/ToastView.swift +++ b/Poppool/Poppool/Presentation/Utills/ToastMaker/ToastView.swift @@ -11,9 +11,9 @@ import SnapKit /// 토스트 메시지를 담는 view 객체입니다 final class ToastView: UIView { - + // MARK: - Properties - + private let bgView: UIView = { let view = UIView() view.backgroundColor = .pb70 @@ -22,42 +22,42 @@ final class ToastView: UIView { view.sizeToFit() return view }() - + private let messageLabel: UILabel = { let label = UILabel() label.textColor = .w100 label.font = .KorFont(style: .regular, size: 15) return label }() - + // MARK: - Initializer - + init(message: String) { super.init(frame: .zero) setup() messageLabel.text = message } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } extension ToastView { - + // MARK: - Method - + private func setup() { addSubview(bgView) bgView.addSubview(messageLabel) - + bgView.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() make.bottom.equalTo(snp.bottom) make.top.equalTo(snp.top) make.height.equalTo(38) } - + messageLabel.snp.makeConstraints { make in make.leading.trailing.equalToSuperview().inset(16) make.centerY.equalToSuperview() From 82931e95ec7332e8c0cfcf188a72093c7cf5d4df Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Thu, 3 Apr 2025 19:44:46 +0900 Subject: [PATCH 53/95] =?UTF-8?q?refactor/#93:=20Swiftlint=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PopUpStoreRegisterViewController.swift | 21 ++++++++++--------- .../CategoryEditModalReactor.swift | 8 +++---- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift b/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift index 9b92e953..bb60c048 100644 --- a/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift +++ b/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift @@ -1,11 +1,13 @@ import UIKit +import CoreLocation +import PhotosUI + +import Then import SnapKit import ReactorKit import RxSwift import RxCocoa -import PhotosUI import Alamofire -import CoreLocation final class PopUpStoreRegisterViewController: BaseViewController { @@ -121,14 +123,13 @@ final class PopUpStoreRegisterViewController: BaseViewController { private let contentView = UIView() // MARK: - Form Background - private let formBackgroundView: UIView = { - let v = UIView() - v.backgroundColor = .white - v.layer.borderWidth = 1 - v.layer.borderColor = UIColor.lightGray.cgColor - v.layer.cornerRadius = 8 - return v - }() + private let formBackgroundView = UIView().then() { + $0.backgroundColor = .white + $0.layer.borderWidth = 1 + $0.layer.borderColor = UIColor.lightGray.cgColor + $0.layer.cornerRadius = 8 + } + private let verticalStack = UIStackView() // MARK: - Bottom Save Button diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/CategoryEditModal/CategoryEditModalReactor.swift b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/CategoryEditModal/CategoryEditModalReactor.swift index d02975cd..2fb1bac2 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/CategoryEditModal/CategoryEditModalReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/CategoryEditModal/CategoryEditModalReactor.swift @@ -90,11 +90,11 @@ final class CategoryEditModalReactor: Reactor { var keepList: [Int64] = [] var deleteList: [Int64] = [] let currentArray = tagSection.inputDataList.filter { $0.isSelected == true }.compactMap { $0.id } - for i in currentArray { - if originSelectedID.contains(i) { - keepList.append(i) + for index in currentArray { + if originSelectedID.contains(index) { + keepList.append(index) } else { - addList.append(i) + addList.append(index) } } deleteList = originSelectedID.filter { !currentArray.contains($0) } From 81f4aafadde5ac9ee77886d0136c7ace4bb0094f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 3 Apr 2025 10:45:20 +0000 Subject: [PATCH 54/95] style/#93: Apply SwiftLint autocorrect --- Poppool/Poppool/Application/AppDelegate.swift | 4 +- .../PopUpStoreRegisterViewController.swift | 89 ++++++------------- .../Admin/Data/MapDomain/MapPopUpStore.swift | 1 - .../MapDomain/Repository/MapRepository.swift | 2 +- .../Map/Common/MapUtilities.swift | 1 - .../Map/Common/NMFMapViewDelegateProxy.swift | 2 +- .../BalloonBackgroundView.swift | 14 +-- .../FillterSheetView/BalloonChipCell.swift | 2 +- .../FilterBottomSheetView.swift | 16 ++-- .../FilterBottomSheetViewController.swift | 26 ++---- .../FullScreenMapViewController.swift | 17 ++-- .../MapGuideView/MapGuideAppService.swift | 2 +- .../MapGuideView/MapGuideViewController.swift | 10 +-- .../Presentation/Map/MapView/MapMarker.swift | 4 +- .../Presentation/Map/MapView/MapReactor.swift | 2 +- .../Presentation/Map/MapView/MapView.swift | 9 +- .../Map/MapView/MapViewController.swift | 39 +++----- .../DetailInfoSectionCell.swift | 1 - 18 files changed, 78 insertions(+), 163 deletions(-) diff --git a/Poppool/Poppool/Application/AppDelegate.swift b/Poppool/Poppool/Application/AppDelegate.swift index 5be33692..5b018dd2 100644 --- a/Poppool/Poppool/Application/AppDelegate.swift +++ b/Poppool/Poppool/Application/AppDelegate.swift @@ -1,7 +1,7 @@ -import UIKit -import KakaoSDKCommon import CoreLocation +import KakaoSDKCommon import NMapsMap +import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { diff --git a/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift b/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift index bb60c048..3c21a2e2 100644 --- a/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift +++ b/Poppool/Poppool/Presentation/Admin/AdminRegister/PopUpStoreRegisterViewController.swift @@ -1,13 +1,13 @@ -import UIKit import CoreLocation import PhotosUI +import UIKit -import Then -import SnapKit +import Alamofire import ReactorKit -import RxSwift import RxCocoa -import Alamofire +import RxSwift +import SnapKit +import Then final class PopUpStoreRegisterViewController: BaseViewController { @@ -24,8 +24,6 @@ final class PopUpStoreRegisterViewController: BaseViewController { private var latField: UITextField? private var lonField: UITextField? private var descTV: UITextView? - - private let popupName: String = "" private var originalImageIds: [String: Int64] = [:] @@ -82,7 +80,6 @@ final class PopUpStoreRegisterViewController: BaseViewController { return lbl }() - private let menuButton: UIButton = { let btn = UIButton(type: .system) btn.setImage(UIImage(systemName: "adminlist"), for: .normal) @@ -107,7 +104,6 @@ final class PopUpStoreRegisterViewController: BaseViewController { return lbl }() - private let addImageButton = UIButton(type: .system).then { $0.setTitle("이미지 추가", for: .normal) $0.setTitleColor(.systemBlue, for: .normal) @@ -123,13 +119,13 @@ final class PopUpStoreRegisterViewController: BaseViewController { private let contentView = UIView() // MARK: - Form Background - private let formBackgroundView = UIView().then() { + private let formBackgroundView = UIView().then { $0.backgroundColor = .white $0.layer.borderWidth = 1 $0.layer.borderColor = UIColor.lightGray.cgColor $0.layer.cornerRadius = 8 } - + private let verticalStack = UIStackView() // MARK: - Bottom Save Button @@ -158,12 +154,12 @@ final class PopUpStoreRegisterViewController: BaseViewController { let btn = UIButton(type: .system) btn.setTitle("카테고리 선택 ▾", for: .normal) btn.setTitleColor(.darkGray, for: .normal) - btn.titleLabel?.font = UIFont.systemFont(ofSize:14) + btn.titleLabel?.font = UIFont.systemFont(ofSize: 14) btn.layer.cornerRadius = 8 btn.layer.borderWidth = 1 btn.layer.borderColor = UIColor.lightGray.cgColor btn.contentHorizontalAlignment = .left - btn.contentEdgeInsets = UIEdgeInsets(top:7, left:8, bottom:7, right:8) + btn.contentEdgeInsets = UIEdgeInsets(top: 7, left: 8, bottom: 7, right: 8) return btn }() @@ -171,12 +167,12 @@ final class PopUpStoreRegisterViewController: BaseViewController { let btn = UIButton(type: .system) btn.setTitle("기간 선택 ▾", for: .normal) btn.setTitleColor(.darkGray, for: .normal) - btn.titleLabel?.font = UIFont.systemFont(ofSize:14) + btn.titleLabel?.font = UIFont.systemFont(ofSize: 14) btn.layer.cornerRadius = 8 btn.layer.borderWidth = 1 btn.layer.borderColor = UIColor.lightGray.cgColor btn.contentHorizontalAlignment = .left - btn.contentEdgeInsets = UIEdgeInsets(top:7, left:8, bottom:7, right:8) + btn.contentEdgeInsets = UIEdgeInsets(top: 7, left: 8, bottom: 7, right: 8) return btn }() @@ -184,12 +180,12 @@ final class PopUpStoreRegisterViewController: BaseViewController { let btn = UIButton(type: .system) btn.setTitle("시간 선택 ▾", for: .normal) btn.setTitleColor(.darkGray, for: .normal) - btn.titleLabel?.font = UIFont.systemFont(ofSize:14) + btn.titleLabel?.font = UIFont.systemFont(ofSize: 14) btn.layer.cornerRadius = 8 btn.layer.borderWidth = 1 btn.layer.borderColor = UIColor.lightGray.cgColor btn.contentHorizontalAlignment = .left - btn.contentEdgeInsets = UIEdgeInsets(top:7, left:8, bottom:7, right:8) + btn.contentEdgeInsets = UIEdgeInsets(top: 7, left: 8, bottom: 7, right: 8) return btn }() @@ -199,8 +195,8 @@ final class PopUpStoreRegisterViewController: BaseViewController { let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap)) tapGesture.cancelsTouchesInView = false view.addGestureRecognizer(tapGesture) - - view.backgroundColor = UIColor(white:0.95, alpha:1) + + view.backgroundColor = UIColor(white: 0.95, alpha: 1) if let store = editingStore { // 삭제된 이미지 ID 복원 @@ -226,11 +222,8 @@ final class PopUpStoreRegisterViewController: BaseViewController { setupAddressField() setupAllFieldListeners() - - } - // MARK: - Navigation private func setupNavigation() { backButton.addTarget(self, action: #selector(onBack), for: .touchUpInside) @@ -375,8 +368,6 @@ final class PopUpStoreRegisterViewController: BaseViewController { } } - - func loadStoreDetail(for storeId: Int64) { Logger.log(message: "상세 정보 요청 시작 - Store ID: \(storeId)", category: .debug) @@ -448,8 +439,6 @@ final class PopUpStoreRegisterViewController: BaseViewController { } } - - // MARK: - Layout private func setupLayout() { // (1) 상단 컨테이너 @@ -601,7 +590,7 @@ final class PopUpStoreRegisterViewController: BaseViewController { // 1) 주소 (TextField) let addressField = makeRoundedTextField("팝업스토어 주소를 입력해 주세요.") self.addressField = addressField - addressField.snp.makeConstraints { make in + addressField.snp.makeConstraints { _ in addressField.addTarget(self, action: #selector(fieldDidChange(_:)), for: .editingChanged) } @@ -613,14 +602,12 @@ final class PopUpStoreRegisterViewController: BaseViewController { self.latField = latField // latField와 연결 latField.addTarget(self, action: #selector(fieldDidChange(_:)), for: .editingChanged) - let lonLabel = makePlainLabel("경도") let lonField = makeRoundedTextField("") self.lonField = lonField // lonField와 연결 lonField.textAlignment = .center lonField.addTarget(self, action: #selector(fieldDidChange(_:)), for: .editingChanged) - let latStack = UIStackView(arrangedSubviews: [latLabel, latField]) latStack.axis = .horizontal latStack.spacing = 8 @@ -642,7 +629,6 @@ final class PopUpStoreRegisterViewController: BaseViewController { locationVStack.spacing = 8 locationVStack.distribution = .fillEqually - // 한 행에 왼쪽 "위치", 오른쪽 2줄(주소 / 위도경도) addRowCustom(leftTitle: "위치", rightView: locationVStack, rowHeight: nil, totalHeight: 80) @@ -671,7 +657,6 @@ final class PopUpStoreRegisterViewController: BaseViewController { markerVStack.spacing = 8 markerVStack.distribution = .fillEqually - // 한 행 => "마커" 라벨, 오른쪽 2줄 (마커명, 스니펫) addRowCustom(leftTitle: "마커", rightView: markerVStack, rowHeight: nil, totalHeight: 80) @@ -702,7 +687,6 @@ final class PopUpStoreRegisterViewController: BaseViewController { } - // MARK: - Row private func addRowTextField(leftTitle: String, placeholder: String) { @@ -796,7 +780,6 @@ final class PopUpStoreRegisterViewController: BaseViewController { saveButton.backgroundColor = isFormValid ? .systemBlue : .lightGray } - private func addRowCustom(leftTitle: String, rightView: UIView, rowHeight: CGFloat? = 36, @@ -889,7 +872,6 @@ final class PopUpStoreRegisterViewController: BaseViewController { } } - private func updatePeriodButtonTitle() { guard let selectedStartDate = selectedStartDate, let selectedEndDate = selectedEndDate else { return } let df = DateFormatter() @@ -997,7 +979,7 @@ final class PopUpStoreRegisterViewController: BaseViewController { private func makeRoundedTextField(_ placeholder: String) -> UITextField { let tf = UITextField() tf.placeholder = placeholder - tf.font = UIFont.systemFont(ofSize:14) + tf.font = UIFont.systemFont(ofSize: 14) tf.textColor = .darkGray tf.borderStyle = .none tf.layer.cornerRadius = 8 @@ -1011,12 +993,12 @@ final class PopUpStoreRegisterViewController: BaseViewController { let btn = UIButton(type: .system) btn.setTitle(title, for: .normal) btn.setTitleColor(.darkGray, for: .normal) - btn.titleLabel?.font = UIFont.systemFont(ofSize:14) + btn.titleLabel?.font = UIFont.systemFont(ofSize: 14) btn.layer.cornerRadius = 8 btn.layer.borderWidth = 1 btn.layer.borderColor = UIColor.lightGray.cgColor btn.contentHorizontalAlignment = .left - btn.contentEdgeInsets = UIEdgeInsets(top:7, left:8, bottom:7, right:8) + btn.contentEdgeInsets = UIEdgeInsets(top: 7, left: 8, bottom: 7, right: 8) return btn } @@ -1025,7 +1007,7 @@ final class PopUpStoreRegisterViewController: BaseViewController { if let icon = UIImage(named: iconName) { btn.setImage(icon, for: .normal) btn.imageView?.contentMode = .scaleAspectFit - btn.titleEdgeInsets = UIEdgeInsets(top:0, left:6, bottom:0, right:0) + btn.titleEdgeInsets = UIEdgeInsets(top: 0, left: 6, bottom: 0, right: 0) } return btn } @@ -1033,7 +1015,7 @@ final class PopUpStoreRegisterViewController: BaseViewController { private func makeSimpleLabel(_ text: String) -> UILabel { let lbl = UILabel() lbl.text = text - lbl.font = UIFont.systemFont(ofSize:14) + lbl.font = UIFont.systemFont(ofSize: 14) lbl.textColor = .darkGray return lbl } @@ -1042,7 +1024,7 @@ final class PopUpStoreRegisterViewController: BaseViewController { // 작은 라벨(위도/경도/마커명/스니펫 등) let lbl = UILabel() lbl.text = text - lbl.font = UIFont.systemFont(ofSize:14) + lbl.font = UIFont.systemFont(ofSize: 14) lbl.textColor = .darkGray lbl.textAlignment = .right lbl.setContentHuggingPriority(.required, for: .horizontal) @@ -1051,12 +1033,12 @@ final class PopUpStoreRegisterViewController: BaseViewController { private func makeRoundedTextView() -> UITextView { let tv = UITextView() - tv.font = UIFont.systemFont(ofSize:14) + tv.font = UIFont.systemFont(ofSize: 14) tv.textColor = .darkGray tv.layer.cornerRadius = 8 tv.layer.borderWidth = 1 tv.layer.borderColor = UIColor.lightGray.cgColor - tv.textContainerInset = UIEdgeInsets(top:7, left:7, bottom:7, right:7) + tv.textContainerInset = UIEdgeInsets(top: 7, left: 7, bottom: 7, right: 7) tv.isScrollEnabled = true return tv } @@ -1064,8 +1046,8 @@ final class PopUpStoreRegisterViewController: BaseViewController { // MARK: - Padding private extension UITextField { - func setLeftPaddingPoints(_ amount: CGFloat){ - let paddingView = UIView(frame: CGRect(x:0, y:0, width:amount, height: frame.size.height)) + func setLeftPaddingPoints(_ amount: CGFloat) { + let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: amount, height: frame.size.height)) leftView = paddingView leftViewMode = .always } @@ -1148,9 +1130,6 @@ private extension PopUpStoreRegisterViewController { updateSaveButtonState() } - - - } extension PopUpStoreRegisterViewController: PHPickerViewControllerDelegate { func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { @@ -1172,7 +1151,7 @@ extension PopUpStoreRegisterViewController: PHPickerViewControllerDelegate { for (index, provider) in itemProviders.enumerated() { if provider.canLoadObject(ofClass: UIImage.self) { dispatchGroup.enter() - provider.loadObject(ofClass: UIImage.self) { [weak self] object, error in + provider.loadObject(ofClass: UIImage.self) { [weak self] object, _ in defer { dispatchGroup.leave() } guard let self = self, let image = object as? UIImage else { return } @@ -1248,7 +1227,6 @@ private extension PopUpStoreRegisterViewController { return false } - // (4) 위도/경도 Logger.log(message: "latField.text = \(latField?.text ?? "nil")", category: .debug) Logger.log(message: "lonField.text = \(lonField?.text ?? "nil")", category: .debug) @@ -1324,7 +1302,6 @@ private extension PopUpStoreRegisterViewController { } } - // 폼 데이터 검증 private func validateFormData() -> Bool { guard let name = nameField?.text, @@ -1420,7 +1397,6 @@ private extension PopUpStoreRegisterViewController { .disposed(by: disposeBag) } - private func uploadImagesForUpdate(_ store: GetAdminPopUpStoreListResponseDTO.PopUpStore) { let uuid = UUID().uuidString let updatedImages = images.enumerated().map { index, image in @@ -1652,7 +1628,6 @@ private extension PopUpStoreRegisterViewController { startDateBeforeEndDate: isValidDateOrder ) - adminUseCase.createStore(request: request) .subscribe( onNext: { [weak self] _ in @@ -1693,7 +1668,6 @@ private extension PopUpStoreRegisterViewController { } } - private func createDateTime(date: Date?, time: Date?) -> Date? { guard let date = date else { return nil } @@ -1732,9 +1706,6 @@ private extension PopUpStoreRegisterViewController { return formatter.string(from: date) } - - - private func prepareDateTime() -> (startDate: String, endDate: String) { // 시작일/시간 결합 let startDateTime = createDateTime(date: selectedStartDate, time: selectedStartTime) @@ -1773,11 +1744,6 @@ private extension PopUpStoreRegisterViewController { return start < end } - - - - - private func showSuccessAlert() { let alert = UIAlertController( title: "등록 성공", @@ -1861,7 +1827,6 @@ extension PopUpStoreRegisterViewController: UITextFieldDelegate { .disposed(by: disposeBag) } - @objc private func addressFieldDidChange(_ textField: UITextField) { guard let address = textField.text, !address.isEmpty else { return } diff --git a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapPopUpStore.swift b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapPopUpStore.swift index 02384864..2be1694e 100644 --- a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapPopUpStore.swift +++ b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/MapPopUpStore.swift @@ -15,7 +15,6 @@ struct MapPopUpStore: Equatable { let markerSnippet: String let mainImageUrl: String? - var nmgCoordinate: NMGLatLng { NMGLatLng(lat: latitude, lng: longitude) } diff --git a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/Repository/MapRepository.swift b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/Repository/MapRepository.swift index 8ac511d9..a10bffc5 100644 --- a/Poppool/Poppool/Presentation/Admin/Data/MapDomain/Repository/MapRepository.swift +++ b/Poppool/Poppool/Presentation/Admin/Data/MapDomain/Repository/MapRepository.swift @@ -62,7 +62,7 @@ class DefaultMapRepository: MapRepository { query: query, categories: categories ), - interceptor: TokenInterceptor() + interceptor: TokenInterceptor() ) .map { $0.popUpStoreList } } diff --git a/Poppool/Poppool/Presentation/Map/Common/MapUtilities.swift b/Poppool/Poppool/Presentation/Map/Common/MapUtilities.swift index 3b4cd3bf..d76dafe1 100644 --- a/Poppool/Poppool/Presentation/Map/Common/MapUtilities.swift +++ b/Poppool/Poppool/Presentation/Map/Common/MapUtilities.swift @@ -28,4 +28,3 @@ public let gyeonggiSouthRegions: [String] = [ "용인시", "화성시", "수원시", "안산시", "부천시", "의왕시", "과천시", "여주시", "양평군", "광주시", "이천시" ] - diff --git a/Poppool/Poppool/Presentation/Map/Common/NMFMapViewDelegateProxy.swift b/Poppool/Poppool/Presentation/Map/Common/NMFMapViewDelegateProxy.swift index dadff963..71e6a303 100644 --- a/Poppool/Poppool/Presentation/Map/Common/NMFMapViewDelegateProxy.swift +++ b/Poppool/Poppool/Presentation/Map/Common/NMFMapViewDelegateProxy.swift @@ -1,6 +1,6 @@ import NMapsMap -import RxSwift import RxCocoa +import RxSwift /// NMFMapViewDelegateProxy는 NMFMapView의 delegate 이벤트를 RxSwift Observable로 변환하는 역할 class NMFMapViewDelegateProxy: DelegateProxy, DelegateProxyType, NMFMapViewDelegate { diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonBackgroundView.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonBackgroundView.swift index 076c61b6..45161c19 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonBackgroundView.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonBackgroundView.swift @@ -89,19 +89,17 @@ final class BalloonBackgroundView: UIView { backgroundColor = .clear self.isUserInteractionEnabled = true containerView.isUserInteractionEnabled = true - collectionView.isUserInteractionEnabled = true + collectionView.isUserInteractionEnabled = true setupLayout() setupCollectionView() } - - required init?(coder: NSCoder) { fatalError() } // MARK: - Setup private func setupLayout() { - + addSubview(containerView) containerView.snp.makeConstraints { make in make.left.right.equalToSuperview() @@ -109,7 +107,6 @@ final class BalloonBackgroundView: UIView { make.bottom.equalToSuperview().priority(.high) } - containerView.addSubview(collectionView) containerView.addSubview(singleRegionIcon) containerView.addSubview(singleRegionTitleLabel) @@ -120,7 +117,6 @@ final class BalloonBackgroundView: UIView { make.bottom.equalToSuperview().priority(.high) } - singleRegionIcon.snp.makeConstraints { make in make.top.equalToSuperview().offset(24) make.centerX.equalToSuperview() @@ -242,14 +238,12 @@ final class BalloonBackgroundView: UIView { collectionView.layoutIfNeeded() - let balloonWidth = self.bounds.width let horizontalSpacing: CGFloat = 8 let leftPadding: CGFloat = 20 let rightPadding: CGFloat = 20 let availableWidth = balloonWidth - leftPadding - rightPadding - var currentRowWidth: CGFloat = 0 var numberOfRows: Int = 1 @@ -269,14 +263,12 @@ final class BalloonBackgroundView: UIView { let itemHeight: CGFloat = 36 let interGroupSpacing: CGFloat = 8 let verticalInset: CGFloat = 20 + 20 - let totalHeight = max( + return max( (itemHeight * CGFloat(numberOfRows)) + (interGroupSpacing * CGFloat(numberOfRows - 1)) + verticalInset, 36 ) - - return totalHeight } private func calculateButtonWidth(for text: String, font: UIFont, isSelected: Bool) -> CGFloat { let textWidth = (text as NSString).size(withAttributes: [.font: font]).width diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonChipCell.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonChipCell.swift index 0d2f0b54..40f33be0 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonChipCell.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonChipCell.swift @@ -11,7 +11,7 @@ final class BalloonChipCell: UICollectionViewCell { font: .KorFont(style: .medium, size: 11), cornerRadius: 15 ) - + button.titleLabel?.lineBreakMode = .byTruncatingTail button.titleLabel?.adjustsFontSizeToFitWidth = false return button diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift index d465e412..c9a288cb 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift @@ -1,5 +1,5 @@ -import UIKit import SnapKit +import UIKit final class FilterBottomSheetView: UIView { // MARK: - UI Components @@ -26,8 +26,7 @@ final class FilterBottomSheetView: UIView { }() let segmentedControl: PPSegmentedControl = { - let control = PPSegmentedControl(type: .tab, segments: ["지역", "카테고리"], selectedSegmentIndex: 0) - return control + return PPSegmentedControl(type: .tab, segments: ["지역", "카테고리"], selectedSegmentIndex: 0) }() let locationScrollView: UIScrollView = { @@ -40,7 +39,7 @@ final class FilterBottomSheetView: UIView { var categoryHeightConstraint: Constraint? let categoryCollectionView: UICollectionView = { - let layout = UICollectionViewCompositionalLayout { section, env in + let layout = UICollectionViewCompositionalLayout { section, _ in let itemSize = NSCollectionLayoutSize( widthDimension: .estimated(26), heightDimension: .absolute(36) @@ -79,13 +78,11 @@ final class FilterBottomSheetView: UIView { let balloonBackgroundView = BalloonBackgroundView() let resetButton: PPButton = { - let button = PPButton(style: .secondary, text: "초기화") - return button + return PPButton(style: .secondary, text: "초기화") }() let saveButton: PPButton = { - let button = PPButton(style: .primary, text: "옵션저장", disabledText: "옵션저장") - return button + return PPButton(style: .primary, text: "옵션저장", disabledText: "옵션저장") }() private let buttonStack: UIStackView = { @@ -97,8 +94,7 @@ final class FilterBottomSheetView: UIView { }() let filterChipsView: FilterChipsView = { - let view = FilterChipsView() - return view + return FilterChipsView() }() private var balloonHeightConstraint: Constraint? diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetViewController.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetViewController.swift index f51ac692..b5261aa8 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetViewController.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetViewController.swift @@ -1,8 +1,8 @@ -import UIKit -import SnapKit -import RxSwift -import RxCocoa import ReactorKit +import RxCocoa +import RxSwift +import SnapKit +import UIKit final class FilterBottomSheetViewController: UIViewController, View { typealias Reactor = FilterBottomSheetReactor @@ -43,7 +43,7 @@ final class FilterBottomSheetViewController: UIViewController, View { setupLayout() setupGestures() setupCollectionView() - + containerView.filterChipsView.onRemoveChip = { [weak self] removedOption in guard let self = self, let reactor = self.reactor else { return } @@ -140,14 +140,11 @@ final class FilterBottomSheetViewController: UIViewController, View { } ) - }) .map { Reactor.Action.resetFilters } .bind(to: reactor.action) .disposed(by: disposeBag) - - containerView.saveButton.rx.tap .bind { [weak self] _ in guard let self = self, let reactor = self.reactor else { return } @@ -164,7 +161,6 @@ final class FilterBottomSheetViewController: UIViewController, View { } .disposed(by: disposeBag) - containerView.closeButton.rx.tap .bind { [weak self] _ in guard let self = self, let reactor = self.reactor else { return } @@ -177,7 +173,6 @@ final class FilterBottomSheetViewController: UIViewController, View { } .disposed(by: disposeBag) - // 5. 탭 변경 reactor.state.map { $0.activeSegment } .distinctUntilChanged() @@ -196,7 +191,6 @@ final class FilterBottomSheetViewController: UIViewController, View { } .disposed(by: disposeBag) - // 6. 위치 데이터 바인딩 let locations = reactor.state .map { $0.locations } @@ -223,7 +217,6 @@ final class FilterBottomSheetViewController: UIViewController, View { } .disposed(by: disposeBag) - let locationAndSubRegions = reactor.state .map { ($0.selectedLocationIndex, $0.selectedSubRegions) } .distinctUntilChanged { prev, curr in @@ -239,7 +232,6 @@ final class FilterBottomSheetViewController: UIViewController, View { guard let self = self, let reactor = self.reactor else { return } let (selectedIndexOptional, selectedSubRegions) = data - guard let selectedIndex = selectedIndexOptional, selectedIndex >= 0, selectedIndex < reactor.currentState.locations.count else { return } @@ -257,7 +249,6 @@ final class FilterBottomSheetViewController: UIViewController, View { } ) - if let button = self.containerView.locationContentView.subviews[selectedIndex] as? UIButton { self.containerView.updateBalloonPosition(for: button) } @@ -296,8 +287,6 @@ final class FilterBottomSheetViewController: UIViewController, View { } .disposed(by: disposeBag) - - reactor.state.map { $0.selectedSubRegions + $0.selectedCategories } .distinctUntilChanged() .bind { [weak self] selectedOptions in @@ -318,7 +307,6 @@ final class FilterBottomSheetViewController: UIViewController, View { } .disposed(by: disposeBag) - reactor.state.map { $0.isSaveEnabled } .distinctUntilChanged() .observe(on: MainScheduler.instance) @@ -418,7 +406,6 @@ final class FilterBottomSheetViewController: UIViewController, View { view.layoutIfNeeded() } - private func setupGestures() { let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapDimmedView)) tapGesture.delegate = self @@ -441,7 +428,6 @@ final class FilterBottomSheetViewController: UIViewController, View { let index = reactor.currentState.locations.firstIndex(where: { $0.main == locations }) { reactor.action.onNext(.selectLocation(index)) - } // 4. 필터 칩 뷰 업데이트 @@ -458,8 +444,6 @@ final class FilterBottomSheetViewController: UIViewController, View { } } - - func hideBottomSheet() { UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseIn) { self.dimmedView.alpha = 0 diff --git a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FullScreenMapViewController.swift b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FullScreenMapViewController.swift index 8eeaa084..82393654 100644 --- a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FullScreenMapViewController.swift +++ b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/FullScreenMapViewController.swift @@ -1,9 +1,9 @@ -import UIKit -import SnapKit -import RxSwift -import RxCocoa -import NMapsMap import CoreLocation +import NMapsMap +import RxCocoa +import RxSwift +import SnapKit +import UIKit class FullScreenMapViewController: MapViewController { // MARK: - Properties @@ -12,7 +12,6 @@ class FullScreenMapViewController: MapViewController { private var markerLocked = false // 마커 상태 잠금 플래그 private var initialMarker: NMFMarker? - // MARK: - Initialization init(store: MapPopUpStore?, existingMarker: NMFMarker? = nil) { self.initialStore = store @@ -20,8 +19,6 @@ class FullScreenMapViewController: MapViewController { super.init() } - - required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -36,7 +33,6 @@ class FullScreenMapViewController: MapViewController { Logger.log(message: "💡 초기 위치 구성 직전: initialStore=\(String(describing: initialStore?.name))", category: .debug) configureInitialMapPosition() - Logger.log(message: "✅ FullScreenMapViewController - viewDidLoad 완료", category: .debug) mainView.mapView.touchDelegate = self @@ -145,8 +141,6 @@ class FullScreenMapViewController: MapViewController { carouselView.isHidden = false } - - override func bind(reactor: MapReactor) { super.bind(reactor: reactor) @@ -192,7 +186,6 @@ class FullScreenMapViewController: MapViewController { super.updateMarkerStyle(marker: marker, selected: selected, isCluster: isCluster, count: count, regionName: regionName) } - override func handleSingleStoreTap(_ marker: NMFMarker, store: MapPopUpStore) -> Bool { isMovingToMarker = true markerLocked = true diff --git a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideAppService.swift b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideAppService.swift index aab14ce8..048c1f2f 100644 --- a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideAppService.swift +++ b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideAppService.swift @@ -1,5 +1,5 @@ -import UIKit import CoreLocation +import UIKit enum MapAppType { case naver diff --git a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift index 4810452c..60d2d07c 100644 --- a/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift +++ b/Poppool/Poppool/Presentation/Map/FindMap/MapGuideView/MapGuideViewController.swift @@ -1,9 +1,9 @@ -import UIKit -import SnapKit -import ReactorKit -import RxSwift import CoreLocation import NMapsMap +import ReactorKit +import RxSwift +import SnapKit +import UIKit final class MapGuideViewController: UIViewController, View { // MARK: - Properties @@ -15,7 +15,7 @@ final class MapGuideViewController: UIViewController, View { private let dimmingView: UIView = { let viewInstance = UIView() - viewInstance.backgroundColor = UIColor.gray.withAlphaComponent(0.7) + viewInstance.backgroundColor = UIColor.gray.withAlphaComponent(0.7) viewInstance.alpha = 0 return viewInstance }() diff --git a/Poppool/Poppool/Presentation/Map/MapView/MapMarker.swift b/Poppool/Poppool/Presentation/Map/MapView/MapMarker.swift index 9ddfb0e1..be525bb3 100644 --- a/Poppool/Poppool/Presentation/Map/MapView/MapMarker.swift +++ b/Poppool/Poppool/Presentation/Map/MapView/MapMarker.swift @@ -1,6 +1,6 @@ -import UIKit -import SnapKit import NMapsMap +import SnapKit +import UIKit final class MapMarker: UIView { // MARK: - Components diff --git a/Poppool/Poppool/Presentation/Map/MapView/MapReactor.swift b/Poppool/Poppool/Presentation/Map/MapView/MapReactor.swift index dc404ec1..b0e82322 100644 --- a/Poppool/Poppool/Presentation/Map/MapView/MapReactor.swift +++ b/Poppool/Poppool/Presentation/Map/MapView/MapReactor.swift @@ -217,7 +217,7 @@ final class MapReactor: Reactor { case .viewDidLoad(let id): return directionRepository.getPopUpDirection(popUpStoreId: id) .do( - onNext: { response in + onNext: { _ in }, onError: { error in Logger.log( diff --git a/Poppool/Poppool/Presentation/Map/MapView/MapView.swift b/Poppool/Poppool/Presentation/Map/MapView/MapView.swift index 3df6ad08..af1529e9 100644 --- a/Poppool/Poppool/Presentation/Map/MapView/MapView.swift +++ b/Poppool/Poppool/Presentation/Map/MapView/MapView.swift @@ -1,12 +1,12 @@ -import UIKit -import SnapKit import NMapsMap +import SnapKit +import UIKit final class MapView: UIView { // MARK: - Components let mapView: NMFMapView = { let view = NMFMapView() - view.positionMode = .disabled + view.positionMode = .disabled view.zoomLevel = 14 view.extent = NMGLatLngBounds( @@ -54,8 +54,7 @@ final class MapView: UIView { }() var storeCard: MapPopupCarouselView = { - let view = MapPopupCarouselView() - return view + return MapPopupCarouselView() }() // MARK: - Init diff --git a/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift b/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift index 93d7d26f..e327b463 100644 --- a/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift +++ b/Poppool/Poppool/Presentation/Map/MapView/MapViewController.swift @@ -1,12 +1,12 @@ -import UIKit +import CoreLocation import FloatingPanel -import SnapKit -import RxSwift -import RxCocoa -import ReactorKit import NMapsMap -import CoreLocation +import ReactorKit +import RxCocoa import RxGesture +import RxSwift +import SnapKit +import UIKit class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NMFMapViewTouchDelegate, NMFMapViewCameraDelegate, UIGestureRecognizerDelegate { typealias Reactor = MapReactor @@ -278,7 +278,6 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM mapViewTapGesture.delegate = self } - private let defaultZoomLevel: Double = 15.0 private func setupPanAndSwipeGestures() { storeListViewController.mainView.grabberHandle.rx.swipeGesture(.up) @@ -347,12 +346,10 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM lng: location.coordinate.longitude ), zoomTo: 15.0) - self.mainView.mapView.moveCamera(cameraUpdate) } .disposed(by: disposeBag) - mainView.filterChips.onRemoveLocation = { [weak self] in guard let self = self else { return } // 필터 제거 액션 @@ -547,7 +544,7 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM updateMarkerStyle(marker: marker, selected: false, isCluster: false, count: 1) // 중요: 마커에 직접 터치 핸들러 추가 - marker.touchHandler = { [weak self] (overlay) -> Bool in + marker.touchHandler = { [weak self] (_) -> Bool in guard let self = self else { return false } Logger.log(message: "마커 터치됨! 위치: \(marker.position), 스토어: \(store.name)", category: .debug) @@ -560,7 +557,6 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM markerDictionary[store.id] = marker } - func updateMarkerStyle(marker: NMFMarker, selected: Bool, isCluster: Bool, count: Int = 1, regionName: String = "") { if selected { marker.width = 44 @@ -660,10 +656,9 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM private func updateMapViewAlpha(for offset: CGFloat, minOffset: CGFloat, maxOffset: CGFloat) { let progress = (maxOffset - offset) / (maxOffset - minOffset) - mainView.mapView.alpha = max(0, min(progress, 1)) + mainView.mapView.alpha = max(0, min(progress, 1)) } - private func animateToState(_ state: ModalState) { guard modalState != state else { return } self.view.layoutIfNeeded() @@ -790,7 +785,7 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM updateMarkerStyle(marker: marker, selected: false, isCluster: false) // 직접 터치 핸들러 추가 - marker.touchHandler = { [weak self] (overlay) -> Bool in + marker.touchHandler = { [weak self] (_) -> Bool in guard let self = self else { return false } print("개별 마커 터치됨! 스토어: \(store.name)") @@ -816,7 +811,7 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM updateMarkerStyle(marker: marker, selected: false, isCluster: false, count: storeGroup.count) // 직접 터치 핸들러 추가 - marker.touchHandler = { [weak self] (overlay) -> Bool in + marker.touchHandler = { [weak self] (_) -> Bool in guard let self = self else { return false } print("마이크로 클러스터 마커 터치됨! 스토어 수: \(storeGroup.count)개") @@ -902,7 +897,6 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM CATransaction.commit() } - private func clearAllMarkers() { individualMarkerDictionary.values.forEach { $0.mapView = nil } individualMarkerDictionary.removeAll() @@ -1024,7 +1018,7 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM currentFilterBottomSheet = nil } - //기본 마커 + // 기본 마커 private func addMarkers(for stores: [MapPopUpStore]) { markerDictionary.values.forEach { $0.mapView = nil } markerDictionary.removeAll() @@ -1037,7 +1031,7 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM updateMarkerStyle(marker: marker, selected: false, isCluster: false) // 직접 터치 핸들러 추가 - marker.touchHandler = { [weak self] (overlay) -> Bool in + marker.touchHandler = { [weak self] (_) -> Bool in guard let self = self else { return false } print("검색 결과 마커 터치됨! 스토어: \(store.name)") @@ -1175,7 +1169,7 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM updateMarkerStyle(marker: marker, selected: false, isCluster: false) // 직접 터치 핸들러 추가 - marker.touchHandler = { [weak self] (overlay) -> Bool in + marker.touchHandler = { [weak self] (_) -> Bool in guard let self = self else { return false } print("클러스터 내 마커 터치됨! 스토어: \(store.name)") @@ -1187,7 +1181,6 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM } } - private func findMarkerForStore(for store: MapPopUpStore) -> NMFMarker? { // individualMarkerDictionary에 저장된 모든 마커를 순회 for marker in individualMarkerDictionary.values { @@ -1508,10 +1501,8 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM cameraUpdate.animation = .easeIn cameraUpdate.animationDuration = 0.3 mainView.mapView.moveCamera(cameraUpdate) - - default: + default: print("기타 레벨 클러스터 처리") - break } // 클러스터에 포함된 스토어들만 표시하도록 마커 업데이트 @@ -1525,7 +1516,6 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM return true } - // 마이크로 클러스터 탭 처리 func handleMicroClusterTap(_ marker: NMFMarker, storeArray: [MapPopUpStore]) -> Bool { // 이미 선택된 마커를 다시 탭할 때 @@ -1644,7 +1634,6 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM return false } - // 지도 탭 이벤트 처리 func mapView(_ mapView: NMFMapView, didTapMap latlng: NMGLatLng, point: CGPoint) { guard !isMovingToMarker else { return } diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailInfoSection/DetailInfoSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailInfoSection/DetailInfoSectionCell.swift index e7029550..1ba18382 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailInfoSection/DetailInfoSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailInfoSection/DetailInfoSectionCell.swift @@ -170,4 +170,3 @@ extension DetailInfoSectionCell: Inputable { addressLabel.setLineHeightText(text: input.address, font: .KorFont(style: .regular, size: 13)) } } - From d6137556ba2bde930819d54863e47ba9a3bf377c Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Fri, 4 Apr 2025 10:51:16 +0900 Subject: [PATCH 55/95] =?UTF-8?q?fix/#82:=20=ED=8C=A8=ED=82=A4=EC=A7=80=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xcshareddata/swiftpm/Package.resolved | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 172ad40b..ea30e126 100644 --- a/Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "a264a064b245047631c2da3a5af0624dcc8a9814c5033d1880880c8dbbdc6a20", + "originHash" : "90182ae75bddc149e01f3583353785f437a618e30e1e9df09cabd3c7f3605747", "pins" : [ { "identity" : "alamofire", @@ -33,8 +33,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/onevcat/Kingfisher.git", "state" : { - "revision" : "3db26ab625d194c38e68c1a40e43d1bc12743fe0", - "version" : "8.2.0" + "revision" : "4c6b067f96953ee19526e49e4189403a2be21fb3", + "version" : "8.3.1" } }, { @@ -141,8 +141,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/scinfu/SwiftSoup", "state" : { - "revision" : "18ad8b8ff0f03f3c0a5544ffccfa2ea1c051ae6e", - "version" : "2.8.0" + "revision" : "bba848db50462894e7fc0891d018dfecad4ef11e", + "version" : "2.8.7" } }, { From 17ac190254e66315255d98bbc8d162c1f4325c54 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Fri, 4 Apr 2025 13:10:03 +0900 Subject: [PATCH 56/95] =?UTF-8?q?style/#82:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=EC=97=90=20=EB=A7=9E=EA=B2=8C=20?= =?UTF-8?q?=EC=83=81=EB=8B=A8=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Scene/Detail/DetailController.swift | 7 ------- .../Poppool/Presentation/Scene/Detail/DetailReactor.swift | 7 ------- .../Presentation/Scene/Home/Main/HomeController.swift | 7 ------- .../Poppool/Presentation/Scene/Home/Main/HomeReactor.swift | 7 ------- .../ImageBannerSection/ImageBannerSectionCell.swift | 7 ------- 5 files changed, 35 deletions(-) diff --git a/Poppool/Poppool/Presentation/Scene/Detail/DetailController.swift b/Poppool/Poppool/Presentation/Scene/Detail/DetailController.swift index 75cfd69c..cc6f6183 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/DetailController.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/DetailController.swift @@ -1,10 +1,3 @@ -// -// DetailController.swift -// Poppool -// -// Created by SeoJunYoung on 12/9/24. -// - import UIKit import SnapKit diff --git a/Poppool/Poppool/Presentation/Scene/Detail/DetailReactor.swift b/Poppool/Poppool/Presentation/Scene/Detail/DetailReactor.swift index 165a8b7a..c8c38941 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/DetailReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/DetailReactor.swift @@ -1,10 +1,3 @@ -// -// DetailReactor.swift -// Poppool -// -// Created by SeoJunYoung on 12/9/24. -// - import UIKit import ReactorKit diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/HomeController.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/HomeController.swift index 5a507a7d..47c5a576 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/HomeController.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/HomeController.swift @@ -1,10 +1,3 @@ -// -// HomeController.swift -// Poppool -// -// Created by SeoJunYoung on 11/28/24. -// - import UIKit import SnapKit diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/HomeReactor.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/HomeReactor.swift index d7bdfb8a..6bba6dea 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/HomeReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/HomeReactor.swift @@ -1,10 +1,3 @@ -// -// HomeReactor.swift -// Poppool -// -// Created by SeoJunYoung on 11/28/24. -// - import UIKit import ReactorKit diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift index 025ec4a9..d72ea488 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift @@ -1,10 +1,3 @@ -// -// ImageBannerSectionCell.swift -// Poppool -// -// Created by SeoJunYoung on 11/28/24. -// - import UIKit import SnapKit From 612fbdef3a5d8db7a7e37ff0bf95e24e703ce88a Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Fri, 4 Apr 2025 13:11:00 +0900 Subject: [PATCH 57/95] =?UTF-8?q?fix/#82:=20=EC=83=81=EB=8B=A8=20=EB=B0=B0?= =?UTF-8?q?=EB=84=88=EC=97=90=20=EC=82=AC=EC=A7=84=EC=9D=B4=20=ED=95=9C?= =?UTF-8?q?=EC=9E=A5=EC=9D=BC=20=EB=95=8C=20=EB=AC=B4=ED=95=9C=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A1=A4=20=EB=B0=8F=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20?= =?UTF-8?q?=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ImageBannerSectionCell.swift | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift index d72ea488..0803d4fa 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift @@ -200,16 +200,20 @@ extension ImageBannerSectionCell: Inputable { if imageSection.isEmpty { pageControl.setNumberOfPages(input.imagePaths.count) let datas = zip(input.imagePaths, input.idList) - let backContents = datas.suffix(1) - let frontContents = datas.prefix(1) - imageSection.inputDataList = datas.map { .init(imagePath: $0.0, id: $0.1) } - imageSection.inputDataList.append(contentsOf: frontContents.map { .init(imagePath: $0.0, id: $0.1) }) - imageSection.inputDataList = backContents.map {.init(imagePath: $0.0, id: $0.1) } + imageSection.inputDataList - DispatchQueue.main.async { [weak self] in - self?.contentCollectionView.scrollToItem( - at: .init(row: 1, section: 0), - at: .centeredHorizontally, animated: false - ) + if input.imagePaths.count > 1 { + let backContents = datas.suffix(1) + let frontContents = datas.prefix(1) + imageSection.inputDataList = datas.map { .init(imagePath: $0.0, id: $0.1) } + imageSection.inputDataList.append(contentsOf: frontContents.map { .init(imagePath: $0.0, id: $0.1) }) + imageSection.inputDataList = backContents.map { .init(imagePath: $0.0, id: $0.1) } + imageSection.inputDataList + DispatchQueue.main.async { [weak self] in + self?.contentCollectionView.scrollToItem( + at: .init(row: 1, section: 0), + at: .centeredHorizontally, animated: false + ) + } + } else { + imageSection.inputDataList = datas.map { .init(imagePath: $0.0, id: $0.1) } } } From 68e8644a97ccf767d9117b36d9151d6e64028cac Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Fri, 4 Apr 2025 13:12:27 +0900 Subject: [PATCH 58/95] =?UTF-8?q?fix/#82:=20=EB=B0=B0=EB=84=88=20=EC=82=AC?= =?UTF-8?q?=EC=A7=84=EC=9D=B4=201=EC=9E=A5=EC=9D=BC=20=EB=95=8C=20?= =?UTF-8?q?=EB=8D=B8=EB=A6=AC=EA=B2=8C=EC=9D=B4=ED=8A=B8=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EB=A1=9C=EC=A7=81=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 셀에서 벗어날 때 해당 델리게이트 메서드가 동작 - 하지만 셀에 이미지가 한장일 때 해당 메서드가 out of index를 유발함 - 해당 부분을 guard문으로 처리 --- .../ImageBannerSection/ImageBannerSectionCell.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift index 0803d4fa..3f650e81 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift @@ -263,6 +263,8 @@ extension ImageBannerSectionCell: UICollectionViewDelegate, UICollectionViewData } func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + guard imageSection.dataCount > 1 else { return } + if currentIndex == 0 { contentCollectionView.scrollToItem( at: .init(row: imageSection.dataCount - 2, section: 0), From 7ce8f3008137f2ab6ab28358cbacb6dea576d7d0 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Fri, 4 Apr 2025 13:13:34 +0900 Subject: [PATCH 59/95] =?UTF-8?q?docs/#82:=20=EC=9D=B4=EC=8A=88=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0=20=EC=A4=91=20=EC=B6=94=EA=B0=80=20=EB=B0=9C?= =?UTF-8?q?=EA=B2=AC=ED=95=9C=20=EB=AC=B8=EC=A0=9C=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 디버깅 중 확인한 부분입니다 - (홈 → 상세) 이동 후에도 홈의 배너 무한스크롤이 계속 작동하는 문제 - 홈의 무한 스크롤이 한번 시작했음에도 계속 다시 시작하는 문제 --- .../ImageBannerSection/ImageBannerSectionCell.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift index 3f650e81..eb9baaca 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift @@ -85,6 +85,8 @@ final class ImageBannerSectionCell: UICollectionViewCell { autoScrollTimer = nil } + // FIXME: (홈 -> 상세) 이동 시 홈의 자동 스크롤이 계속 돌아감. + // FIXME: 또한 오토 스크롤을 한번만 실행하면 되는데, 사진이 넘어갈 때 마다 실행되는것으로 보임 func startAutoScroll(interval: TimeInterval = 3.0) { stopAutoScroll() // 기존 타이머를 중지 stopButton.isHidden = false From 7177fc073edee96b0a49c8e70d213ab1a6cbd778 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Fri, 4 Apr 2025 19:42:41 +0900 Subject: [PATCH 60/95] =?UTF-8?q?style/#93:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=EC=97=90=20=EB=A7=9E=EA=B2=8C=20?= =?UTF-8?q?=EC=9D=BC=EB=B6=80=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: JunYoung --- Poppool/Poppool.xcodeproj/project.pbxproj | 4 ---- .../ResponseDTO/GetMyProfileResponseDTO.swift | 11 ++++++++- .../NetworkLayer/Provider/ProviderImpl.swift | 17 ++++++-------- .../AdminBottomSheetView.swift | 4 ++-- .../Presentation/Components/PPButton.swift | 2 +- .../Components/PPCancelHeaderView.swift | 2 +- .../Presentation/Components/PPLabel.swift | 2 +- .../Presentation/Components/PPPicker.swift | 4 ++-- .../Components/PPReturnHeaderView.swift | 2 +- .../Components/PPSegmentedControl.swift | 12 +++++----- .../Presentation/Extension/Optional+.swift | 14 ----------- .../Presentation/Extension/UIFont+.swift | 12 ++-------- .../LocationPermissionBottomSheet.swift | 8 +++---- .../FillterSheetView/BalloonChipCell.swift | 6 ++--- .../FilterBottomSheetView.swift | 6 ++--- .../CommentDetailController.swift | 2 +- .../CommentDetailContentSectionCell.swift | 2 +- .../CommentMyMenu/CommentMyMenuView.swift | 6 ++--- .../CommentUserInfoController.swift | 2 +- .../CommentUserInfo/CommentUserInfoView.swift | 6 ++--- .../CommentListTitleSectionCell.swift | 2 +- .../CommentSelected/CommentSelectedView.swift | 6 ++--- .../CommentUserBlockController.swift | 2 +- .../InstaComment/InstaCommentAddReactor.swift | 8 +++---- .../View/InstaCommentAddView.swift | 6 ++--- .../InstaGuideChildSectionCell.swift | 2 +- .../AddCommentDescriptionSectionCell.swift | 2 +- .../AddCommentSectionCell.swift | 2 +- .../AddCommentTitleSectionCell.swift | 2 +- .../View/NormalCommentAddView.swift | 2 +- .../NormalCommentEditView.swift | 2 +- .../OtherUserCommentSectionCell.swift | 8 +++---- .../View/OtherUserCommentView.swift | 2 +- .../Scene/Detail/DetailController.swift | 4 ++-- .../Scene/Detail/DetailReactor.swift | 23 ++++++++----------- .../DetailCommentSectionCell.swift | 16 ++++++------- .../DetailCommentTitleSectionCell.swift | 4 ++-- .../DetailContentSectionCell.swift | 2 +- .../DetailEmptyCommetSectionCell.swift | 2 +- .../DetailInfoSectionCell.swift | 6 ++--- .../DetailSimilarSectionCell.swift | 6 ++--- .../DetailTitleSectionCell.swift | 2 +- .../Scene/Home/Main/HomeReactor.swift | 11 ++++++++- .../HomeCardSection/HomeCardSectionCell.swift | 10 ++++---- .../HomePopularCardSectionCell.swift | 4 ++-- .../HomeTitleSectionCell.swift | 6 ++--- .../Scene/Login/LastLoginView.swift | 2 +- .../Scene/Login/Main/LoginView.swift | 4 ++-- .../Scene/Login/Sub/SubLoginView.swift | 4 ++-- .../BlockUserListSectionCell.swift | 6 ++--- .../Block/View/BlockUserManageView.swift | 2 +- .../Main/MyPageBookmarkController.swift | 4 ++-- .../Bookmark/Main/MyPageBookmarkView.swift | 4 ++-- .../PopUpCardSectionCell.swift | 8 +++---- .../View/PopUpCardSection/PopUpCardView.swift | 8 +++---- .../FAQDropdownSectionCell.swift | 6 ++--- .../Scene/MyPage/FAQ/View/FAQView.swift | 2 +- .../MyPageCommentSectionCell.swift | 2 +- .../MyPageListSectionCell.swift | 4 ++-- .../MyPageMyCommentTitleSectionCell.swift | 4 ++-- .../MyPageProfileSectionCell.swift | 6 ++--- .../ListCountButtonSectionCell.swift | 4 ++-- .../MyComment/Main/View/MyCommentView.swift | 2 +- .../MyCommentedPopUpGridSectionCell.swift | 6 ++--- .../Detail/MyPageNoticeDetailController.swift | 6 ++--- .../Detail/MyPageNoticeDetailView.swift | 2 +- .../Notice/List/View/MyPageNoticeView.swift | 2 +- .../NoticeListSectionCell.swift | 4 ++-- .../Main/ProfileEditController.swift | 4 ++-- .../ProfileEdit/Main/ProfileEditReactor.swift | 2 +- .../Main/View/ProfileEditView.swift | 14 +++++------ .../MyPage/Recent/View/MyPageRecentView.swift | 2 +- .../MyPage/Terms/MyPageTermsController.swift | 2 +- .../WithdrawlCheckModalController.swift | 2 +- .../CheckModal/WithdrawlCheckModalView.swift | 4 ++-- .../Complete/WithdrawlCompleteView.swift | 2 +- .../View/WithdrawlCheckSectionCell.swift | 4 ++-- .../View/WithdrawlReasonView.swift | 6 ++--- .../CancelableTagSectionCell.swift | 4 ++-- .../SearchTitleSectionCell.swift | 4 ++-- .../Scene/Search/Main/SearchMainView.swift | 6 ++--- .../SignUpCompleteController.swift | 4 ++-- .../SignUp/Step2/SignUpStep2Reactor.swift | 2 +- .../Scene/SignUp/Step2/SignUpStep2View.swift | 6 ++--- .../View/TagSection/TagSectionCell.swift | 4 ++-- .../TermsDetail/TermsDetailController.swift | 2 +- .../TabbarController/TabbarController.swift | 4 ++-- .../Utills/ToastMaker/BookMarkToastView.swift | 6 ++--- .../Utills/ToastMaker/ToastView.swift | 2 +- 89 files changed, 214 insertions(+), 230 deletions(-) delete mode 100644 Poppool/Poppool/Presentation/Extension/Optional+.swift diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index 74fce4bf..3e794526 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -354,7 +354,6 @@ 08DC620B2CF8AE0F002A2F44 /* ImageBannerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DC620A2CF8AE0F002A2F44 /* ImageBannerSection.swift */; }; 08DC620D2CF8AE16002A2F44 /* ImageBannerSectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DC620C2CF8AE16002A2F44 /* ImageBannerSectionCell.swift */; }; 08DC62112CF8B446002A2F44 /* SortedRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DC62102CF8B446002A2F44 /* SortedRequestDTO.swift */; }; - 08DC62132CF8B833002A2F44 /* Optional+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DC62122CF8B833002A2F44 /* Optional+.swift */; }; 08DE8A0D2D5236840049BCAC /* PutCommentRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DE8A0C2D5236840049BCAC /* PutCommentRequestDTO.swift */; }; 08DE8A102D5255110049BCAC /* DetailEmptyCommetSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DE8A0F2D5255110049BCAC /* DetailEmptyCommetSection.swift */; }; 08DE8A122D5255180049BCAC /* DetailEmptyCommetSectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DE8A112D5255180049BCAC /* DetailEmptyCommetSectionCell.swift */; }; @@ -821,7 +820,6 @@ 08DC620A2CF8AE0F002A2F44 /* ImageBannerSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageBannerSection.swift; sourceTree = ""; }; 08DC620C2CF8AE16002A2F44 /* ImageBannerSectionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageBannerSectionCell.swift; sourceTree = ""; }; 08DC62102CF8B446002A2F44 /* SortedRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortedRequestDTO.swift; sourceTree = ""; }; - 08DC62122CF8B833002A2F44 /* Optional+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+.swift"; sourceTree = ""; }; 08DE8A0C2D5236840049BCAC /* PutCommentRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PutCommentRequestDTO.swift; sourceTree = ""; }; 08DE8A0F2D5255110049BCAC /* DetailEmptyCommetSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailEmptyCommetSection.swift; sourceTree = ""; }; 08DE8A112D5255180049BCAC /* DetailEmptyCommetSectionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailEmptyCommetSectionCell.swift; sourceTree = ""; }; @@ -2325,7 +2323,6 @@ 08B191362CF366670057BC04 /* UITableViewCell+.swift */, 08B1913A2CF366A00057BC04 /* UIApplication+.swift */, 08B191772CF442230057BC04 /* UIImage+.swift */, - 08DC62122CF8B833002A2F44 /* Optional+.swift */, 0841BAAB2CFA35F300049E31 /* UILabel+.swift */, 0841BABD2CFB5AA600049E31 /* Date?+.swift */, 086DD8C72CFDEA9200B97D3B /* UIView+.swift */, @@ -3332,7 +3329,6 @@ 0899524D2D033AA70022AEF9 /* GetOpenPopUpListResponseDTO.swift in Sources */, 081898962D2965C90067BF01 /* ProfileEditView.swift in Sources */, 083C86602D0EC496003F441C /* InstaCommentAddView.swift in Sources */, - 08DC62132CF8B833002A2F44 /* Optional+.swift in Sources */, 081898F02D33A3A30067BF01 /* MyCommentSortedModalReactor.swift in Sources */, 081898E42D3391550067BF01 /* GetMyCommentedPopUpResponseDTO.swift in Sources */, 088DE25D2D145E3A0030FA9E /* DetailSimilarSection.swift in Sources */, diff --git a/Poppool/Poppool/Data/Network/UserAPI/ResponseDTO/GetMyProfileResponseDTO.swift b/Poppool/Poppool/Data/Network/UserAPI/ResponseDTO/GetMyProfileResponseDTO.swift index 02b18df9..5704cf9b 100644 --- a/Poppool/Poppool/Data/Network/UserAPI/ResponseDTO/GetMyProfileResponseDTO.swift +++ b/Poppool/Poppool/Data/Network/UserAPI/ResponseDTO/GetMyProfileResponseDTO.swift @@ -20,6 +20,15 @@ struct GetMyProfileResponseDTO: Decodable { extension GetMyProfileResponseDTO { func toDomain() -> GetMyProfileResponse { - return .init(profileImageUrl: profileImageUrl, nickname: nickname, email: email, instagramId: instagramId, intro: intro, gender: gender, age: age, interestCategoryList: interestCategoryList.map { $0.toDomain() }) + return .init( + profileImageUrl: profileImageUrl, + nickname: nickname, + email: email, + instagramId: instagramId, + intro: intro, + gender: gender, + age: age, + interestCategoryList: interestCategoryList.map { $0.toDomain() } + ) } } diff --git a/Poppool/Poppool/Infrastructure/NetworkLayer/Provider/ProviderImpl.swift b/Poppool/Poppool/Infrastructure/NetworkLayer/Provider/ProviderImpl.swift index 643a0e1f..a7fa9517 100644 --- a/Poppool/Poppool/Infrastructure/NetworkLayer/Provider/ProviderImpl.swift +++ b/Poppool/Poppool/Infrastructure/NetworkLayer/Provider/ProviderImpl.swift @@ -1,10 +1,3 @@ -// -// ProviderImpl.swift -// MomsVillage -// -// Created by SeoJunYoung on 8/16/24. -// - import Alamofire import Foundation import RxSwift @@ -50,9 +43,13 @@ final class ProviderImpl: Provider { case .success(let data): // 빈 응답 처리 if R.self == EmptyResponse.self && data.isEmpty { - observer.onNext(EmptyResponse() as! R) - observer.onCompleted() - return + if let response = EmptyResponse() as? R { + observer.onNext(response) + observer.onCompleted() + return + } else { + observer.onError(NetworkError.decodeError) + } } do { // JSON 디코딩 diff --git a/Poppool/Poppool/Presentation/Admin/AdminBottomSheet/AdminBottomSheetView.swift b/Poppool/Poppool/Presentation/Admin/AdminBottomSheet/AdminBottomSheetView.swift index 88d37c32..b23047dc 100644 --- a/Poppool/Poppool/Presentation/Admin/AdminBottomSheet/AdminBottomSheetView.swift +++ b/Poppool/Poppool/Presentation/Admin/AdminBottomSheet/AdminBottomSheetView.swift @@ -93,7 +93,7 @@ final class AdminBottomSheetView: UIView { let button = PPButton( style: .secondary, text: "초기화", - font: .KorFont(style: .medium, size: 16), + font: .korFont(style: .medium, size: 16), cornerRadius: 4 ) button.isEnabled = false @@ -111,7 +111,7 @@ final class AdminBottomSheetView: UIView { style: .primary, text: "옵션저장", disabledText: "옵션저장", - font: .KorFont(style: .medium, size: 16), + font: .korFont(style: .medium, size: 16), cornerRadius: 4 ) button.isEnabled = false diff --git a/Poppool/Poppool/Presentation/Components/PPButton.swift b/Poppool/Poppool/Presentation/Components/PPButton.swift index 5d446403..5d99f435 100644 --- a/Poppool/Poppool/Presentation/Components/PPButton.swift +++ b/Poppool/Poppool/Presentation/Components/PPButton.swift @@ -73,7 +73,7 @@ class PPButton: UIButton { style: ButtonStyle, text: String, disabledText: String = "", - font: UIFont? = .KorFont(style: .medium, size: 16), + font: UIFont? = .korFont(style: .medium, size: 16), cornerRadius: CGFloat = 4 ) { super.init(frame: .zero) diff --git a/Poppool/Poppool/Presentation/Components/PPCancelHeaderView.swift b/Poppool/Poppool/Presentation/Components/PPCancelHeaderView.swift index eac352f6..4e1bd069 100644 --- a/Poppool/Poppool/Presentation/Components/PPCancelHeaderView.swift +++ b/Poppool/Poppool/Presentation/Components/PPCancelHeaderView.swift @@ -22,7 +22,7 @@ final class PPCancelHeaderView: UIView { let cancelButton: UIButton = { let button = UIButton(type: .system) button.setTitle("취소", for: .normal) - button.titleLabel?.font = .KorFont(style: .regular, size: 14) + button.titleLabel?.font = .korFont(style: .regular, size: 14) button.setTitleColor(.black, for: .normal) return button }() diff --git a/Poppool/Poppool/Presentation/Components/PPLabel.swift b/Poppool/Poppool/Presentation/Components/PPLabel.swift index 25f16a0d..614638ac 100644 --- a/Poppool/Poppool/Presentation/Components/PPLabel.swift +++ b/Poppool/Poppool/Presentation/Components/PPLabel.swift @@ -16,7 +16,7 @@ class PPLabel: UILabel { lineHeight: CGFloat = 1.2 ) { super.init(frame: .zero) - self.font = .KorFont(style: style, size: fontSize) + self.font = .korFont(style: style, size: fontSize) let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineHeightMultiple = lineHeight self.attributedText = NSMutableAttributedString( diff --git a/Poppool/Poppool/Presentation/Components/PPPicker.swift b/Poppool/Poppool/Presentation/Components/PPPicker.swift index 82fa75fc..acaa9456 100644 --- a/Poppool/Poppool/Presentation/Components/PPPicker.swift +++ b/Poppool/Poppool/Presentation/Components/PPPicker.swift @@ -97,10 +97,10 @@ extension PPPicker: UIPickerViewDelegate, UIPickerViewDataSource { let label = UILabel() label.text = components[row] label.textAlignment = .center - label.font = .KorFont(style: .medium, size: 16) + label.font = .korFont(style: .medium, size: 16) DispatchQueue.main.async { if let label = pickerView.view(forRow: row, forComponent: component) as? UILabel { - label.font = .KorFont(style: .bold, size: 18) + label.font = .korFont(style: .bold, size: 18) } } pickerView.subviews[1].isHidden = true diff --git a/Poppool/Poppool/Presentation/Components/PPReturnHeaderView.swift b/Poppool/Poppool/Presentation/Components/PPReturnHeaderView.swift index eb91144d..f8589886 100644 --- a/Poppool/Poppool/Presentation/Components/PPReturnHeaderView.swift +++ b/Poppool/Poppool/Presentation/Components/PPReturnHeaderView.swift @@ -20,7 +20,7 @@ final class PPReturnHeaderView: UIView { let headerLabel: UILabel = { let label = UILabel() - label.font = .KorFont(style: .regular, size: 15) + label.font = .korFont(style: .regular, size: 15) label.textColor = .g1000 return label }() diff --git a/Poppool/Poppool/Presentation/Components/PPSegmentedControl.swift b/Poppool/Poppool/Presentation/Components/PPSegmentedControl.swift index 7566851e..e9cde785 100644 --- a/Poppool/Poppool/Presentation/Components/PPSegmentedControl.swift +++ b/Poppool/Poppool/Presentation/Components/PPSegmentedControl.swift @@ -85,8 +85,8 @@ private extension PPSegmentedControl { } } self.selectedSegmentTintColor = .blu500 - setFont(color: .w100, font: .KorFont(style: .bold, size: 15), state: .selected) - setFont(color: .g400, font: .KorFont(style: .medium, size: 15), state: .normal) + setFont(color: .w100, font: .korFont(style: .bold, size: 15), state: .selected) + setFont(color: .g400, font: .korFont(style: .medium, size: 15), state: .normal) case .base: // background color 변경이 g50값으로 변경이 되지 않아 subview에 접근하여 layer를 Hidden처리 하고 새로운 뷰를 덮어씌워서 색상을 적용 for (index, view) in self.subviews.enumerated() { @@ -100,8 +100,8 @@ private extension PPSegmentedControl { } } self.selectedSegmentTintColor = .blu500 - setFont(color: .w100, font: .KorFont(style: .bold, size: 15), state: .selected) - setFont(color: .g400, font: .KorFont(style: .medium, size: 14), state: .normal) + setFont(color: .w100, font: .korFont(style: .bold, size: 15), state: .selected) + setFont(color: .g400, font: .korFont(style: .medium, size: 14), state: .normal) case .tab: self.clipsToBounds = false self.setBackgroundImage(emptyImage, for: .normal, barMetrics: .default) @@ -118,8 +118,8 @@ private extension PPSegmentedControl { make.height.equalTo(2) make.bottom.equalTo(bottomLineView.snp.bottom) } - setFont(color: .blu500, font: .KorFont(style: .bold, size: 15), state: .selected) - setFont(color: .g400, font: .KorFont(style: .medium, size: 15), state: .normal) + setFont(color: .blu500, font: .korFont(style: .bold, size: 15), state: .selected) + setFont(color: .g400, font: .korFont(style: .medium, size: 15), state: .normal) } } diff --git a/Poppool/Poppool/Presentation/Extension/Optional+.swift b/Poppool/Poppool/Presentation/Extension/Optional+.swift deleted file mode 100644 index 9cb9f785..00000000 --- a/Poppool/Poppool/Presentation/Extension/Optional+.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// Optional+.swift -// Poppool -// -// Created by SeoJunYoung on 11/28/24. -// - -import Foundation - -extension Optional where Wrapped: Collection { - var orEmpty: Wrapped { - return self ?? [] as! Wrapped - } -} diff --git a/Poppool/Poppool/Presentation/Extension/UIFont+.swift b/Poppool/Poppool/Presentation/Extension/UIFont+.swift index 098bc0da..75bdb25b 100644 --- a/Poppool/Poppool/Presentation/Extension/UIFont+.swift +++ b/Poppool/Poppool/Presentation/Extension/UIFont+.swift @@ -1,20 +1,12 @@ -// -// UIFont+.swift -// PopPool -// -// Created by Porori on 6/20/24. -// - import Foundation import UIKit extension UIFont { - - static func KorFont(style: FontStyle, size: CGFloat) -> UIFont? { + static func korFont(style: FontStyle, size: CGFloat) -> UIFont? { return UIFont(name: "GothicA1\(style.rawValue)", size: size) } - static func EngFont(style: FontStyle, size: CGFloat) -> UIFont? { + static func engFont(style: FontStyle, size: CGFloat) -> UIFont? { return UIFont(name: "Poppins\(style.rawValue)", size: size) } diff --git a/Poppool/Poppool/Presentation/Map/Common/LocationPermissionBottomSheet.swift b/Poppool/Poppool/Presentation/Map/Common/LocationPermissionBottomSheet.swift index 4362eb92..1b5d50da 100644 --- a/Poppool/Poppool/Presentation/Map/Common/LocationPermissionBottomSheet.swift +++ b/Poppool/Poppool/Presentation/Map/Common/LocationPermissionBottomSheet.swift @@ -10,7 +10,7 @@ final class LocationPermissionBottomSheet: UIViewController { private let titleLabel: UILabel = { let label = UILabel() label.text = "내 위치를 중심으로\n보기 위한 준비가 필요해요" - label.font = UIFont.KorFont(style: .bold, size: 18) + label.font = UIFont.korFont(style: .bold, size: 18) label.textColor = .g1000 label.numberOfLines = 2 label.textAlignment = .center @@ -21,7 +21,7 @@ final class LocationPermissionBottomSheet: UIViewController { private let descriptionLabel: UILabel = { let label = UILabel() label.text = "설정 > 위치 권한을 허용하신 후에\n내 주변의 다양한 팝업스토어를 볼 수 있어요." - label.font = UIFont.KorFont(style: .regular, size: 14) + label.font = UIFont.korFont(style: .regular, size: 14) label.textColor = .g600 label.numberOfLines = 2 label.textAlignment = .center @@ -33,7 +33,7 @@ final class LocationPermissionBottomSheet: UIViewController { let button = UIButton() button.setTitle("취소", for: .normal) button.setTitleColor(.g600, for: .normal) - button.titleLabel?.font = UIFont.KorFont(style: .medium, size: 16) + button.titleLabel?.font = UIFont.korFont(style: .medium, size: 16) button.backgroundColor = .g50 button.layer.cornerRadius = 10 return button @@ -44,7 +44,7 @@ final class LocationPermissionBottomSheet: UIViewController { let button = UIButton() button.setTitle("권한설정", for: .normal) button.setTitleColor(.white, for: .normal) - button.titleLabel?.font = UIFont.KorFont(style: .medium, size: 16) + button.titleLabel?.font = UIFont.korFont(style: .medium, size: 16) button.backgroundColor = .blu500 button.layer.cornerRadius = 10 return button diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonChipCell.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonChipCell.swift index 40f33be0..964e5995 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonChipCell.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/BalloonChipCell.swift @@ -8,7 +8,7 @@ final class BalloonChipCell: UICollectionViewCell { let button = PPButton( style: .secondary, text: "", - font: .KorFont(style: .medium, size: 11), + font: .korFont(style: .medium, size: 11), cornerRadius: 15 ) @@ -45,7 +45,7 @@ final class BalloonChipCell: UICollectionViewCell { button.setBackgroundColor(.blu500, for: .normal) button.setTitleColor(.white, for: .normal) button.layer.borderWidth = 0 - button.titleLabel?.font = .KorFont(style: .bold, size: 11) + button.titleLabel?.font = .korFont(style: .bold, size: 11) } else { button.setImage(nil, for: .normal) @@ -56,7 +56,7 @@ final class BalloonChipCell: UICollectionViewCell { button.setTitleColor(.g400, for: .normal) button.layer.borderWidth = 1 button.layer.borderColor = UIColor.g200.cgColor - button.titleLabel?.font = .KorFont(style: .medium, size: 11) + button.titleLabel?.font = .korFont(style: .medium, size: 11) } } diff --git a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift index c9a288cb..b7414a4b 100644 --- a/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift +++ b/Poppool/Poppool/Presentation/Map/FillterSheetView/FilterBottomSheetView.swift @@ -305,7 +305,7 @@ final class FilterBottomSheetView: UIView { let button = PPButton( style: .secondary, text: title, - font: .KorFont(style: .medium, size: 13), + font: .korFont(style: .medium, size: 13), cornerRadius: 18 ) button.setBackgroundColor(.w100, for: .normal) @@ -331,12 +331,12 @@ final class FilterBottomSheetView: UIView { button.setBackgroundColor(.blu500, for: .normal) button.setTitleColor(.w100, for: .normal) button.layer.borderWidth = 0 - button.titleLabel?.font = .KorFont(style: .bold, size: 13) + button.titleLabel?.font = .korFont(style: .bold, size: 13) } else { button.setBackgroundColor(.w100, for: .normal) button.setTitleColor(.g400, for: .normal) button.layer.borderColor = UIColor.g200.cgColor - button.titleLabel?.font = .KorFont(style: .medium, size: 13) + button.titleLabel?.font = .korFont(style: .medium, size: 13) button.layer.borderWidth = 1 } } diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/CommentDetailController.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/CommentDetailController.swift index 4196ffe8..49d47a25 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/CommentDetailController.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/CommentDetailController.swift @@ -92,7 +92,7 @@ extension CommentDetailController { owner.mainView.profileView.dateLabel.text = state.commentData.date owner.mainView.profileView.nickNameLabel.text = state.commentData.nickName owner.mainView.profileView.profileImageView.setPPImage(path: state.commentData.profileImagePath) - owner.mainView.likeButtonTitleLabel.setLineHeightText(text: "도움돼요 \(state.commentData.likeCount)", font: .KorFont(style: .medium, size: 13)) + owner.mainView.likeButtonTitleLabel.setLineHeightText(text: "도움돼요 \(state.commentData.likeCount)", font: .korFont(style: .medium, size: 13)) if state.commentData.isLike { owner.mainView.likeButtonImageView.image = UIImage(named: "icon_like_blue") owner.mainView.likeButtonTitleLabel.textColor = .blu500 diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/View/CommentDetailContentSection/CommentDetailContentSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/View/CommentDetailContentSection/CommentDetailContentSectionCell.swift index 0d814d0a..43237524 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/View/CommentDetailContentSection/CommentDetailContentSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentDetail/View/CommentDetailContentSection/CommentDetailContentSectionCell.swift @@ -49,6 +49,6 @@ extension CommentDetailContentSectionCell: Inputable { } func injection(with input: Input) { - contentLabel.setLineHeightText(text: input.content, font: .KorFont(style: .medium, size: 13)) + contentLabel.setLineHeightText(text: input.content, font: .korFont(style: .medium, size: 13)) } } diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentMyMenu/CommentMyMenuView.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentMyMenu/CommentMyMenuView.swift index 93153e71..a6034782 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentMyMenu/CommentMyMenuView.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentMyMenu/CommentMyMenuView.swift @@ -14,7 +14,7 @@ final class CommentMyMenuView: UIView { // MARK: - Components let titleLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 18) - label.setLineHeightText(text: "내가 작성한 코멘트", font: .KorFont(style: .bold, size: 18)) + label.setLineHeightText(text: "내가 작성한 코멘트", font: .korFont(style: .bold, size: 18)) return label }() @@ -28,7 +28,7 @@ final class CommentMyMenuView: UIView { let button = UIButton() button.setTitle("코멘트 삭제하기", for: .normal) button.setTitleColor(.g1000, for: .normal) - button.titleLabel?.font = .KorFont(style: .medium, size: 15) + button.titleLabel?.font = .korFont(style: .medium, size: 15) button.contentHorizontalAlignment = .leading return button }() @@ -43,7 +43,7 @@ final class CommentMyMenuView: UIView { let button = UIButton() button.setTitle("코멘트 수정하기", for: .normal) button.setTitleColor(.g1000, for: .normal) - button.titleLabel?.font = .KorFont(style: .medium, size: 15) + button.titleLabel?.font = .korFont(style: .medium, size: 15) button.contentHorizontalAlignment = .leading return button }() diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentUserInfo/CommentUserInfoController.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentUserInfo/CommentUserInfoController.swift index 190de3ae..41bbe402 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentUserInfo/CommentUserInfoController.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentUserInfo/CommentUserInfoController.swift @@ -70,7 +70,7 @@ extension CommentUserInfoController { .subscribe { (owner, state) in owner.mainView.titleLabel.setLineHeightText( text: "\(state.nickName ?? "")님에 대해 더 알아보기", - font: .KorFont(style: .bold, size: 18) + font: .korFont(style: .bold, size: 18) ) } .disposed(by: disposeBag) diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentUserInfo/CommentUserInfoView.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentUserInfo/CommentUserInfoView.swift index 0ee09c62..f3a6927e 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentUserInfo/CommentUserInfoView.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentInfoMenu/CommentUserInfo/CommentUserInfoView.swift @@ -14,7 +14,7 @@ final class CommentUserInfoView: UIView { // MARK: - Components let titleLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 18) - label.setLineHeightText(text: "님에 대해 더 알아보기", font: .KorFont(style: .bold, size: 18)) + label.setLineHeightText(text: "님에 대해 더 알아보기", font: .korFont(style: .bold, size: 18)) return label }() @@ -28,7 +28,7 @@ final class CommentUserInfoView: UIView { let button = UIButton() button.setTitle("코멘트를 작성한 팝업 모두보기", for: .normal) button.setTitleColor(.g1000, for: .normal) - button.titleLabel?.font = .KorFont(style: .medium, size: 15) + button.titleLabel?.font = .korFont(style: .medium, size: 15) button.contentHorizontalAlignment = .leading return button }() @@ -43,7 +43,7 @@ final class CommentUserInfoView: UIView { let button = UIButton() button.setTitle("이 유저 차단하기", for: .normal) button.setTitleColor(.g1000, for: .normal) - button.titleLabel?.font = .KorFont(style: .medium, size: 15) + button.titleLabel?.font = .korFont(style: .medium, size: 15) button.contentHorizontalAlignment = .leading return button }() diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentList/View/CommentListTitleSection/CommentListTitleSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentList/View/CommentListTitleSection/CommentListTitleSectionCell.swift index f8b33c01..da43fc9a 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentList/View/CommentListTitleSection/CommentListTitleSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentList/View/CommentListTitleSection/CommentListTitleSectionCell.swift @@ -49,6 +49,6 @@ extension CommentListTitleSectionCell: Inputable { } func injection(with input: Input) { - countLabel.setLineHeightText(text: "총 \(input.count)\(input.unit)", font: .KorFont(style: .regular, size: 13)) + countLabel.setLineHeightText(text: "총 \(input.count)\(input.unit)", font: .korFont(style: .regular, size: 13)) } } diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentSelected/CommentSelectedView.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentSelected/CommentSelectedView.swift index 7c41e5bd..e77d939b 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentSelected/CommentSelectedView.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentSelected/CommentSelectedView.swift @@ -14,7 +14,7 @@ final class CommentSelectedView: UIView { // MARK: - Components private let titleLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 18) - label.setLineHeightText(text: "코멘트 작성 방법 선택", font: .KorFont(style: .bold, size: 18)) + label.setLineHeightText(text: "코멘트 작성 방법 선택", font: .korFont(style: .bold, size: 18)) return label }() @@ -28,7 +28,7 @@ final class CommentSelectedView: UIView { let button = UIButton() button.setTitle("일반 코멘트 작성하기", for: .normal) button.setTitleColor(.g1000, for: .normal) - button.titleLabel?.font = .KorFont(style: .medium, size: 15) + button.titleLabel?.font = .korFont(style: .medium, size: 15) button.contentHorizontalAlignment = .leading return button }() @@ -43,7 +43,7 @@ final class CommentSelectedView: UIView { let button = UIButton() button.setTitle("인스타그램 연동 코멘트 작성하기", for: .normal) button.setTitleColor(.g1000, for: .normal) - button.titleLabel?.font = .KorFont(style: .medium, size: 15) + button.titleLabel?.font = .korFont(style: .medium, size: 15) button.contentHorizontalAlignment = .leading return button }() diff --git a/Poppool/Poppool/Presentation/Scene/Comment/CommentUserBlock/CommentUserBlockController.swift b/Poppool/Poppool/Presentation/Scene/Comment/CommentUserBlock/CommentUserBlockController.swift index 1c4871af..75166c4d 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/CommentUserBlock/CommentUserBlockController.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/CommentUserBlock/CommentUserBlockController.swift @@ -59,7 +59,7 @@ extension CommentUserBlockController { .subscribe { (owner, state) in owner.mainView.titleLabel.setLineHeightText( text: "\(state.nickName ?? "")님을 차단할까요?", - font: .KorFont(style: .bold, size: 18) + font: .korFont(style: .bold, size: 18) ) } .disposed(by: disposeBag) diff --git a/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/InstaCommentAddReactor.swift b/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/InstaCommentAddReactor.swift index e05674c3..c9d0e127 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/InstaCommentAddReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/InstaCommentAddReactor.swift @@ -59,7 +59,7 @@ final class InstaCommentAddReactor: Reactor { { let title = "아래 인스타그램 열기\n버튼을 터치해 앱 열기" let attributedTitle = NSMutableAttributedString(string: title) - let koreanFont = UIFont.KorFont(style: .bold, size: 20)! + let koreanFont = UIFont.korFont(style: .bold, size: 20)! attributedTitle.addAttribute(.font, value: koreanFont, range: NSRange(location: 0, length: title.count)) attributedTitle.addAttribute(.foregroundColor, value: UIColor.blu500.cgColor, range: (title as NSString).range(of: "인스타그램 열기")) let paragraphStyle = NSMutableParagraphStyle() @@ -70,7 +70,7 @@ final class InstaCommentAddReactor: Reactor { { let title = "원하는 피드의 이미지로 이동 후\n공유하기 > 링크복사 터치하기" let attributedTitle = NSMutableAttributedString(string: title) - let koreanFont = UIFont.KorFont(style: .bold, size: 20)! + let koreanFont = UIFont.korFont(style: .bold, size: 20)! attributedTitle.addAttribute(.font, value: koreanFont, range: NSRange(location: 0, length: title.count)) attributedTitle.addAttribute(.foregroundColor, value: UIColor.blu500.cgColor, range: (title as NSString).range(of: "공유하기 > 링크복사")) let paragraphStyle = NSMutableParagraphStyle() @@ -81,7 +81,7 @@ final class InstaCommentAddReactor: Reactor { { let title = "아래 이미지 영역을 터치해\n팝풀 앱으로 돌아오기" let attributedTitle = NSMutableAttributedString(string: title) - let koreanFont = UIFont.KorFont(style: .bold, size: 20)! + let koreanFont = UIFont.korFont(style: .bold, size: 20)! attributedTitle.addAttribute(.font, value: koreanFont, range: NSRange(location: 0, length: title.count)) attributedTitle.addAttribute(.foregroundColor, value: UIColor.blu500.cgColor, range: (title as NSString).range(of: "팝풀 앱")) let paragraphStyle = NSMutableParagraphStyle() @@ -92,7 +92,7 @@ final class InstaCommentAddReactor: Reactor { { let title = "복사된 인스타 피드 이미지와\n함께할 글을 입력 후 등록하기" let attributedTitle = NSMutableAttributedString(string: title) - let koreanFont = UIFont.KorFont(style: .bold, size: 20)! + let koreanFont = UIFont.korFont(style: .bold, size: 20)! attributedTitle.addAttribute(.font, value: koreanFont, range: NSRange(location: 0, length: title.count)) attributedTitle.addAttribute(.foregroundColor, value: UIColor.blu500.cgColor, range: (title as NSString).range(of: "글을 입력 후 등록")) let paragraphStyle = NSMutableParagraphStyle() diff --git a/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaCommentAddView.swift b/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaCommentAddView.swift index 9e7fc499..c7c9faa0 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaCommentAddView.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaCommentAddView.swift @@ -14,7 +14,7 @@ final class InstaCommentAddView: UIView { // MARK: - Components let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() - view.headerLabel.setLineHeightText(text: "코멘트 작성하기", font: .KorFont(style: .regular, size: 15)) + view.headerLabel.setLineHeightText(text: "코멘트 작성하기", font: .korFont(style: .regular, size: 15)) return view }() @@ -27,10 +27,10 @@ final class InstaCommentAddView: UIView { let title = "Instagram 열기" let attributedTitle = NSMutableAttributedString(string: title) - let englishFont = UIFont.EngFont(style: .medium, size: 15)! + let englishFont = UIFont.engFont(style: .medium, size: 15)! attributedTitle.addAttribute(.font, value: englishFont, range: (title as NSString).range(of: "Instagram")) - let koreanFont = UIFont.KorFont(style: .medium, size: 15)! + let koreanFont = UIFont.korFont(style: .medium, size: 15)! attributedTitle.addAttribute(.font, value: koreanFont, range: (title as NSString).range(of: "열기")) button.setAttributedTitle(attributedTitle, for: .normal) diff --git a/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaGuideSection/InstaGuideChildSection/InstaGuideChildSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaGuideSection/InstaGuideChildSection/InstaGuideChildSectionCell.swift index 508c459f..5a4bff39 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaGuideSection/InstaGuideChildSection/InstaGuideChildSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/InstaComment/View/InstaGuideSection/InstaGuideChildSection/InstaGuideChildSectionCell.swift @@ -26,7 +26,7 @@ final class InstaGuideChildSectionCell: UICollectionViewCell { private let indexLabel: UILabel = { let label = UILabel() - label.font = .EngFont(style: .medium, size: 16) + label.font = .engFont(style: .medium, size: 16) label.textColor = .w100 label.textAlignment = .center return label diff --git a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentDescriptionSection/AddCommentDescriptionSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentDescriptionSection/AddCommentDescriptionSectionCell.swift index 910cf5f2..dd8cd85f 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentDescriptionSection/AddCommentDescriptionSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentDescriptionSection/AddCommentDescriptionSectionCell.swift @@ -50,6 +50,6 @@ extension AddCommentDescriptionSectionCell: Inputable { } func injection(with input: Input) { - descriptionLabel.setLineHeightText(text: input.description, font: .KorFont(style: .regular, size: 13)) + descriptionLabel.setLineHeightText(text: input.description, font: .korFont(style: .regular, size: 13)) } } diff --git a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentSection/AddCommentSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentSection/AddCommentSectionCell.swift index b264381b..b81b98f3 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentSection/AddCommentSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentSection/AddCommentSectionCell.swift @@ -21,7 +21,7 @@ final class AddCommentSectionCell: UICollectionViewCell { let view = UITextView() view.textContainerInset = .zero view.textContainer.lineFragmentPadding = 0 - view.font = .KorFont(style: .medium, size: 14) + view.font = .korFont(style: .medium, size: 14) return view }() diff --git a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentTitleSection/AddCommentTitleSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentTitleSection/AddCommentTitleSectionCell.swift index c71411bf..b5847ee0 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentTitleSection/AddCommentTitleSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/AddCommentTitleSection/AddCommentTitleSectionCell.swift @@ -47,6 +47,6 @@ extension AddCommentTitleSectionCell: Inputable { } func injection(with input: Input) { - titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .bold, size: 16)) + titleLabel.setLineHeightText(text: input.title, font: .korFont(style: .bold, size: 16)) } } diff --git a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/NormalCommentAddView.swift b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/NormalCommentAddView.swift index 1913249a..819e9200 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/NormalCommentAddView.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentAdd/View/NormalCommentAddView.swift @@ -15,7 +15,7 @@ final class NormalCommentAddView: UIView { let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() - view.headerLabel.setLineHeightText(text: "코멘트 작성하기", font: .KorFont(style: .regular, size: 15)) + view.headerLabel.setLineHeightText(text: "코멘트 작성하기", font: .korFont(style: .regular, size: 15)) return view }() diff --git a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentEdit/NormalCommentEditView.swift b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentEdit/NormalCommentEditView.swift index b3d9d1b2..ee06753a 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentEdit/NormalCommentEditView.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/NormalCommentEdit/NormalCommentEditView.swift @@ -15,7 +15,7 @@ final class NormalCommentEditView: UIView { let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() - view.headerLabel.setLineHeightText(text: "코멘트 수정하기", font: .KorFont(style: .regular, size: 15)) + view.headerLabel.setLineHeightText(text: "코멘트 수정하기", font: .korFont(style: .regular, size: 15)) return view }() diff --git a/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/View/OtherUserCommentSection/OtherUserCommentSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/View/OtherUserCommentSection/OtherUserCommentSectionCell.swift index b086e771..7f1a0770 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/View/OtherUserCommentSection/OtherUserCommentSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/View/OtherUserCommentSection/OtherUserCommentSectionCell.swift @@ -121,9 +121,9 @@ extension OtherUserCommentSectionCell: Inputable { func injection(with input: Input) { imageView.setPPImage(path: input.imagePath) - titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .bold, size: 11)) - contentLabel.setLineHeightText(text: input.comment, font: .KorFont(style: .medium, size: 11)) - dateLabel.setLineHeightText(text: input.date, font: .KorFont(style: .regular, size: 11)) - likeCountLabel.setLineHeightText(text: "\(input.likeCount)", font: .KorFont(style: .regular, size: 11)) + titleLabel.setLineHeightText(text: input.title, font: .korFont(style: .bold, size: 11)) + contentLabel.setLineHeightText(text: input.comment, font: .korFont(style: .medium, size: 11)) + dateLabel.setLineHeightText(text: input.date, font: .korFont(style: .regular, size: 11)) + likeCountLabel.setLineHeightText(text: "\(input.likeCount)", font: .korFont(style: .regular, size: 11)) } } diff --git a/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/View/OtherUserCommentView.swift b/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/View/OtherUserCommentView.swift index 89715fd4..0cd89426 100644 --- a/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/View/OtherUserCommentView.swift +++ b/Poppool/Poppool/Presentation/Scene/Comment/OtherUserComment/View/OtherUserCommentView.swift @@ -14,7 +14,7 @@ final class OtherUserCommentView: UIView { // MARK: - Components let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() - view.headerLabel.setLineHeightText(text: "코멘트 작성 팝업", font: .KorFont(style: .regular, size: 15)) + view.headerLabel.setLineHeightText(text: "코멘트 작성 팝업", font: .korFont(style: .regular, size: 15)) return view }() diff --git a/Poppool/Poppool/Presentation/Scene/Detail/DetailController.swift b/Poppool/Poppool/Presentation/Scene/Detail/DetailController.swift index d6f3535e..a8a17fff 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/DetailController.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/DetailController.swift @@ -204,12 +204,12 @@ extension DetailController: UICollectionViewDelegate, UICollectionViewDataSource .subscribe { (collectionView, _) in cell.isOpen.toggle() if cell.isOpen { - cell.buttonTitleLabel.setLineHeightText(text: "닫기", font: .KorFont(style: .medium, size: 13)) + cell.buttonTitleLabel.setLineHeightText(text: "닫기", font: .korFont(style: .medium, size: 13)) cell.contentLabel.numberOfLines = 0 cell.buttonImageView.image = UIImage(named: "icon_dropdown_top_gray") } else { cell.contentLabel.numberOfLines = 3 - cell.buttonTitleLabel.setLineHeightText(text: "더보기", font: .KorFont(style: .medium, size: 13)) + cell.buttonTitleLabel.setLineHeightText(text: "더보기", font: .korFont(style: .medium, size: 13)) cell.buttonImageView.image = UIImage(named: "icon_dropdown_bottom_gray") } collectionView.reloadData() diff --git a/Poppool/Poppool/Presentation/Scene/Detail/DetailReactor.swift b/Poppool/Poppool/Presentation/Scene/Detail/DetailReactor.swift index 74097657..964cdb5f 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/DetailReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/DetailReactor.swift @@ -1,10 +1,3 @@ -// -// DetailReactor.swift -// Poppool -// -// Created by SeoJunYoung on 12/9/24. -// - import UIKit import LinkPresentation @@ -26,7 +19,7 @@ final class DetailReactor: Reactor { case commentMenuButtonTapped(controller: BaseViewController, indexPath: IndexPath) case commentDetailButtonTapped(controller: BaseViewController, indexPath: IndexPath) case commentLikeButtonTapped(indexPath: IndexPath) - case commentImageTapped(controller: BaseViewController, cellRow: Int, ImageRow: Int) + case commentImageTapped(controller: BaseViewController, cellRow: Int, imageRow: Int) case similarSectionTapped(controller: BaseViewController, indexPath: IndexPath) case backButtonTapped(controller: BaseViewController) case loginButtonTapped(controller: BaseViewController) @@ -44,7 +37,7 @@ final class DetailReactor: Reactor { case moveToDetailScene(controller: BaseViewController, indexPath: IndexPath) case moveToRecentScene(controller: BaseViewController) case moveToLoginScene(controller: BaseViewController) - case moveToImageDetailScene(controller: BaseViewController, cellRow: Int, ImageRow: Int) + case moveToImageDetailScene(controller: BaseViewController, cellRow: Int, imageRow: Int) } private var commentButtonIsEnable: Bool = false @@ -135,8 +128,8 @@ final class DetailReactor: Reactor { return Observable.just(.moveToRecentScene(controller: controller)) case .loginButtonTapped(let controller): return Observable.just(.moveToLoginScene(controller: controller)) - case .commentImageTapped(let controller, let cellRow, let ImageRow): - return Observable.just(.moveToImageDetailScene(controller: controller, cellRow: cellRow, ImageRow: ImageRow)) + case .commentImageTapped(let controller, let cellRow, let imageRow): + return Observable.just(.moveToImageDetailScene(controller: controller, cellRow: cellRow, imageRow: imageRow)) } } @@ -212,8 +205,8 @@ final class DetailReactor: Reactor { let nextController = UINavigationController(rootViewController: loginController) nextController.modalPresentationStyle = .fullScreen controller.present(nextController, animated: true) - case .moveToImageDetailScene(let controller, let cellRow, let ImageRow): - let imagePath = commentSection.inputDataList[cellRow].imageList[ImageRow] + case .moveToImageDetailScene(let controller, let cellRow, let imageRow): + let imagePath = commentSection.inputDataList[cellRow].imageList[imageRow] let nextController = ImageDetailController() nextController.reactor = ImageDetailReactor(imagePath: imagePath) nextController.modalPresentationStyle = .overCurrentContext @@ -221,7 +214,10 @@ final class DetailReactor: Reactor { } return newState } +} +// MARK: Section function +extension DetailReactor { func getSection() -> [any Sectionable] { if similarSection.inputDataList.isEmpty { if commentSection.inputDataList.isEmpty { @@ -304,7 +300,6 @@ final class DetailReactor: Reactor { ] } } - } func setContent() -> Observable { diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentSection/DetailCommentSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentSection/DetailCommentSectionCell.swift index b7fceb6c..4251a119 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentSection/DetailCommentSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentSection/DetailCommentSectionCell.swift @@ -112,7 +112,7 @@ final class DetailCommentSectionCell: UICollectionViewCell { let loginButton: UIButton = { let button = UIButton() button.setTitle("로그인하고 후기보기", for: .normal) - button.titleLabel?.font = .KorFont(style: .medium, size: 13) + button.titleLabel?.font = .korFont(style: .medium, size: 13) button.setTitleColor(.w100, for: .normal) button.layer.cornerRadius = 4 button.backgroundColor = .blu500 @@ -282,10 +282,10 @@ extension DetailCommentSectionCell: Inputable { let comment = input.comment ?? "" profileView.profileImageView.setPPImage(path: input.profileImagePath) - profileView.nickNameLabel.setLineHeightText(text: input.nickName, font: .KorFont(style: .bold, size: 13)) - profileView.dateLabel.setLineHeightText(text: input.date, font: .KorFont(style: .regular, size: 12)) - contentLabel.setLineHeightText(text: input.comment, font: .KorFont(style: .regular, size: 13)) - likeButtonTitleLabel.setLineHeightText(text: "도움돼요 \(input.likeCount)", font: .KorFont(style: .regular, size: 13)) + profileView.nickNameLabel.setLineHeightText(text: input.nickName, font: .korFont(style: .bold, size: 13)) + profileView.dateLabel.setLineHeightText(text: input.date, font: .korFont(style: .regular, size: 12)) + contentLabel.setLineHeightText(text: input.comment, font: .korFont(style: .regular, size: 13)) + likeButtonTitleLabel.setLineHeightText(text: "도움돼요 \(input.likeCount)", font: .korFont(style: .regular, size: 13)) if input.isLike { likeButtonImageView.image = UIImage(named: "icon_like_blue") likeButtonTitleLabel.textColor = .blu500 @@ -326,20 +326,20 @@ extension DetailCommentSectionCell: Inputable { // 기본 스타일 (폰트, 색상 등) let normalAttributes: [NSAttributedString.Key: Any] = [ - .font: UIFont.KorFont(style: .regular, size: 14)!, + .font: UIFont.korFont(style: .regular, size: 14)!, .foregroundColor: UIColor.g1000, .paragraphStyle: paragraphStyle ] // 스타일을 다르게 할 부분 (팝업스토어명, 생생한 후기) let popupStoreAttributes: [NSAttributedString.Key: Any] = [ - .font: UIFont.KorFont(style: .bold, size: 14)!, // 다른 폰트 스타일 + .font: UIFont.korFont(style: .bold, size: 14)!, // 다른 폰트 스타일 .foregroundColor: UIColor.blu500, // 다른 색상 .paragraphStyle: paragraphStyle ] let reviewAttributes: [NSAttributedString.Key: Any] = [ - .font: UIFont.KorFont(style: .bold, size: 14)!, // 이탤릭체 + .font: UIFont.korFont(style: .bold, size: 14)!, // 이탤릭체 .foregroundColor: UIColor.g1000, // 다른 색상 .paragraphStyle: paragraphStyle ] diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentTitleSection/DetailCommentTitleSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentTitleSection/DetailCommentTitleSectionCell.swift index 26024838..f7c37325 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentTitleSection/DetailCommentTitleSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailCommentTitleSection/DetailCommentTitleSectionCell.swift @@ -28,7 +28,7 @@ final class DetailCommentTitleSectionCell: UICollectionViewCell { let attributedTitle = NSAttributedString( string: "전체보기", attributes: [ - .font: UIFont.KorFont(style: .regular, size: 13)!, // 커스텀 폰트 적용 + .font: UIFont.korFont(style: .regular, size: 13)!, // 커스텀 폰트 적용 .underlineStyle: NSUnderlineStyle.single.rawValue // 밑줄 스타일 ] ) @@ -82,7 +82,7 @@ extension DetailCommentTitleSectionCell: Inputable { } func injection(with input: Input) { - countLabel.setLineHeightText(text: "총 \(input.commentCount)개", font: .KorFont(style: .regular, size: 13)) + countLabel.setLineHeightText(text: "총 \(input.commentCount)개", font: .korFont(style: .regular, size: 13)) totalViewButton.isHidden = input.buttonIsHidden } } diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailContentSection/DetailContentSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailContentSection/DetailContentSectionCell.swift index e319d2ab..02e98e1c 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailContentSection/DetailContentSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailContentSection/DetailContentSectionCell.swift @@ -99,7 +99,7 @@ extension DetailContentSectionCell: Inputable { func injection(with input: Input) { let text = input.content ?? "" - contentLabel.setLineHeightText(text: text, font: .KorFont(style: .regular, size: 13)) + contentLabel.setLineHeightText(text: text, font: .korFont(style: .regular, size: 13)) if text.count >= 68 { dropDownButton.isHidden = false } else { diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailEmptyCommetSection/DetailEmptyCommetSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailEmptyCommetSection/DetailEmptyCommetSectionCell.swift index aa27bcbc..76e43892 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailEmptyCommetSection/DetailEmptyCommetSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailEmptyCommetSection/DetailEmptyCommetSectionCell.swift @@ -27,7 +27,7 @@ final class DetailEmptyCommetSectionCell: UICollectionViewCell { let attributedTitle = NSAttributedString( string: "첫번째 코멘트 남기기", attributes: [ - .font: UIFont.KorFont(style: .regular, size: 13)!, // 커스텀 폰트 적용 + .font: UIFont.korFont(style: .regular, size: 13)!, // 커스텀 폰트 적용 .underlineStyle: NSUnderlineStyle.single.rawValue // 밑줄 스타일 ] ) diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailInfoSection/DetailInfoSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailInfoSection/DetailInfoSectionCell.swift index 1ba18382..2a81ca35 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailInfoSection/DetailInfoSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailInfoSection/DetailInfoSectionCell.swift @@ -165,8 +165,8 @@ extension DetailInfoSectionCell: Inputable { let startTime = input.startTime ?? "?" let endTime = input.endTime ?? "?" - dateLabel.setLineHeightText(text: startDate + " ~ " + endDate, font: .KorFont(style: .regular, size: 14)) - timeLabel.setLineHeightText(text: startTime + " ~ " + endTime, font: .KorFont(style: .regular, size: 14)) - addressLabel.setLineHeightText(text: input.address, font: .KorFont(style: .regular, size: 13)) + dateLabel.setLineHeightText(text: startDate + " ~ " + endDate, font: .korFont(style: .regular, size: 14)) + timeLabel.setLineHeightText(text: startTime + " ~ " + endTime, font: .korFont(style: .regular, size: 14)) + addressLabel.setLineHeightText(text: input.address, font: .korFont(style: .regular, size: 13)) } } diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailSimilarSection/DetailSimilarSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailSimilarSection/DetailSimilarSectionCell.swift index e4b66e5f..3ca56575 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailSimilarSection/DetailSimilarSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailSimilarSection/DetailSimilarSectionCell.swift @@ -22,7 +22,7 @@ final class DetailSimilarSectionCell: UICollectionViewCell { private let dateLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 11) - label.font = .EngFont(style: .regular, size: 11) + label.font = .engFont(style: .regular, size: 11) label.textColor = .g400 return label }() @@ -142,8 +142,8 @@ extension DetailSimilarSectionCell: Inputable { func injection(with input: Input) { let date = input.date ?? "" imageView.setPPImage(path: input.imagePath) - dateLabel.setLineHeightText(text: "~" + date, font: .EngFont(style: .regular, size: 11)) - titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .bold, size: 12)) + dateLabel.setLineHeightText(text: "~" + date, font: .engFont(style: .regular, size: 11)) + titleLabel.setLineHeightText(text: input.title, font: .korFont(style: .bold, size: 12)) if let isBookMark = input.isBookMark { bookMarkButton.isHidden = false if isBookMark { diff --git a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailTitleSection/DetailTitleSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailTitleSection/DetailTitleSectionCell.swift index 2f7f7767..907df869 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/View/DetailTitleSection/DetailTitleSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/View/DetailTitleSection/DetailTitleSectionCell.swift @@ -82,7 +82,7 @@ extension DetailTitleSectionCell: Inputable { func injection(with input: Input) { let bookMarkImage = input.isBookMark ? UIImage(named: "icon_bookmark_blue") : UIImage(named: "icon_bookmark_gray") bookMarkButton.setImage(bookMarkImage, for: .normal) - titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .bold, size: 18)) + titleLabel.setLineHeightText(text: input.title, font: .korFont(style: .bold, size: 18)) bookMarkButton.isHidden = !input.isLogin } } diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/HomeReactor.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/HomeReactor.swift index 4759ee2c..83fadfc2 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/HomeReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/HomeReactor.swift @@ -70,7 +70,16 @@ final class HomeReactor: Reactor { private var popularTitleSection = HomeTitleSection(inputDataList: [ .init(blueText: "팝풀이", topSubText: "들은 지금 이런", bottomText: "팝업에 가장 관심있어요", backgroundColor: .g700, textColor: .w100) ]) - private var popularSection = HomePopularCardSection(inputDataList: [], decorationItems: [SectionDecorationItem(elementKind: "BackgroundView", reusableView: SectionBackGroundDecorationView(), viewInput: .init(backgroundColor: .g700))]) + private var popularSection = HomePopularCardSection( + inputDataList: [], + decorationItems: [ + SectionDecorationItem( + elementKind: "BackgroundView", + reusableView: SectionBackGroundDecorationView(), + viewInput: .init(backgroundColor: .g700) + ) + ] + ) private var newTitleSection = HomeTitleSection(inputDataList: [.init(blueText: "제일 먼저", topSubText: "피드 올리는", bottomText: "신규 오픈 팝업")]) private var newSection = HomeCardSection(inputDataList: []) private var spaceClear48Section = SpacingSection(inputDataList: [.init(spacing: 48)]) diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeCardSection/HomeCardSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeCardSection/HomeCardSectionCell.swift index 8ca3caf6..14c03267 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeCardSection/HomeCardSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeCardSection/HomeCardSectionCell.swift @@ -157,11 +157,11 @@ extension HomeCardSectionCell: Inputable { } func injection(with input: Input) { - categoryLabel.setLineHeightText(text: "#" + (input.category ?? ""), font: .KorFont(style: .bold, size: 11)) - titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .bold, size: 14)) - addressLabel.setLineHeightText(text: input.address, font: .KorFont(style: .medium, size: 11)) + categoryLabel.setLineHeightText(text: "#" + (input.category ?? ""), font: .korFont(style: .bold, size: 11)) + titleLabel.setLineHeightText(text: input.title, font: .korFont(style: .bold, size: 14)) + addressLabel.setLineHeightText(text: input.address, font: .korFont(style: .medium, size: 11)) let date = input.startDate.toDate().toPPDateString() + " ~ " + input.endDate.toDate().toPPDateString() - dateLabel.setLineHeightText(text: date, font: .KorFont(style: .medium, size: 11)) + dateLabel.setLineHeightText(text: date, font: .korFont(style: .medium, size: 11)) let bookmarkImage = input.isBookmark ? UIImage(named: "icon_bookmark_fill") : UIImage(named: "icon_bookmark") bookmarkButton.setImage(bookmarkImage, for: .normal) imageView.setPPImage(path: input.imagePath) @@ -169,7 +169,7 @@ extension HomeCardSectionCell: Inputable { rankLabel.isHidden = !input.isPopular let rank = input.row ?? 0 - rankLabel.setLineHeightText(text: "\(rank + 1)위", font: .KorFont(style: .medium, size: 11), lineHeight: 1) + rankLabel.setLineHeightText(text: "\(rank + 1)위", font: .korFont(style: .medium, size: 11), lineHeight: 1) rankLabel.textAlignment = .center if rank > 2 { rankLabel.isHidden = true diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSectionCell.swift index f613e678..9e8f66af 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSectionCell.swift @@ -134,7 +134,7 @@ extension HomePopularCardSectionCell: Inputable { func injection(with input: Input) { let date = "#\(input.endDate.toDate().toPPDateMonthString())까지 열리는" - dateLabel.setLineHeightText(text: date, font: .KorFont(style: .regular, size: 16)) + dateLabel.setLineHeightText(text: date, font: .korFont(style: .regular, size: 16)) let category = "#\(input.category ?? "")" if let addressArray = input.address?.components(separatedBy: " ") { if addressArray.count > 2 { @@ -144,7 +144,7 @@ extension HomePopularCardSectionCell: Inputable { } categoryLabel.text = category - titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .regular, size: 16)) + titleLabel.setLineHeightText(text: input.title, font: .korFont(style: .regular, size: 16)) backGroundImageView.setPPImage(path: input.imagePath) } } diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeTitleSection/HomeTitleSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeTitleSection/HomeTitleSectionCell.swift index d25f06bf..cf0f58cb 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeTitleSection/HomeTitleSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeTitleSection/HomeTitleSectionCell.swift @@ -90,9 +90,9 @@ extension HomeTitleSectionCell: Inputable { } func injection(with input: Input) { - blueLabel.setLineHeightText(text: input.blueText, font: .KorFont(style: .bold, size: 16)) - subLabel.setLineHeightText(text: input.topSubText, font: .KorFont(style: .bold, size: 16)) - bottomLabel.setLineHeightText(text: input.bottomText, font: .KorFont(style: .bold, size: 16)) + blueLabel.setLineHeightText(text: input.blueText, font: .korFont(style: .bold, size: 16)) + subLabel.setLineHeightText(text: input.topSubText, font: .korFont(style: .bold, size: 16)) + bottomLabel.setLineHeightText(text: input.bottomText, font: .korFont(style: .bold, size: 16)) contentView.backgroundColor = input.backgroundColor subLabel.textColor = input.textColor bottomLabel.textColor = input.textColor diff --git a/Poppool/Poppool/Presentation/Scene/Login/LastLoginView.swift b/Poppool/Poppool/Presentation/Scene/Login/LastLoginView.swift index fb04c749..eeebbd5a 100644 --- a/Poppool/Poppool/Presentation/Scene/Login/LastLoginView.swift +++ b/Poppool/Poppool/Presentation/Scene/Login/LastLoginView.swift @@ -46,7 +46,7 @@ final class LastLoginView: UIView { private let notificationLabel: UILabel = { let label = UILabel() - label.font = .KorFont(style: .medium, size: 13) + label.font = .korFont(style: .medium, size: 13) return label }() diff --git a/Poppool/Poppool/Presentation/Scene/Login/Main/LoginView.swift b/Poppool/Poppool/Presentation/Scene/Login/Main/LoginView.swift index 8333b589..437232c9 100644 --- a/Poppool/Poppool/Presentation/Scene/Login/Main/LoginView.swift +++ b/Poppool/Poppool/Presentation/Scene/Login/Main/LoginView.swift @@ -15,7 +15,7 @@ final class LoginView: UIView { let guestButton: UIButton = { let button = UIButton(type: .system) button.setTitle("둘러보기", for: .normal) - button.titleLabel?.font = .KorFont(style: .regular, size: 14) + button.titleLabel?.font = .korFont(style: .regular, size: 14) button.setTitleColor(.g1000, for: .normal) return button }() @@ -57,7 +57,7 @@ final class LoginView: UIView { let inquiryButton: UIButton = { let button = UIButton(type: .system) button.setTitle("로그인이 어려우신가요?", for: .normal) - button.titleLabel?.font = .KorFont(style: .regular, size: 12) + button.titleLabel?.font = .korFont(style: .regular, size: 12) button.setTitleColor(.g1000, for: .normal) return button }() diff --git a/Poppool/Poppool/Presentation/Scene/Login/Sub/SubLoginView.swift b/Poppool/Poppool/Presentation/Scene/Login/Sub/SubLoginView.swift index 00e54ac7..c1a34e01 100644 --- a/Poppool/Poppool/Presentation/Scene/Login/Sub/SubLoginView.swift +++ b/Poppool/Poppool/Presentation/Scene/Login/Sub/SubLoginView.swift @@ -28,7 +28,7 @@ final class SubLoginView: UIView { private let titleLabel: PPLabel = { let label = PPLabel(style: .bold, fontSize: 16, text: "간편하게 SNS 로그인하고\n공감가는 코멘트에 반응해볼까요?\n다른 코멘트를 확인해볼까요?") - label.setLineHeightText(text: "간편하게 SNS 로그인하고\n공감가는 코멘트에 반응해볼까요?\n다른 코멘트를 확인해볼까요?", font: .KorFont(style: .bold, size: 16), lineHeight: 1.3) + label.setLineHeightText(text: "간편하게 SNS 로그인하고\n공감가는 코멘트에 반응해볼까요?\n다른 코멘트를 확인해볼까요?", font: .korFont(style: .bold, size: 16), lineHeight: 1.3) label.numberOfLines = 0 label.textAlignment = .center return label @@ -57,7 +57,7 @@ final class SubLoginView: UIView { let inquiryButton: UIButton = { let button = UIButton(type: .system) button.setTitle("로그인이 어려우신가요?", for: .normal) - button.titleLabel?.font = .KorFont(style: .regular, size: 12) + button.titleLabel?.font = .korFont(style: .regular, size: 12) button.setTitleColor(.g1000, for: .normal) return button }() diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Block/View/BlockUserListSection/BlockUserListSectionCell.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Block/View/BlockUserListSection/BlockUserListSectionCell.swift index e7380373..dc08a239 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Block/View/BlockUserListSection/BlockUserListSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Block/View/BlockUserListSection/BlockUserListSectionCell.swift @@ -86,16 +86,16 @@ extension BlockUserListSectionCell: Inputable { func injection(with input: Input) { profileImageView.setPPImage(path: input.profileImagePath) - nickNameLabel.setLineHeightText(text: input.nickName, font: .KorFont(style: .bold, size: 14)) + nickNameLabel.setLineHeightText(text: input.nickName, font: .korFont(style: .bold, size: 14)) if input.isBlocked { blockButton.setTitle("차단완료", for: .normal) - blockButton.titleLabel?.font = .KorFont(style: .medium, size: 13) + blockButton.titleLabel?.font = .korFont(style: .medium, size: 13) blockButton.backgroundColor = .re600 blockButton.setTitleColor(.w100, for: .normal) blockButton.layer.borderWidth = 0 } else { blockButton.setTitle("차단해제", for: .normal) - blockButton.titleLabel?.font = .KorFont(style: .medium, size: 13) + blockButton.titleLabel?.font = .korFont(style: .medium, size: 13) blockButton.backgroundColor = .w100 blockButton.setTitleColor(.g300, for: .normal) blockButton.layer.borderWidth = 1 diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Block/View/BlockUserManageView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Block/View/BlockUserManageView.swift index f3fb7ef3..4fb424f1 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Block/View/BlockUserManageView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Block/View/BlockUserManageView.swift @@ -14,7 +14,7 @@ final class BlockUserManageView: UIView { // MARK: - Components let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() - view.headerLabel.setLineHeightText(text: "차단한 사용자 관리", font: .KorFont(style: .regular, size: 15)) + view.headerLabel.setLineHeightText(text: "차단한 사용자 관리", font: .korFont(style: .regular, size: 15)) return view }() diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/MyPageBookmarkController.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/MyPageBookmarkController.swift index 5b24ba1c..55e98c2b 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/MyPageBookmarkController.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/MyPageBookmarkController.swift @@ -166,8 +166,8 @@ extension MyPageBookmarkController { owner.mainView.contentCollectionView.isHidden = state.isEmptyCase owner.mainView.emptyLabel.isHidden = !state.isEmptyCase owner.mainView.emptyButton.isHidden = !state.isEmptyCase - owner.mainView.countButtonView.buttonTitleLabel.setLineHeightText(text: state.buttonTitle, font: .KorFont(style: .regular, size: 13)) - owner.mainView.countButtonView.countLabel.setLineHeightText(text: "총 \(state.count)개", font: .KorFont(style: .regular, size: 13)) + owner.mainView.countButtonView.buttonTitleLabel.setLineHeightText(text: state.buttonTitle, font: .korFont(style: .regular, size: 13)) + owner.mainView.countButtonView.countLabel.setLineHeightText(text: "총 \(state.count)개", font: .korFont(style: .regular, size: 13)) if state.buttonTitle != owner.viewType { owner.mainView.contentCollectionView.scrollsToTop = true diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/MyPageBookmarkView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/MyPageBookmarkView.swift index 8c5a7f1d..1bf108c0 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/MyPageBookmarkView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/MyPageBookmarkView.swift @@ -14,7 +14,7 @@ final class MyPageBookmarkView: UIView { // MARK: - Components let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() - view.headerLabel.setLineHeightText(text: "찜한 팝업", font: .KorFont(style: .regular, size: 15)) + view.headerLabel.setLineHeightText(text: "찜한 팝업", font: .korFont(style: .regular, size: 15)) return view }() @@ -42,7 +42,7 @@ final class MyPageBookmarkView: UIView { let buttonTitle = NSAttributedString( string: "추천 팝업 보러가기", attributes: [ - .font: UIFont.KorFont(style: .regular, size: 13)!, + .font: UIFont.korFont(style: .regular, size: 13)!, .underlineStyle: NSUnderlineStyle.single.rawValue, .foregroundColor: UIColor.g1000 ] diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/PopUpCardSection/PopUpCardSectionCell.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/PopUpCardSection/PopUpCardSectionCell.swift index a957011f..33ac884c 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/PopUpCardSection/PopUpCardSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/PopUpCardSection/PopUpCardSectionCell.swift @@ -23,7 +23,7 @@ final class PopUpCardSectionCell: UICollectionViewCell { private let dateLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 11) - label.font = .EngFont(style: .regular, size: 11) + label.font = .engFont(style: .regular, size: 11) label.textColor = .g1000 return label }() @@ -159,10 +159,10 @@ extension PopUpCardSectionCell: Inputable { func injection(with input: Input) { let date = input.date ?? "" imageView.setPPImage(path: input.imagePath) - dateLabel.setLineHeightText(text: date, font: .EngFont(style: .regular, size: 13)) - titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .bold, size: 16)) + dateLabel.setLineHeightText(text: date, font: .engFont(style: .regular, size: 13)) + titleLabel.setLineHeightText(text: input.title, font: .korFont(style: .bold, size: 16)) titleLabel.textAlignment = .center - addressLabel.setLineHeightText(text: input.address, font: .KorFont(style: .regular, size: 14)) + addressLabel.setLineHeightText(text: input.address, font: .korFont(style: .regular, size: 14)) addressLabel.textAlignment = .center if input.isBookMark { diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/PopUpCardSection/PopUpCardView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/PopUpCardSection/PopUpCardView.swift index 41ffc78c..c24d05f2 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/PopUpCardSection/PopUpCardView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Bookmark/Main/View/PopUpCardSection/PopUpCardView.swift @@ -25,7 +25,7 @@ final class PopUpCardView: UIView { private let dateLabel: PPLabel = { let label = PPLabel(style: .regular, fontSize: 11) - label.font = .EngFont(style: .regular, size: 11) + label.font = .engFont(style: .regular, size: 11) label.textColor = .g1000 return label }() @@ -158,9 +158,9 @@ extension PopUpCardView: Inputable { func injection(with input: Input) { let date = input.date ?? "" imageView.setPPImage(path: input.imagePath) - dateLabel.setLineHeightText(text: date, font: .EngFont(style: .regular, size: 13)) - titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .bold, size: 16)) - addressLabel.setLineHeightText(text: input.address, font: .KorFont(style: .regular, size: 14)) + dateLabel.setLineHeightText(text: date, font: .engFont(style: .regular, size: 13)) + titleLabel.setLineHeightText(text: input.title, font: .korFont(style: .bold, size: 16)) + addressLabel.setLineHeightText(text: input.address, font: .korFont(style: .regular, size: 14)) if input.isBookMark { bookMarkButton.setImage(UIImage(named: "icon_bookmark_fill"), for: .normal) diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/View/FAQDropdownSection/FAQDropdownSectionCell.swift b/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/View/FAQDropdownSection/FAQDropdownSectionCell.swift index 57a7e833..36493d0c 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/View/FAQDropdownSection/FAQDropdownSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/View/FAQDropdownSection/FAQDropdownSectionCell.swift @@ -23,7 +23,7 @@ final class FAQDropdownSectionCell: UICollectionViewCell { let qLabel: UILabel = { let label = UILabel() - label.setLineHeightText(text: "Q", font: .EngFont(style: .bold, size: 16), lineHeight: 1) + label.setLineHeightText(text: "Q", font: .engFont(style: .bold, size: 16), lineHeight: 1) label.textColor = .blu500 return label }() @@ -112,8 +112,8 @@ extension FAQDropdownSectionCell: Inputable { } func injection(with input: Input) { - titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .medium, size: 14)) - dropContentLabel.setLineHeightText(text: input.content, font: .KorFont(style: .regular, size: 14), lineHeight: 1.5) + titleLabel.setLineHeightText(text: input.title, font: .korFont(style: .medium, size: 14)) + dropContentLabel.setLineHeightText(text: input.content, font: .korFont(style: .regular, size: 14), lineHeight: 1.5) dropContentLabel.lineBreakStrategy = .hangulWordPriority dropContentLabel.textColor = .g600 if input.isOpen { diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/View/FAQView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/View/FAQView.swift index 93a270ec..77680e11 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/View/FAQView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/FAQ/View/FAQView.swift @@ -14,7 +14,7 @@ final class FAQView: UIView { // MARK: - Components let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() - view.headerLabel.setLineHeightText(text: "고객문의", font: .KorFont(style: .regular, size: 15)) + view.headerLabel.setLineHeightText(text: "고객문의", font: .korFont(style: .regular, size: 15)) return view }() diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageCommentSection/MyPageCommentSectionCell.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageCommentSection/MyPageCommentSectionCell.swift index 72b73d0a..1415b4f1 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageCommentSection/MyPageCommentSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageCommentSection/MyPageCommentSectionCell.swift @@ -102,7 +102,7 @@ extension MyPageCommentSectionCell: Inputable { func injection(with input: Input) { imageView.setPPImage(path: input.popUpImagePath) - titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .regular, size: 11)) + titleLabel.setLineHeightText(text: input.title, font: .korFont(style: .regular, size: 11)) titleLabel.textAlignment = .center if input.isFirstCell { diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageListSection/MyPageListSectionCell.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageListSection/MyPageListSectionCell.swift index 31d71249..0cd90311 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageListSection/MyPageListSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageListSection/MyPageListSectionCell.swift @@ -67,7 +67,7 @@ extension MyPageListSectionCell: Inputable { } func injection(with input: Input) { - titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .regular, size: 15)) + titleLabel.setLineHeightText(text: input.title, font: .korFont(style: .regular, size: 15)) if input.subTitle == nil { rightImageView.isHidden = false @@ -75,7 +75,7 @@ extension MyPageListSectionCell: Inputable { } else { rightImageView.isHidden = true subTitleLabel.isHidden = false - subTitleLabel.setLineHeightText(text: input.subTitle, font: .KorFont(style: .regular, size: 13)) + subTitleLabel.setLineHeightText(text: input.subTitle, font: .korFont(style: .regular, size: 13)) subTitleLabel.textColor = . blu500 } } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageMyCommentTitleSection/MyPageMyCommentTitleSectionCell.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageMyCommentTitleSection/MyPageMyCommentTitleSectionCell.swift index e6ccae2a..db582281 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageMyCommentTitleSection/MyPageMyCommentTitleSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageMyCommentTitleSection/MyPageMyCommentTitleSectionCell.swift @@ -61,13 +61,13 @@ extension MyPageMyCommentTitleSectionCell: Inputable { } func injection(with input: Input) { - titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .bold, size: 16)) + titleLabel.setLineHeightText(text: input.title, font: .korFont(style: .bold, size: 16)) if input.buttonTitle != nil { let buttonTitle = NSAttributedString( string: input.buttonTitle ?? "", attributes: [ - .font: UIFont.KorFont(style: .regular, size: 13)!, + .font: UIFont.korFont(style: .regular, size: 13)!, .underlineStyle: NSUnderlineStyle.single.rawValue, .foregroundColor: UIColor.g600 ] diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageProfileSection/MyPageProfileSectionCell.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageProfileSection/MyPageProfileSectionCell.swift index 642118cf..639252c2 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageProfileSection/MyPageProfileSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Main/View/MyPageProfileSection/MyPageProfileSectionCell.swift @@ -75,7 +75,7 @@ final class MyPageProfileSectionCell: UICollectionViewCell { let button = UIButton() button.setTitle("로그인/회원가입", for: .normal) button.backgroundColor = .w10 - button.titleLabel?.font = .KorFont(style: .medium, size: 13) + button.titleLabel?.font = .korFont(style: .medium, size: 13) button.setTitleColor(.w100, for: .normal) button.layer.cornerRadius = 4 return button @@ -223,8 +223,8 @@ extension MyPageProfileSectionCell: Inputable { loginLabel.isHidden = true loginButton.isHidden = true blurView.isHidden = false - nickNameLabel.setLineHeightText(text: input.nickName, font: .KorFont(style: .bold, size: 16)) - descriptionLabel.setLineHeightText(text: input.description ?? "", font: .KorFont(style: .light, size: 11)) + nickNameLabel.setLineHeightText(text: input.nickName, font: .korFont(style: .bold, size: 16)) + descriptionLabel.setLineHeightText(text: input.description ?? "", font: .korFont(style: .light, size: 11)) backGroundImageView.image = nil backGroundImageView.setPPImage(path: input.profileImagePath) profileImageView.setPPImage(path: input.profileImagePath) { [weak self] in diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/ListCountButtonSection/ListCountButtonSectionCell.swift b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/ListCountButtonSection/ListCountButtonSectionCell.swift index 41fb7421..35d9658e 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/ListCountButtonSection/ListCountButtonSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/ListCountButtonSection/ListCountButtonSectionCell.swift @@ -89,7 +89,7 @@ extension ListCountButtonSectionCell: Inputable { } func injection(with input: Input) { - countLabel.setLineHeightText(text: "총 \(input.count)개", font: .KorFont(style: .regular, size: 13)) - buttonTitleLabel.setLineHeightText(text: input.buttonTitle, font: .KorFont(style: .regular, size: 13)) + countLabel.setLineHeightText(text: "총 \(input.count)개", font: .korFont(style: .regular, size: 13)) + buttonTitleLabel.setLineHeightText(text: input.buttonTitle, font: .korFont(style: .regular, size: 13)) } } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/MyCommentView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/MyCommentView.swift index 8d0b9c95..f1cf8955 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/MyCommentView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/MyCommentView.swift @@ -14,7 +14,7 @@ final class MyCommentView: UIView { // MARK: - Components let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() - view.headerLabel.setLineHeightText(text: "내가 코멘트한 팝업", font: .KorFont(style: .regular, size: 15)) + view.headerLabel.setLineHeightText(text: "내가 코멘트한 팝업", font: .korFont(style: .regular, size: 15)) return view }() diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/MyCommentedPopUpGridSection/MyCommentedPopUpGridSectionCell.swift b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/MyCommentedPopUpGridSection/MyCommentedPopUpGridSectionCell.swift index 0c88ef82..f8dfa86a 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/MyCommentedPopUpGridSection/MyCommentedPopUpGridSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/MyComment/Main/View/MyCommentedPopUpGridSection/MyCommentedPopUpGridSectionCell.swift @@ -99,9 +99,9 @@ extension MyCommentedPopUpGridSectionCell: Inputable { func injection(with input: Input) { contentImageView.setPPImage(path: input.imageURL) - titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .bold, size: 11)) - contentLabel.setLineHeightText(text: input.content, font: .KorFont(style: .medium, size: 11)) + titleLabel.setLineHeightText(text: input.title, font: .korFont(style: .bold, size: 11)) + contentLabel.setLineHeightText(text: input.content, font: .korFont(style: .medium, size: 11)) contentLabel.numberOfLines = 2 - dateLabel.setLineHeightText(text: "\(input.startDate ?? "") ~ \(input.endDate ?? "")", font: .EngFont(style: .regular, size: 11)) + dateLabel.setLineHeightText(text: "\(input.startDate ?? "") ~ \(input.endDate ?? "")", font: .engFont(style: .regular, size: 11)) } } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/Detail/MyPageNoticeDetailController.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/Detail/MyPageNoticeDetailController.swift index 535b6b12..678153d9 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/Detail/MyPageNoticeDetailController.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/Detail/MyPageNoticeDetailController.swift @@ -60,9 +60,9 @@ extension MyPageNoticeDetailController { reactor.state .withUnretained(self) .subscribe { (owner, state) in - owner.mainView.titleLabel.setLineHeightText(text: state.title, font: .KorFont(style: .bold, size: 18)) - owner.mainView.dateLabel.setLineHeightText(text: state.date, font: .EngFont(style: .regular, size: 14)) - owner.mainView.contentLabel.setLineHeightText(text: state.content, font: .KorFont(style: .regular, size: 14)) + owner.mainView.titleLabel.setLineHeightText(text: state.title, font: .korFont(style: .bold, size: 18)) + owner.mainView.dateLabel.setLineHeightText(text: state.date, font: .engFont(style: .regular, size: 14)) + owner.mainView.contentLabel.setLineHeightText(text: state.content, font: .korFont(style: .regular, size: 14)) } .disposed(by: disposeBag) } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/Detail/MyPageNoticeDetailView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/Detail/MyPageNoticeDetailView.swift index a4ae7b9d..64125f43 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/Detail/MyPageNoticeDetailView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/Detail/MyPageNoticeDetailView.swift @@ -14,7 +14,7 @@ final class MyPageNoticeDetailView: UIView { // MARK: - Components let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() - view.headerLabel.setLineHeightText(text: "공지사항", font: .KorFont(style: .regular, size: 15)) + view.headerLabel.setLineHeightText(text: "공지사항", font: .korFont(style: .regular, size: 15)) return view }() diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/View/MyPageNoticeView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/View/MyPageNoticeView.swift index bb0af74b..874e228c 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/View/MyPageNoticeView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/View/MyPageNoticeView.swift @@ -14,7 +14,7 @@ final class MyPageNoticeView: UIView { // MARK: - Components let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() - view.headerLabel.setLineHeightText(text: "공지사항", font: .KorFont(style: .regular, size: 15)) + view.headerLabel.setLineHeightText(text: "공지사항", font: .korFont(style: .regular, size: 15)) return view }() diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/View/NoticeListSection/NoticeListSectionCell.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/View/NoticeListSection/NoticeListSectionCell.swift index c06f9e4a..6708b6a8 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/View/NoticeListSection/NoticeListSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Notice/List/View/NoticeListSection/NoticeListSectionCell.swift @@ -73,7 +73,7 @@ extension NoticeListSectionCell: Inputable { } func injection(with input: Input) { - titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .medium, size: 14)) - dateLabel.setLineHeightText(text: input.date, font: .EngFont(style: .regular, size: 12)) + titleLabel.setLineHeightText(text: input.title, font: .korFont(style: .medium, size: 14)) + dateLabel.setLineHeightText(text: input.date, font: .engFont(style: .regular, size: 12)) } } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/ProfileEditController.swift b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/ProfileEditController.swift index a6cee295..95801cc2 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/ProfileEditController.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/ProfileEditController.swift @@ -162,10 +162,10 @@ extension ProfileEditController { categoryTitle = "" } owner.mainView.categoryButton.subTitleLabel - .setLineHeightText(text: categoryTitle, font: .KorFont(style: .regular, size: 13), lineHeight: 1) + .setLineHeightText(text: categoryTitle, font: .korFont(style: .regular, size: 13), lineHeight: 1) let userInfoTitle = "\(originProfileData.gender ?? "")・\(originProfileData.age)세" owner.mainView.infoButton.subTitleLabel - .setLineHeightText(text: userInfoTitle, font: .KorFont(style: .regular, size: 13), lineHeight: 1) + .setLineHeightText(text: userInfoTitle, font: .korFont(style: .regular, size: 13), lineHeight: 1) // NickName TextField 설정 owner.mainView.nickNameTextFieldTrailingView.layer.borderColor = state.nickNameState.borderColor?.cgColor diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/ProfileEditReactor.swift b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/ProfileEditReactor.swift index fef97600..5719a881 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/ProfileEditReactor.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/ProfileEditReactor.swift @@ -241,7 +241,7 @@ final class ProfileEditReactor: Reactor { // kor and end Check let pattern = "^[가-힣a-zA-Z\\s]+$" // 허용하는 문자만 검사 - let regex = try! NSRegularExpression(pattern: pattern) + guard let regex = try? NSRegularExpression(pattern: pattern) else { return .empty } let range = NSRange(location: 0, length: text.utf16.count) if regex.firstMatch(in: text, options: [], range: range) == nil { return isActive ? .korAndEngActive : .korAndEng } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/View/ProfileEditView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/View/ProfileEditView.swift index c2adb140..ea14a361 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/View/ProfileEditView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/ProfileEdit/Main/View/ProfileEditView.swift @@ -14,7 +14,7 @@ final class ProfileEditView: UIView { // MARK: - Components let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() - view.headerLabel.setLineHeightText(text: "프로필 설정", font: .KorFont(style: .regular, size: 15)) + view.headerLabel.setLineHeightText(text: "프로필 설정", font: .korFont(style: .regular, size: 15)) return view }() let saveButton: PPButton = { @@ -69,7 +69,7 @@ final class ProfileEditView: UIView { let nickNameTextField: UITextField = { let textField = UITextField() textField.placeholder = "별명을 입력해주세요" - textField.font = .KorFont(style: .medium, size: 14) + textField.font = .korFont(style: .medium, size: 14) return textField }() let nickNameClearButton: UIButton = { @@ -96,7 +96,7 @@ final class ProfileEditView: UIView { let attributedTitle = NSAttributedString( string: title, attributes: [ - .font: UIFont.KorFont(style: .regular, size: 13)!, // 폰트 + .font: UIFont.korFont(style: .regular, size: 13)!, // 폰트 .underlineStyle: NSUnderlineStyle.single.rawValue, // 밑줄 스타일 .foregroundColor: UIColor.g1000 // 텍스트 색상 ] @@ -104,7 +104,7 @@ final class ProfileEditView: UIView { let disabledAttributedTitle = NSAttributedString( string: title, attributes: [ - .font: UIFont.KorFont(style: .regular, size: 13)!, // 폰트 + .font: UIFont.korFont(style: .regular, size: 13)!, // 폰트 .underlineStyle: NSUnderlineStyle.single.rawValue, // 밑줄 스타일 .foregroundColor: UIColor.g300 // 텍스트 색상 ] @@ -130,7 +130,7 @@ final class ProfileEditView: UIView { let view = UITextView() view.textContainerInset = .zero view.contentInset = .zero - view.font = .KorFont(style: .medium, size: 14) + view.font = .korFont(style: .medium, size: 14) return view }() let introTextCountLabel: PPLabel = { @@ -154,13 +154,13 @@ final class ProfileEditView: UIView { let categoryButton: ProfileEditListButton = { let button = ProfileEditListButton() - button.mainTitleLabel.setLineHeightText(text: "관심 카테고리", font: .KorFont(style: .regular, size: 15)) + button.mainTitleLabel.setLineHeightText(text: "관심 카테고리", font: .korFont(style: .regular, size: 15)) return button }() let infoButton: ProfileEditListButton = { let button = ProfileEditListButton() - button.mainTitleLabel.setLineHeightText(text: "사용자 정보", font: .KorFont(style: .regular, size: 15)) + button.mainTitleLabel.setLineHeightText(text: "사용자 정보", font: .korFont(style: .regular, size: 15)) return button }() diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Recent/View/MyPageRecentView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Recent/View/MyPageRecentView.swift index a608b4e9..d9f8fe3b 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Recent/View/MyPageRecentView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Recent/View/MyPageRecentView.swift @@ -14,7 +14,7 @@ final class MyPageRecentView: UIView { // MARK: - Components let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() - view.headerLabel.setLineHeightText(text: "최근 본 팝업", font: .KorFont(style: .regular, size: 15)) + view.headerLabel.setLineHeightText(text: "최근 본 팝업", font: .korFont(style: .regular, size: 15)) return view }() diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Terms/MyPageTermsController.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Terms/MyPageTermsController.swift index 68883315..2cd74d2e 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Terms/MyPageTermsController.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Terms/MyPageTermsController.swift @@ -21,7 +21,7 @@ final class MyPageTermsController: BaseViewController, View { private let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() - view.headerLabel.setLineHeightText(text: "약관", font: .KorFont(style: .regular, size: 15)) + view.headerLabel.setLineHeightText(text: "약관", font: .korFont(style: .regular, size: 15)) return view }() diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/CheckModal/WithdrawlCheckModalController.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/CheckModal/WithdrawlCheckModalController.swift index b8fbe817..c0d6bdd2 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/CheckModal/WithdrawlCheckModalController.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/CheckModal/WithdrawlCheckModalController.swift @@ -24,7 +24,7 @@ final class WithdrawlCheckModalController: BaseViewController, View { init(nickName: String?) { super.init() let title = "\(nickName ?? "")님, 팝풀 서비스를\n정말 탈퇴하시겠어요?" - mainView.titleLabel.setLineHeightText(text: title, font: .KorFont(style: .bold, size: 18), lineHeight: 1.312) + mainView.titleLabel.setLineHeightText(text: title, font: .korFont(style: .bold, size: 18), lineHeight: 1.312) mainView.titleLabel.numberOfLines = 2 } diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/CheckModal/WithdrawlCheckModalView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/CheckModal/WithdrawlCheckModalView.swift index 5c72ed50..bf7e88f2 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/CheckModal/WithdrawlCheckModalView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/CheckModal/WithdrawlCheckModalView.swift @@ -34,7 +34,7 @@ final class WithdrawlCheckModalView: UIView { let firstLabel: UILabel = { let text = "서비스 탈퇴 시 회원 전용 서비스 이용이 불가하며 회원 데이터는 일괄 삭제 처리돼요." let label = UILabel() - label.setLineHeightText(text: text, font: .KorFont(style: .regular, size: 13), lineHeight: 1.4) + label.setLineHeightText(text: text, font: .korFont(style: .regular, size: 13), lineHeight: 1.4) label.numberOfLines = 2 label.textColor = .g600 return label @@ -43,7 +43,7 @@ final class WithdrawlCheckModalView: UIView { let secondLabel: UILabel = { let text = "탈퇴 후에는 계정을 다시 살리거나 복구할 수 없어요." let label = UILabel() - label.setLineHeightText(text: text, font: .KorFont(style: .regular, size: 13), lineHeight: 1.4) + label.setLineHeightText(text: text, font: .korFont(style: .regular, size: 13), lineHeight: 1.4) label.numberOfLines = 2 label.textColor = .g600 return label diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/Complete/WithdrawlCompleteView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/Complete/WithdrawlCompleteView.swift index e85f5a9a..0fd5844a 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/Complete/WithdrawlCompleteView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/Complete/WithdrawlCompleteView.swift @@ -29,7 +29,7 @@ final class WithdrawlCompleteView: UIView { private let descriptionLabel: PPLabel = { let text = "고객님이 만족하실 수 있는\n팝풀이 되도록 앞으로도 노력할게요 :)" let label = PPLabel(style: .regular, fontSize: 15, text: text) - label.setLineHeightText(text: text, font: .KorFont(style: .regular, size: 15), lineHeight: 1.5) + label.setLineHeightText(text: text, font: .korFont(style: .regular, size: 15), lineHeight: 1.5) label.numberOfLines = 2 label.textAlignment = .center label.textColor = .g600 diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/View/WithdrawlCheckSectionCell.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/View/WithdrawlCheckSectionCell.swift index 2e0f1eb5..8ef20cb3 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/View/WithdrawlCheckSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/View/WithdrawlCheckSectionCell.swift @@ -26,7 +26,7 @@ final class WithdrawlCheckSectionCell: UICollectionViewCell { let view = UITextView() view.textContainerInset = .zero view.contentInset = .zero - view.font = .KorFont(style: .medium, size: 14) + view.font = .korFont(style: .medium, size: 14) view.backgroundColor = .clear return view }() @@ -157,7 +157,7 @@ extension WithdrawlCheckSectionCell: Inputable { let image = input.isSelected ? UIImage(named: "icon_check_fill") : UIImage(named: "icon_check") checkImageView.image = image let title = input.title ?? "" - titleLabel.setLineHeightText(text: title, font: .KorFont(style: .regular, size: 14)) + titleLabel.setLineHeightText(text: title, font: .korFont(style: .regular, size: 14)) bind() if input.isSelected { if title == "기타" { diff --git a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/View/WithdrawlReasonView.swift b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/View/WithdrawlReasonView.swift index de4f289b..250a933b 100644 --- a/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/View/WithdrawlReasonView.swift +++ b/Poppool/Poppool/Presentation/Scene/MyPage/Withdrawl/SelectedReason/View/WithdrawlReasonView.swift @@ -14,14 +14,14 @@ final class WithdrawlReasonView: UIView { // MARK: - Components let headerView: PPReturnHeaderView = { let view = PPReturnHeaderView() - view.headerLabel.setLineHeightText(text: "회원 탈퇴", font: .KorFont(style: .regular, size: 15)) + view.headerLabel.setLineHeightText(text: "회원 탈퇴", font: .korFont(style: .regular, size: 15)) return view }() private let titleLabel: UILabel = { let text = "탈퇴하려는 이유가\n무엇인가요?" let label = UILabel() - label.setLineHeightText(text: text, font: .KorFont(style: .bold, size: 20), lineHeight: 1.312) + label.setLineHeightText(text: text, font: .korFont(style: .bold, size: 20), lineHeight: 1.312) label.numberOfLines = 2 return label }() @@ -31,7 +31,7 @@ final class WithdrawlReasonView: UIView { let label = PPLabel(style: .regular, fontSize: 15, text: text) label.textColor = .g600 label.numberOfLines = 2 - label.setLineHeightText(text: text, font: .KorFont(style: .regular, size: 15), lineHeight: 1.4) + label.setLineHeightText(text: text, font: .korFont(style: .regular, size: 15), lineHeight: 1.4) return label }() diff --git a/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/CancelableTagSection/CancelableTagSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/CancelableTagSection/CancelableTagSectionCell.swift index e89783de..c7773c88 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/CancelableTagSection/CancelableTagSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/CancelableTagSection/CancelableTagSectionCell.swift @@ -86,12 +86,12 @@ extension CancelableTagSectionCell: Inputable { cancelButton.setImage(xmarkImage, for: .normal) if input.isSelected { contentView.backgroundColor = .blu500 - titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .bold, size: 11), lineHeight: 1.15) + titleLabel.setLineHeightText(text: input.title, font: .korFont(style: .bold, size: 11), lineHeight: 1.15) titleLabel.textColor = .w100 contentView.layer.borderColor = UIColor.blu500.cgColor } else { contentView.backgroundColor = .clear - titleLabel.setLineHeightText(text: input.title, font: .KorFont(style: .medium, size: 11), lineHeight: 1.15) + titleLabel.setLineHeightText(text: input.title, font: .korFont(style: .medium, size: 11), lineHeight: 1.15) titleLabel.textColor = .g400 contentView.layer.borderColor = UIColor.g200.cgColor } diff --git a/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchTitleSection/SearchTitleSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchTitleSection/SearchTitleSectionCell.swift index 97d074a2..63dad364 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchTitleSection/SearchTitleSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/BeforeSearch/View/SearchTitleSection/SearchTitleSectionCell.swift @@ -18,7 +18,7 @@ final class SearchTitleSectionCell: UICollectionViewCell { private let sectionTitleLabel: UILabel = { let label = UILabel() - label.font = .KorFont(style: .bold, size: 16) + label.font = .korFont(style: .bold, size: 16) return label }() @@ -73,7 +73,7 @@ extension SearchTitleSectionCell: Inputable { titleButton.isHidden = false let attributes: [NSAttributedString.Key: Any] = [ .underlineStyle: NSUnderlineStyle.single.rawValue, - .font: UIFont.KorFont(style: .regular, size: 13)! + .font: UIFont.korFont(style: .regular, size: 13)! ] let attributedTitle = NSAttributedString(string: buttonTitle, attributes: attributes) titleButton.setAttributedTitle(attributedTitle, for: .normal) diff --git a/Poppool/Poppool/Presentation/Scene/Search/Main/SearchMainView.swift b/Poppool/Poppool/Presentation/Scene/Search/Main/SearchMainView.swift index 89364b85..3d3ffda9 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/Main/SearchMainView.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/Main/SearchMainView.swift @@ -37,15 +37,15 @@ final class SearchMainView: UIView { let button = UIButton(type: .system) button.setTitle("취소", for: .normal) button.setTitleColor(.g1000, for: .normal) - button.titleLabel?.font = .KorFont(style: .regular, size: 14) + button.titleLabel?.font = .korFont(style: .regular, size: 14) button.imageView?.contentMode = .scaleAspectFit return button }() let searchTextField: UITextField = { let view = UITextField() - view.font = .KorFont(style: .regular, size: 14) - view.setPlaceholder(text: "팝업스토어명을 입력해보세요", color: .g400, font: .KorFont(style: .regular, size: 14)!) + view.font = .korFont(style: .regular, size: 14) + view.setPlaceholder(text: "팝업스토어명을 입력해보세요", color: .g400, font: .korFont(style: .regular, size: 14)!) return view }() diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteController.swift b/Poppool/Poppool/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteController.swift index b7f26876..7556c572 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteController.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteController.swift @@ -69,13 +69,13 @@ extension SignUpCompleteController { let attributedText = NSMutableAttributedString( string: categoryString, attributes: [ - .font: UIFont.KorFont(style: .bold, size: 15)!, + .font: UIFont.korFont(style: .bold, size: 15)!, .foregroundColor: UIColor.g600, NSAttributedString.Key.paragraphStyle: paragraphStyle ] ) attributedText.append(NSAttributedString(string: "와 연관된 팝업스토어 정보를 안내해드릴게요.", attributes: [ - .font: UIFont.KorFont(style: .regular, size: 15)!, + .font: UIFont.korFont(style: .regular, size: 15)!, .foregroundColor: UIColor.g600, NSAttributedString.Key.paragraphStyle: paragraphStyle ])) diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step2/SignUpStep2Reactor.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step2/SignUpStep2Reactor.swift index 13b7df79..9604a80d 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step2/SignUpStep2Reactor.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step2/SignUpStep2Reactor.swift @@ -99,7 +99,7 @@ final class SignUpStep2Reactor: Reactor { // kor and end Check let pattern = "^[가-힣a-zA-Z\\s]+$" // 허용하는 문자만 검사 - let regex = try! NSRegularExpression(pattern: pattern) + guard let regex = try? NSRegularExpression(pattern: pattern) else { return .empty } let range = NSRange(location: 0, length: text.utf16.count) if regex.firstMatch(in: text, options: [], range: range) == nil { return isActive ? .korAndEngActive : .korAndEng } diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step2/SignUpStep2View.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step2/SignUpStep2View.swift index b40c3bc2..984e0d56 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step2/SignUpStep2View.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step2/SignUpStep2View.swift @@ -45,7 +45,7 @@ final class SignUpStep2View: UIView { let textField: UITextField = { let textField = UITextField() textField.placeholder = "별명을 입력해주세요" - textField.font = .KorFont(style: .medium, size: 14) + textField.font = .korFont(style: .medium, size: 14) return textField }() @@ -75,7 +75,7 @@ final class SignUpStep2View: UIView { let attributedTitle = NSAttributedString( string: title, attributes: [ - .font: UIFont.KorFont(style: .regular, size: 13)!, // 폰트 + .font: UIFont.korFont(style: .regular, size: 13)!, // 폰트 .underlineStyle: NSUnderlineStyle.single.rawValue, // 밑줄 스타일 .foregroundColor: UIColor.g1000 // 텍스트 색상 ] @@ -83,7 +83,7 @@ final class SignUpStep2View: UIView { let disabledAttributedTitle = NSAttributedString( string: title, attributes: [ - .font: UIFont.KorFont(style: .regular, size: 13)!, // 폰트 + .font: UIFont.korFont(style: .regular, size: 13)!, // 폰트 .underlineStyle: NSUnderlineStyle.single.rawValue, // 밑줄 스타일 .foregroundColor: UIColor.g300 // 텍스트 색상 ] diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/Step3/View/TagSection/TagSectionCell.swift b/Poppool/Poppool/Presentation/Scene/SignUp/Step3/View/TagSection/TagSectionCell.swift index ed6bb5a6..28e1b592 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/Step3/View/TagSection/TagSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/Step3/View/TagSection/TagSectionCell.swift @@ -58,12 +58,12 @@ extension TagSectionCell: Inputable { contentView.backgroundColor = .blu500 contentView.layer.borderWidth = 0 titleLabel.textColor = .w100 - titleLabel.font = . KorFont(style: .medium, size: 13) + titleLabel.font = . korFont(style: .medium, size: 13) } else { contentView.backgroundColor = .clear contentView.layer.borderWidth = 1 titleLabel.textColor = .g400 - titleLabel.font = . KorFont(style: .medium, size: 13) + titleLabel.font = . korFont(style: .medium, size: 13) } } } diff --git a/Poppool/Poppool/Presentation/Scene/SignUp/TermsDetail/TermsDetailController.swift b/Poppool/Poppool/Presentation/Scene/SignUp/TermsDetail/TermsDetailController.swift index fdfa8afe..79f1c5f2 100644 --- a/Poppool/Poppool/Presentation/Scene/SignUp/TermsDetail/TermsDetailController.swift +++ b/Poppool/Poppool/Presentation/Scene/SignUp/TermsDetail/TermsDetailController.swift @@ -24,7 +24,7 @@ final class TermsDetailController: BaseViewController { paragraphStyle.lineHeightMultiple = 1.2 let attributes: [NSAttributedString.Key: Any] = [ - .font: UIFont.KorFont(style: .regular, size: 14), + .font: UIFont.korFont(style: .regular, size: 14), .paragraphStyle: paragraphStyle ] diff --git a/Poppool/Poppool/Presentation/Scene/TabbarController/TabbarController.swift b/Poppool/Poppool/Presentation/Scene/TabbarController/TabbarController.swift index 14c76db0..efac523a 100644 --- a/Poppool/Poppool/Presentation/Scene/TabbarController/TabbarController.swift +++ b/Poppool/Poppool/Presentation/Scene/TabbarController/TabbarController.swift @@ -237,11 +237,11 @@ class WaveTabBarController: UITabBarController, UITabBarControllerDelegate { // 폰트 설정 let appearance = UITabBarAppearance() appearance.stackedLayoutAppearance.normal.titleTextAttributes = [ - .font: UIFont.KorFont(style: .medium, size: 11)!, + .font: UIFont.korFont(style: .medium, size: 11)!, .paragraphStyle: paragraphStyle ] appearance.stackedLayoutAppearance.selected.titleTextAttributes = [ - .font: UIFont.KorFont(style: .bold, size: 11)!, + .font: UIFont.korFont(style: .bold, size: 11)!, .paragraphStyle: paragraphStyle ] diff --git a/Poppool/Poppool/Presentation/Utills/ToastMaker/BookMarkToastView.swift b/Poppool/Poppool/Presentation/Utills/ToastMaker/BookMarkToastView.swift index 8ec3d045..27f6d3df 100644 --- a/Poppool/Poppool/Presentation/Utills/ToastMaker/BookMarkToastView.swift +++ b/Poppool/Poppool/Presentation/Utills/ToastMaker/BookMarkToastView.swift @@ -23,14 +23,14 @@ final class BookMarkToastView: UIView { private let bookMarkLabel: UILabel = { let label = UILabel() - label.setLineHeightText(text: "찜한 팝업에 저장했어요", font: .KorFont(style: .regular, size: 15), lineHeight: 1) + label.setLineHeightText(text: "찜한 팝업에 저장했어요", font: .korFont(style: .regular, size: 15), lineHeight: 1) label.textColor = .w100 return label }() private let unbookMarkLabel: UILabel = { let label = PPLabel(style: .regular, fontSize: 15, text: "찜한 팝업을 해제했어요") - label.setLineHeightText(text: "찜한 팝업을 해제했어요", font: .KorFont(style: .regular, size: 15), lineHeight: 1) + label.setLineHeightText(text: "찜한 팝업을 해제했어요", font: .korFont(style: .regular, size: 15), lineHeight: 1) label.textColor = .w100 return label }() @@ -42,7 +42,7 @@ final class BookMarkToastView: UIView { button.setTitleColor(.blu300, for: .normal) button.layer.cornerRadius = 4 button.clipsToBounds = true - button.titleLabel?.font = .KorFont(style: .medium, size: 12) + button.titleLabel?.font = .korFont(style: .medium, size: 12) return button }() diff --git a/Poppool/Poppool/Presentation/Utills/ToastMaker/ToastView.swift b/Poppool/Poppool/Presentation/Utills/ToastMaker/ToastView.swift index bd04535e..79124cca 100644 --- a/Poppool/Poppool/Presentation/Utills/ToastMaker/ToastView.swift +++ b/Poppool/Poppool/Presentation/Utills/ToastMaker/ToastView.swift @@ -26,7 +26,7 @@ final class ToastView: UIView { private let messageLabel: UILabel = { let label = UILabel() label.textColor = .w100 - label.font = .KorFont(style: .regular, size: 15) + label.font = .korFont(style: .regular, size: 15) return label }() From 4b8a89bb9198ed5771a3cd603f151f8ff510293d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 5 Apr 2025 03:54:00 +0000 Subject: [PATCH 61/95] style/#82: Apply SwiftLint autocorrect --- .../ImageBannerSection/ImageBannerSectionCell.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift index 56817f73..c6d16cef 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/ImageBannerSection/ImageBannerSection/ImageBannerSectionCell.swift @@ -264,7 +264,7 @@ extension ImageBannerSectionCell: UICollectionViewDelegate, UICollectionViewData func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { guard imageSection.dataCount > 1 else { return } - + if currentIndex == 0 { contentCollectionView.scrollToItem( at: .init(row: imageSection.dataCount - 2, section: 0), From e8d28831b84c4eade07e718d282eff8557a0c52f Mon Sep 17 00:00:00 2001 From: YeongHoon Song Date: Sat, 5 Apr 2025 13:50:03 +0900 Subject: [PATCH 62/95] =?UTF-8?q?fix/#93:=20CI=EA=B0=80=20dev=EC=97=90=20p?= =?UTF-8?q?ush=20=EB=90=A0=EB=95=8C=EB=8A=94=20=EB=8F=8C=EC=95=84=EA=B0=80?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 오직 PR의 이벤트에만 반응하도록 수정 - PR이 오픈될 때 - PR에 추가 커밋이 들어올 때 - PR이 dev 브랜치를 타겟팅 하고 있을 때 --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01cd77eb..852aaace 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,8 +3,6 @@ name: CI on: pull_request: branches: [dev] - push: - branches: [dev] jobs: autocorrect: From 6db82d35484d6fbec64384195f4adf31f63fa45d Mon Sep 17 00:00:00 2001 From: JunYoung Date: Sun, 6 Apr 2025 18:03:19 +0900 Subject: [PATCH 63/95] =?UTF-8?q?refactor/#92:=20Kingfisher=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20=EA=B4=80=EB=A0=A8=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0,=20=EB=8C=80=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Poppool/Poppool.xcodeproj/project.pbxproj | 17 ----------- .../xcshareddata/swiftpm/Package.resolved | 11 +------ .../Presentation/Extension/String?+.swift | 9 ------ .../Presentation/Extension/UIImageView+.swift | 29 +++---------------- .../MapPopupCardView/PopupCardCell.swift | 1 - .../HomeCardSection/HomeCardSectionCell.swift | 8 ----- .../HomePopularCardSectionCell.swift | 8 ----- 7 files changed, 5 insertions(+), 78 deletions(-) diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index d4a99af2..70e891ae 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -474,7 +474,6 @@ BDCA41E22CF35AC1005EECF6 /* PoppoolUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDCA41E12CF35AC1005EECF6 /* PoppoolUITests.swift */; }; BDCA41E42CF35AC1005EECF6 /* PoppoolUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDCA41E32CF35AC1005EECF6 /* PoppoolUITestsLaunchTests.swift */; }; BDCA41F22CF35D0D005EECF6 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = BDCA41F12CF35D0D005EECF6 /* SnapKit */; }; - BDCA41F52CF35D33005EECF6 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = BDCA41F42CF35D33005EECF6 /* Kingfisher */; }; BDCA41F82CF35D9A005EECF6 /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = BDCA41F72CF35D9A005EECF6 /* RxSwift */; }; BDCA41FE2CF35EE7005EECF6 /* ReactorKit in Frameworks */ = {isa = PBXBuildFile; productRef = BDCA41FD2CF35EE7005EECF6 /* ReactorKit */; }; BDCA42012CF35EFE005EECF6 /* RxKeyboard in Frameworks */ = {isa = PBXBuildFile; productRef = BDCA42002CF35EFE005EECF6 /* RxKeyboard */; }; @@ -975,7 +974,6 @@ files = ( BDCA41F82CF35D9A005EECF6 /* RxSwift in Frameworks */, BDCA420D2CF35FD2005EECF6 /* RxGesture in Frameworks */, - BDCA41F52CF35D33005EECF6 /* Kingfisher in Frameworks */, BDCA42072CF35FA6005EECF6 /* Tabman in Frameworks */, BDCA42042CF35F76005EECF6 /* PanModal in Frameworks */, 082197A12D426DCB0054094A /* Then in Frameworks */, @@ -3088,7 +3086,6 @@ name = Poppool; packageProductDependencies = ( BDCA41F12CF35D0D005EECF6 /* SnapKit */, - BDCA41F42CF35D33005EECF6 /* Kingfisher */, BDCA41F72CF35D9A005EECF6 /* RxSwift */, BDCA41FD2CF35EE7005EECF6 /* ReactorKit */, BDCA42002CF35EFE005EECF6 /* RxKeyboard */, @@ -3179,7 +3176,6 @@ mainGroup = BDCA41B42CF35AC0005EECF6; packageReferences = ( BDCA41F02CF35D0D005EECF6 /* XCRemoteSwiftPackageReference "SnapKit" */, - BDCA41F32CF35D33005EECF6 /* XCRemoteSwiftPackageReference "Kingfisher" */, BDCA41F62CF35D9A005EECF6 /* XCRemoteSwiftPackageReference "RxSwift" */, BDCA41FC2CF35EE7005EECF6 /* XCRemoteSwiftPackageReference "ReactorKit" */, BDCA41FF2CF35EFE005EECF6 /* XCRemoteSwiftPackageReference "RxKeyboard" */, @@ -4123,14 +4119,6 @@ minimumVersion = 5.7.1; }; }; - BDCA41F32CF35D33005EECF6 /* XCRemoteSwiftPackageReference "Kingfisher" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/onevcat/Kingfisher.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 8.1.1; - }; - }; BDCA41F62CF35D9A005EECF6 /* XCRemoteSwiftPackageReference "RxSwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/ReactiveX/RxSwift.git"; @@ -4238,11 +4226,6 @@ package = BDCA41F02CF35D0D005EECF6 /* XCRemoteSwiftPackageReference "SnapKit" */; productName = SnapKit; }; - BDCA41F42CF35D33005EECF6 /* Kingfisher */ = { - isa = XCSwiftPackageProductDependency; - package = BDCA41F32CF35D33005EECF6 /* XCRemoteSwiftPackageReference "Kingfisher" */; - productName = Kingfisher; - }; BDCA41F72CF35D9A005EECF6 /* RxSwift */ = { isa = XCSwiftPackageProductDependency; package = BDCA41F62CF35D9A005EECF6 /* XCRemoteSwiftPackageReference "RxSwift" */; diff --git a/Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 5c6fc0d1..78d0f8f5 100644 --- a/Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "408890f5035d25ec9b1705255e27f587050890efcc8e3a540c8d9edbae7aeece", + "originHash" : "000b8c18b59df01ac73af8ca8c37167230915d09ad120950c2d361fbb92fe695", "pins" : [ { "identity" : "alamofire", @@ -37,15 +37,6 @@ "version" : "2.24.0" } }, - { - "identity" : "kingfisher", - "kind" : "remoteSourceControl", - "location" : "https://github.com/onevcat/Kingfisher.git", - "state" : { - "revision" : "3db26ab625d194c38e68c1a40e43d1bc12743fe0", - "version" : "8.2.0" - } - }, { "identity" : "lottie-spm", "kind" : "remoteSourceControl", diff --git a/Poppool/Poppool/Presentation/Extension/String?+.swift b/Poppool/Poppool/Presentation/Extension/String?+.swift index c1c5a916..13785af3 100644 --- a/Poppool/Poppool/Presentation/Extension/String?+.swift +++ b/Poppool/Poppool/Presentation/Extension/String?+.swift @@ -1,14 +1,5 @@ -// -// String?+.swift -// Poppool -// -// Created by SeoJunYoung on 11/30/24. -// - import UIKit -import Kingfisher - extension Optional where Wrapped == String { /// ISO 8601 형식의 문자열을 `Date`로 변환하는 메서드 func toDate() -> Date? { diff --git a/Poppool/Poppool/Presentation/Extension/UIImageView+.swift b/Poppool/Poppool/Presentation/Extension/UIImageView+.swift index f2670759..284957d6 100644 --- a/Poppool/Poppool/Presentation/Extension/UIImageView+.swift +++ b/Poppool/Poppool/Presentation/Extension/UIImageView+.swift @@ -1,14 +1,5 @@ -// -// UIImageView+.swift -// Poppool -// -// Created by SeoJunYoung on 12/3/24. -// - import UIKit -import Kingfisher - extension UIImageView { func setPPImage(path: String?) { guard let path = path else { @@ -22,15 +13,6 @@ extension UIImageView { self?.image = image } } -// let imageURL = URL(string: cenvertimageURL) -// self.kf.setImage(with: imageURL) { result in -// switch result { -// case .failure(let error): -// Logger.log(message: "\(path) image Load Fail: \(error.localizedDescription)", category: .error) -// default: -// break -// } -// } } } @@ -43,13 +25,10 @@ extension UIImageView { let imageURLString = Secrets.popPoolS3BaseURL.rawValue + path if let cenvertimageURL = imageURLString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) { let imageURL = URL(string: cenvertimageURL) - self.kf.setImage(with: imageURL) { result in - completion() - switch result { - case .failure(let error): - Logger.log(message: "\(path) image Load Fail: \(error.localizedDescription)", category: .error) - default: - break + ImageLoader.shared.loadImage(with: cenvertimageURL, defaultImage: UIImage(named: "image_default"), imageQuality: .origin) { [weak self] image in + DispatchQueue.main.async { + completion() + self?.image = image } } } diff --git a/Poppool/Poppool/Presentation/Map/Common/MapPopupCardView/PopupCardCell.swift b/Poppool/Poppool/Presentation/Map/Common/MapPopupCardView/PopupCardCell.swift index 4d1ee89d..87ad36d5 100644 --- a/Poppool/Poppool/Presentation/Map/Common/MapPopupCardView/PopupCardCell.swift +++ b/Poppool/Poppool/Presentation/Map/Common/MapPopupCardView/PopupCardCell.swift @@ -1,6 +1,5 @@ import UIKit import SnapKit -import Kingfisher final class PopupCardCell: UICollectionViewCell { static let identifier = "PopupCardCell" diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeCardSection/HomeCardSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeCardSection/HomeCardSectionCell.swift index 5bc534e3..d81a8abd 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeCardSection/HomeCardSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeCardSection/HomeCardSectionCell.swift @@ -1,15 +1,7 @@ -// -// HomeCardSectionCell.swift -// Poppool -// -// Created by SeoJunYoung on 11/30/24. -// - import UIKit import SnapKit import RxSwift -import Kingfisher final class HomeCardSectionCell: UICollectionViewCell { diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSectionCell.swift index aa395113..05bd9715 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSectionCell.swift @@ -1,15 +1,7 @@ -// -// HomePopularCardSectionCell.swift -// Poppool -// -// Created by SeoJunYoung on 11/30/24. -// - import UIKit import SnapKit import RxSwift -import Kingfisher final class HomePopularCardSectionCell: UICollectionViewCell { From d80a512d7f4bebe3eca14ed073d12bd82f2aa41d Mon Sep 17 00:00:00 2001 From: JunYoung Date: Sun, 6 Apr 2025 18:08:33 +0900 Subject: [PATCH 64/95] =?UTF-8?q?fix/#92:=20DiskStorage=20TImer=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ImageLoader/DiskStorage.swift | 38 +++++++------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/Poppool/Poppool/Infrastructure/ImageLoader/DiskStorage.swift b/Poppool/Poppool/Infrastructure/ImageLoader/DiskStorage.swift index 92c0f05c..95d4b70d 100644 --- a/Poppool/Poppool/Infrastructure/ImageLoader/DiskStorage.swift +++ b/Poppool/Poppool/Infrastructure/ImageLoader/DiskStorage.swift @@ -120,36 +120,26 @@ final class DiskStorage { } /// 주기적으로 만료된 캐시를 삭제하는 메서드 - /// - 5분(300초)마다 실행되며, 만료된 이미지와 메타데이터를 정리함. private func startCacheCleanup() { - DispatchQueue.global(qos: .background).async { [weak self] in - guard let self = self else { return } + let files = (try? self.fileManager.contentsOfDirectory(at: self.cacheDirectory, includingPropertiesForKeys: nil)) ?? [] - let cleanTimer = Timer.scheduledTimer(withTimeInterval: 300, repeats: true) { _ in - let files = (try? self.fileManager.contentsOfDirectory(at: self.cacheDirectory, includingPropertiesForKeys: nil)) ?? [] + for file in files { + if file.pathExtension == "metadata", + let metadataData = try? Data(contentsOf: file), + let metadata = try? JSONSerialization.jsonObject(with: metadataData) as? [String: TimeInterval], + let expirationTime = metadata["expiration"] { - for file in files { - if file.pathExtension == "metadata", - let metadataData = try? Data(contentsOf: file), - let metadata = try? JSONSerialization.jsonObject(with: metadataData) as? [String: TimeInterval], - let expirationTime = metadata["expiration"] { - - // 만료 시간이 지나면 이미지와 메타데이터 삭제 - if Date().timeIntervalSince1970 > expirationTime { - let imageFileURL = file.deletingPathExtension() // 메타데이터와 동일한 이름의 이미지 파일 - do { - try self.fileManager.removeItem(at: imageFileURL) - try self.fileManager.removeItem(at: file) // 메타데이터 삭제 - } catch { - print("Failed to delete expired cache: \(error)") - } - } + // 만료 시간이 지나면 이미지와 메타데이터 삭제 + if Date().timeIntervalSince1970 > expirationTime { + let imageFileURL = file.deletingPathExtension() // 메타데이터와 동일한 이름의 이미지 파일 + do { + try self.fileManager.removeItem(at: imageFileURL) + try self.fileManager.removeItem(at: file) // 메타데이터 삭제 + } catch { + print("Failed to delete expired cache: \(error)") } } } - // 백그라운드에서 실행되는 타이머를 메인 루프에 추가 - RunLoop.current.add(cleanTimer, forMode: .common) - RunLoop.current.run() // 백그라운드 스레드에서 타이머를 계속 실행하기 위해 RunLoop를 유지 } } } From 72b9de63e56b8c55dd3cd428aae5f203bed8f1bd Mon Sep 17 00:00:00 2001 From: JunYoung Date: Mon, 7 Apr 2025 23:33:48 +0900 Subject: [PATCH 65/95] =?UTF-8?q?refactor/#92:=20import=20Kingfisher=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20=EB=B9=8C=EB=93=9C=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=ED=95=98=EA=B2=8C=20=EC=BD=94=EB=93=9C=20=EC=9D=BC?= =?UTF-8?q?=EB=B6=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Map/Common/MapPopupCardView/PopupCardCell.swift | 4 ++-- .../Presentation/Scene/Detail/DetailController.swift | 2 +- .../Main/View/HomeCardSection/HomeCardSectionCell.swift | 8 -------- .../HomePopularCardSectionCell.swift | 7 ------- 4 files changed, 3 insertions(+), 18 deletions(-) diff --git a/Poppool/Poppool/Presentation/Map/Common/MapPopupCardView/PopupCardCell.swift b/Poppool/Poppool/Presentation/Map/Common/MapPopupCardView/PopupCardCell.swift index 27350c36..5d2f8f11 100644 --- a/Poppool/Poppool/Presentation/Map/Common/MapPopupCardView/PopupCardCell.swift +++ b/Poppool/Poppool/Presentation/Map/Common/MapPopupCardView/PopupCardCell.swift @@ -1,7 +1,7 @@ -import Kingfisher -import SnapKit import UIKit +import SnapKit + final class PopupCardCell: UICollectionViewCell { static let identifier = "PopupCardCell" diff --git a/Poppool/Poppool/Presentation/Scene/Detail/DetailController.swift b/Poppool/Poppool/Presentation/Scene/Detail/DetailController.swift index d9030086..ee001146 100644 --- a/Poppool/Poppool/Presentation/Scene/Detail/DetailController.swift +++ b/Poppool/Poppool/Presentation/Scene/Detail/DetailController.swift @@ -224,7 +224,7 @@ extension DetailController: UICollectionViewDelegate, UICollectionViewDataSource cell.imageCollectionView.rx.itemSelected .withUnretained(self) .map { (owner, cellIndexPath) in - Reactor.Action.commentImageTapped(controller: owner, cellRow: indexPath.row, ImageRow: cellIndexPath.row) + Reactor.Action.commentImageTapped(controller: owner, cellRow: indexPath.row, imageRow: cellIndexPath.row) } .bind(to: reactor.action) .disposed(by: cell.disposeBag) diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeCardSection/HomeCardSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeCardSection/HomeCardSectionCell.swift index 14c03267..30bb15e1 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeCardSection/HomeCardSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomeCardSection/HomeCardSectionCell.swift @@ -1,13 +1,5 @@ -// -// HomeCardSectionCell.swift -// Poppool -// -// Created by SeoJunYoung on 11/30/24. -// - import UIKit -import Kingfisher import RxSwift import SnapKit diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSectionCell.swift index 9e8f66af..54d97a52 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSectionCell.swift @@ -1,10 +1,3 @@ -// -// HomePopularCardSectionCell.swift -// Poppool -// -// Created by SeoJunYoung on 11/30/24. -// - import UIKit import Kingfisher From 8b2674edf15c2de4fa7953f14bd07e7f169e0830 Mon Sep 17 00:00:00 2001 From: JunYoung Date: Mon, 7 Apr 2025 23:58:05 +0900 Subject: [PATCH 66/95] =?UTF-8?q?refactor/#92:=20import=20Kingfisher=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/HomePopularCardSection/HomePopularCardSectionCell.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSectionCell.swift b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSectionCell.swift index 54d97a52..b4bd7862 100644 --- a/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSectionCell.swift +++ b/Poppool/Poppool/Presentation/Scene/Home/Main/View/HomePopularCardSection/HomePopularCardSectionCell.swift @@ -1,6 +1,5 @@ import UIKit -import Kingfisher import RxSwift import SnapKit From ec1848bc53ad13278bd57a4c436f88c2fb95e1b6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 7 Apr 2025 15:04:14 +0000 Subject: [PATCH 67/95] style/#92: Apply SwiftLint autocorrect --- .../ImageLoader/DiskStorage.swift | 40 +++++++++---------- .../ImageLoader/ImageLoader.swift | 36 ++++++++--------- .../ImageLoader/MemoryStorage.swift | 24 +++++------ 3 files changed, 50 insertions(+), 50 deletions(-) diff --git a/Poppool/Poppool/Infrastructure/ImageLoader/DiskStorage.swift b/Poppool/Poppool/Infrastructure/ImageLoader/DiskStorage.swift index 95d4b70d..127639b3 100644 --- a/Poppool/Poppool/Infrastructure/ImageLoader/DiskStorage.swift +++ b/Poppool/Poppool/Infrastructure/ImageLoader/DiskStorage.swift @@ -1,30 +1,30 @@ -import UIKit import CryptoKit +import UIKit /// 디스크에 이미지를 캐싱하는 클래스 final class DiskStorage { - + /// 싱글톤 인스턴스 static let shared = DiskStorage() - + /// 파일 관리 객체 private let fileManager = FileManager.default - + /// 이미지 캐시 디렉터리 경로 private let cacheDirectory: URL - + /// 초기화 메서드 (캐시 디렉터리 생성 및 자동 삭제 스케줄 시작) private init() { let urls = fileManager.urls(for: .cachesDirectory, in: .userDomainMask) cacheDirectory = urls[0].appendingPathComponent("ImageCache") - + // 디렉터리가 존재하지 않으면 생성 if !fileManager.fileExists(atPath: cacheDirectory.path) { try? fileManager.createDirectory(at: cacheDirectory, withIntermediateDirectories: true, attributes: nil) } startCacheCleanup() } - + /// URL을 안전한 파일명으로 변환하는 메서드 /// - Parameter url: 원본 URL 문자열 /// - Returns: 파일명으로 변환된 문자열 @@ -33,7 +33,7 @@ final class DiskStorage { let hashed = SHA256.hash(data: data) return hashed.compactMap { String(format: "%02x", $0) }.joined() } - + /// 이미지를 디스크에 저장하는 메서드 /// - Parameters: /// - image: 저장할 UIImage 객체 @@ -42,7 +42,7 @@ final class DiskStorage { let fileName = cacheFileName(for: url) let fileURL = cacheDirectory.appendingPathComponent(fileName) let metadataURL = cacheDirectory.appendingPathComponent("\(fileName).metadata") - + // 이미지 데이터를 JPEG 형식으로 변환하여 저장 if let data = image.jpegData(compressionQuality: 0.8) { do { @@ -51,11 +51,11 @@ final class DiskStorage { print("Error writing image data to disk: \(error)") } } - + // 만료 시간 기록 let expirationDate = Date().addingTimeInterval(ImageLoader.shared.configure.diskCacheExpiration) let metadata = ["expiration": expirationDate.timeIntervalSince1970] - + // 만료 정보를 JSON 형태로 저장 if let metadataData = try? JSONSerialization.data(withJSONObject: metadata) { do { @@ -65,7 +65,7 @@ final class DiskStorage { } } } - + /// 디스크에서 이미지를 불러오는 메서드 (만료된 경우 자동 삭제) /// - Parameter url: 이미지의 원본 URL 문자열 /// - Returns: UIImage 객체 (없거나 만료된 경우 nil) @@ -73,34 +73,34 @@ final class DiskStorage { let fileName = cacheFileName(for: url) let fileURL = cacheDirectory.appendingPathComponent(fileName) let metadataURL = cacheDirectory.appendingPathComponent("\(fileName).metadata") - + // 만료 시간 확인 if let metadataData = try? Data(contentsOf: metadataURL), let metadata = try? JSONSerialization.jsonObject(with: metadataData) as? [String: TimeInterval], let expirationTime = metadata["expiration"] { - + // 만료 시간이 현재 시각을 초과하면 삭제 후 nil 반환 if Date().timeIntervalSince1970 > expirationTime { removeImage(url: url) return nil } } - + // 이미지 파일이 존재하면 로드하여 반환 if let data = try? Data(contentsOf: fileURL) { return UIImage(data: data) } - + return nil } - + /// 특정 URL에 해당하는 이미지를 디스크에서 삭제하는 메서드 /// - Parameter url: 삭제할 이미지의 원본 URL 문자열 func removeImage(url: String) { let fileName = cacheFileName(for: url) let fileURL = cacheDirectory.appendingPathComponent(fileName) let metadataURL = cacheDirectory.appendingPathComponent("\(fileName).metadata") - + do { try fileManager.removeItem(at: fileURL) // 이미지 파일 삭제 try fileManager.removeItem(at: metadataURL) // 메타데이터 파일 삭제 @@ -108,7 +108,7 @@ final class DiskStorage { print("Failed to remove image: \(error)") } } - + /// 모든 캐시 데이터를 삭제하는 메서드 func clearCache() { do { @@ -118,7 +118,7 @@ final class DiskStorage { print("Failed to clear cache: \(error)") } } - + /// 주기적으로 만료된 캐시를 삭제하는 메서드 private func startCacheCleanup() { let files = (try? self.fileManager.contentsOfDirectory(at: self.cacheDirectory, includingPropertiesForKeys: nil)) ?? [] diff --git a/Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift b/Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift index 9e254f47..efaecd9d 100644 --- a/Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift +++ b/Poppool/Poppool/Infrastructure/ImageLoader/ImageLoader.swift @@ -11,7 +11,7 @@ enum ImageSizeOption { case middle case high case origin - + var size: CGSize { switch self { case .low: @@ -35,14 +35,14 @@ class ImageLoaderConfigure { /// URL을 통해 이미지를 비동기적으로 로드하는 클래스 final class ImageLoader { - + static let shared = ImageLoader() - + /// 이미지 로더 설정 객체 let configure = ImageLoaderConfigure() - + private init() {} - + /// URL을 통해 이미지를 로드하고, 실패 시 기본 이미지를 반환하는 메서드 /// - Parameters: /// - stringURL: 이미지 URL 문자열 @@ -66,7 +66,7 @@ final class ImageLoader { } private extension ImageLoader { - + /// URL을 통해 이미지를 로드하는 내부 메서드 /// - Parameters: /// - stringURL: 이미지 URL 문자열 @@ -76,13 +76,13 @@ private extension ImageLoader { completion(.failure(ImageLoaderError.invalidURL)) return } - + // 메모리 캐시에서 이미지 조회 if let cachedImage = MemoryStorage.shared.fetchImage(url: stringURL) { completion(.success(cachedImage)) return } - + // 디스크 캐시 확인 if let diskImage = DiskStorage.shared.fetchImage(url: stringURL) { // 메모리 캐시에 저장 후 반환 @@ -90,7 +90,7 @@ private extension ImageLoader { completion(.success(diskImage)) return } - + // 네트워크에서 데이터 요청 fetchDataFrom(url: url) { result in switch result { @@ -107,13 +107,13 @@ private extension ImageLoader { } } } - + /// URL을 통해 데이터를 요청하는 메서드 /// - Parameters: /// - url: 요청할 URL 객체 /// - completion: 요청 완료 후 호출되는 클로저 func fetchDataFrom(url: URL, completion: @escaping (Result) -> Void) { - let task = URLSession.shared.dataTask(with: url) { data, response, error in + let task = URLSession.shared.dataTask(with: url) { data, _, error in if let error = error { completion(.failure(ImageLoaderError.networkError(description: "Network Error: \(error.localizedDescription)"))) return @@ -122,26 +122,26 @@ private extension ImageLoader { } task.resume() } - + func resizeImage(_ image: UIImage?, defaultImage: UIImage?, with sizeOption: ImageSizeOption) -> UIImage? { guard let image else { return defaultImage } - + if sizeOption == .origin { return image } - + let targetSize = sizeOption.size - + // 비율 유지 리사이징 let aspectRatio = image.size.width / image.size.height var newSize = targetSize - + if aspectRatio > 1 { // 가로 이미지 newSize.height = targetSize.width / aspectRatio } else { // 세로 이미지 newSize.width = targetSize.height * aspectRatio } - + let renderer = UIGraphicsImageRenderer(size: newSize) - + return renderer.image { _ in image.draw(in: CGRect(origin: .zero, size: newSize)) } diff --git a/Poppool/Poppool/Infrastructure/ImageLoader/MemoryStorage.swift b/Poppool/Poppool/Infrastructure/ImageLoader/MemoryStorage.swift index 517bc9a7..2d4b30dc 100644 --- a/Poppool/Poppool/Infrastructure/ImageLoader/MemoryStorage.swift +++ b/Poppool/Poppool/Infrastructure/ImageLoader/MemoryStorage.swift @@ -4,7 +4,7 @@ import UIKit class StorageData: NSObject { let image: UIImage? /// 캐시된 이미지 let expirationDate: Date /// 캐시 만료 시간 - + /// 초기화 메서드 /// - Parameters: /// - image: 저장할 이미지 @@ -13,7 +13,7 @@ class StorageData: NSObject { self.image = image self.expirationDate = Date().addingTimeInterval(expiration) } - + /// 캐시가 만료되었는지 확인하는 메서드 /// - Returns: 만료 여부 (true: 만료됨, false: 유효함) func isExpired() -> Bool { @@ -23,21 +23,21 @@ class StorageData: NSObject { /// 메모리 캐시를 관리하는 클래스 final class MemoryStorage { - + /// 싱글톤 인스턴스 static let shared = MemoryStorage() - + /// 이미지 캐시 저장소 private let cache = NSCache() - + /// 현재 캐시에 저장된 키 목록 private var cachedKeys: Set = [] - + /// 초기화 (자동 캐시 정리 시작) private init() { startCacheCleanup() } - + /// 이미지를 캐시에 저장하는 메서드 /// - Parameters: /// - image: 저장할 이미지 @@ -47,7 +47,7 @@ final class MemoryStorage { cache.setObject(cachedData, forKey: url as NSString) cachedKeys.insert(url) } - + /// 캐시에서 이미지를 가져오는 메서드 /// - Parameter url: 이미지 URL 문자열 /// - Returns: 캐시된 UIImage (없으면 nil) @@ -59,25 +59,25 @@ final class MemoryStorage { return nil } } - + /// 특정 URL의 캐시 데이터를 제거하는 메서드 /// - Parameter url: 제거할 이미지의 URL 문자열 func removeData(url: String) { cache.removeObject(forKey: url as NSString) cachedKeys.remove(url) } - + /// 모든 캐시 데이터를 삭제하는 메서드 func clearCache() { cache.removeAllObjects() cachedKeys.removeAll() } - + /// 주기적으로 만료된 캐시를 정리하는 메서드 private func startCacheCleanup() { DispatchQueue.global(qos: .background).async { [weak self] in guard let self = self else { return } - + let cleanTimer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { _ in for key in self.cachedKeys { let nsKey = key as NSString From e09c017db224a82df72fd7f22b09b8f3fcf8e4a3 Mon Sep 17 00:00:00 2001 From: YeongHoon Song Date: Tue, 8 Apr 2025 21:32:31 +0900 Subject: [PATCH 68/95] =?UTF-8?q?refactor:=20CI=EA=B0=80=20=ED=83=80?= =?UTF-8?q?=EA=B2=9F=ED=95=98=EB=8A=94=20=EB=B8=8C=EB=9E=9C=EC=B9=98=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=EC=9D=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 852aaace..abb6166d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: CI on: pull_request: - branches: [dev] + branches: [develop] jobs: autocorrect: From c193b059842c608be560a5b6b8ea9e474d881e85 Mon Sep 17 00:00:00 2001 From: YeongHoon Song Date: Tue, 8 Apr 2025 22:36:49 +0900 Subject: [PATCH 69/95] =?UTF-8?q?refactor:=20CI=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CI가 새로운 브랜치들을 추적할 수 있도록 수정 - 자동 수정이 develop 브랜치에서만 동작하도록 수정 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index abb6166d..04668907 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,13 +2,13 @@ name: CI on: pull_request: - branches: [develop] + branches: [main, develop, 'release/*'] jobs: autocorrect: name: 🤖 Autocorrect Workflow runs-on: macos-15 # 최신 macOS 15 환경에서 실행 - if: github.actor != 'github-actions[bot]' # Actions 봇 커밋은 무시 + if: github.actor != 'github-actions[bot]'&& github.base_ref == 'develop' # Actions 봇 커밋은 무시 && develop에서만 자동 수정 진행 steps: - name: Checkout Repository # 저장소 코드 체크아웃 From c40ee06be606abfc1e3a220c1496fffee5305139 Mon Sep 17 00:00:00 2001 From: YeongHoon Song Date: Wed, 9 Apr 2025 19:55:48 +0900 Subject: [PATCH 70/95] =?UTF-8?q?feat:=20TestFlight=20=EB=B0=B0=ED=8F=AC?= =?UTF-8?q?=20=EC=9E=90=EB=8F=99=ED=99=94=20yml=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy_on_release.yml | 88 +++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 .github/workflows/deploy_on_release.yml diff --git a/.github/workflows/deploy_on_release.yml b/.github/workflows/deploy_on_release.yml new file mode 100644 index 00000000..a4604535 --- /dev/null +++ b/.github/workflows/deploy_on_release.yml @@ -0,0 +1,88 @@ +name: Distribution to TestFlight + +on: + pull_request: + branches: [ release/* ] + +jobs: + deploy: + name: 🚀 Distribution to TestFlight Workflow + runs-on: macos-15 # 최신 macOS 15 환경에서 실행 + env: + ## 프로젝트 이름을 변수로 설정 + PROJECT_NAME: Poppool + + # app archive 및 export 에 쓰일 환경 변수 설정 + XC_PROJECT: ${{ env.PROJECT_NAME }}/${{ env.PROJECT_NAME }}.xcodeproj + XC_SCHEME: ${{ env.PROJECT_NAME }} + XC_ARCHIVE: ${{ env.PROJECT_NAME }}.xcarchive + + # certificate + ENCRYPTED_CERT_FILE_PATH: ${{ '.github/secrets/certification.p12.gpg' }} + DECRYPTED_CERT_FILE_PATH: ${{ '.github/secrets/certification.p12' }} + CERT_ENCRYPTION_KEY: ${{ secrets.CERT_ENCRYPTION_PWD }} # gpg로 파일 암호화할 때 사용한 암호 + + # provisioning + ENCRYPTED_PROVISION_FILE_PATH: ${{ '.github/secrets/' }}${{ env.PROJECT_NAME }}GithubActions.mobileprovision.gpg + DECRYPTED_PROVISION_FILE_PATH: ${{ '.github/secrets/' }}${{ env.PROJECT_NAME }}GithubActions.mobileprovision + PROVISIONING_ENCRYPTION_KEY: ${{ secrets.PROVISION_ENCRYPTION_PWD }} # gpg로 파일 암호화할 때 사용한 암호 + + # certification export key + CERT_EXPORT_KEY: ${{ secrets.CERT_EXPORT_PWD }} + + KEYCHAIN: ${{ 'test.keychain' }} + + steps: + - name: Checkout Repository # 저장소 코드 체크아웃 + uses: actions/checkout@v4 + + - name: 🛠️ Set up Xcode # Xcode 16.2 선택 + run: sudo xcode-select -s /Applications/Xcode_16.2.app + + - name: 🔑 Configure Keychain # 키체인 초기화 -> 임시 키체인 생성 + run: | + security create-keychain -p "" "$KEYCHAIN" + security list-keychains -s "$KEYCHAIN" + security default-keychain -s "$KEYCHAIN" + security unlock-keychain -p "" "$KEYCHAIN" + security set-keychain-settings + + - name : ©️ Configure Code Signing + run: | + # certificate 복호화 + gpg -d -o "$DECRYPTED_CERT_FILE_PATH" --pinentry-mode=loopback --passphrase "$CERT_ENCRYPTION_KEY" "$ENCRYPTED_CERT_FILE_PATH" + + # provisioning 복호화 + gpg -d -o "$DECRYPTED_PROVISION_FILE_PATH" --pinentry-mode=loopback --passphrase "$PROVISIONING_ENCRYPTION_KEY" "$ENCRYPTED_PROVISION_FILE_PATH" + + # security를 사용하여 인증서와 개인 키를 새로 만든 키 체인으로 가져옴 + security import "$DECRYPTED_CERT_FILE_PATH" -k "$KEYCHAIN" -P "$CERT_EXPORT_KEY" -A + security set-key-partition-list -S apple-tool:,apple: -s -k "" "$KEYCHAIN" + + # Xcode에서 찾을 수 있는 프로비저닝 프로필 설치하기 위해 우선 프로비저닝 디렉토리를 생성 + mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles" + + # 디버깅 용 echo 명령어 + echo `ls .github/secrets/*.mobileprovision` + # 모든 프로비저닝 프로파일을 rename 하고 위에서 만든 디렉토리로 복사하는 과정 + for PROVISION in `ls .github/secrets/*.mobileprovision` + do + UUID=`/usr/libexec/PlistBuddy -c 'Print :UUID' /dev/stdin <<< $(security cms -D -i ./$PROVISION)` + cp "./$PROVISION" "$HOME/Library/MobileDevice/Provisioning Profiles/$UUID.mobileprovision" + done + + - name: ⬇️ Archive app # 빌드 및 아카이브 + run: | + xcodebuild clean archive -project $XC_PROJECT -scheme $XC_SCHEME -configuration release -archivePath $XC_ARCHIVE + + - name: ⬆️ Export app # export 를 통해 ipa 파일 만듦 + run: | + xcodebuild -exportArchive -archivePath $XC_ARCHIVE -exportOptionsPlist ExportOptions.plist -exportPath . -allowProvisioningUpdates + + - name: 🚀 Upload app to TestFlight # TestFlight에 아카이브된 앱 등록 + uses: apple-actions/upload-testflight-build@v1 + with: + app-path: '${{ env.PROJECT_NAME }}.ipa' + issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }} + api-key-id: ${{ secrets.APPSTORE_API_KEY_ID }} + api-private-key: ${{ secrets.APPSTORE_API_PRIVATE_KEY }} From bf40154dc2c52c7f1cc59e724e7eb7cd3dd2d8b9 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 9 Apr 2025 19:59:50 +0900 Subject: [PATCH 71/95] =?UTF-8?q?chore:=20=EC=9E=90=EB=8F=99=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EC=95=94=ED=98=B8?= =?UTF-8?q?=ED=99=94=EB=90=9C=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 상상도 못한 정체 같이 감(패키지 ㅎ) --- .../PoppoolGitHubAction.mobileprovision.gpg | Bin 0 -> 7965 bytes .github/secrets/certification.p12.gpg | Bin 0 -> 3341 bytes .../xcshareddata/swiftpm/Package.resolved | 168 ++++++++++++++++++ 3 files changed, 168 insertions(+) create mode 100644 .github/secrets/PoppoolGitHubAction.mobileprovision.gpg create mode 100644 .github/secrets/certification.p12.gpg create mode 100644 Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/.github/secrets/PoppoolGitHubAction.mobileprovision.gpg b/.github/secrets/PoppoolGitHubAction.mobileprovision.gpg new file mode 100644 index 0000000000000000000000000000000000000000..71dce0814f5e3a99b534ac27d1ebacb3cf0220b7 GIT binary patch literal 7965 zcmV+&AL8JQ4Fm}T2tz&B!J$Ql^8V850S{u4h4;(f#4`W>e$aS%IQQ5h+A1mC><%Wx z0Pux6vyBi2rCQ7#HECf@q|E=kb%o%A5X%tS6f6B2e_~EvZw^@z-r3d zV8LPB@WXe>E}<~5GGGEMUoNVi-M?Gcq;fbDaq7Xe=Dq4{tLI>lPQb1YS7GoMa&;^Y zXx^(~B-Y)KBdqe_qFMFsZq#muc{(59k6$Zp2f^YGx_c_O2NkG7T1b z_4E}E3bJ<-vObKPT5qou;~6!vw`lLNP=n@%Wrc^7O3A#n`nz3l!%=$W{ru0O6J5nu1eW`Jlg3B%h0W7 z92It+L%c6BzGj!!XaNfX*A9{6PNal=qBGHbOH_MJXx8iQ+DqImI0~3I?VYu=n^$Lh zN7(NWC$FUOVRy8q%62Sa`I@!`!y;x-tM45*-4VLo8^QksN_!#x@6tzwzx$O8&RPltq zu2&TWy6Lp{b~Vtg^Q3bO`Vl80&9@>yM;vzqsO~B^Ps&;4ND{CWp5~wml~ye#&WnhG z33Xjg?c4N7>N3meSSQUUcSz16F_1XoM^WiT7oCaCE?n-WQk(%VK^Iciyd?a}36_AE zeXTG*K5G#GRlE$zGzZRhG>er$bv3QpdCK~x*(KyUT z^9&K;VsjoQ6>At!q(uqfRo`^5IY>DQKbNv_Y@&b3q{ zBTBPF$9Tw!ATpr0MY|KYOp>(PuLn7MqVS*;qv;zd@FNQ~7OvGwWA!s47GNL>3xI@+ zI+*_%Cqx$wI`;!2k*h~-s;F9_%O>bbSBtG`wyC=5VqS978s(^Kjk4@xSD>E`Rb!U2 z_a&Fr|24bd?%g5+|9B09NWg2d$iY>IQx;U@P>ObPq1TX|kH%ZfO9~F#AFrYYxD=SB zAJGFX(r)Az6@;EQH}$#9QlPM^Vy8CC>o{#ChnQ98=>86EV*v*nwDDI8`WaYMru{ij=g?!yeU0?#!@?p_VwO!76^ik=ezAMf}*eP-Uawv1Uva- z@n#rP6OAE*%|bf8su{q@a`d;hMos(dOme}zQsX0pyz~~>yBUVGdb#|6z~F^(0f>Qs z{!C@}dpV-1_X42h1%v-#^{9~y!)#&ypSH9TO+h>vA1m`;ln>LgiXt(%`7*WfKwa>R z=do{gkIg|ykAMqw_NFH%+b)paPzL`+%l8Fok#>1;BEU|_&VKK^2!)uhh`N*d5IAzI zy)79DWCMAS+m8p3UdibA3-S-dF_nMMfHB-;Es~r+tv4!SV?upks;E#T8xtGqu7!86 zaFC`QkvJLC>gl7(XRwv9zU(v`kK;`D{40-Ad_8Z*{kATQ%I7o{=3>l{xL+I>yY18`Y^gtmjT`2!&P796@ggaG*ltb?lkA3S)0TUWwX>Q^w^c0_?wW;!FMA z2j~dXUg_E8MD&*Wx#eg*+-HQ@rOQ0ppB(F|C6N8SlkwXil1c zQRN{@9n8>Oj!TsI#^ayD&($wt{%lQ98TTd1U)Gz{l91lvL1wtlXU%BO#;6K3_jQb0(Cx*h^-5mPFIs!6Ze$VDd0B9y>&K4~ z1|M#>WviLBV^%-CsUA1;zZUdwI;LDs?ngS3Mn zwWmj&O(BtCr}LSdbT7TWr1s^K1IW)>1Ey#!aFRTXSZ%;6p!8wFUv>BQgh|&S3)aP3 z1K<$^2pMZUu$517jEIzOjh;6S9jQBO$KnZ2 zTG^tnIKuk~_hQ$J|1g7ciCcT#hE%vrGFAo?lW$|z*J*UZmmk=Xif~H{WVZJBoXVz&dF9#azip)yfb6rOOUuWN z6jz2>5j5NZ7zq*L1xMTFE`BnT{kuMrkr7=2cs5)0u{Fc#0@$d3wy+jrAcTdGRbg1M zG^c1XF8N#aoXfUWfuN^?>F&f$3+`ZLcj!@t&3WFaw zr!{mu?Ic&HX*$Y`q0hc9Z9D+<7CKu1QK)}_a^3+DL3i2KTVFik*-o}efubA)x>{dg zM($*gR}-Jl=*rzixW;C60zJ)X_B&gwrS~)l*Ozd25=?bGL`}c>dZlE~+q+#HS@5*% z@T-+kHl=BWl=%?b(rkzZC*bai*UT{8N)eLp!f3TS6ofqF6#bQbY{$q%sw53#RoP|Y zT2jm0GJQ>I@7GlFLV5!#cK!^tAcp9=<&gD%M>pd{wJ3DDF9Upt;}&+YKKSU~C-ToT3D~G0;{B)s^*!T4ZFe(C{+jF$+GMs@%Q9 zKoHzj(tZNy8KNfl(bS>PdyAm3)J8GNhC8gg%_SG9^p#UE2`IME94K$+%*}WPNg}8i zn{IOh?N8lX194=n;Fna`8*%|2A+vmSjIh4fEkoX5#%J(+tXbrVyI$9wT?WUd49i)g zSyhHMH)+}@3Q6RcKvU+(kt-Ms$!*(T$ye!u$>nhdsQy5`ujcbKIiJpSe zrEVlZm6c**fFzOCH}FQ3v1@aY1ek)8loZ%Mba$h!nFpzgxf=G;mY{iiJXg-_9XRMQ zxRR3e0u~Ujl+4zjGR|>re7J6m24$@I+M9U0+5FIOV`vit-l_zg|^UCgbi+K5jF&>n*Yj4(HP!z<}YO8D=%WTE)I^8Wz|)8mK4t`&uBb{OPnHLSlGY-*yc}un;vlfK3W1->hkL#?E%+e z`H8^c(>_TBeBcTHK2VcP*oWImgC1_v@3b%V1_1-d;z(&w=kNFF+dA*ru!HxuHu}I4 zG&ccMX|r*yI+7EMh74*>&A9cd`plmtlha}&Sr%9SbuZ$4dStfzwDF4YKH+uA@BX*> zdp5d0HYHK~Je;bg2`84nBQ=O$Cx}%97n0T}}{Iumt;A=&+QA=Fcc2hb)OQ zDfUR#FyQ5|;w-YG`^r?X{9wv<5y+g93$0={pd`Hj3lx>4(@f5B))5G2Ich{2$YH@_ zh(ZT%8p@>gHfm^UW5{9!3_{`uGsFhKe-#KOFbCcqYnnu7M6gzYTx$k+C2WYGF1wEz z{w|9l1m~=3zsRtH@C**wpjLg*yuX|L@MSBZ;Cgz7n=g5>R4lz#1Az!9%^jRXX$GVA z3IXXG_?h=N;3m9W)>}TW*Zk}ZWXB#q^2yikOpA{eSh&2f?nHWkOxKrbaCZn4uSV4| zZZ9y;{CVG@A1&u0R7wS+e328$OEnzN;;vr)@nJKV+1{OxJR^+&5pXSdMXG&mD(bg2 z*sBe_uf+80pn?@tRdm7N*kjQ(pUseGu1&w|?O3o`r+Z@c&D&lZ$Kf;?$-x<%THpUp z?Et1qRuG|FMsAlb-H|!LHlRr-yD)wG$K}r=?HyW2fXmpnW7#S7Q%_SgV>>we8%anZ zw--CLIcsS19G2umv87?Bm1bXKDpEX0x+*nSFTjc#84ax_7`jnBlY}B49l2cJS{xjp z>u>bQgBp6m_w+)t({JA1Nb5~5x|~@6Ka*=e-0DO1_yv2oJvj?W#>$vPQ}1ptz@kyq zFg?Q>FZmCdYf+k!>&La%IN;dfqSrkWL*~92rgfp9o&GnzPQG7=NN1V$fY|Phdg4Wt z_8(9K!l|*=l%+20GQMSeP&0VU*t=Tvx7P#J#jk7Xrfz>3+jXp+?#;VU=1b@x0Nrlq zx+qGGPENZ(=*mno8W_O7u=x=N9_RrA{}d|LJ9bAtG=hr-RHYH-cs-Ji4O>DS^bl0_ zJ&YcWVn9owHFj#p!`5{|*?~jj=td$gw-{s`43$Y-MceXc>PFT@Fs2>%Se@ud<6|8p zxp;g9uL#2-#TmI*a9Y(z3#G}p)2ZJ}536Id5A-3j_2znZbQjpiK8MNIobso3jMstD zb0?ABE-=sOM%P<^Z#v3MHc-@L3e_`mjeH=yQ*nkW zQ9?)pB`b#aGu^Hhho1GGxi!*;q)k&faB5nFV56|Xj##YTQv7R!YzeF#MGTF;7>Yfr zKE1#lMQFmE2pNO9hAojtU4cnMdYCyJf{k9Ju7stRYl5X51O7la^^FdLm2`5lV+fK@ zD@0uh2#Go2t|G`8Up~$3R4--XivjOASH*@V zz&G4P!)yphkVO3A#5h48*5u^nfkBWgk!o+yexFvGl@;~r3r5LihqWJOTDEbOF$9&p zTN79EqcOp0>v=o)o{In0C2!M6pB$ca>Mh2p)Ck{|p0BjjYcZYp->rbUe$dXmdSh{u z?2k%re3`>=nDrchjeGjcku{U)9g2E1A!@}N(qNE96XF|^PnPDuf*AT1XqZDagv*IL z#g5(Bi9P5J6{|HvnOvH&t9R=<(s!{fhnF@WcEOoi&;_s+${G1-xV48%_En$6wmt#P z0?;4fU~d+m1pQs#Z0~#iSd4J4w)dZ0S@uTMFC=~1iQ{m2r4=sjl z4@jdH9UNz49{aXfnztYW8osTTtg(&vYJK=W4->;%pI=Gc_?PxDt8%#Ew$Qg7@&sHfNeiUIn(oSuri zkGx)hhV$2sJ&DwISAfCNWL-cJ`^ypJ{6X8Y%zpJJlTrG{QSgTQ-IINE{0#Y#>uA$JO zhlX?s8Yiwa=bN4K=M~Zzd))S;h%fq7BNgE2CkoZH8aDMzuX`g2f7z0-@D1(zG#V_} zPhe6AD;n!7jw8@LX#@P3J1HQV`3~u!b#_ueB2pvvQW&a?3WJ4RHlj3dB>y_jT?*L0J@fP){ynf2V- zZoxa{7Bdbz7CtU7a=fNN3fA5@8K-F#_(sz5BcV;=5@hN>CX-yG)|VG_$RKph!>c$hMyz zqff;3I|eb1{eH!nz$ozOl3-Qb{jjD!7HdEYljfoe2A}nh4YshX$xPSyFB;W*%a|av z9;J)oP*ax%PFX^M-vt1!n2Iwq)Z1rDFUoEoW`fFXcG))r$CWB*>R!5G`ZBOWzXNgX zsMsSB7{u`mB(Qb&-WF-iiDc}A-E_o8_E}4x1LBobKW{#?DJQ()SXJ7UT+cQJ;h?q3 z-bQ}vP!Kqs9kRJ|vCmi{h{QP^Ilp9-lftHqJ9&%?eDf4sksl^M@jR&*#ao_*AeMxdXj{&R8@1$vkqK zbo6+hF~DuLJn5D-_{otvd#(%a&CEd8rZ{~L{udVGX48?Q7GLC6qJ+|9B%msV+d~Fw zF-hT|5Og<0w;>J_MvP67ScwBsx)`#c1yPGW^Di1wEC=5s>eb@y@J+aMV3Xd>!1IcRo(nymTSwiGJMoN;VSS$S z@@X!`s*uBwjzO3c_DS}83~tBh!$`z(=G*f0|6cX7&zN%c9#6*_3v_@LS*+LEolMQ) zAq0P5jGLajZo}#^zG~9dfQjH4Jr5wi=oS^V8RSj6pzy#G9#%{s4CeX$SijKVkOb6J zIuiiNdVm)+x!I30Oqkcg8*;C5sCDcDxP(eYLyIP*NW+?7UY;ti04T5^_ZD+g8Zhav zlgxLOdb0RYP*8E;YyI1T(ltJ1@5hQaiE~KCTDaHu0ZXEZ9NgD$kEEi0+MD7Rf4Nd- z7VV+ztF{yngJ?f%d|w2Gw(acjSmW6YxJVl2W2u-D!uKcin|LFX)e~)wJiSkkIujF* z5HAiot7Kz<`^KN{>Qw_N!hHpbL)pby06Zqg7}WRD?mP*}*URbk>X%*)RmVH<2_oKk zcVkAZ1zyV%nK@c*Cu1~6{Bc1(fo6Y$qJ`Z{FN?>XPee^99X%0rtT?kC3+UHhl*&tb z`+V-ehS-vC`xf5W8fs)6q%N|um?bzpmg!zHhNUE%N_bw2jn6BK?|ukt0Bh?Jv@824 zZ$c*cjotp|V$u28^{?CwM)66dKE^RdZgjig=ReY{$?KqvgzZsub3jD*{;4$2mMqE8 z!B3uRl-uA`k}ko7fBS6gm!=ZwNm$!L3_1EM0|cZRej;_V zErXU25a?hMHya5g@*iq6@(tvzk7PszqlxAlNn=h58|Q(&5j&Docws|*k9W{*t+F?+O3|74Xpl~F+4K4in8$!{UWMYWN7Mc z0v`>RE&|!djL7EiS&P0Hl&I6UQulIzORJDi{T$iM~f9C(+Iih`X_RL=K#?Wpr2ShWeqpoiwW({!2ZF z4QBXPi@tu1`$|@Pl1th(5*SCX!cQHqI|uWfV$ftv1>u947BAvcE-r8`H#AdbQ2^TkT0S2vF zHcBh^{KlimMj2WJz($N}Ff8K!8H!;Rcb|C>D$jlq`=`0i zH)e|zV3g0Ba6Wun_S$r70+U>34yt0JyQct^xAdx(qAr+m(?wnIXZrgO*N)BI?ba2~ zfHB%}?;$F`Yj=@!*D@+M8PHquketTO5G>=h*^L}kH7X~LuesSwaf1F%kIf>{KN7Ws zuaGNy70hu|9EloJXHuf8o%M$ENr<4r literal 0 HcmV?d00001 diff --git a/.github/secrets/certification.p12.gpg b/.github/secrets/certification.p12.gpg new file mode 100644 index 0000000000000000000000000000000000000000..05c0e8d586ad4faee9a9b448029748eee74afd75 GIT binary patch literal 3341 zcmV+o4f67g4Fm}T2&7k@D}#-1vi{QQ0hn-zE&#RIKy2aTIpn8vcZlD5hG@mSU&%^! zd#dmK1p-=27r4GZ9pZ@>ytk=u0yH8_1a{j2sql`hV}6kGq>iLx8+TYEeYf{^6QRG= z(fw@9TPlaz_OH3$&Vv4_Q$hRa(=1~TyO?5u18N ze*MJ2|8hDJV`X=x$ycYcflYhRM?&s_G0o553L!g$%4}@;SdQoRNVZm#y7DnyL&4Dc zsZ~4Uhdp1OXGWR@bJI-VOYu>?jmWQAH;9HCgMr7W2XTj7@59J^&$j+28Rlq+PIJ(h z_2*D%^A(8=B;GOSS!1i-ecuy4T)Tx()9vk0w8{n-7cMd^jB^~62m+Ku{3vK0oBAP@V43jHy=XBu++4f!1eat|g z0yMzBy9E!PL=Ztjzmj-W&`|>js3{?AN1zI2%cYs;T8i2n_%b1?Tv_(06nA_K&*rWMy7>W%;#!5EsxplQcJoVI$fJk zOQznBWh!r7-!Oz_9MyU9qR)sW692f1&+xo}<%^eLa`a!Khrj7|CC_mYXTxBo6v%AJ z$ibYr$$g}cCQtSUZ6$cNcBJO{Th1V2Wfik=$;*%;stGd&+rZlGuF-r3oh>c8hp3!j zV3k{rvCRqWLu}#PX$RuF4USP(mU5#=L)N$L)5!)ynID(|Nd%xZ{@oM6Teh%fL{xa% zNzHs|6O_TRlNL|!t8Pi$MeMkt_I#y%a2Dcl%KD|^=&6fCTrdSih8@!JBtgkmH|i07 zaAm#FXK+4KiY_Q>UwuvQ{iD)lq%43dfQPnvMu9Y?;2pI)6Y-2ZO%{_58yA; zX4!Kixo6mnIv;zdGk3ss@8_OgDQ5`IhStW@96-}JUv1YL->K&|=Y zHNQ$h%PS6nUSGT@^bNAB6Q zara7+DO6?&wgBWwX#)|^{@Ou1TL?ofPNaE@fX25pFbG-^MM7@J4Jx)hW z`JVF(6Mjh}bxi%<$HU59E816Xi-$F-Jdzbcp|F}m-q(?u7HKiw0nO)L@tA1|)~eUI zGK(B~5eJeZ9b=gfmzo~?XGNPsrZaGD*XPHbZft~C@X1_JdMYUYad~dduIXO2#)OEb zMGRDK)u>il-hms;b&RSVN;Obg*Ay{2x#TC*>S;e2;~ghp4h&Y)=$0qfWy;=~evv^e z5UwFN{w4yPLMvU(IzegIBsiC=ju$vPFV^7k62#E5s2-E`Bk~H3 zMu~c-9U=+vB*o<%yXOY(m;cZTDt$ybON*laTYUk0Ts1!Oys`l2kXY2*DRQE2=~@8Y zmhGm~u4RCpDD^Nk8hMAjXUk}58<>F9z9wH3pd`@8dVj)!W|pb<$OmeI*VX4&jy;GM zMQF;Zs74HD4B0sl51$99B5FFa*Qum76U-Z|82x0kD*-PEhKiTJ5u;#cq~CC&+n)I0 zp7Wo`r2jww?L1kKq~XY+LF_6M2Af|l^v=g^EsZ9>^Lw|%H-od z2#tRR17h|)+oqp+0pknVI+AX5KVf8ufd^@?Vae&htO-Zx5=m$O^OEM_fMN0hFHAt@uEeT$d9)1~kT0HdDaCs-64fSgKAM2Z`L4oCh94&IHBqCCe8Y!3ypow- z%la*OqkG4oY|(Q;1%uaWNC!7;d{5)&u>jP@K!)s)?pE*7?73bXl4N}qOX{~<8KL&-tJiS zA+ib!h4A|HHxHzvc#nzin=jFIm4#WP{Xag#Hs{RY*yix?S)0ob_OdCwJ9h!K%Gppe zn@PP6lI{`mPLqtN;)p1wy=*eVaDAJm!+-fC4*CxrMY!?4GHzs+leP z!ynAR_oxuxT5A?&;!{%Qg?Z5Xt*|*WD2y398C(BW`vzR({|(@t;jSMkDxpOHJC z^OsC*e~y_UjwbgQC&zdJy7=i%Md|YR*FBHwBehcP{j9Gk^?*Ss9HTYe9W6FRcu3zy z$9rCcjhH9B`dmF}L9mK;wwZ)E%+3p~Ea#?yJ(*G)0MG^BFdw>6>?__es>8P1 z599QjFpa|>Nza6GT12P>Xq^9qqS##1ts{)@?>;s}>nNQ#&+00x_P^sKtbx@=y{;m@ zl+D}$#TepUsnrSuP}ZH|Hll+BNhw%_EK!imvz>^uaKWh7qZth#AB`n|UX3_sSZic( z>*+w|In+yq-Rof-3-V3LnJ(OD(NKVhN=cra!)pSvULZSl9^r07w1cwb1C_qd@ly3RYJr%cn6K@|os z8~1}bf(n1VUW&`c6HmAY2E8I<3ev6{D%R?zp0A|U&i#E|A#YLn;$AML&VP$$>9a($ zhb?&A^2uCw+hkYBP@dXCypYL&&pbXhNAhzM@k<|LI<7WU?#WL0$MuK>eC^p93}M3C z58DnoFRv|2OD|-Y@tH4#tzw~Q3M1F4cinE(Ctm@Meo4dlb5=IIxhC_NT~(lf0sSffXZTPvavGJeuo+tpMI|Jg<(XP5APZoO#Rdv2>;UkmOPaz=qU5?q^P8U|^( z^^#uVqhQr1Lf`CgYxS<8nAd~7IgX4k0!6RtM9Djl--(vj`jkumjcX3~nxovlzlLgP z9_qm~Q8q3J>Z|1|DBE)JFE!kw>2!qr5r??10Yn3^+-1&|+oq3V6lFGWYaWeoThOLf zWfm@)C(Lj@60#h(Dt?md@aT^LUbXKdg@$)e)4{+v!YD=CT$_YtepT=cyom2!lcGeE z6Pbd$;Ql04U8AvKII8MpE0Zb4aK+OS01VpOZIIjq{2S~%e`^AoFGdcaHGP4sQ0ct8 zYSEShBE3Si4f+~xj>{8ByJ&;xS%`ZSkpjZ={q}bP5nfX6ULojNT X_A3lWwA6lX692w!m&XlR<2%EWX0)9g literal 0 HcmV?d00001 diff --git a/Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..810c5679 --- /dev/null +++ b/Poppool/Poppool.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,168 @@ +{ + "originHash" : "893e9d5956a6d674d140654334c58c276c99001321206803c07c48415f2e6ac3", + "pins" : [ + { + "identity" : "alamofire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/Alamofire.git", + "state" : { + "revision" : "513364f870f6bfc468f9d2ff0a95caccc10044c5", + "version" : "5.10.2" + } + }, + { + "identity" : "floatingpanel", + "kind" : "remoteSourceControl", + "location" : "https://github.com/scenee/FloatingPanel.git", + "state" : { + "revision" : "b6e8928b1a3ad909e6db6a0278d286c33cfd0dc3", + "version" : "2.8.6" + } + }, + { + "identity" : "kakao-ios-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kakao/kakao-ios-sdk.git", + "state" : { + "revision" : "bfe2fe42f730ccfe59e85f6e9eda2f4578e9a307", + "version" : "2.24.0" + } + }, + { + "identity" : "lottie-spm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/airbnb/lottie-spm.git", + "state" : { + "revision" : "8c6edf4f0fa84fe9c058600a4295eb0c01661c69", + "version" : "4.5.1" + } + }, + { + "identity" : "pageboy", + "kind" : "remoteSourceControl", + "location" : "https://github.com/uias/Pageboy.git", + "state" : { + "revision" : "be0c1f6f1964cfb07f9d819b0863f2c3f255f612", + "version" : "4.2.0" + } + }, + { + "identity" : "panmodal", + "kind" : "remoteSourceControl", + "location" : "https://github.com/slackhq/PanModal.git", + "state" : { + "revision" : "b012aecb6b67a8e46369227f893c12544846613f", + "version" : "1.2.7" + } + }, + { + "identity" : "reactorkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ReactorKit/ReactorKit.git", + "state" : { + "revision" : "8fa33f09c6f6621a2aa536d739956d53b84dd139", + "version" : "3.2.0" + } + }, + { + "identity" : "rxdatasources", + "kind" : "remoteSourceControl", + "location" : "https://github.com/RxSwiftCommunity/RxDataSources.git", + "state" : { + "revision" : "90c29b48b628479097fe775ed1966d75ac374518", + "version" : "5.0.2" + } + }, + { + "identity" : "rxgesture", + "kind" : "remoteSourceControl", + "location" : "https://github.com/RxSwiftCommunity/RxGesture.git", + "state" : { + "revision" : "1b137c576b4aaaab949235752278956697c9e4a0", + "version" : "4.0.4" + } + }, + { + "identity" : "rxkeyboard", + "kind" : "remoteSourceControl", + "location" : "https://github.com/RxSwiftCommunity/RxKeyboard.git", + "state" : { + "revision" : "63f6377975c962a1d89f012a6f1e5bebb2c502b7", + "version" : "2.0.1" + } + }, + { + "identity" : "rxswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ReactiveX/RxSwift.git", + "state" : { + "revision" : "5dd1907d64f0d36f158f61a466bab75067224893", + "version" : "6.9.0" + } + }, + { + "identity" : "snapkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SnapKit/SnapKit.git", + "state" : { + "revision" : "2842e6e84e82eb9a8dac0100ca90d9444b0307f4", + "version" : "5.7.1" + } + }, + { + "identity" : "spm-nmapsgeometry", + "kind" : "remoteSourceControl", + "location" : "https://github.com/navermaps/SPM-NMapsGeometry.git", + "state" : { + "revision" : "436d5e2e684f557faf5ef5862fd6633a42d7af11", + "version" : "1.0.2" + } + }, + { + "identity" : "spm-nmapsmap", + "kind" : "remoteSourceControl", + "location" : "https://github.com/navermaps/SPM-NMapsMap", + "state" : { + "revision" : "ad89e53fdfec3b8d8994280fb0414b5a7b1c3e8e", + "version" : "3.21.0" + } + }, + { + "identity" : "swiftsoup", + "kind" : "remoteSourceControl", + "location" : "https://github.com/scinfu/SwiftSoup", + "state" : { + "revision" : "bba848db50462894e7fc0891d018dfecad4ef11e", + "version" : "2.8.7" + } + }, + { + "identity" : "tabman", + "kind" : "remoteSourceControl", + "location" : "https://github.com/uias/Tabman.git", + "state" : { + "revision" : "3b2213290eb93e55bb50b49d1a179033005c11ab", + "version" : "3.2.0" + } + }, + { + "identity" : "then", + "kind" : "remoteSourceControl", + "location" : "https://github.com/devxoul/Then", + "state" : { + "revision" : "d41ef523faef0f911369f79c0b96815d9dbb6d7a", + "version" : "3.0.0" + } + }, + { + "identity" : "weakmaptable", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ReactorKit/WeakMapTable.git", + "state" : { + "revision" : "cb05d64cef2bbf51e85c53adee937df46540a74e", + "version" : "1.2.1" + } + } + ], + "version" : 3 +} From 529850f673c1d27352a85ccf36ca404c818dcabd Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 9 Apr 2025 20:07:03 +0900 Subject: [PATCH 72/95] =?UTF-8?q?fix/#103:=20=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=EB=A5=BC=20=EC=B0=BE=EC=A7=80=20=EB=AA=BB?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 프로젝트 이름 환경변수 제거 --- .github/workflows/deploy_on_release.yml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/deploy_on_release.yml b/.github/workflows/deploy_on_release.yml index a4604535..fe1e3f0d 100644 --- a/.github/workflows/deploy_on_release.yml +++ b/.github/workflows/deploy_on_release.yml @@ -9,13 +9,10 @@ jobs: name: 🚀 Distribution to TestFlight Workflow runs-on: macos-15 # 최신 macOS 15 환경에서 실행 env: - ## 프로젝트 이름을 변수로 설정 - PROJECT_NAME: Poppool - # app archive 및 export 에 쓰일 환경 변수 설정 - XC_PROJECT: ${{ env.PROJECT_NAME }}/${{ env.PROJECT_NAME }}.xcodeproj - XC_SCHEME: ${{ env.PROJECT_NAME }} - XC_ARCHIVE: ${{ env.PROJECT_NAME }}.xcarchive + XC_PROJECT: Poppool/Poppool.xcodeproj + XC_SCHEME: Poppool + XC_ARCHIVE: Poppool.xcarchive # certificate ENCRYPTED_CERT_FILE_PATH: ${{ '.github/secrets/certification.p12.gpg' }} @@ -23,8 +20,8 @@ jobs: CERT_ENCRYPTION_KEY: ${{ secrets.CERT_ENCRYPTION_PWD }} # gpg로 파일 암호화할 때 사용한 암호 # provisioning - ENCRYPTED_PROVISION_FILE_PATH: ${{ '.github/secrets/' }}${{ env.PROJECT_NAME }}GithubActions.mobileprovision.gpg - DECRYPTED_PROVISION_FILE_PATH: ${{ '.github/secrets/' }}${{ env.PROJECT_NAME }}GithubActions.mobileprovision + ENCRYPTED_PROVISION_FILE_PATH: '.github/secrets/Poppool.mobileprovision.gpg' + DECRYPTED_PROVISION_FILE_PATH: '.github/secrets/PoppoolGithubActions.mobileprovision' PROVISIONING_ENCRYPTION_KEY: ${{ secrets.PROVISION_ENCRYPTION_PWD }} # gpg로 파일 암호화할 때 사용한 암호 # certification export key From 841c9edc042314803e110da50a8e0fb94be54fe3 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 9 Apr 2025 20:10:22 +0900 Subject: [PATCH 73/95] =?UTF-8?q?fix/#103:=20=ED=94=84=EB=A1=9C=EB=B9=84?= =?UTF-8?q?=EC=A0=80=EB=8B=9D=20=EA=B2=BD=EB=A1=9C=20=EC=9D=B4=EC=83=81=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy_on_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy_on_release.yml b/.github/workflows/deploy_on_release.yml index fe1e3f0d..2e160fe4 100644 --- a/.github/workflows/deploy_on_release.yml +++ b/.github/workflows/deploy_on_release.yml @@ -20,7 +20,7 @@ jobs: CERT_ENCRYPTION_KEY: ${{ secrets.CERT_ENCRYPTION_PWD }} # gpg로 파일 암호화할 때 사용한 암호 # provisioning - ENCRYPTED_PROVISION_FILE_PATH: '.github/secrets/Poppool.mobileprovision.gpg' + ENCRYPTED_PROVISION_FILE_PATH: '.github/secrets/PoppoolGithubActions.mobileprovision.gpg' DECRYPTED_PROVISION_FILE_PATH: '.github/secrets/PoppoolGithubActions.mobileprovision' PROVISIONING_ENCRYPTION_KEY: ${{ secrets.PROVISION_ENCRYPTION_PWD }} # gpg로 파일 암호화할 때 사용한 암호 From 101b203c46c87caaa670004a393604c07e187667 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 9 Apr 2025 20:13:02 +0900 Subject: [PATCH 74/95] =?UTF-8?q?fix/#103:=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=EB=8B=A4=EC=8B=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 멍청하게 파일명을 잘못 넣음 ㅠ --- .github/workflows/deploy_on_release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy_on_release.yml b/.github/workflows/deploy_on_release.yml index 2e160fe4..e9c91e99 100644 --- a/.github/workflows/deploy_on_release.yml +++ b/.github/workflows/deploy_on_release.yml @@ -20,8 +20,8 @@ jobs: CERT_ENCRYPTION_KEY: ${{ secrets.CERT_ENCRYPTION_PWD }} # gpg로 파일 암호화할 때 사용한 암호 # provisioning - ENCRYPTED_PROVISION_FILE_PATH: '.github/secrets/PoppoolGithubActions.mobileprovision.gpg' - DECRYPTED_PROVISION_FILE_PATH: '.github/secrets/PoppoolGithubActions.mobileprovision' + ENCRYPTED_PROVISION_FILE_PATH: '.github/secrets/PoppoolGitHubAction.mobileprovision.gpg' + DECRYPTED_PROVISION_FILE_PATH: '.github/secrets/PoppoolGitHubAction.mobileprovision' PROVISIONING_ENCRYPTION_KEY: ${{ secrets.PROVISION_ENCRYPTION_PWD }} # gpg로 파일 암호화할 때 사용한 암호 # certification export key From 691c3cad667098735a003d805e954ad17b1b5ab1 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 9 Apr 2025 20:20:18 +0900 Subject: [PATCH 75/95] =?UTF-8?q?fix/#103:=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=ED=99=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 자동화 코드에서 xcconfig를 생성하도록 수정 - 프로젝트 설정에 프로비저닝을 빌드용으로 변경 --- .github/workflows/deploy_on_release.yml | 10 ++++++++++ Poppool/Poppool.xcodeproj/project.pbxproj | 10 +++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy_on_release.yml b/.github/workflows/deploy_on_release.yml index e9c91e99..0d49db4d 100644 --- a/.github/workflows/deploy_on_release.yml +++ b/.github/workflows/deploy_on_release.yml @@ -36,6 +36,16 @@ jobs: - name: 🛠️ Set up Xcode # Xcode 16.2 선택 run: sudo xcode-select -s /Applications/Xcode_16.2.app + - name: ⚙️ Generate xcconfig + run: | + cat < Poppool/Poppool/Resource/Debug.xcconfig + KAKAO_AUTH_APP_KEY=${{ secrets.KAKAO_AUTH_APP_KEY }} + NAVER_MAP_CLIENT_ID=${{ secrets.NAVER_MAP_CLIENT_ID }} + POPPOOL_BASE_URL=${{ secrets.POPPOOL_BASE_URL }} + POPPOOL_S3_BASE_URL=${{ secrets.POPPOOL_S3_BASE_URL }} + POPPOOL_API_KEY=${{ secrets.POPPOOL_API_KEY }} + EOF + - name: 🔑 Configure Keychain # 키체인 초기화 -> 임시 키체인 생성 run: | security create-keychain -p "" "$KEYCHAIN" diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index 91185e2b..e9518457 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -3359,7 +3359,6 @@ BD9103652CF6149D00BBCCAE /* HomeAPIEndpoint.swift in Sources */, 086DD8E32CFF356300B97D3B /* HomeCardGridSection.swift in Sources */, 0841BABE2CFB5AA600049E31 /* Date?+.swift in Sources */, - 083A258D2CF361F90099B58E /* ConventionCollectionViewCell.swift in Sources */, 08CFD3922D9BDE99004CDD50 /* DiskStorage.swift in Sources */, 4E685EE12D12CEB6001EF91C /* StoreListReactor.swift in Sources */, 4E685EE52D12CEB6001EF91C /* MapMarker.swift in Sources */, @@ -3676,6 +3675,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Apple Distribution"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; @@ -3706,7 +3706,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Poppool/Poppool.entitlements; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; @@ -3732,7 +3732,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.poppoolIOS.poppool; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = poppoolProvisioningProfile; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = PoppoolGitHubAction; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -3750,7 +3750,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Poppool/Poppool.entitlements; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; @@ -3776,7 +3776,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.poppoolIOS.poppool; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = poppoolProvisioningProfile; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = PoppoolGitHubAction; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; From 60ff137eb1dd9a061dae3ca00398ccf5e0b93320 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 9 Apr 2025 20:38:42 +0900 Subject: [PATCH 76/95] =?UTF-8?q?fix/#103:=20=EB=B9=A0=EC=A0=B8=EC=9E=88?= =?UTF-8?q?=EB=8D=98=20=EC=B6=94=EC=B6=9C=20=EC=98=B5=EC=85=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EC=A0=81?= =?UTF-8?q?=20=EA=B2=BD=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/secrets/ExportOptions.plist | 29 +++++++++++++++++++++++++ .github/workflows/deploy_on_release.yml | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 .github/secrets/ExportOptions.plist diff --git a/.github/secrets/ExportOptions.plist b/.github/secrets/ExportOptions.plist new file mode 100644 index 00000000..940c9086 --- /dev/null +++ b/.github/secrets/ExportOptions.plist @@ -0,0 +1,29 @@ + + + + + destination + export + manageAppVersionAndBuildNumber + + method + app-store-connect + provisioningProfiles + + com.poppoolIOS.poppool + PoppoolGitHubAction + + signingCertificate + 82F980617C0479150A4BCB89DC90498DCB319F8F + signingStyle + manual + stripSwiftSymbols + + teamID + W5QTRMS954 + testFlightInternalTestingOnly + + uploadSymbols + + + diff --git a/.github/workflows/deploy_on_release.yml b/.github/workflows/deploy_on_release.yml index 0d49db4d..9e4ca74f 100644 --- a/.github/workflows/deploy_on_release.yml +++ b/.github/workflows/deploy_on_release.yml @@ -84,7 +84,7 @@ jobs: - name: ⬆️ Export app # export 를 통해 ipa 파일 만듦 run: | - xcodebuild -exportArchive -archivePath $XC_ARCHIVE -exportOptionsPlist ExportOptions.plist -exportPath . -allowProvisioningUpdates + xcodebuild -exportArchive -archivePath $XC_ARCHIVE -exportOptionsPlist .github/secrets/ExportOptions.plist -exportPath . -allowProvisioningUpdates - name: 🚀 Upload app to TestFlight # TestFlight에 아카이브된 앱 등록 uses: apple-actions/upload-testflight-build@v1 From e7aea50c2ec66492d243307341fda95d74576d45 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 9 Apr 2025 21:25:20 +0900 Subject: [PATCH 77/95] =?UTF-8?q?fix/#103:=20ipa=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EB=AA=85=20=EC=B6=94=EC=A0=81=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy_on_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy_on_release.yml b/.github/workflows/deploy_on_release.yml index 9e4ca74f..aa6896da 100644 --- a/.github/workflows/deploy_on_release.yml +++ b/.github/workflows/deploy_on_release.yml @@ -89,7 +89,7 @@ jobs: - name: 🚀 Upload app to TestFlight # TestFlight에 아카이브된 앱 등록 uses: apple-actions/upload-testflight-build@v1 with: - app-path: '${{ env.PROJECT_NAME }}.ipa' + app-path: 'Poppool.ipa' issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }} api-key-id: ${{ secrets.APPSTORE_API_KEY_ID }} api-private-key: ${{ secrets.APPSTORE_API_PRIVATE_KEY }} From 93827b100fc991ee4e35803c49f45b00acc714e5 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 9 Apr 2025 21:32:12 +0900 Subject: [PATCH 78/95] =?UTF-8?q?fix/#103:=20=EB=B9=8C=EB=93=9C=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EC=A4=91=EB=B3=B5=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 테플에서 중복된 빌드 번호가 중복될 경우 배포가 안되는 문제를 해결하기 위해 빌드번호를 자동으로 추가하도록 수정 --- .github/workflows/deploy_on_release.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/deploy_on_release.yml b/.github/workflows/deploy_on_release.yml index aa6896da..9f7493f6 100644 --- a/.github/workflows/deploy_on_release.yml +++ b/.github/workflows/deploy_on_release.yml @@ -78,6 +78,11 @@ jobs: cp "./$PROVISION" "$HOME/Library/MobileDevice/Provisioning Profiles/$UUID.mobileprovision" done + - name: 🔢 Bump Build Number + run: | + BUILD_NUMBER=$(date +%s) + /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" Poppool/Poppool/Info.plist + - name: ⬇️ Archive app # 빌드 및 아카이브 run: | xcodebuild clean archive -project $XC_PROJECT -scheme $XC_SCHEME -configuration release -archivePath $XC_ARCHIVE From 0be45d3ab0e24e52f695d8fb9c50880951db6548 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 9 Apr 2025 21:35:39 +0900 Subject: [PATCH 79/95] =?UTF-8?q?fix/#103:=20info.plsit=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy_on_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy_on_release.yml b/.github/workflows/deploy_on_release.yml index 9f7493f6..e8539296 100644 --- a/.github/workflows/deploy_on_release.yml +++ b/.github/workflows/deploy_on_release.yml @@ -81,7 +81,7 @@ jobs: - name: 🔢 Bump Build Number run: | BUILD_NUMBER=$(date +%s) - /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" Poppool/Poppool/Info.plist + /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" Poppool/Poppool/Resource/Info.plist - name: ⬇️ Archive app # 빌드 및 아카이브 run: | From 831807da86198bbbeb232426211de76f0d0ea683 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 9 Apr 2025 21:52:45 +0900 Subject: [PATCH 80/95] =?UTF-8?q?fix/#103:=20=EB=B9=8C=EB=93=9C=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84=20=EC=9E=90=EB=8F=99=20=EC=A6=9D=EA=B0=80=20?= =?UTF-8?q?run=20script=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 빌드 번호 증가를 위한 Actions step 제거 --- .github/workflows/deploy_on_release.yml | 5 ----- Poppool/Poppool.xcodeproj/project.pbxproj | 19 +++++++++++++++++ Poppool/Poppool/Resource/Info.plist | 26 ++++++++++++++--------- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/.github/workflows/deploy_on_release.yml b/.github/workflows/deploy_on_release.yml index e8539296..aa6896da 100644 --- a/.github/workflows/deploy_on_release.yml +++ b/.github/workflows/deploy_on_release.yml @@ -78,11 +78,6 @@ jobs: cp "./$PROVISION" "$HOME/Library/MobileDevice/Provisioning Profiles/$UUID.mobileprovision" done - - name: 🔢 Bump Build Number - run: | - BUILD_NUMBER=$(date +%s) - /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" Poppool/Poppool/Resource/Info.plist - - name: ⬇️ Archive app # 빌드 및 아카이브 run: | xcodebuild clean archive -project $XC_PROJECT -scheme $XC_SCHEME -configuration release -archivePath $XC_ARCHIVE diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index e9518457..3a4203bc 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -2992,6 +2992,7 @@ buildConfigurationList = BDCA41E72CF35AC2005EECF6 /* Build configuration list for PBXNativeTarget "Poppool" */; buildPhases = ( 05229DCF2D99507C00D88E73 /* Run SwiftLint */, + 051E7D992DA6A23600F92DA8 /* Run Bump build version */, BDCA41B92CF35AC0005EECF6 /* Sources */, BDCA41BA2CF35AC0005EECF6 /* Frameworks */, BDCA41BB2CF35AC0005EECF6 /* Resources */, @@ -3100,6 +3101,24 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 051E7D992DA6A23600F92DA8 /* Run Bump build version */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run Bump build version"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "#!/bin/bash\n\nINFO_PLIST=\"$INFOPLIST_FILE\"\n\n# 기존 값 가져오기\nbuildDay=$(/usr/libexec/PlistBuddy -c \"Print buildDay\" \"$INFO_PLIST\" 2>/dev/null)\nbuildCount=$(/usr/libexec/PlistBuddy -c \"Print buildCount\" \"$INFO_PLIST\" 2>/dev/null)\ntoday=$(date +%Y%m%d)\n\n# 첫 실행 또는 키 없음\nif [ -z \"$buildDay\" ]; then\n buildDay=$today\n buildCount=1\nelse\n if [ \"$buildDay\" != \"$today\" ]; then\n buildDay=$today\n buildCount=1\n else\n buildCount=$((buildCount + 1))\n fi\nfi\n\nprintf -v zeroPadBuildCount \"%03d\" $buildCount\nbuildNumber=\"${buildDay}${zeroPadBuildCount}\"\n\n# Info.plist 업데이트\n/usr/libexec/PlistBuddy -c \"Delete buildDay\" \"$INFO_PLIST\" 2>/dev/null\n/usr/libexec/PlistBuddy -c \"Add buildDay string $buildDay\" \"$INFO_PLIST\"\n\n/usr/libexec/PlistBuddy -c \"Delete buildCount\" \"$INFO_PLIST\" 2>/dev/null\n/usr/libexec/PlistBuddy -c \"Add buildCount string $buildCount\" \"$INFO_PLIST\"\n\n/usr/libexec/PlistBuddy -c \"Delete CFBundleVersion\" \"$INFO_PLIST\" 2>/dev/null\n/usr/libexec/PlistBuddy -c \"Add CFBundleVersion string $buildNumber\" \"$INFO_PLIST\"\n\necho \"✅ CFBundleVersion set to $buildNumber\"\n"; + }; 05229DCF2D99507C00D88E73 /* Run SwiftLint */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; diff --git a/Poppool/Poppool/Resource/Info.plist b/Poppool/Poppool/Resource/Info.plist index c4884906..d5ec3550 100644 --- a/Poppool/Poppool/Resource/Info.plist +++ b/Poppool/Poppool/Resource/Info.plist @@ -2,16 +2,6 @@ - KAKAO_AUTH_APP_KEY - ${KAKAO_AUTH_APP_KEY} - POPPOOL_BASE_URL - ${POPPOOL_BASE_URL} - POPPOOL_S3_BASE_URL - ${POPPOOL_S3_BASE_URL} - POPPOOL_API_KEY - ${POPPOOL_API_KEY} - NAVER_MAP_CLIENT_ID - ${NAVER_MAP_CLIENT_ID} CFBundleURLTypes @@ -23,8 +13,12 @@ + CFBundleVersion + 20250409004 ITSAppUsesNonExemptEncryption + KAKAO_AUTH_APP_KEY + ${KAKAO_AUTH_APP_KEY} LSApplicationQueriesSchemes instagram @@ -35,6 +29,14 @@ kakaokompassauth kakaotalk + NAVER_MAP_CLIENT_ID + ${NAVER_MAP_CLIENT_ID} + POPPOOL_API_KEY + ${POPPOOL_API_KEY} + POPPOOL_BASE_URL + ${POPPOOL_BASE_URL} + POPPOOL_S3_BASE_URL + ${POPPOOL_S3_BASE_URL} UIAppFonts GothicA1-Bold.ttf @@ -63,5 +65,9 @@ + buildCount + 4 + buildDay + 20250409 From 50c8334fc2a0f98d470b80d10ae707603fbb0887 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 9 Apr 2025 22:13:35 +0900 Subject: [PATCH 81/95] =?UTF-8?q?fix/#103:=20=EB=B9=8C=EB=93=9C=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=A6=9D=EA=B0=80=20Run=20Script=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 같이 변경된 info도 동반할게요 --- Poppool/Poppool.xcodeproj/project.pbxproj | 2 +- Poppool/Poppool/Resource/Info.plist | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index 3a4203bc..154f7baf 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -3117,7 +3117,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "#!/bin/bash\n\nINFO_PLIST=\"$INFOPLIST_FILE\"\n\n# 기존 값 가져오기\nbuildDay=$(/usr/libexec/PlistBuddy -c \"Print buildDay\" \"$INFO_PLIST\" 2>/dev/null)\nbuildCount=$(/usr/libexec/PlistBuddy -c \"Print buildCount\" \"$INFO_PLIST\" 2>/dev/null)\ntoday=$(date +%Y%m%d)\n\n# 첫 실행 또는 키 없음\nif [ -z \"$buildDay\" ]; then\n buildDay=$today\n buildCount=1\nelse\n if [ \"$buildDay\" != \"$today\" ]; then\n buildDay=$today\n buildCount=1\n else\n buildCount=$((buildCount + 1))\n fi\nfi\n\nprintf -v zeroPadBuildCount \"%03d\" $buildCount\nbuildNumber=\"${buildDay}${zeroPadBuildCount}\"\n\n# Info.plist 업데이트\n/usr/libexec/PlistBuddy -c \"Delete buildDay\" \"$INFO_PLIST\" 2>/dev/null\n/usr/libexec/PlistBuddy -c \"Add buildDay string $buildDay\" \"$INFO_PLIST\"\n\n/usr/libexec/PlistBuddy -c \"Delete buildCount\" \"$INFO_PLIST\" 2>/dev/null\n/usr/libexec/PlistBuddy -c \"Add buildCount string $buildCount\" \"$INFO_PLIST\"\n\n/usr/libexec/PlistBuddy -c \"Delete CFBundleVersion\" \"$INFO_PLIST\" 2>/dev/null\n/usr/libexec/PlistBuddy -c \"Add CFBundleVersion string $buildNumber\" \"$INFO_PLIST\"\n\necho \"✅ CFBundleVersion set to $buildNumber\"\n"; + shellScript = "buildDay=$(/usr/libexec/PlistBuddy -c \"Print buildDay\" \"$INFOPLIST_FILE\")\nbuildCount=$(/usr/libexec/PlistBuddy -c \"Print buildCount\" \"$INFOPLIST_FILE\")\ntoday=$(date +%Y%m%d)\n\nif [ x$buildDay == x ]; then\n buildDay=${today}\n buildCount=1\n buildNumber=${buildDay}${buildCount}\n\n /usr/libexec/PlistBuddy -c \"Add :buildDay string $buildDay\" \"$INFOPLIST_FILE\"\n /usr/libexec/PlistBuddy -c \"Add :buildCount string $buildCount\" \"$INFOPLIST_FILE\"\n /usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"$INFOPLIST_FILE\"\n\nelif [ $buildDay != $today ]; then\n buildDay=${today}\n buildCount=1\n buildNumber=${buildDay}${buildCount}\n\n /usr/libexec/PlistBuddy -c \"Set :buildDay $buildDay\" \"$INFOPLIST_FILE\"\n /usr/libexec/PlistBuddy -c \"Set :buildCount $buildCount\" \"$INFOPLIST_FILE\"\n /usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"$INFOPLIST_FILE\"\n\nelse\n buildCount=$(($buildCount + 1))\n buildNumber=${buildDay}${buildCount}\n\n /usr/libexec/PlistBuddy -c \"Set :buildDay $buildDay\" \"$INFOPLIST_FILE\"\n /usr/libexec/PlistBuddy -c \"Set :buildCount $buildCount\" \"$INFOPLIST_FILE\"\n /usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"$INFOPLIST_FILE\"\n\nfi\n"; }; 05229DCF2D99507C00D88E73 /* Run SwiftLint */ = { isa = PBXShellScriptBuildPhase; diff --git a/Poppool/Poppool/Resource/Info.plist b/Poppool/Poppool/Resource/Info.plist index d5ec3550..194184c8 100644 --- a/Poppool/Poppool/Resource/Info.plist +++ b/Poppool/Poppool/Resource/Info.plist @@ -14,7 +14,7 @@ CFBundleVersion - 20250409004 + 2025040911 ITSAppUsesNonExemptEncryption KAKAO_AUTH_APP_KEY @@ -66,7 +66,7 @@ buildCount - 4 + 11 buildDay 20250409 From ad0c4eb5c4ff9c7c064ee048a93dc2087029b4ea Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 9 Apr 2025 22:25:46 +0900 Subject: [PATCH 82/95] =?UTF-8?q?chore/#103:=20=EB=B9=8C=EB=93=9C=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20echo=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy_on_release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/deploy_on_release.yml b/.github/workflows/deploy_on_release.yml index aa6896da..620e9725 100644 --- a/.github/workflows/deploy_on_release.yml +++ b/.github/workflows/deploy_on_release.yml @@ -86,6 +86,10 @@ jobs: run: | xcodebuild -exportArchive -archivePath $XC_ARCHIVE -exportOptionsPlist .github/secrets/ExportOptions.plist -exportPath . -allowProvisioningUpdates + - name: 🔍 Check final CFBundleVersion + run: | + /usr/libexec/PlistBuddy -c "Print :CFBundleVersion" Poppool.xcarchive/Products/Applications/Poppool.app/Info.plist + - name: 🚀 Upload app to TestFlight # TestFlight에 아카이브된 앱 등록 uses: apple-actions/upload-testflight-build@v1 with: From ec97e621bac410b207296b4aa78424553e010fe8 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 9 Apr 2025 22:40:31 +0900 Subject: [PATCH 83/95] =?UTF-8?q?fix/#103:=20=EB=B9=8C=EB=93=9C=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EC=A6=9D=EA=B0=80=EB=A5=BC=20CD=EC=97=90?= =?UTF-8?q?=20=EB=A7=A1=EA=B8=B0=EB=8F=84=EB=A1=9D=20=EC=9B=90=EB=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy_on_release.yml | 5 +++++ Poppool/Poppool.xcodeproj/project.pbxproj | 19 ------------------- Poppool/Poppool/Resource/Info.plist | 6 ------ 3 files changed, 5 insertions(+), 25 deletions(-) diff --git a/.github/workflows/deploy_on_release.yml b/.github/workflows/deploy_on_release.yml index 620e9725..30823858 100644 --- a/.github/workflows/deploy_on_release.yml +++ b/.github/workflows/deploy_on_release.yml @@ -78,6 +78,11 @@ jobs: cp "./$PROVISION" "$HOME/Library/MobileDevice/Provisioning Profiles/$UUID.mobileprovision" done + - name: 🔢 Set Build Number to datetime + run: | + BUILD_NUMBER=$(date +%Y%m%d%H%M) + /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" Poppool/Poppool/ResourceInfo.plist + - name: ⬇️ Archive app # 빌드 및 아카이브 run: | xcodebuild clean archive -project $XC_PROJECT -scheme $XC_SCHEME -configuration release -archivePath $XC_ARCHIVE diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index 154f7baf..e9518457 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -2992,7 +2992,6 @@ buildConfigurationList = BDCA41E72CF35AC2005EECF6 /* Build configuration list for PBXNativeTarget "Poppool" */; buildPhases = ( 05229DCF2D99507C00D88E73 /* Run SwiftLint */, - 051E7D992DA6A23600F92DA8 /* Run Bump build version */, BDCA41B92CF35AC0005EECF6 /* Sources */, BDCA41BA2CF35AC0005EECF6 /* Frameworks */, BDCA41BB2CF35AC0005EECF6 /* Resources */, @@ -3101,24 +3100,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 051E7D992DA6A23600F92DA8 /* Run Bump build version */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Run Bump build version"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "buildDay=$(/usr/libexec/PlistBuddy -c \"Print buildDay\" \"$INFOPLIST_FILE\")\nbuildCount=$(/usr/libexec/PlistBuddy -c \"Print buildCount\" \"$INFOPLIST_FILE\")\ntoday=$(date +%Y%m%d)\n\nif [ x$buildDay == x ]; then\n buildDay=${today}\n buildCount=1\n buildNumber=${buildDay}${buildCount}\n\n /usr/libexec/PlistBuddy -c \"Add :buildDay string $buildDay\" \"$INFOPLIST_FILE\"\n /usr/libexec/PlistBuddy -c \"Add :buildCount string $buildCount\" \"$INFOPLIST_FILE\"\n /usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"$INFOPLIST_FILE\"\n\nelif [ $buildDay != $today ]; then\n buildDay=${today}\n buildCount=1\n buildNumber=${buildDay}${buildCount}\n\n /usr/libexec/PlistBuddy -c \"Set :buildDay $buildDay\" \"$INFOPLIST_FILE\"\n /usr/libexec/PlistBuddy -c \"Set :buildCount $buildCount\" \"$INFOPLIST_FILE\"\n /usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"$INFOPLIST_FILE\"\n\nelse\n buildCount=$(($buildCount + 1))\n buildNumber=${buildDay}${buildCount}\n\n /usr/libexec/PlistBuddy -c \"Set :buildDay $buildDay\" \"$INFOPLIST_FILE\"\n /usr/libexec/PlistBuddy -c \"Set :buildCount $buildCount\" \"$INFOPLIST_FILE\"\n /usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"$INFOPLIST_FILE\"\n\nfi\n"; - }; 05229DCF2D99507C00D88E73 /* Run SwiftLint */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; diff --git a/Poppool/Poppool/Resource/Info.plist b/Poppool/Poppool/Resource/Info.plist index 194184c8..ab02d47f 100644 --- a/Poppool/Poppool/Resource/Info.plist +++ b/Poppool/Poppool/Resource/Info.plist @@ -13,8 +13,6 @@ - CFBundleVersion - 2025040911 ITSAppUsesNonExemptEncryption KAKAO_AUTH_APP_KEY @@ -65,9 +63,5 @@ - buildCount - 11 - buildDay - 20250409 From c8447cc3f5612b6122f9d0039be53d31f20cda20 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 9 Apr 2025 22:42:36 +0900 Subject: [PATCH 84/95] =?UTF-8?q?chore/#103:=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy_on_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy_on_release.yml b/.github/workflows/deploy_on_release.yml index 30823858..95812bf2 100644 --- a/.github/workflows/deploy_on_release.yml +++ b/.github/workflows/deploy_on_release.yml @@ -78,7 +78,7 @@ jobs: cp "./$PROVISION" "$HOME/Library/MobileDevice/Provisioning Profiles/$UUID.mobileprovision" done - - name: 🔢 Set Build Number to datetime + - name: 🔢 Set Build Number # 빌드 번호 자동 수정 run: | BUILD_NUMBER=$(date +%Y%m%d%H%M) /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" Poppool/Poppool/ResourceInfo.plist From 8233967f63396558d118044f31954eb6504af493 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 9 Apr 2025 22:44:22 +0900 Subject: [PATCH 85/95] =?UTF-8?q?fix/#103:=20plist=EA=B2=BD=EB=A1=9C=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy_on_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy_on_release.yml b/.github/workflows/deploy_on_release.yml index 95812bf2..036a3a30 100644 --- a/.github/workflows/deploy_on_release.yml +++ b/.github/workflows/deploy_on_release.yml @@ -81,7 +81,7 @@ jobs: - name: 🔢 Set Build Number # 빌드 번호 자동 수정 run: | BUILD_NUMBER=$(date +%Y%m%d%H%M) - /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" Poppool/Poppool/ResourceInfo.plist + /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" Poppool/Poppool/Resource/Info.plist - name: ⬇️ Archive app # 빌드 및 아카이브 run: | From d5333c196c4e62c5652c4d026fa44553d546cf27 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 9 Apr 2025 22:51:34 +0900 Subject: [PATCH 86/95] =?UTF-8?q?fix/#103:=20=EB=B2=88=EB=93=A4=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84=20=EC=97=86=EC=9C=BC=EB=A9=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy_on_release.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy_on_release.yml b/.github/workflows/deploy_on_release.yml index 036a3a30..dc037a12 100644 --- a/.github/workflows/deploy_on_release.yml +++ b/.github/workflows/deploy_on_release.yml @@ -81,7 +81,13 @@ jobs: - name: 🔢 Set Build Number # 빌드 번호 자동 수정 run: | BUILD_NUMBER=$(date +%Y%m%d%H%M) - /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" Poppool/Poppool/Resource/Info.plist + PLIST_PATH="Poppool/Poppool/Resource/Info.plist" + + if /usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$PLIST_PATH"; then + /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" "$PLIST_PATH" + else + /usr/libexec/PlistBuddy -c "Add :CFBundleVersion string $BUILD_NUMBER" "$PLIST_PATH" + fi - name: ⬇️ Archive app # 빌드 및 아카이브 run: | From c18a7d40e7156ce4cc423319299d47942775909f Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Wed, 9 Apr 2025 23:03:18 +0900 Subject: [PATCH 87/95] =?UTF-8?q?fix/#103:=20=EC=84=A4=EB=A7=88=20?= =?UTF-8?q?=EB=84=88=EB=83=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Poppool/Poppool.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index e9518457..74b1e0a7 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -3708,7 +3708,7 @@ CODE_SIGN_ENTITLEMENTS = Poppool/Poppool.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 20; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5QTRMS954; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3752,7 +3752,7 @@ CODE_SIGN_ENTITLEMENTS = Poppool/Poppool.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 20; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5QTRMS954; ENABLE_USER_SCRIPT_SANDBOXING = NO; From 58205d21ba39fb61cc8d20d0bba6edac00c1cc4c Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Thu, 10 Apr 2025 02:27:43 +0900 Subject: [PATCH 88/95] =?UTF-8?q?fix/#103:=20=EC=9E=90=EB=8F=99=20?= =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EB=B2=88=ED=98=B8=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EC=A0=95=EC=83=81=ED=99=94(=EC=B0=90=EB=A7=89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 빌드 번호 자동 증가를 run script에서 하도록 수정 - run script는 archive 동작에만 변경되도록 수정 - info.plist에 CFBundleVersion, CFBundleShortVersion 추가 --- .github/workflows/deploy_on_release.yml | 11 --------- Poppool/Poppool.xcodeproj/project.pbxproj | 27 +++++++++++++++++++---- Poppool/Poppool/Resource/Info.plist | 6 +++-- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/.github/workflows/deploy_on_release.yml b/.github/workflows/deploy_on_release.yml index dc037a12..620e9725 100644 --- a/.github/workflows/deploy_on_release.yml +++ b/.github/workflows/deploy_on_release.yml @@ -78,17 +78,6 @@ jobs: cp "./$PROVISION" "$HOME/Library/MobileDevice/Provisioning Profiles/$UUID.mobileprovision" done - - name: 🔢 Set Build Number # 빌드 번호 자동 수정 - run: | - BUILD_NUMBER=$(date +%Y%m%d%H%M) - PLIST_PATH="Poppool/Poppool/Resource/Info.plist" - - if /usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$PLIST_PATH"; then - /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" "$PLIST_PATH" - else - /usr/libexec/PlistBuddy -c "Add :CFBundleVersion string $BUILD_NUMBER" "$PLIST_PATH" - fi - - name: ⬇️ Archive app # 빌드 및 아카이브 run: | xcodebuild clean archive -project $XC_PROJECT -scheme $XC_SCHEME -configuration release -archivePath $XC_ARCHIVE diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index 74b1e0a7..b031f273 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -2991,6 +2991,7 @@ isa = PBXNativeTarget; buildConfigurationList = BDCA41E72CF35AC2005EECF6 /* Build configuration list for PBXNativeTarget "Poppool" */; buildPhases = ( + 051E7DD22DA6DD8100F92DA8 /* Run Build Bump Script */, 05229DCF2D99507C00D88E73 /* Run SwiftLint */, BDCA41B92CF35AC0005EECF6 /* Sources */, BDCA41BA2CF35AC0005EECF6 /* Frameworks */, @@ -3100,6 +3101,24 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 051E7DD22DA6DD8100F92DA8 /* Run Build Bump Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run Build Bump Script"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "#!/bin/bash\n\n# Only run during Archive\nif [ \"$ACTION\" != \"install\" ]; then\n echo \"Skipping build number bump (not archiving).\"\n exit 0\nfi\n\n# Get current date in YYMMDD format\ncurrentDate=$(date +%y%m%d)\n\n# Path to Info.plist\nplistPath=\"${PROJECT_DIR}/${INFOPLIST_FILE}\"\n\n# Get current build number from plist\ncurrentBuild=$(/usr/libexec/PlistBuddy -c \"Print CFBundleVersion\" \"$plistPath\" 2>/dev/null)\n\n# Extract previous date and count if formatted correctly\nif [[ \"$currentBuild\" == *\".\"* ]]; then\n previousDate=$(echo \"$currentBuild\" | cut -d '.' -f 1)\n previousCount=$(echo \"$currentBuild\" | cut -d '.' -f 2)\nelse\n previousDate=\"\"\n previousCount=0\nfi\n\n# If the date has changed, reset count to 1, otherwise increment\nif [[ \"$previousDate\" == \"$currentDate\" ]]; then\n newCount=$((previousCount + 1))\nelse\n newCount=1\nfi\n\n# Combine into new build number\nnewBuild=\"${currentDate}.${newCount}\"\n\n# Set new build number in plist\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $newBuild\" \"$plistPath\"\n\necho \"Build number set to $newBuild\"\n"; + }; 05229DCF2D99507C00D88E73 /* Run SwiftLint */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -3708,12 +3727,12 @@ CODE_SIGN_ENTITLEMENTS = Poppool/Poppool.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 20; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5QTRMS954; ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Poppool/Resource/Info.plist; + INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_NSCameraUsageDescription = "사용자가 프로필 사진을 업로드하거나 댓글에 사진을 추가할 수 있도록 카메라를 사용합니다."; INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "사용자의 현재 위치를 기반으로 주변 게시글을 추천하는 데 사용됩니다."; INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "사용자가 프로필 사진을 업로드하거나 댓글에 사진을 추가할 수 있도록 사진라이브러리에 접근합니다."; @@ -3728,7 +3747,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; + MARKETING_VERSION = 1.0.1; PRODUCT_BUNDLE_IDENTIFIER = com.poppoolIOS.poppool; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3752,12 +3771,12 @@ CODE_SIGN_ENTITLEMENTS = Poppool/Poppool.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 20; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5QTRMS954; ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Poppool/Resource/Info.plist; + INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_NSCameraUsageDescription = "사용자가 프로필 사진을 업로드하거나 댓글에 사진을 추가할 수 있도록 카메라를 사용합니다."; INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "사용자의 현재 위치를 기반으로 주변 게시글을 추천하는 데 사용됩니다."; INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "사용자가 프로필 사진을 업로드하거나 댓글에 사진을 추가할 수 있도록 사진라이브러리에 접근합니다."; @@ -3772,7 +3791,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; + MARKETING_VERSION = 1.0.1; PRODUCT_BUNDLE_IDENTIFIER = com.poppoolIOS.poppool; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Poppool/Poppool/Resource/Info.plist b/Poppool/Poppool/Resource/Info.plist index ab02d47f..2529719b 100644 --- a/Poppool/Poppool/Resource/Info.plist +++ b/Poppool/Poppool/Resource/Info.plist @@ -2,6 +2,8 @@ + CFBundleShortVersionString + $(MARKETING_VERSION) CFBundleURLTypes @@ -13,8 +15,8 @@ - ITSAppUsesNonExemptEncryption - + CFBundleVersion + 250410.3 KAKAO_AUTH_APP_KEY ${KAKAO_AUTH_APP_KEY} LSApplicationQueriesSchemes From 6d463b9bf2b58fdbf8c30150c35138ca3b386700 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Thu, 10 Apr 2025 23:20:54 +0900 Subject: [PATCH 89/95] =?UTF-8?q?fix/#103:=20agvtool=EC=9D=84=20=EC=9D=B4?= =?UTF-8?q?=EC=9A=A9=ED=95=B4=EC=84=9C=20=EB=B9=8C=EB=93=9C=20=EB=B2=94?= =?UTF-8?q?=ED=94=84=EB=A5=BC=20=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bundle version이 CFBundleVersion을 따르지 않았음 - Info를 기반으로 pbxproj를 수정하는 방식은 옳지 않다 생각했음 - 커스텀된 빌드 버전 넘버링을 버리고 agvtool을 이용하도록 수정 --- .github/workflows/deploy_on_release.yml | 3 +++ Poppool/Poppool.xcodeproj/project.pbxproj | 23 ++++------------------- Poppool/Poppool/Resource/Info.plist | 2 +- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/.github/workflows/deploy_on_release.yml b/.github/workflows/deploy_on_release.yml index 620e9725..f67a25a4 100644 --- a/.github/workflows/deploy_on_release.yml +++ b/.github/workflows/deploy_on_release.yml @@ -36,6 +36,9 @@ jobs: - name: 🛠️ Set up Xcode # Xcode 16.2 선택 run: sudo xcode-select -s /Applications/Xcode_16.2.app + - name: 📈 Bump Bundle Version # agvtool을 사용하여 번들 버전을 자동 증가시킴 + run: agvtool next-version + - name: ⚙️ Generate xcconfig run: | cat < Poppool/Poppool/Resource/Debug.xcconfig diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index b031f273..99403291 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -2991,7 +2991,6 @@ isa = PBXNativeTarget; buildConfigurationList = BDCA41E72CF35AC2005EECF6 /* Build configuration list for PBXNativeTarget "Poppool" */; buildPhases = ( - 051E7DD22DA6DD8100F92DA8 /* Run Build Bump Script */, 05229DCF2D99507C00D88E73 /* Run SwiftLint */, BDCA41B92CF35AC0005EECF6 /* Sources */, BDCA41BA2CF35AC0005EECF6 /* Frameworks */, @@ -3101,24 +3100,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 051E7DD22DA6DD8100F92DA8 /* Run Build Bump Script */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Run Build Bump Script"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "#!/bin/bash\n\n# Only run during Archive\nif [ \"$ACTION\" != \"install\" ]; then\n echo \"Skipping build number bump (not archiving).\"\n exit 0\nfi\n\n# Get current date in YYMMDD format\ncurrentDate=$(date +%y%m%d)\n\n# Path to Info.plist\nplistPath=\"${PROJECT_DIR}/${INFOPLIST_FILE}\"\n\n# Get current build number from plist\ncurrentBuild=$(/usr/libexec/PlistBuddy -c \"Print CFBundleVersion\" \"$plistPath\" 2>/dev/null)\n\n# Extract previous date and count if formatted correctly\nif [[ \"$currentBuild\" == *\".\"* ]]; then\n previousDate=$(echo \"$currentBuild\" | cut -d '.' -f 1)\n previousCount=$(echo \"$currentBuild\" | cut -d '.' -f 2)\nelse\n previousDate=\"\"\n previousCount=0\nfi\n\n# If the date has changed, reset count to 1, otherwise increment\nif [[ \"$previousDate\" == \"$currentDate\" ]]; then\n newCount=$((previousCount + 1))\nelse\n newCount=1\nfi\n\n# Combine into new build number\nnewBuild=\"${currentDate}.${newCount}\"\n\n# Set new build number in plist\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $newBuild\" \"$plistPath\"\n\necho \"Build number set to $newBuild\"\n"; - }; 05229DCF2D99507C00D88E73 /* Run SwiftLint */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -3727,6 +3708,7 @@ CODE_SIGN_ENTITLEMENTS = Poppool/Poppool.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5QTRMS954; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3759,6 +3741,7 @@ SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; + VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; @@ -3771,6 +3754,7 @@ CODE_SIGN_ENTITLEMENTS = Poppool/Poppool.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5QTRMS954; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3803,6 +3787,7 @@ SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; + VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; diff --git a/Poppool/Poppool/Resource/Info.plist b/Poppool/Poppool/Resource/Info.plist index 2529719b..5ac6908f 100644 --- a/Poppool/Poppool/Resource/Info.plist +++ b/Poppool/Poppool/Resource/Info.plist @@ -16,7 +16,7 @@ CFBundleVersion - 250410.3 + $(CURRENT_PROJECT_VERSION) KAKAO_AUTH_APP_KEY ${KAKAO_AUTH_APP_KEY} LSApplicationQueriesSchemes From d674ede5449e9d40c0259b0a6934e4d40c5fee81 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Thu, 10 Apr 2025 23:27:47 +0900 Subject: [PATCH 90/95] =?UTF-8?q?fix/#103:=20=EC=95=BC=EB=9E=84=20?= =?UTF-8?q?=EC=BB=A4=EB=B0=8B=20=EC=8B=9C=EC=9E=91=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - agvtool을 실행하기 위해 프로젝트 파일이 있는 경로로 이동하도록 수정 --- .github/workflows/deploy_on_release.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy_on_release.yml b/.github/workflows/deploy_on_release.yml index f67a25a4..5d0247f4 100644 --- a/.github/workflows/deploy_on_release.yml +++ b/.github/workflows/deploy_on_release.yml @@ -37,7 +37,9 @@ jobs: run: sudo xcode-select -s /Applications/Xcode_16.2.app - name: 📈 Bump Bundle Version # agvtool을 사용하여 번들 버전을 자동 증가시킴 - run: agvtool next-version + run: | + cd Poppool + agvtool next-version - name: ⚙️ Generate xcconfig run: | From da384d9c3966275cdabeefebbf435c86153a0f85 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Thu, 10 Apr 2025 23:57:31 +0900 Subject: [PATCH 91/95] =?UTF-8?q?fix/#103:=20info.plist=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EB=94=94=EC=8A=A4=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9B=B9=ED=9B=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존에 수출 관리 규정 우회를 pbxproj에서 관리되던 것을 info.plist로 수정 - 테스트플라이트 배포 성공 시 Discord로 알림이 가도록 웹훅 추가 --- .github/workflows/deploy_on_release.yml | 29 +++++++++++++++++++++++ Poppool/Poppool.xcodeproj/project.pbxproj | 2 -- Poppool/Poppool/Resource/Info.plist | 2 ++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy_on_release.yml b/.github/workflows/deploy_on_release.yml index 5d0247f4..1b3ce513 100644 --- a/.github/workflows/deploy_on_release.yml +++ b/.github/workflows/deploy_on_release.yml @@ -102,3 +102,32 @@ jobs: issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }} api-key-id: ${{ secrets.APPSTORE_API_KEY_ID }} api-private-key: ${{ secrets.APPSTORE_API_PRIVATE_KEY }} + + - name: 📣 Notify to Discord + if: success() + run: | + MARKETING_VERSION=$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" Poppool.xcarchive/Products/Applications/Poppool.app/Info.plist) + BUNDLE_VERSION=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" Poppool.xcarchive/Products/Applications/Poppool.app/Info.plist) + + curl -H "Content-Type: application/json" \ + -X POST \ + -d "{ + \"embeds\": [ + { + \"title\": \"🚀 TestFlight 배포 완료!\", + \"description\": \"앱이 성공적으로 업로드되었습니다.\", + \"fields\": [ + { + \"name\": \"🔖 마케팅 버전 (CFBundleShortVersionString)\", + \"value\": \"$MARKETING_VERSION\" + }, + { + \"name\": \"📦 빌드 버전 (CFBundleVersion)\", + \"value\": \"$BUNDLE_VERSION\" + } + ], + \"color\": 3066993 + } + ] + }" \ + ${{ secrets.TESTFLIGHT_WEBHOOK_URL }} diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index 99403291..22a67e02 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -3714,7 +3714,6 @@ ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Poppool/Resource/Info.plist; - INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_NSCameraUsageDescription = "사용자가 프로필 사진을 업로드하거나 댓글에 사진을 추가할 수 있도록 카메라를 사용합니다."; INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "사용자의 현재 위치를 기반으로 주변 게시글을 추천하는 데 사용됩니다."; INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "사용자가 프로필 사진을 업로드하거나 댓글에 사진을 추가할 수 있도록 사진라이브러리에 접근합니다."; @@ -3760,7 +3759,6 @@ ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Poppool/Resource/Info.plist; - INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_NSCameraUsageDescription = "사용자가 프로필 사진을 업로드하거나 댓글에 사진을 추가할 수 있도록 카메라를 사용합니다."; INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "사용자의 현재 위치를 기반으로 주변 게시글을 추천하는 데 사용됩니다."; INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "사용자가 프로필 사진을 업로드하거나 댓글에 사진을 추가할 수 있도록 사진라이브러리에 접근합니다."; diff --git a/Poppool/Poppool/Resource/Info.plist b/Poppool/Poppool/Resource/Info.plist index 5ac6908f..560ae1a9 100644 --- a/Poppool/Poppool/Resource/Info.plist +++ b/Poppool/Poppool/Resource/Info.plist @@ -2,6 +2,8 @@ + ITSAppUsesNonExemptEncryption + CFBundleShortVersionString $(MARKETING_VERSION) CFBundleURLTypes From d40cebadcac6cee9b6c96068dd716cf9791ae40f Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Fri, 11 Apr 2025 00:26:50 +0900 Subject: [PATCH 92/95] =?UTF-8?q?fix/#103:=20=EC=88=98=EB=8F=99=20?= =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EB=84=98=EB=B2=84=EB=A7=81=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=ED=95=B4=EC=95=BC=EB=90=A0=EA=B2=83=20=EA=B0=99?= =?UTF-8?q?=EC=9D=8C...=20=E3=85=A0=E3=85=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy_on_release.yml | 5 +---- Poppool/Poppool.xcodeproj/project.pbxproj | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploy_on_release.yml b/.github/workflows/deploy_on_release.yml index 1b3ce513..82a2e41b 100644 --- a/.github/workflows/deploy_on_release.yml +++ b/.github/workflows/deploy_on_release.yml @@ -36,10 +36,7 @@ jobs: - name: 🛠️ Set up Xcode # Xcode 16.2 선택 run: sudo xcode-select -s /Applications/Xcode_16.2.app - - name: 📈 Bump Bundle Version # agvtool을 사용하여 번들 버전을 자동 증가시킴 - run: | - cd Poppool - agvtool next-version + # 추후에 자동 빌드 넘버링 추가가 필요(뭔 짓을 해도 안돼서 포기... ㅠㅠ) - name: ⚙️ Generate xcconfig run: | diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index 22a67e02..f3dc67d0 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -3708,7 +3708,7 @@ CODE_SIGN_ENTITLEMENTS = Poppool/Poppool.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5QTRMS954; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3753,7 +3753,7 @@ CODE_SIGN_ENTITLEMENTS = Poppool/Poppool.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5QTRMS954; ENABLE_USER_SCRIPT_SANDBOXING = NO; From 3ffa2013d8fbed38ef3a64b4cf2ca9ae591c3cf6 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Fri, 11 Apr 2025 01:33:23 +0900 Subject: [PATCH 93/95] =?UTF-8?q?fix/#103:=20=EC=97=AC=EB=9F=AC=EA=B0=80?= =?UTF-8?q?=EC=A7=80=20=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95(=EC=84=A4?= =?UTF-8?q?=EB=AA=85=20=ED=99=95=EC=9D=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 자동 빌드 번호 부여하기 찐막 - 웹훅 단순화 - xcconfig가 제대로 적용되지 않는 문제 수정(테플에서 키가 제대로 안먹었음) --- .github/workflows/deploy_on_release.yml | 32 +++++++++-------------- Poppool/Poppool.xcodeproj/project.pbxproj | 4 +-- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/.github/workflows/deploy_on_release.yml b/.github/workflows/deploy_on_release.yml index 82a2e41b..afddd19e 100644 --- a/.github/workflows/deploy_on_release.yml +++ b/.github/workflows/deploy_on_release.yml @@ -35,17 +35,21 @@ jobs: - name: 🛠️ Set up Xcode # Xcode 16.2 선택 run: sudo xcode-select -s /Applications/Xcode_16.2.app - - # 추후에 자동 빌드 넘버링 추가가 필요(뭔 짓을 해도 안돼서 포기... ㅠㅠ) + + - name: "#️⃣ Set Build Number" + run: | + BUILD_NUMBER=$(date +%y%m%d.%H%M) + cd Poppool + agvtool new-version -all "$BUILD_NUMBER" - name: ⚙️ Generate xcconfig run: | cat < Poppool/Poppool/Resource/Debug.xcconfig - KAKAO_AUTH_APP_KEY=${{ secrets.KAKAO_AUTH_APP_KEY }} - NAVER_MAP_CLIENT_ID=${{ secrets.NAVER_MAP_CLIENT_ID }} - POPPOOL_BASE_URL=${{ secrets.POPPOOL_BASE_URL }} - POPPOOL_S3_BASE_URL=${{ secrets.POPPOOL_S3_BASE_URL }} - POPPOOL_API_KEY=${{ secrets.POPPOOL_API_KEY }} + POPPOOL_BASE_URL = ${{ secrets.POPPOOL_BASE_URL }} + POPPOOL_S3_BASE_URL = ${{ secrets.POPPOOL_S3_BASE_URL }} + POPPOOL_API_KEY = ${{ secrets.POPPOOL_API_KEY }} + KAKAO_AUTH_APP_KEY = ${{ secrets.KAKAO_AUTH_APP_KEY }} + NAVER_MAP_CLIENT_ID = ${{ secrets.NAVER_MAP_CLIENT_ID }} EOF - name: 🔑 Configure Keychain # 키체인 초기화 -> 임시 키체인 생성 @@ -111,18 +115,8 @@ jobs: -d "{ \"embeds\": [ { - \"title\": \"🚀 TestFlight 배포 완료!\", - \"description\": \"앱이 성공적으로 업로드되었습니다.\", - \"fields\": [ - { - \"name\": \"🔖 마케팅 버전 (CFBundleShortVersionString)\", - \"value\": \"$MARKETING_VERSION\" - }, - { - \"name\": \"📦 빌드 버전 (CFBundleVersion)\", - \"value\": \"$BUNDLE_VERSION\" - } - ], + \"title\": \"## 🚀 TestFlight 배포 완료\", + \"description\": \"v$MARKETING_VERSION ($BUNDLE_VERSION)\", \"color\": 3066993 } ] diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index f3dc67d0..22a67e02 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -3708,7 +3708,7 @@ CODE_SIGN_ENTITLEMENTS = Poppool/Poppool.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5QTRMS954; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3753,7 +3753,7 @@ CODE_SIGN_ENTITLEMENTS = Poppool/Poppool.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5QTRMS954; ENABLE_USER_SCRIPT_SANDBOXING = NO; From 3b064c46ef5534ffc2bf163d56682a1f4a61a347 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Fri, 11 Apr 2025 01:56:49 +0900 Subject: [PATCH 94/95] =?UTF-8?q?debug/#103:=20xcconfig=EA=B0=80=20?= =?UTF-8?q?=EC=9E=98=20=EC=A0=81=EC=9A=A9=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8A=94=EA=B2=83=EC=9C=BC=EB=A1=9C=20=EB=B3=B4=EC=9D=B4?= =?UTF-8?q?=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=EB=94=94=EB=B2=84=EA=B9=85?= =?UTF-8?q?=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy_on_release.yml | 27 ++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/.github/workflows/deploy_on_release.yml b/.github/workflows/deploy_on_release.yml index afddd19e..99f83c8b 100644 --- a/.github/workflows/deploy_on_release.yml +++ b/.github/workflows/deploy_on_release.yml @@ -38,20 +38,29 @@ jobs: - name: "#️⃣ Set Build Number" run: | - BUILD_NUMBER=$(date +%y%m%d.%H%M) + BUILD_NUMBER=$(TZ=Asia/Seoul date +%y%m%d.%H%M) cd Poppool agvtool new-version -all "$BUILD_NUMBER" - name: ⚙️ Generate xcconfig run: | - cat < Poppool/Poppool/Resource/Debug.xcconfig - POPPOOL_BASE_URL = ${{ secrets.POPPOOL_BASE_URL }} - POPPOOL_S3_BASE_URL = ${{ secrets.POPPOOL_S3_BASE_URL }} - POPPOOL_API_KEY = ${{ secrets.POPPOOL_API_KEY }} - KAKAO_AUTH_APP_KEY = ${{ secrets.KAKAO_AUTH_APP_KEY }} - NAVER_MAP_CLIENT_ID = ${{ secrets.NAVER_MAP_CLIENT_ID }} - EOF - + echo "POPPOOL_BASE_URL=${POPPOOL_BASE_URL}" > Poppool/Poppool/Resource/Debug.xcconfig + echo "POPPOOL_S3_BASE_URL=${POPPOOL_S3_BASE_URL}" >> Poppool/Poppool/Resource/Debug.xcconfig + echo "POPPOOL_API_KEY=${POPPOOL_API_KEY}" >> Poppool/Poppool/Resource/Debug.xcconfig + echo "KAKAO_AUTH_APP_KEY=${KAKAO_AUTH_APP_KEY}" >> Poppool/Poppool/Resource/Debug.xcconfig + echo "NAVER_MAP_CLIENT_ID=${NAVER_MAP_CLIENT_ID}" >> Poppool/Poppool/Resource/Debug.xcconfig + env: + POPPOOL_BASE_URL: ${{ secrets.POPPOOL_BASE_URL }} + POPPOOL_S3_BASE_URL: ${{ secrets.POPPOOL_S3_BASE_URL }} + POPPOOL_API_KEY: ${{ secrets.POPPOOL_API_KEY }} + KAKAO_AUTH_APP_KEY: ${{ secrets.KAKAO_AUTH_APP_KEY }} + NAVER_MAP_CLIENT_ID: ${{ secrets.NAVER_MAP_CLIENT_ID }} + + - name: 🔒 Verify xcconfig keys (masked) + run: | + echo "Masked xcconfig preview:" + awk -F= '{prefix=substr($2, 1, 8); masked="****************"; print $1 "=" prefix masked}' Poppool/Poppool/Resource/Debug.xcconfig + - name: 🔑 Configure Keychain # 키체인 초기화 -> 임시 키체인 생성 run: | security create-keychain -p "" "$KEYCHAIN" From a4f3b0aa9adf68c603aca2c3641aeade28dbfe44 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Fri, 11 Apr 2025 02:21:54 +0900 Subject: [PATCH 95/95] =?UTF-8?q?refactor/#103:=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20step=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20em?= =?UTF-8?q?bed=20=EB=82=B4=EC=9A=A9=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy_on_release.yml | 36 ++++++++++++++----------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/.github/workflows/deploy_on_release.yml b/.github/workflows/deploy_on_release.yml index 99f83c8b..7c12c739 100644 --- a/.github/workflows/deploy_on_release.yml +++ b/.github/workflows/deploy_on_release.yml @@ -36,13 +36,13 @@ jobs: - name: 🛠️ Set up Xcode # Xcode 16.2 선택 run: sudo xcode-select -s /Applications/Xcode_16.2.app - - name: "#️⃣ Set Build Number" + - name: "#️⃣ Set Build Number" # 자동 빌드 넘버 세팅 run: | BUILD_NUMBER=$(TZ=Asia/Seoul date +%y%m%d.%H%M) cd Poppool agvtool new-version -all "$BUILD_NUMBER" - - name: ⚙️ Generate xcconfig + - name: ⚙️ Generate xcconfig # 빌드에 필요한 xcconfig 생성 run: | echo "POPPOOL_BASE_URL=${POPPOOL_BASE_URL}" > Poppool/Poppool/Resource/Debug.xcconfig echo "POPPOOL_S3_BASE_URL=${POPPOOL_S3_BASE_URL}" >> Poppool/Poppool/Resource/Debug.xcconfig @@ -55,11 +55,6 @@ jobs: POPPOOL_API_KEY: ${{ secrets.POPPOOL_API_KEY }} KAKAO_AUTH_APP_KEY: ${{ secrets.KAKAO_AUTH_APP_KEY }} NAVER_MAP_CLIENT_ID: ${{ secrets.NAVER_MAP_CLIENT_ID }} - - - name: 🔒 Verify xcconfig keys (masked) - run: | - echo "Masked xcconfig preview:" - awk -F= '{prefix=substr($2, 1, 8); masked="****************"; print $1 "=" prefix masked}' Poppool/Poppool/Resource/Debug.xcconfig - name: 🔑 Configure Keychain # 키체인 초기화 -> 임시 키체인 생성 run: | @@ -69,7 +64,7 @@ jobs: security unlock-keychain -p "" "$KEYCHAIN" security set-keychain-settings - - name : ©️ Configure Code Signing + - name : ©️ Configure Code Signing # 코드 사이닝 추가 run: | # certificate 복호화 gpg -d -o "$DECRYPTED_CERT_FILE_PATH" --pinentry-mode=loopback --passphrase "$CERT_ENCRYPTION_KEY" "$ENCRYPTED_CERT_FILE_PATH" @@ -101,10 +96,6 @@ jobs: run: | xcodebuild -exportArchive -archivePath $XC_ARCHIVE -exportOptionsPlist .github/secrets/ExportOptions.plist -exportPath . -allowProvisioningUpdates - - name: 🔍 Check final CFBundleVersion - run: | - /usr/libexec/PlistBuddy -c "Print :CFBundleVersion" Poppool.xcarchive/Products/Applications/Poppool.app/Info.plist - - name: 🚀 Upload app to TestFlight # TestFlight에 아카이브된 앱 등록 uses: apple-actions/upload-testflight-build@v1 with: @@ -124,9 +115,24 @@ jobs: -d "{ \"embeds\": [ { - \"title\": \"## 🚀 TestFlight 배포 완료\", - \"description\": \"v$MARKETING_VERSION ($BUNDLE_VERSION)\", - \"color\": 3066993 + \"title\": \"🚀 TestFlight 배포 완료\", + \"description\": \"Poppool 앱이 성공적으로 TestFlight에 업로드되었습니다!\", + \"color\": 3066993, + \"fields\": [ + { + \"name\": \"🏷️ 마케팅 버전\", + \"value\": \"$MARKETING_VERSION\", + \"inline\": true + }, + { + \"name\": \"🛠️ 빌드 번호\", + \"value\": \"$BUNDLE_VERSION\", + \"inline\": true + } + ], + \"footer\": { + \"text\": \"TestFlight에서 위 버전을 설치하세요\" + } } ] }" \