From f3047d3b66d58d4150d2ddda6d9282b420b7302e Mon Sep 17 00:00:00 2001 From: cirtuare Date: Wed, 15 Jan 2025 01:11:14 +0900 Subject: [PATCH 01/44] =?UTF-8?q?[Feat]=20LocalVerificationVC=20UI=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#21)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj | 24 ++++ .../Global/Extensions/UIButton+.swift | 30 +++++ .../Global/Literals/StringLiterals.swift | 14 ++ .../LocalVerification/Contents.json | 6 + .../icRadio.imageset/Contents.json | 21 +++ .../icRadio.imageset/ic_radio_20.svg | 4 + .../icRadioSelected.imageset/Contents.json | 21 +++ .../ic_radio_pressed_20.svg | 4 + .../View/LocalVerificationView.swift | 121 ++++++++++++++++++ .../LocalVerificationViewController.swift | 84 ++++++++++++ .../Login/View/LoginViewController.swift | 2 +- 11 files changed, 330 insertions(+), 1 deletion(-) create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/Contents.json create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/Contents.json create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/ic_radio_20.svg create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/Contents.json create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/ic_radio_pressed_20.svg create mode 100644 ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationView.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationViewController.swift diff --git a/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj b/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj index e84fcb11..3b4b6ff3 100644 --- a/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj +++ b/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj @@ -67,6 +67,8 @@ 74B25C2E2D2FEFD3008BDCB7 /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 74B25C2D2D2FEFD3008BDCB7 /* Debug.xcconfig */; }; 74B25C302D2FEFE1008BDCB7 /* Release.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 74B25C2F2D2FEFE1008BDCB7 /* Release.xcconfig */; }; 74B25C322D2FF022008BDCB7 /* Shared.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 74B25C312D2FF022008BDCB7 /* Shared.xcconfig */; }; + 74CCB0802D36AAA000B254B7 /* LocalVerificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CCB07F2D36AA8400B254B7 /* LocalVerificationView.swift */; }; + 74CCB0822D36B3B200B254B7 /* LocalVerificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CCB0812D36B3AA00B254B7 /* LocalVerificationViewController.swift */; }; 74CDCE542D310A1C00E3A21A /* ACFontStyleType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CDCE532D310A1100E3A21A /* ACFontStyleType.swift */; }; 74CDCE562D310B1600E3A21A /* String+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CDCE552D310B1300E3A21A /* String+.swift */; }; /* End PBXBuildFile section */ @@ -128,6 +130,8 @@ 74B25C2D2D2FEFD3008BDCB7 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; 74B25C2F2D2FEFE1008BDCB7 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 74B25C312D2FF022008BDCB7 /* Shared.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = ""; }; + 74CCB07F2D36AA8400B254B7 /* LocalVerificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalVerificationView.swift; sourceTree = ""; }; + 74CCB0812D36B3AA00B254B7 /* LocalVerificationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalVerificationViewController.swift; sourceTree = ""; }; 74CDCE532D310A1100E3A21A /* ACFontStyleType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACFontStyleType.swift; sourceTree = ""; }; 74CDCE552D310B1300E3A21A /* String+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+.swift"; sourceTree = ""; }; B28431BAB67B99CD7B85C705 /* Pods_ACON_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ACON_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -289,6 +293,7 @@ 748D6F702D2BCA46007690B4 /* Presentation */ = { isa = PBXGroup; children = ( + 74CCB07D2D36AA5000B254B7 /* LocalVerification */, 74220DD12D34361E000684BF /* Upload */, 1558BADF2D31D41400ECDEF8 /* Profile */, 1558BADC2D31AB5100ECDEF8 /* Place */, @@ -467,6 +472,23 @@ name = Products; sourceTree = ""; }; + 74CCB07D2D36AA5000B254B7 /* LocalVerification */ = { + isa = PBXGroup; + children = ( + 74CCB07E2D36AA8000B254B7 /* View */, + ); + path = LocalVerification; + sourceTree = ""; + }; + 74CCB07E2D36AA8000B254B7 /* View */ = { + isa = PBXGroup; + children = ( + 74CCB0812D36B3AA00B254B7 /* LocalVerificationViewController.swift */, + 74CCB07F2D36AA8400B254B7 /* LocalVerificationView.swift */, + ); + path = View; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -627,6 +649,7 @@ 748D6FAA2D2C3FF1007690B4 /* BaseCollectionViewCell.swift in Sources */, 748ECA6B2D31918D00BBC981 /* LoginView.swift in Sources */, 748D6F972D2BD544007690B4 /* DummyType.swift in Sources */, + 74CCB0822D36B3B200B254B7 /* LocalVerificationViewController.swift in Sources */, 74220DD42D34363B000684BF /* SpotUploadViewController.swift in Sources */, 741A07592D355F1400778219 /* ReviewFinishedViewController.swift in Sources */, 748D6F892D2BD294007690B4 /* UIViewController+.swift in Sources */, @@ -645,6 +668,7 @@ 748D6FA42D2C3C48007690B4 /* BaseNavViewController.swift in Sources */, 1558BADB2D31AAF900ECDEF8 /* ACTabBarItemType.swift in Sources */, 744BAF172D300AB900F95B4A /* DummyComponent.swift in Sources */, + 74CCB0802D36AAA000B254B7 /* LocalVerificationView.swift in Sources */, 741A06CA2D35415800778219 /* DropAcornViewController.swift in Sources */, 748D6F992D2BD54E007690B4 /* DummyProtocol.swift in Sources */, 748ECA6E2D3196F100BBC981 /* LoginViewController.swift in Sources */, diff --git a/ACON-iOS/ACON-iOS/Global/Extensions/UIButton+.swift b/ACON-iOS/ACON-iOS/Global/Extensions/UIButton+.swift index 5c9b0d72..b04c582d 100644 --- a/ACON-iOS/ACON-iOS/Global/Extensions/UIButton+.swift +++ b/ACON-iOS/ACON-iOS/Global/Extensions/UIButton+.swift @@ -30,4 +30,34 @@ extension UIButton { self.setAttributedTitle(attributedString, for: state) } + func setPartialTitle( + fullText: String, + textStyles: [(text: String, style: ACFontStyleType, color: UIColor)] + ) { + let attributedString = NSMutableAttributedString(string: fullText) + + textStyles.forEach { textStyle in + if let range = fullText.range(of: textStyle.text) { + let nsRange = NSRange(range, in: fullText) + let attributes: [NSAttributedString.Key: Any] = [ + .font: textStyle.style.font, + .kern: textStyle.style.kerning, + .paragraphStyle: { + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.minimumLineHeight = textStyle.style.lineHeight + paragraphStyle.maximumLineHeight = textStyle.style.lineHeight + return paragraphStyle + }(), + .foregroundColor: textStyle.color + ] + + attributes.forEach { key, value in + attributedString.addAttribute(key, value: value, range: nsRange) + } + } + } + + self.setAttributedTitle(attributedString, for: state) + } + } diff --git a/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift b/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift index 1a7937f3..e55c3192 100644 --- a/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift +++ b/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift @@ -95,4 +95,18 @@ enum StringLiterals { } + enum LocalVerification { + + static let needLocalVerification = "로컬 맛집 추천을 위해\n지역 인증이 필요해요" + + static let doLocalVerification = "자주 가는 동네로 지역 인증을 해보세요" + + static let new = "새로운" + + static let verifyLocal = " 나의 동네 인증하기" + + static let next = "다음" + + } + } diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/Contents.json b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/Contents.json b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/Contents.json new file mode 100644 index 00000000..e7723ccc --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "ic_radio_20.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/ic_radio_20.svg b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/ic_radio_20.svg new file mode 100644 index 00000000..b51431c6 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/ic_radio_20.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/Contents.json b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/Contents.json new file mode 100644 index 00000000..c0a131ff --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "ic_radio_pressed_20.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/ic_radio_pressed_20.svg b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/ic_radio_pressed_20.svg new file mode 100644 index 00000000..8c0ce618 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/ic_radio_pressed_20.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationView.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationView.swift new file mode 100644 index 00000000..785f8423 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationView.swift @@ -0,0 +1,121 @@ +// +// LocalVerificationView.swift +// ACON-iOS +// +// Created by 이수민 on 1/14/25. +// + +import UIKit + +import SnapKit +import Then + +final class LocalVerificationView: BaseView { + + // MARK: - UI Properties + + private let weNeedYourAddressLabel: UILabel = UILabel() + + private let doLocalVerificationLabel: UILabel = UILabel() + + var verifyNewLocalButton: UIButton = UIButton() + + var nextButton: UIButton = UIButton() + + var verifyNewLocalButtonConfiguration: UIButton.Configuration = { + var configuration = UIButton.Configuration.plain() + configuration.imagePlacement = .leading + configuration.imagePadding = 8 + configuration.titleAlignment = .leading + configuration.preferredSymbolConfigurationForImage = UIImage.SymbolConfiguration(pointSize: 20) + configuration.contentInsets = NSDirectionalEdgeInsets(top: 16, + leading: 16, + bottom: 16, + trailing: 139) + return configuration + }() + + // MARK: - Lifecycle + + override func setHierarchy() { + super.setHierarchy() + + self.addSubviews(weNeedYourAddressLabel, + doLocalVerificationLabel, + verifyNewLocalButton, + nextButton) + } + + override func setLayout() { + super.setLayout() + + weNeedYourAddressLabel.snp.makeConstraints { + $0.top.equalToSuperview().inset(ScreenUtils.height*32/780) + $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) + $0.height.equalTo(56) + } + + doLocalVerificationLabel.snp.makeConstraints { + $0.top.equalToSuperview().inset(ScreenUtils.height*96/780) + $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) + $0.height.equalTo(18) + } + + verifyNewLocalButton.snp.makeConstraints { + $0.top.equalToSuperview().inset(ScreenUtils.height*146/780) + $0.centerX.equalToSuperview() + $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) + $0.height.equalTo(ScreenUtils.height*52/780) + } + + nextButton.snp.makeConstraints { + $0.bottom.equalToSuperview().inset(ScreenUtils.height*36/780) + $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) + $0.height.equalTo(52) + } + + } + + override func setStyle() { + super.setStyle() + + weNeedYourAddressLabel.do { + $0.setLabel(text: StringLiterals.LocalVerification.needLocalVerification, + style: .h6, + color: .acWhite) + } + + doLocalVerificationLabel.do { + $0.setLabel(text: StringLiterals.LocalVerification.doLocalVerification, + style: .b3, + color: .gray3) + } + + verifyNewLocalButton.do { + $0.configuration = verifyNewLocalButtonConfiguration + $0.backgroundColor = .gray9 + $0.roundedButton(cornerRadius: 4, maskedCorners: [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner]) + $0.layer.borderWidth = 1 + $0.layer.borderColor = UIColor(resource: .gray8).cgColor + $0.setImage(.icRadio, for: .normal) + $0.setImage(.icRadioSelected, for: .selected) + $0.setPartialTitle(fullText: StringLiterals.LocalVerification.new + StringLiterals.LocalVerification.verifyLocal, + textStyles: [(StringLiterals.LocalVerification.new, .s2, .org1), (StringLiterals.LocalVerification.verifyLocal, .s2, .acWhite)]) + } + + nextButton.do { + $0.setAttributedTitle(text: StringLiterals.LocalVerification.next, + style: .h8, + color: .gray6, + for: .disabled) + $0.setAttributedTitle(text: StringLiterals.LocalVerification.next, + style: .h8, + color: .acWhite, + for: .normal) + $0.backgroundColor = .gray8 + $0.roundedButton(cornerRadius: 6, maskedCorners: [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner]) + } + } + +} + diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationViewController.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationViewController.swift new file mode 100644 index 00000000..36b7cfe9 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationViewController.swift @@ -0,0 +1,84 @@ +// +// LocalVerificationViewController.swift +// ACON-iOS +// +// Created by 이수민 on 1/15/25. +// + +import UIKit + +import SnapKit +import Then + +class LocalVerificationViewController: BaseNavViewController { + + // MARK: - UI Properties + + private let localVerificationView = LocalVerificationView() + + // MARK: - LifeCycle + + override func viewDidLoad() { + super.viewDidLoad() + + self.setXButton() + addTarget() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(false) + + self.tabBarController?.tabBar.isHidden = true + } + + override func setHierarchy() { + super.setHierarchy() + + self.contentView.addSubview(localVerificationView) + } + + override func setLayout() { + super.setLayout() + + localVerificationView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + } + + override func setStyle() { + super.setStyle() + + self.localVerificationView.nextButton.isEnabled = false + } + + func addTarget() { + localVerificationView.verifyNewLocalButton.addTarget(self, + action: #selector(verifyLocationButtonTapped), + for: .touchUpInside) + localVerificationView.nextButton.addTarget(self, + action: #selector(nextButtonTapped), + for: .touchUpInside) + } + +} + + +// MARK: - @objc functions + +private extension LocalVerificationViewController { + + @objc + func verifyLocationButtonTapped() { + localVerificationView.verifyNewLocalButton.isSelected.toggle() + localVerificationView.nextButton.isEnabled = localVerificationView.verifyNewLocalButton.isSelected + localVerificationView.nextButton.backgroundColor = localVerificationView.verifyNewLocalButton.isSelected ? .gray5 : .gray8 + } + + @objc + func nextButtonTapped() { + // TODO: - push to map + let vc = ViewController() + navigationController?.pushViewController(vc, animated: false) + } + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/Login/View/LoginViewController.swift b/ACON-iOS/ACON-iOS/Presentation/Login/View/LoginViewController.swift index 0071105a..d5cf9041 100644 --- a/ACON-iOS/ACON-iOS/Presentation/Login/View/LoginViewController.swift +++ b/ACON-iOS/ACON-iOS/Presentation/Login/View/LoginViewController.swift @@ -90,7 +90,7 @@ extension LoginViewController { // TODO: - 나중에 서버 로그인 Success 시 이동하는 것으로 변경 (ObservablePattern) func navigateToLocalVerificationVC() { - let vc = ViewController() + let vc = LocalVerificationViewController() self.navigationController?.pushViewController(vc, animated: false) } From 029851c2b6991e019b609f51cf9cc922b61f9f6d Mon Sep 17 00:00:00 2001 From: cirtuare Date: Wed, 15 Jan 2025 02:47:57 +0900 Subject: [PATCH 02/44] =?UTF-8?q?[Feat]=20LocalMapVC=20UI=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#21)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj | 8 ++ .../Global/Literals/StringLiterals.swift | 5 +- .../LocalVerification/View/LocalMapView.swift | 64 ++++++++++++++++ .../View/LocalMapViewController.swift | 73 +++++++++++++++++++ .../LocalVerificationViewController.swift | 9 ++- 5 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapView.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift diff --git a/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj b/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj index 3b4b6ff3..dbad4832 100644 --- a/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj +++ b/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj @@ -69,6 +69,8 @@ 74B25C322D2FF022008BDCB7 /* Shared.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 74B25C312D2FF022008BDCB7 /* Shared.xcconfig */; }; 74CCB0802D36AAA000B254B7 /* LocalVerificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CCB07F2D36AA8400B254B7 /* LocalVerificationView.swift */; }; 74CCB0822D36B3B200B254B7 /* LocalVerificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CCB0812D36B3AA00B254B7 /* LocalVerificationViewController.swift */; }; + 74CCB0842D36C43C00B254B7 /* LocalMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CCB0832D36C43400B254B7 /* LocalMapView.swift */; }; + 74CCB0862D36D4AA00B254B7 /* LocalMapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CCB0852D36D4A400B254B7 /* LocalMapViewController.swift */; }; 74CDCE542D310A1C00E3A21A /* ACFontStyleType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CDCE532D310A1100E3A21A /* ACFontStyleType.swift */; }; 74CDCE562D310B1600E3A21A /* String+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CDCE552D310B1300E3A21A /* String+.swift */; }; /* End PBXBuildFile section */ @@ -132,6 +134,8 @@ 74B25C312D2FF022008BDCB7 /* Shared.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = ""; }; 74CCB07F2D36AA8400B254B7 /* LocalVerificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalVerificationView.swift; sourceTree = ""; }; 74CCB0812D36B3AA00B254B7 /* LocalVerificationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalVerificationViewController.swift; sourceTree = ""; }; + 74CCB0832D36C43400B254B7 /* LocalMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMapView.swift; sourceTree = ""; }; + 74CCB0852D36D4A400B254B7 /* LocalMapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMapViewController.swift; sourceTree = ""; }; 74CDCE532D310A1100E3A21A /* ACFontStyleType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACFontStyleType.swift; sourceTree = ""; }; 74CDCE552D310B1300E3A21A /* String+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+.swift"; sourceTree = ""; }; B28431BAB67B99CD7B85C705 /* Pods_ACON_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ACON_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -483,6 +487,8 @@ 74CCB07E2D36AA8000B254B7 /* View */ = { isa = PBXGroup; children = ( + 74CCB0852D36D4A400B254B7 /* LocalMapViewController.swift */, + 74CCB0832D36C43400B254B7 /* LocalMapView.swift */, 74CCB0812D36B3AA00B254B7 /* LocalVerificationViewController.swift */, 74CCB07F2D36AA8400B254B7 /* LocalVerificationView.swift */, ); @@ -642,9 +648,11 @@ 748D6FA82D2C3F93007690B4 /* BaseViewController.swift in Sources */, 748D6F6A2D2BCA1C007690B4 /* SceneDelegate.swift in Sources */, 748ECA712D31A72A00BBC981 /* LoginViewModel.swift in Sources */, + 74CCB0862D36D4AA00B254B7 /* LocalMapViewController.swift in Sources */, 748D6F872D2BD247007690B4 /* UIView+.swift in Sources */, 74CDCE562D310B1600E3A21A /* String+.swift in Sources */, 748D6F8B2D2BD31B007690B4 /* UIStackView+.swift in Sources */, + 74CCB0842D36C43C00B254B7 /* LocalMapView.swift in Sources */, 748D6F912D2BD450007690B4 /* UILabel+.swift in Sources */, 748D6FAA2D2C3FF1007690B4 /* BaseCollectionViewCell.swift in Sources */, 748ECA6B2D31918D00BBC981 /* LoginView.swift in Sources */, diff --git a/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift b/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift index e55c3192..f1399a40 100644 --- a/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift +++ b/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift @@ -97,7 +97,7 @@ enum StringLiterals { enum LocalVerification { - static let needLocalVerification = "로컬 맛집 추천을 위해\n지역 인증이 필요해요" + static let needLocalVerification = "로컬 맛집 추천을 위해\n지역 인증이 필요해요." static let doLocalVerification = "자주 가는 동네로 지역 인증을 해보세요" @@ -107,6 +107,9 @@ enum StringLiterals { static let next = "다음" + static let finishVerification = "인증완료" + + static let letsStart = "시작하기" } } diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapView.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapView.swift new file mode 100644 index 00000000..54823c8a --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapView.swift @@ -0,0 +1,64 @@ +// +// LocalMapView.swift +// ACON-iOS +// +// Created by 이수민 on 1/15/25. +// + +import UIKit + +import SnapKit +import Then + +final class LocalMapView: BaseView { + + // MARK: - UI Properties + + let localMapImageView: UIImageView = UIImageView() + + var finishVerificationButton: UIButton = UIButton() + + // MARK: - Lifecycle + + override func setHierarchy() { + super.setHierarchy() + + self.addSubviews(localMapImageView, + finishVerificationButton) + } + + override func setLayout() { + super.setLayout() + + localMapImageView.snp.makeConstraints { + $0.top.horizontalEdges.equalToSuperview() + $0.height.equalTo(ScreenUtils.height*564/780) + } + + finishVerificationButton.snp.makeConstraints { + $0.bottom.equalToSuperview().inset(ScreenUtils.height*36/780) + $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) + $0.height.equalTo(52) + } + + } + + override func setStyle() { + super.setStyle() + + localMapImageView.do { + $0.backgroundColor = .gray + } + + finishVerificationButton.do { + $0.setAttributedTitle(text: StringLiterals.LocalVerification.finishVerification, + style: .h8, + color: .acWhite, + for: .normal) + $0.backgroundColor = .gray5 + $0.roundedButton(cornerRadius: 6, maskedCorners: [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner]) + } + } + +} + diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift new file mode 100644 index 00000000..db5c4f07 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift @@ -0,0 +1,73 @@ +// +// LocalMapViewController.swift +// ACON-iOS +// +// Created by 이수민 on 1/15/25. +// + +import UIKit + +import SnapKit +import Then + +class LocalMapViewController: BaseNavViewController { + + // MARK: - UI Properties + + private let localMapView = LocalMapView() + + // MARK: - LifeCycle + + override func viewDidLoad() { + super.viewDidLoad() + + self.setXButton() + addTarget() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(false) + + self.tabBarController?.tabBar.isHidden = true + } + + override func setHierarchy() { + super.setHierarchy() + + self.contentView.addSubview(localMapView) + } + + override func setLayout() { + super.setLayout() + + localMapView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + } + + override func setStyle() { + super.setStyle() + + self.setXButton() + self.setSecondTitleLabelStyle(title: "지도에서 위치 확인하기") + } + + func addTarget() { + localMapView.finishVerificationButton.addTarget(self, + action: #selector(finishVerificationButtonTapped), + for: .touchUpInside) + } + +} + + +// MARK: - @objc functions + +private extension LocalMapViewController { + + @objc + func finishVerificationButtonTapped() { + // TODO: - 인증완료모달 + } + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationViewController.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationViewController.swift index 36b7cfe9..45b47869 100644 --- a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationViewController.swift +++ b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationViewController.swift @@ -70,14 +70,15 @@ private extension LocalVerificationViewController { @objc func verifyLocationButtonTapped() { localVerificationView.verifyNewLocalButton.isSelected.toggle() - localVerificationView.nextButton.isEnabled = localVerificationView.verifyNewLocalButton.isSelected - localVerificationView.nextButton.backgroundColor = localVerificationView.verifyNewLocalButton.isSelected ? .gray5 : .gray8 + let isSelected = localVerificationView.verifyNewLocalButton.isSelected + localVerificationView.verifyNewLocalButton.configuration?.baseBackgroundColor = isSelected ? .gray7 : .gray9 + localVerificationView.nextButton.isEnabled = isSelected + localVerificationView.nextButton.backgroundColor = isSelected ? .gray5 : .gray8 } @objc func nextButtonTapped() { - // TODO: - push to map - let vc = ViewController() + let vc = LocalMapViewController() navigationController?.pushViewController(vc, animated: false) } From e3fc8e3faadf4d66a7cddb424d9c875a6d8a96e3 Mon Sep 17 00:00:00 2001 From: cirtuare Date: Wed, 15 Jan 2025 06:13:43 +0900 Subject: [PATCH 03/44] =?UTF-8?q?[Feat]=20=EB=8F=99=EB=84=A4=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=EC=A7=80=EB=8F=84=20=EA=B5=AC=ED=98=84=20(#21)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ACON-iOS/Application/AppDelegate.swift | 3 ++ .../Global/Literals/StringLiterals.swift | 2 + .../Global/Settings/Config/Config.swift | 8 ++-- ACON-iOS/ACON-iOS/Global/Settings/Info.plist | 4 +- .../LocalVerification/View/LocalMapView.swift | 18 ++++++--- .../View/LocalMapViewController.swift | 39 ++++++++++++++++++- 6 files changed, 62 insertions(+), 12 deletions(-) diff --git a/ACON-iOS/ACON-iOS/Application/AppDelegate.swift b/ACON-iOS/ACON-iOS/Application/AppDelegate.swift index 84c5c1f3..285d03c3 100644 --- a/ACON-iOS/ACON-iOS/Application/AppDelegate.swift +++ b/ACON-iOS/ACON-iOS/Application/AppDelegate.swift @@ -7,11 +7,14 @@ import UIKit +//import NMapsMap + @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. +// NMFAuthManager.shared().clientId = Config.nMapClientKey return true } diff --git a/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift b/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift index f1399a40..60899fb3 100644 --- a/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift +++ b/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift @@ -110,6 +110,8 @@ enum StringLiterals { static let finishVerification = "인증완료" static let letsStart = "시작하기" + + static let locateOnMap = "지도에서 위치 확인하기" } } diff --git a/ACON-iOS/ACON-iOS/Global/Settings/Config/Config.swift b/ACON-iOS/ACON-iOS/Global/Settings/Config/Config.swift index 91547faa..53ca9275 100644 --- a/ACON-iOS/ACON-iOS/Global/Settings/Config/Config.swift +++ b/ACON-iOS/ACON-iOS/Global/Settings/Config/Config.swift @@ -19,7 +19,7 @@ enum Config { static let googleWebClientID = "GOOGLE_WEB_CLIENT_ID" - static let nmapAPIKey = "NMAP_API_KEY" + static let nMapClientKey = "NMFClientId" } @@ -58,9 +58,9 @@ extension Config { return key }() - static let nmapAPIKey: String = { - guard let key = Config.infoDictionary[Keys.Plist.nmapAPIKey] as? String else { - fatalError("nmapAPIKey is not set in plist for this configuration") + static let nMapClientKey: String = { + guard let key = Config.infoDictionary[Keys.Plist.nMapClientKey] as? String else { + fatalError("nMapClientKey is not set in plist for this configuration") } return key }() diff --git a/ACON-iOS/ACON-iOS/Global/Settings/Info.plist b/ACON-iOS/ACON-iOS/Global/Settings/Info.plist index 46c6b042..538e3704 100644 --- a/ACON-iOS/ACON-iOS/Global/Settings/Info.plist +++ b/ACON-iOS/ACON-iOS/Global/Settings/Info.plist @@ -23,8 +23,8 @@ ${GOOGLE_CLIENT_ID} GOOGLE_WEB_CLIENT_ID ${GOOGLE_WEB_CLIENT_ID} - NMAP_API_KEY - ${NMAP_API_KEY} + NMFClientId + ${NMAP_CLIENT_KEY} NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapView.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapView.swift index 54823c8a..856509a6 100644 --- a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapView.swift +++ b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapView.swift @@ -7,6 +7,7 @@ import UIKit +import NMapsMap import SnapKit import Then @@ -14,7 +15,7 @@ final class LocalMapView: BaseView { // MARK: - UI Properties - let localMapImageView: UIImageView = UIImageView() + let nMapView: NMFNaverMapView = NMFNaverMapView() var finishVerificationButton: UIButton = UIButton() @@ -23,14 +24,14 @@ final class LocalMapView: BaseView { override func setHierarchy() { super.setHierarchy() - self.addSubviews(localMapImageView, + self.addSubviews(nMapView, finishVerificationButton) } override func setLayout() { super.setLayout() - localMapImageView.snp.makeConstraints { + nMapView.snp.makeConstraints { $0.top.horizontalEdges.equalToSuperview() $0.height.equalTo(ScreenUtils.height*564/780) } @@ -46,8 +47,15 @@ final class LocalMapView: BaseView { override func setStyle() { super.setStyle() - localMapImageView.do { - $0.backgroundColor = .gray + nMapView.do { + $0.showLocationButton = true + $0.showZoomControls = false + $0.showScaleBar = false + $0.showCompass = false + $0.mapView.positionMode = .normal + $0.mapView.zoomLevel = 17 + $0.mapView.minZoomLevel = 14 + $0.mapView.maxZoomLevel = 18 } finishVerificationButton.do { diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift index db5c4f07..d85da653 100644 --- a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift +++ b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift @@ -7,6 +7,7 @@ import UIKit +import NMapsMap import SnapKit import Then @@ -23,12 +24,22 @@ class LocalMapViewController: BaseNavViewController { self.setXButton() addTarget() + ACLocationManager.shared.addDelegate(self) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(false) self.tabBarController?.tabBar.isHidden = true + ACLocationManager.shared.checkUserDeviceLocationServiceAuthorization() + if CLLocationManager.authorizationStatus() == .authorizedWhenInUse || + CLLocationManager.authorizationStatus() == .authorizedAlways { + ACLocationManager.shared.startUpdatingLocation() + } + } + + deinit { + ACLocationManager.shared.removeDelegate(self) } override func setHierarchy() { @@ -49,7 +60,7 @@ class LocalMapViewController: BaseNavViewController { super.setStyle() self.setXButton() - self.setSecondTitleLabelStyle(title: "지도에서 위치 확인하기") + self.setSecondTitleLabelStyle(title: StringLiterals.LocalVerification.locateOnMap) } func addTarget() { @@ -71,3 +82,29 @@ private extension LocalMapViewController { } } + + +// MARK: - Map Functions + +extension LocalMapViewController { + + func moveCameraToLocation(latitude: Double, longitude: Double) { + let position = NMGLatLng(lat: latitude, lng: longitude) + let cameraUpdate = NMFCameraUpdate(scrollTo: position, zoomTo: 17) + localMapView.nMapView.mapView.moveCamera(cameraUpdate) + // NOTE: 최초 1회만 받고 중지 + ACLocationManager.shared.stopUpdatingLocation() + } + +} + + +// MARK: - LocationManagerDelegate + +extension LocalMapViewController: ACLocationManagerDelegate { + + func locationManager(_ manager: ACLocationManager, didUpdateLocation coordinate: CLLocationCoordinate2D) { + moveCameraToLocation(latitude: coordinate.latitude, longitude: coordinate.longitude) + } + +} From d30d71460d9e12e8a748d20a58ed87483514a401 Mon Sep 17 00:00:00 2001 From: cirtuare Date: Wed, 15 Jan 2025 18:48:49 +0900 Subject: [PATCH 04/44] =?UTF-8?q?[Chore]=20=EB=B0=94=ED=85=80=EC=8B=9C?= =?UTF-8?q?=ED=8A=B8=20handler=20=EC=97=90=EC=85=8B=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=20(#21)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../btn_bottomsheet bar.imageset/btn_bottomsheet bar.svg | 3 --- .../Contents.json | 2 +- .../Upload/btn_bottomsheet_bar.imageset/Rectangle 34627096.svg | 3 +++ 3 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet bar.imageset/btn_bottomsheet bar.svg rename ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/{btn_bottomsheet bar.imageset => btn_bottomsheet_bar.imageset}/Contents.json (85%) create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet_bar.imageset/Rectangle 34627096.svg diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet bar.imageset/btn_bottomsheet bar.svg b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet bar.imageset/btn_bottomsheet bar.svg deleted file mode 100644 index d055efee..00000000 --- a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet bar.imageset/btn_bottomsheet bar.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet bar.imageset/Contents.json b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet_bar.imageset/Contents.json similarity index 85% rename from ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet bar.imageset/Contents.json rename to ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet_bar.imageset/Contents.json index 153852f9..f19f00a2 100644 --- a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet bar.imageset/Contents.json +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet_bar.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "btn_bottomsheet bar.svg", + "filename" : "Rectangle 34627096.svg", "idiom" : "universal", "scale" : "1x" }, diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet_bar.imageset/Rectangle 34627096.svg b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet_bar.imageset/Rectangle 34627096.svg new file mode 100644 index 00000000..c0f290cb --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet_bar.imageset/Rectangle 34627096.svg @@ -0,0 +1,3 @@ + + + From 0f39d585557b626d47a56a5aea7adf20537148b8 Mon Sep 17 00:00:00 2001 From: cirtuare Date: Wed, 15 Jan 2025 18:49:49 +0900 Subject: [PATCH 05/44] =?UTF-8?q?[Feat]=20=EB=B0=94=ED=85=80=EC=8B=9C?= =?UTF-8?q?=ED=8A=B8=20=ED=95=B8=EB=93=A4=EB=9F=AC=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20(#21)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ACON-iOS/Presentation/Base/BaseView.swift | 22 +++++++++++++++++++ .../Upload/View/SpotSearchView.swift | 18 ++------------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/ACON-iOS/ACON-iOS/Presentation/Base/BaseView.swift b/ACON-iOS/ACON-iOS/Presentation/Base/BaseView.swift index c071ac53..073f5dc9 100644 --- a/ACON-iOS/ACON-iOS/Presentation/Base/BaseView.swift +++ b/ACON-iOS/ACON-iOS/Presentation/Base/BaseView.swift @@ -12,6 +12,8 @@ import Then class BaseView: UIView { + private let handlerImageView: UIImageView = UIImageView() + // MARK: - Initializer override init(frame: CGRect) { @@ -35,3 +37,23 @@ class BaseView: UIView { } } + +extension BaseView { + + func setHandlerImageView() { + self.addSubview(handlerImageView) + + handlerImageView.snp.makeConstraints { + $0.top.equalToSuperview().inset(ScreenUtils.height*4/780) + $0.centerX.equalToSuperview() + $0.width.equalTo(ScreenUtils.height*36/780) + $0.height.equalTo(ScreenUtils.height*3/780) + } + + handlerImageView.do { + $0.image = .btnBottomsheetBar + $0.contentMode = .scaleAspectFit + } + } + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchView.swift b/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchView.swift index 41443995..c6aa73b0 100644 --- a/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchView.swift +++ b/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchView.swift @@ -14,8 +14,6 @@ final class SpotSearchView: BaseView { // MARK: - UI Properties - private let handlerImageView: UIImageView = UIImageView() - private let spotUploadLabel: UILabel = UILabel() let searchView: UIView = UIView() @@ -55,8 +53,7 @@ final class SpotSearchView: BaseView { override func setHierarchy() { super.setHierarchy() - self.addSubviews(handlerImageView, - spotUploadLabel, + self.addSubviews(spotUploadLabel, searchView, doneButton, recommendedSpotScrollView, @@ -72,13 +69,6 @@ final class SpotSearchView: BaseView { override func setLayout() { super.setLayout() - handlerImageView.snp.makeConstraints { - $0.top.equalToSuperview().inset(ScreenUtils.height*4/780) - $0.centerX.equalToSuperview() - $0.width.equalTo(ScreenUtils.height*64/780) - $0.height.equalTo(ScreenUtils.height*3/780) - } - spotUploadLabel.snp.makeConstraints { $0.top.equalToSuperview().inset(ScreenUtils.height*19/780) $0.centerX.equalToSuperview() @@ -162,11 +152,7 @@ final class SpotSearchView: BaseView { self.backgroundColor = .glaW10 self.backgroundColor?.withAlphaComponent(0.95) - - handlerImageView.do { - $0.image = .btnBottomsheetBar - $0.contentMode = .scaleAspectFit - } + self.setHandlerImageView() spotUploadLabel.do { $0.setLabel(text: StringLiterals.Upload.spotUpload2, From 4ab6a820a872f8b78eb56754935ff1f435af3520 Mon Sep 17 00:00:00 2001 From: cirtuare Date: Wed, 15 Jan 2025 18:55:40 +0900 Subject: [PATCH 06/44] =?UTF-8?q?[Add]=20=EB=A1=9C=EC=BB=AC=20/=20?= =?UTF-8?q?=EC=9D=BC=EB=B0=98=20=EB=8F=84=ED=86=A0=EB=A6=AC=20=EC=97=90?= =?UTF-8?q?=EC=85=8B=20=EC=B6=94=EA=B0=80=20(#21)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../icRadio.imageset/Contents.json | 11 +----- .../icRadioSelected.imageset/Contents.json | 11 +----- .../localAcorn.imageset/Contents.json | 12 ++++++ .../localAcorn.imageset/ic_local acon.svg | 39 +++++++++++++++++++ .../plainAcorn.imageset/Contents.json | 12 ++++++ .../plainAcorn.imageset/ic_normal acon.svg | 21 ++++++++++ 6 files changed, 86 insertions(+), 20 deletions(-) create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/localAcorn.imageset/Contents.json create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/localAcorn.imageset/ic_local acon.svg create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/plainAcorn.imageset/Contents.json create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/plainAcorn.imageset/ic_normal acon.svg diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/Contents.json b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/Contents.json index e7723ccc..e35b49fd 100644 --- a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/Contents.json +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/Contents.json @@ -2,16 +2,7 @@ "images" : [ { "filename" : "ic_radio_20.svg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/Contents.json b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/Contents.json index c0a131ff..3598d078 100644 --- a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/Contents.json +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/Contents.json @@ -2,16 +2,7 @@ "images" : [ { "filename" : "ic_radio_pressed_20.svg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/localAcorn.imageset/Contents.json b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/localAcorn.imageset/Contents.json new file mode 100644 index 00000000..4ba1bb85 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/localAcorn.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_local acon.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/localAcorn.imageset/ic_local acon.svg b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/localAcorn.imageset/ic_local acon.svg new file mode 100644 index 00000000..900a2b8d --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/localAcorn.imageset/ic_local acon.svg @@ -0,0 +1,39 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/plainAcorn.imageset/Contents.json b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/plainAcorn.imageset/Contents.json new file mode 100644 index 00000000..1751ef8d --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/plainAcorn.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_normal acon.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/plainAcorn.imageset/ic_normal acon.svg b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/plainAcorn.imageset/ic_normal acon.svg new file mode 100644 index 00000000..99614451 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/plainAcorn.imageset/ic_normal acon.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + From a75d4989a67beb70bf6c00656034fda31ab0ba42 Mon Sep 17 00:00:00 2001 From: cirtuare Date: Wed, 15 Jan 2025 22:00:37 +0900 Subject: [PATCH 07/44] =?UTF-8?q?[Feat]=20middleCustomDetent=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#21)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Global/Extensions/UIViewController+.swift | 14 ++++++++++++++ .../ACON-iOS/Global/Literals/StringLiterals.swift | 2 ++ ACON-iOS/ACON-iOS/Global/Utils/SheetUtils.swift | 9 +++++++++ 3 files changed, 25 insertions(+) diff --git a/ACON-iOS/ACON-iOS/Global/Extensions/UIViewController+.swift b/ACON-iOS/ACON-iOS/Global/Extensions/UIViewController+.swift index 090f0fd1..43f4629d 100644 --- a/ACON-iOS/ACON-iOS/Global/Extensions/UIViewController+.swift +++ b/ACON-iOS/ACON-iOS/Global/Extensions/UIViewController+.swift @@ -49,6 +49,20 @@ extension UIViewController { } } + func setMiddleSheetLayout() { + self.modalPresentationStyle = .pageSheet + + if let sheet = self.sheetPresentationController { + let sheetUtils = SheetUtils() + sheet.detents = [sheetUtils.acMiddleDetent] + sheet.prefersScrollingExpandsWhenScrolledToEdge = false + sheet.prefersGrabberVisible = false + + sheet.selectedDetentIdentifier = sheetUtils.middleDetentIdentifier + } + } + + func setLongSheetLayout() { self.modalPresentationStyle = .pageSheet diff --git a/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift b/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift index 60899fb3..dc8d7f9b 100644 --- a/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift +++ b/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift @@ -55,6 +55,8 @@ enum StringLiterals { static let shortDetent = "acShortDetent" + static let middleDetent = "acMiddleDetent" + static let longDetent = "acLongDetent" } diff --git a/ACON-iOS/ACON-iOS/Global/Utils/SheetUtils.swift b/ACON-iOS/ACON-iOS/Global/Utils/SheetUtils.swift index 947997ae..25525557 100644 --- a/ACON-iOS/ACON-iOS/Global/Utils/SheetUtils.swift +++ b/ACON-iOS/ACON-iOS/Global/Utils/SheetUtils.swift @@ -10,6 +10,7 @@ import UIKit struct SheetUtils { let shortDetentIdentifier = UISheetPresentationController.Detent.Identifier(StringLiterals.SheetUtils.shortDetent) + let middleDetentIdentifier = UISheetPresentationController.Detent.Identifier(StringLiterals.SheetUtils.middleDetent) let longDetentIdentifier = UISheetPresentationController.Detent.Identifier(StringLiterals.SheetUtils.longDetent) var acShortDetent: UISheetPresentationController.Detent { @@ -20,6 +21,14 @@ struct SheetUtils { } } + var acMiddleDetent: UISheetPresentationController.Detent { + return UISheetPresentationController.Detent.custom(identifier: longDetentIdentifier) { _ in + let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene + let safeAreaBottom = windowScene?.windows.first?.safeAreaInsets.bottom ?? 0 + return ScreenUtils.height*558/780 - safeAreaBottom + } + } + var acLongDetent: UISheetPresentationController.Detent { return UISheetPresentationController.Detent.custom(identifier: longDetentIdentifier) { _ in let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene From e35cce4dc8d41181a7d6c4abcb0ce62c9a9bf86b Mon Sep 17 00:00:00 2001 From: cirtuare Date: Wed, 15 Jan 2025 22:03:19 +0900 Subject: [PATCH 08/44] =?UTF-8?q?[Feat]=20LocalVerificationFinishedVC=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#21)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj | 8 + .../Global/Literals/StringLiterals.swift | 10 ++ .../View/LocalMapViewController.swift | 4 +- .../View/LocalVerificationFinishedView.swift | 142 ++++++++++++++++++ ...alVerificationFinishedViewController.swift | 85 +++++++++++ 5 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedView.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedViewController.swift diff --git a/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj b/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj index dbad4832..d8e54e17 100644 --- a/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj +++ b/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj @@ -71,6 +71,8 @@ 74CCB0822D36B3B200B254B7 /* LocalVerificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CCB0812D36B3AA00B254B7 /* LocalVerificationViewController.swift */; }; 74CCB0842D36C43C00B254B7 /* LocalMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CCB0832D36C43400B254B7 /* LocalMapView.swift */; }; 74CCB0862D36D4AA00B254B7 /* LocalMapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CCB0852D36D4A400B254B7 /* LocalMapViewController.swift */; }; + 74CCB0882D370B9100B254B7 /* LocalVerificationFinishedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CCB0872D370B6F00B254B7 /* LocalVerificationFinishedViewController.swift */; }; + 74CCB08A2D370BB100B254B7 /* LocalVerificationFinishedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CCB0892D370BAC00B254B7 /* LocalVerificationFinishedView.swift */; }; 74CDCE542D310A1C00E3A21A /* ACFontStyleType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CDCE532D310A1100E3A21A /* ACFontStyleType.swift */; }; 74CDCE562D310B1600E3A21A /* String+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CDCE552D310B1300E3A21A /* String+.swift */; }; /* End PBXBuildFile section */ @@ -136,6 +138,8 @@ 74CCB0812D36B3AA00B254B7 /* LocalVerificationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalVerificationViewController.swift; sourceTree = ""; }; 74CCB0832D36C43400B254B7 /* LocalMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMapView.swift; sourceTree = ""; }; 74CCB0852D36D4A400B254B7 /* LocalMapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMapViewController.swift; sourceTree = ""; }; + 74CCB0872D370B6F00B254B7 /* LocalVerificationFinishedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalVerificationFinishedViewController.swift; sourceTree = ""; }; + 74CCB0892D370BAC00B254B7 /* LocalVerificationFinishedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalVerificationFinishedView.swift; sourceTree = ""; }; 74CDCE532D310A1100E3A21A /* ACFontStyleType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACFontStyleType.swift; sourceTree = ""; }; 74CDCE552D310B1300E3A21A /* String+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+.swift"; sourceTree = ""; }; B28431BAB67B99CD7B85C705 /* Pods_ACON_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ACON_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -487,6 +491,8 @@ 74CCB07E2D36AA8000B254B7 /* View */ = { isa = PBXGroup; children = ( + 74CCB0892D370BAC00B254B7 /* LocalVerificationFinishedView.swift */, + 74CCB0872D370B6F00B254B7 /* LocalVerificationFinishedViewController.swift */, 74CCB0852D36D4A400B254B7 /* LocalMapViewController.swift */, 74CCB0832D36C43400B254B7 /* LocalMapView.swift */, 74CCB0812D36B3AA00B254B7 /* LocalVerificationViewController.swift */, @@ -640,6 +646,7 @@ 748D6FAE2D2EBB9C007690B4 /* ACFont.swift in Sources */, 748D6F692D2BCA1C007690B4 /* AppDelegate.swift in Sources */, 74054ECE2D32549F00D1CDE4 /* ACLocationManager.swift in Sources */, + 74CCB08A2D370BB100B254B7 /* LocalVerificationFinishedView.swift in Sources */, 745C7E0D2D35A04A0074DBDB /* RelatedSearchCollectionViewCell.swift in Sources */, 748D6FA62D2C3F44007690B4 /* BaseView.swift in Sources */, 748D6F852D2BD230007690B4 /* DummyService.swift in Sources */, @@ -672,6 +679,7 @@ 1558BA1A2D318FFC00ECDEF8 /* ACTabBarController.swift in Sources */, 74CDCE542D310A1C00E3A21A /* ACFontStyleType.swift in Sources */, 748D6F802D2BCD94007690B4 /* ObservablePattern.swift in Sources */, + 74CCB0882D370B9100B254B7 /* LocalVerificationFinishedViewController.swift in Sources */, 1558BADE2D31AB6C00ECDEF8 /* SpotListViewController.swift in Sources */, 748D6FA42D2C3C48007690B4 /* BaseNavViewController.swift in Sources */, 1558BADB2D31AAF900ECDEF8 /* ACTabBarItemType.swift in Sources */, diff --git a/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift b/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift index dc8d7f9b..65f4d142 100644 --- a/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift +++ b/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift @@ -114,6 +114,16 @@ enum StringLiterals { static let letsStart = "시작하기" static let locateOnMap = "지도에서 위치 확인하기" + + static let now = "이제 " + + static let localAcornTitle = "에\n로컬 도토리를 떨어트릴 수 있어요!" + + static let localAcornExplaination = "로컬 도토리는 로컬 맛집을 보증하는 도토리에요.\n만족스러운 식사 후 리뷰에 사용해보세요!" + + static let localAcorn = "로컬 도토리" + + static let plainAcorn = "일반 도토리" } } diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift index d85da653..4ab5a477 100644 --- a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift +++ b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift @@ -78,7 +78,9 @@ private extension LocalMapViewController { @objc func finishVerificationButtonTapped() { - // TODO: - 인증완료모달 + let vc = LocalVerificationFinishedViewController() + vc.setMiddleSheetLayout() + self.present(vc, animated: true) } } diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedView.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedView.swift new file mode 100644 index 00000000..e8c52900 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedView.swift @@ -0,0 +1,142 @@ +// +// LocalVerificationFinishedView.swift +// ACON-iOS +// +// Created by 이수민 on 1/15/25. +// + +import UIKit + +import SnapKit +import Then + +final class LocalVerificationFinishedView: BaseView { + + // MARK: - UI Properties + + var titleLabel: UILabel = UILabel() + + private let explainationLabel: UILabel = UILabel() + + private let localAcornImageView: UIImageView = UIImageView() + + private let plainAcornImageView: UIImageView = UIImageView() + + private let localAcornLabel: UILabel = UILabel() + + private let plainAcornLabel: UILabel = UILabel() + + var startButton: UIButton = UIButton() + + + // MARK: - Lifecycle + + override func setHierarchy() { + super.setHierarchy() + + self.addSubviews(titleLabel, + explainationLabel, + localAcornImageView, + plainAcornImageView, + localAcornLabel, + plainAcornLabel, + startButton) + } + + override func setLayout() { + super.setLayout() + + titleLabel.snp.makeConstraints { + $0.top.equalToSuperview().inset(ScreenUtils.height*32/780) + $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) + $0.height.equalTo(56) + } + + explainationLabel.snp.makeConstraints { + $0.top.equalToSuperview().inset(ScreenUtils.height*96/780) + $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) + $0.height.equalTo(36) + } + + localAcornImageView.snp.makeConstraints { + $0.top.equalToSuperview().inset(ScreenUtils.height*258/780) + $0.leading.equalToSuperview().inset(ScreenUtils.width*73/360) + $0.width.height.equalTo(80) + } + + plainAcornImageView.snp.makeConstraints { + $0.top.equalToSuperview().inset(ScreenUtils.height*258/780) + $0.trailing.equalToSuperview().inset(ScreenUtils.width*73/360) + $0.width.height.equalTo(80) + } + + localAcornLabel.snp.makeConstraints { + $0.top.equalToSuperview().inset(ScreenUtils.height*346/780) + $0.leading.equalToSuperview().inset(ScreenUtils.width*66/360) + $0.width.equalTo(94) + } + + plainAcornLabel.snp.makeConstraints { + $0.top.equalToSuperview().inset(ScreenUtils.height*346/780) + $0.trailing.equalToSuperview().inset(ScreenUtils.width*66/360) + $0.width.equalTo(94) + } + + startButton.snp.makeConstraints { + $0.bottom.equalToSuperview().inset(ScreenUtils.height*36/780) + $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) + $0.height.equalTo(52) + } + + } + + override func setStyle() { + super.setStyle() + + self.setHandlerImageView() + + explainationLabel.do { + $0.setLabel(text: StringLiterals.LocalVerification.localAcornExplaination, + style: .b3, + color: .gray3) + } + + localAcornImageView.do { + $0.image = .localAcorn + $0.contentMode = .scaleAspectFit + } + + plainAcornImageView.do { + $0.image = .plainAcorn + $0.contentMode = .scaleAspectFit + } + + localAcornLabel.do { + $0.setLabel(text: StringLiterals.LocalVerification.localAcorn, + style: .s1, + color: .acWhite, + alignment: .center) + } + + plainAcornLabel.do { + $0.setLabel(text: StringLiterals.LocalVerification.plainAcorn, + style: .s1, + color: .acWhite, + alignment: .center) + } + + startButton.do { + $0.setAttributedTitle(text: StringLiterals.LocalVerification.letsStart, + style: .h8, + color: .gray6, + for: .disabled) + $0.setAttributedTitle(text: StringLiterals.LocalVerification.letsStart, + style: .h8, + color: .acWhite, + for: .normal) + $0.backgroundColor = .gray8 + $0.roundedButton(cornerRadius: 6, maskedCorners: [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner]) + } + } + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedViewController.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedViewController.swift new file mode 100644 index 00000000..5a2dd791 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedViewController.swift @@ -0,0 +1,85 @@ +// +// LocalVerificationFinishedViewController.swift +// ACON-iOS +// +// Created by 이수민 on 1/15/25. +// + +import UIKit + +import SnapKit +import Then + +class LocalVerificationFinishedViewController: BaseViewController { + + // MARK: - UI Properties + + private let localVerificationFinishedView = LocalVerificationFinishedView() + + let localName = "동교동" + + // MARK: - LifeCycle + + override func viewDidLoad() { + super.viewDidLoad() + + addTarget() + } + + override func setHierarchy() { + super.setHierarchy() + + self.view.addSubview(localVerificationFinishedView) + } + + override func setLayout() { + super.setLayout() + + localVerificationFinishedView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + } + + override func setStyle() { + super.setStyle() + + localVerificationFinishedView.titleLabel.do { + $0.setLabel(text: StringLiterals.LocalVerification.now + localName + StringLiterals.LocalVerification.localAcornTitle, + style: .h6, + color: .acWhite) + } + } + + func addTarget() { + localVerificationFinishedView.startButton.addTarget(self, + action: #selector(startButtonTapped), + for: .touchUpInside) + } + +} + + +// MARK: - @objc functions + +private extension LocalVerificationFinishedViewController { + + @objc + func startButtonTapped() { + closeView() + } + +} + + +// MARK: - Close View + +private extension LocalVerificationFinishedViewController { + + @objc + func closeView() { + if let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate { + sceneDelegate.window?.rootViewController = ACTabBarController() + } + } + +} From e25716d9528944f8a4f2bae51dd295421ed59ebe Mon Sep 17 00:00:00 2001 From: cirtuare Date: Wed, 15 Jan 2025 22:18:47 +0900 Subject: [PATCH 09/44] =?UTF-8?q?[Feat]=20=EB=B0=94=ED=85=80=EC=8B=9C?= =?UTF-8?q?=ED=8A=B8=20=EB=92=A4=20=EB=B8=94=EB=9F=AC=20=ED=9A=A8=EA=B3=BC?= =?UTF-8?q?=20(#21)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Colors/dim_b_60.colorset/Contents.json | 20 +++++++++++++++++++ .../View/LocalMapViewController.swift | 9 ++++++++- .../View/LocalVerificationFinishedView.swift | 2 ++ ...alVerificationFinishedViewController.swift | 9 +++++++++ .../View/SpotSearchViewController.swift | 2 +- 5 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Colors/dim_b_60.colorset/Contents.json diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Colors/dim_b_60.colorset/Contents.json b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Colors/dim_b_60.colorset/Contents.json new file mode 100644 index 00000000..c1bebfb9 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Colors/dim_b_60.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "0.600", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift index 4ab5a477..e45062ef 100644 --- a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift +++ b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift @@ -17,6 +17,8 @@ class LocalMapViewController: BaseNavViewController { private let localMapView = LocalMapView() + private var viewBlurEffect: UIVisualEffectView = UIVisualEffectView() + // MARK: - LifeCycle override func viewDidLoad() { @@ -79,7 +81,12 @@ private extension LocalMapViewController { @objc func finishVerificationButtonTapped() { let vc = LocalVerificationFinishedViewController() + vc.dismissCompletion = { [weak self] in + self?.removeBlurView() + } + vc.setMiddleSheetLayout() + self.addBlurView() self.present(vc, animated: true) } @@ -97,7 +104,7 @@ extension LocalMapViewController { // NOTE: 최초 1회만 받고 중지 ACLocationManager.shared.stopUpdatingLocation() } - + } diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedView.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedView.swift index e8c52900..45443ea4 100644 --- a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedView.swift +++ b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedView.swift @@ -94,6 +94,8 @@ final class LocalVerificationFinishedView: BaseView { super.setStyle() self.setHandlerImageView() + self.backgroundColor = .dimB60 + self.backgroundColor?.withAlphaComponent(0.8) explainationLabel.do { $0.setLabel(text: StringLiterals.LocalVerification.localAcornExplaination, diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedViewController.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedViewController.swift index 5a2dd791..eeaaa782 100644 --- a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedViewController.swift +++ b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedViewController.swift @@ -26,6 +26,15 @@ class LocalVerificationFinishedViewController: BaseViewController { addTarget() } + var dismissCompletion: (() -> Void)? + + override func viewDidDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + if isBeingDismissed { + dismissCompletion?() + } + } + override func setHierarchy() { super.setHierarchy() diff --git a/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchViewController.swift b/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchViewController.swift index ff71e4fd..7ae205d3 100644 --- a/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchViewController.swift +++ b/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchViewController.swift @@ -42,7 +42,7 @@ class SpotSearchViewController: BaseViewController { var dismissCompletion: (() -> Void)? override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) + super.viewDidDisappear(animated) if isBeingDismissed { dismissCompletion?() } From 843092cfa25522379e8db8bd90539af4d6359a15 Mon Sep 17 00:00:00 2001 From: cirtuare Date: Thu, 16 Jan 2025 00:28:24 +0900 Subject: [PATCH 10/44] =?UTF-8?q?[Feat]=20=EC=9C=84=EC=B9=98=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95=20(#21)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/LocalMapViewController.swift | 35 +++++++------------ .../LocalVerificationViewController.swift | 29 ++++++++++++++- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift index e45062ef..848aa64b 100644 --- a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift +++ b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift @@ -19,6 +19,8 @@ class LocalMapViewController: BaseNavViewController { private var viewBlurEffect: UIVisualEffectView = UIVisualEffectView() + private let coordinate: CLLocationCoordinate2D + // MARK: - LifeCycle override func viewDidLoad() { @@ -26,22 +28,22 @@ class LocalMapViewController: BaseNavViewController { self.setXButton() addTarget() - ACLocationManager.shared.addDelegate(self) + } + + init(coordinate: CLLocationCoordinate2D) { + self.coordinate = coordinate + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(false) self.tabBarController?.tabBar.isHidden = true - ACLocationManager.shared.checkUserDeviceLocationServiceAuthorization() - if CLLocationManager.authorizationStatus() == .authorizedWhenInUse || - CLLocationManager.authorizationStatus() == .authorizedAlways { - ACLocationManager.shared.startUpdatingLocation() - } - } - - deinit { - ACLocationManager.shared.removeDelegate(self) + moveCameraToLocation(latitude: coordinate.latitude, longitude: coordinate.longitude) } override func setHierarchy() { @@ -101,19 +103,6 @@ extension LocalMapViewController { let position = NMGLatLng(lat: latitude, lng: longitude) let cameraUpdate = NMFCameraUpdate(scrollTo: position, zoomTo: 17) localMapView.nMapView.mapView.moveCamera(cameraUpdate) - // NOTE: 최초 1회만 받고 중지 - ACLocationManager.shared.stopUpdatingLocation() } } - - -// MARK: - LocationManagerDelegate - -extension LocalMapViewController: ACLocationManagerDelegate { - - func locationManager(_ manager: ACLocationManager, didUpdateLocation coordinate: CLLocationCoordinate2D) { - moveCameraToLocation(latitude: coordinate.latitude, longitude: coordinate.longitude) - } - -} diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationViewController.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationViewController.swift index 45b47869..e3c2614a 100644 --- a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationViewController.swift +++ b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationViewController.swift @@ -6,6 +6,7 @@ // import UIKit +import CoreLocation import SnapKit import Then @@ -16,6 +17,8 @@ class LocalVerificationViewController: BaseNavViewController { private let localVerificationView = LocalVerificationView() + private var userCoordinate: CLLocationCoordinate2D? + // MARK: - LifeCycle override func viewDidLoad() { @@ -23,6 +26,11 @@ class LocalVerificationViewController: BaseNavViewController { self.setXButton() addTarget() + ACLocationManager.shared.addDelegate(self) + } + + deinit { + ACLocationManager.shared.removeDelegate(self) } override func viewWillAppear(_ animated: Bool) { @@ -78,7 +86,26 @@ private extension LocalVerificationViewController { @objc func nextButtonTapped() { - let vc = LocalMapViewController() + ACLocationManager.shared.checkUserDeviceLocationServiceAuthorization() + } + +} + +extension LocalVerificationViewController: ACLocationManagerDelegate { + + func locationManager(_ manager: ACLocationManager, didUpdateLocation coordinate: CLLocationCoordinate2D) { + print("성공 - 위도: \(coordinate.latitude), 경도: \(coordinate.longitude)") + self.userCoordinate = coordinate + pushToLocalMapVC() + } + +} + +extension LocalVerificationViewController { + + func pushToLocalMapVC() { + guard let coordinate = userCoordinate else { return } + let vc = LocalMapViewController(coordinate: coordinate) navigationController?.pushViewController(vc, animated: false) } From fd68f4e5f93878f41398913cdc82201b1a281215 Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 05:56:36 +0900 Subject: [PATCH 11/44] =?UTF-8?q?[Chore]=20=EC=8B=9C=EC=9E=91=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EB=A1=9C=EA=B7=B8=EC=9D=B8VC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20(#30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ACON-iOS/ACON-iOS/Application/SceneDelegate.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ACON-iOS/ACON-iOS/Application/SceneDelegate.swift b/ACON-iOS/ACON-iOS/Application/SceneDelegate.swift index 556192d8..87220d74 100644 --- a/ACON-iOS/ACON-iOS/Application/SceneDelegate.swift +++ b/ACON-iOS/ACON-iOS/Application/SceneDelegate.swift @@ -17,8 +17,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { self.window = UIWindow(windowScene: windowScene) let navigationController = UINavigationController(rootViewController: LoginViewController()) navigationController.navigationBar.isHidden = true -// self.window?.rootViewController = navigationController - self.window?.rootViewController = SpotListFilterViewController() + self.window?.rootViewController = navigationController self.window?.makeKeyAndVisible() } From b918b699f2e5193d82e09e454deae22bafd7335c Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 05:57:12 +0900 Subject: [PATCH 12/44] =?UTF-8?q?[Feat]=20ratio=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=8D=BC=ED=8B=B0=20=EC=83=9D=EC=84=B1=20(#30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ACON-iOS/ACON-iOS/Global/Utils/ScreenUtils.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ACON-iOS/ACON-iOS/Global/Utils/ScreenUtils.swift b/ACON-iOS/ACON-iOS/Global/Utils/ScreenUtils.swift index 4e688272..d140206b 100644 --- a/ACON-iOS/ACON-iOS/Global/Utils/ScreenUtils.swift +++ b/ACON-iOS/ACON-iOS/Global/Utils/ScreenUtils.swift @@ -22,4 +22,17 @@ struct ScreenUtils { return UIScreen.main.bounds.height } + + // MARK: - width 비율 + + static var widthRatio: CGFloat { + let figmaWidth: CGFloat = 360 + return width / figmaWidth + } + + static var heightRatio: CGFloat { + let figmaHeight: CGFloat = 780 + return height / figmaHeight + } + } From c85f047e694a0a1b29ee4a6cecb3f81588654939 Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 06:02:31 +0900 Subject: [PATCH 13/44] =?UTF-8?q?[Chore]=20=EC=A3=BC=EC=84=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ACON-iOS/ACON-iOS/Global/Utils/ScreenUtils.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ACON-iOS/ACON-iOS/Global/Utils/ScreenUtils.swift b/ACON-iOS/ACON-iOS/Global/Utils/ScreenUtils.swift index d140206b..5fc0c380 100644 --- a/ACON-iOS/ACON-iOS/Global/Utils/ScreenUtils.swift +++ b/ACON-iOS/ACON-iOS/Global/Utils/ScreenUtils.swift @@ -23,7 +23,7 @@ struct ScreenUtils { } - // MARK: - width 비율 + // MARK: - 비율 프로퍼티 static var widthRatio: CGFloat { let figmaWidth: CGFloat = 360 From a42d4de130ecf56e1e470622cb7cd80f3ea6f430 Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 08:13:28 +0900 Subject: [PATCH 14/44] =?UTF-8?q?[Feat]=20ACStyle=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EB=8B=A4=EB=A5=B8=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ACON-iOS/Global/Extensions/String+.swift | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ACON-iOS/ACON-iOS/Global/Extensions/String+.swift b/ACON-iOS/ACON-iOS/Global/Extensions/String+.swift index 6ab9e608..de88ef32 100644 --- a/ACON-iOS/ACON-iOS/Global/Extensions/String+.swift +++ b/ACON-iOS/ACON-iOS/Global/Extensions/String+.swift @@ -26,4 +26,21 @@ extension String { return NSAttributedString(string: self, attributes: attributes) } + static func ACStyle(_ style: ACFontStyleType, _ color: UIColor = .acWhite) -> [NSAttributedString.Key: Any] { + let attributes: [NSAttributedString.Key: Any] = [ + .font: style.font, + .kern: style.kerning, + .paragraphStyle: { + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.minimumLineHeight = style.lineHeight + paragraphStyle.maximumLineHeight = style.lineHeight + return paragraphStyle + }(), + .foregroundColor: color, + .baselineOffset: (style.lineHeight - style.font.lineHeight) / 2 + ] + + return attributes + } + } From 79c24c31664c47aa8441287689ad5d8448c81be4 Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 08:13:52 +0900 Subject: [PATCH 15/44] =?UTF-8?q?[Chore]=20StringLiterals=20=EC=84=B8?= =?UTF-8?q?=EA=B7=B8=EB=A8=BC=ED=8A=B8=EC=BB=A8=ED=8A=B8=EB=A1=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift b/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift index b7f9f270..86af1c3d 100644 --- a/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift +++ b/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift @@ -101,4 +101,10 @@ enum StringLiterals { } + enum SpotListFilter { + + static let restaurant = "음식점" + static let cafe = "카페" + } + } From 70f1cf37aac89c30b26b5173873427bfba3b90d8 Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 08:14:10 +0900 Subject: [PATCH 16/44] =?UTF-8?q?[Feat]=20=EC=84=B8=EA=B7=B8=EB=A8=BC?= =?UTF-8?q?=ED=8A=B8=EC=BB=A8=ED=8A=B8=EB=A1=A4=20=EC=B6=94=EA=B0=80=20(#2?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/SpotListFilterView.swift | 50 +++++++++++++++++-- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift index 3689af76..914731ff 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift @@ -11,7 +11,12 @@ class SpotListFilterView: BaseView { // MARK: - UI Properties - let spotTypeSegmentControl = UISegmentedControl() + private let segmentItems = [StringLiterals.SpotListFilter.restaurant, + StringLiterals.SpotListFilter.cafe] + + private lazy var segmentedControl = UISegmentedControl(items: segmentItems) + + private let segmentedControlBgView = UIView() let spotFeatureStackView = SpotFilterTagButtonStackView() @@ -23,23 +28,60 @@ class SpotListFilterView: BaseView { override func setHierarchy() { super.setHierarchy() - self.addSubviews(spotTypeSegmentControl, + self.addSubviews(segmentedControlBgView, spotFeatureStackView) + + segmentedControlBgView.addSubview(segmentedControl) } override func setLayout() { super.setLayout() + segmentedControlBgView.snp.makeConstraints { + $0.top.equalToSuperview().offset(ScreenUtils.heightRatio * 17) + $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.heightRatio * 20) + $0.height.equalTo(ScreenUtils.heightRatio * 37) + } + + segmentedControl.snp.makeConstraints { + $0.edges.equalToSuperview().inset(ScreenUtils.heightRatio * 3) + } + spotFeatureStackView.snp.makeConstraints { - $0.top.equalTo(self.safeAreaLayoutGuide).offset(17) - $0.horizontalEdges.equalToSuperview().inset(20) + $0.top.equalTo(segmentedControl.snp.bottom).offset(ScreenUtils.heightRatio * 12) + $0.horizontalEdges.equalTo(segmentedControl) } } override func setStyle() { super.setStyle() + setSegmentControl() + + // TODO: 추후 추가 예정 } } + + +// MARK: - UI Settings + +private extension SpotListFilterView { + + func setSegmentControl() { + segmentedControlBgView.do { + $0.backgroundColor = .gray8 + $0.layer.cornerRadius = ScreenUtils.heightRatio * 6 + } + + segmentedControl.do { + $0.selectedSegmentIndex = 0 + $0.selectedSegmentTintColor = .acWhite + $0.backgroundColor = .gray8 + $0.setTitleTextAttributes(String.ACStyle(.s2, .gray5), for: .normal) + $0.setTitleTextAttributes(String.ACStyle(.s2, .gray9), for: .selected) + } + } + +} From 73f747daea3f9e28a43b97d4e1ac32ed5a37e8aa Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 08:14:35 +0900 Subject: [PATCH 17/44] =?UTF-8?q?[Add,=20Feat]=20=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=ED=85=80=20=EC=84=B8=EA=B7=B8=EB=A8=BC=ED=8A=B8=EC=BB=A8?= =?UTF-8?q?=ED=8A=B8=EB=A1=A4=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj | 4 +++ .../Component/CustomSegmentedControl.swift | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/CustomSegmentedControl.swift diff --git a/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj b/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj index ec45973c..2f837349 100644 --- a/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj +++ b/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ 1558BAE12D31D41F00ECDEF8 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1558BAE02D31D41F00ECDEF8 /* ProfileViewController.swift */; }; 15A3F6A62D36C49F00577E16 /* SpotListItemSizeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15A3F6A52D36C49F00577E16 /* SpotListItemSizeType.swift */; }; 15A3F6A82D36D5B900577E16 /* FilterTagButtonStackLineType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15A3F6A72D36D5B900577E16 /* FilterTagButtonStackLineType.swift */; }; + 15A3F6E02D38724A00577E16 /* CustomSegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15A3F6DF2D38724A00577E16 /* CustomSegmentedControl.swift */; }; 71DE8AE7F1C4CF9051C631A3 /* Pods_ACON_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B28431BAB67B99CD7B85C705 /* Pods_ACON_iOS.framework */; }; 74054ECA2D32534200D1CDE4 /* MultitaskDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74054EC92D32533800D1CDE4 /* MultitaskDelegate.swift */; }; 74054ECE2D32549F00D1CDE4 /* ACLocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74054ECD2D32549800D1CDE4 /* ACLocationManager.swift */; }; @@ -106,6 +107,7 @@ 1558BAE02D31D41F00ECDEF8 /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; 15A3F6A52D36C49F00577E16 /* SpotListItemSizeType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotListItemSizeType.swift; sourceTree = ""; }; 15A3F6A72D36D5B900577E16 /* FilterTagButtonStackLineType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterTagButtonStackLineType.swift; sourceTree = ""; }; + 15A3F6DF2D38724A00577E16 /* CustomSegmentedControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSegmentedControl.swift; sourceTree = ""; }; 50546F80D11F73C6A00B6C92 /* Pods-ACON-iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ACON-iOS.release.xcconfig"; path = "Target Support Files/Pods-ACON-iOS/Pods-ACON-iOS.release.xcconfig"; sourceTree = ""; }; 74054EC92D32533800D1CDE4 /* MultitaskDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultitaskDelegate.swift; sourceTree = ""; }; 74054ECD2D32549800D1CDE4 /* ACLocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACLocationManager.swift; sourceTree = ""; }; @@ -286,6 +288,7 @@ children = ( 1547A88D2D35B13700E96616 /* FilterTagButton.swift */, 1547A8912D363E0000E96616 /* SpotFilterTagButtonStackView.swift */, + 15A3F6DF2D38724A00577E16 /* CustomSegmentedControl.swift */, ); path = Component; sourceTree = ""; @@ -743,6 +746,7 @@ 748D6F822D2BCDB3007690B4 /* ScreenUtils.swift in Sources */, 745C7E122D35A62B0074DBDB /* UploadModel.swift in Sources */, 748D6FAE2D2EBB9C007690B4 /* ACFont.swift in Sources */, + 15A3F6E02D38724A00577E16 /* CustomSegmentedControl.swift in Sources */, 748D6F692D2BCA1C007690B4 /* AppDelegate.swift in Sources */, 74054ECE2D32549F00D1CDE4 /* ACLocationManager.swift in Sources */, 745C7E0D2D35A04A0074DBDB /* RelatedSearchCollectionViewCell.swift in Sources */, diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/CustomSegmentedControl.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/CustomSegmentedControl.swift new file mode 100644 index 00000000..d5fa5342 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/CustomSegmentedControl.swift @@ -0,0 +1,35 @@ +// +// SpotSegmentedControl.swift +// ACON-iOS +// +// Created by 김유림 on 1/16/25. +// + +import UIKit + +class CustomSegmentedControl: UISegmentedControl { + + override init(frame: CGRect) { + super.init(frame: frame) + self.removeBackgroundAndDivider() + } + + override init(items: [Any]?) { + super.init(items: items) + self.removeBackgroundAndDivider() + } + + required init?(coder: NSCoder) { + fatalError() + } + + private func removeBackgroundAndDivider() { + let image = UIImage() + self.setBackgroundImage(image, for: .normal, barMetrics: .default) + self.setBackgroundImage(image, for: .selected, barMetrics: .default) + self.setBackgroundImage(image, for: .highlighted, barMetrics: .default) + + self.setDividerImage(image, forLeftSegmentState: .selected, rightSegmentState: .normal, barMetrics: .default) + } + +} From 9420ce86a752a20f06cba8138d94609e8f02bde5 Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 09:48:34 +0900 Subject: [PATCH 18/44] =?UTF-8?q?[Fix]=20emptyView=20=EC=A0=81=EC=9A=A9=20?= =?UTF-8?q?=EC=95=88=20=EB=90=98=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Component/SpotFilterTagButtonStackView.swift | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/SpotFilterTagButtonStackView.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/SpotFilterTagButtonStackView.swift index c30ef910..5c663125 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/SpotFilterTagButtonStackView.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/SpotFilterTagButtonStackView.swift @@ -68,10 +68,13 @@ extension SpotFilterTagButtonStackView { func addEmptyView() { // TODO: [Fix] priority low로 자동으로 작게 설정되지 않는 듯. [프랜차이즈 제외]가 2줄로 됨 - let emptyView = UIView() - emptyView.setContentHuggingPriority(.defaultLow, for: .horizontal) - firstLineStackView.addArrangedSubview(emptyView) - secondLineStackView.addArrangedSubview(emptyView) + let emptyView1 = UIView() + let emptyView2 = UIView() + [emptyView1, emptyView2].forEach { + $0.setContentHuggingPriority(.defaultLow, for: .horizontal) + } + firstLineStackView.addArrangedSubview(emptyView1) + secondLineStackView.addArrangedSubview(emptyView2) } private func clearStackView(from stackView: UIStackView) { From 12dd96fd84256a843eb32490cd24c0687433b414 Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 09:49:10 +0900 Subject: [PATCH 19/44] =?UTF-8?q?[Chore]=20segmentedControlBgView=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=EC=B2=98=EB=A6=AC=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SpotListFilter/View/SpotListFilterView.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift index 914731ff..041d2aab 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift @@ -14,7 +14,7 @@ class SpotListFilterView: BaseView { private let segmentItems = [StringLiterals.SpotListFilter.restaurant, StringLiterals.SpotListFilter.cafe] - private lazy var segmentedControl = UISegmentedControl(items: segmentItems) + lazy var segmentedControl = UISegmentedControl(items: segmentItems) private let segmentedControlBgView = UIView() @@ -71,10 +71,11 @@ private extension SpotListFilterView { func setSegmentControl() { segmentedControlBgView.do { - $0.backgroundColor = .gray8 +// $0.backgroundColor = .gray8 $0.layer.cornerRadius = ScreenUtils.heightRatio * 6 } + // TODO: 배경 없애기 커스텀 segmentedControl.do { $0.selectedSegmentIndex = 0 $0.selectedSegmentTintColor = .acWhite From 2403f3e65311db0927c0e644bcf395b11b1b0a7e Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 09:50:09 +0900 Subject: [PATCH 20/44] =?UTF-8?q?[Feat]=20segmentedControl=20=EC=95=A1?= =?UTF-8?q?=EC=85=98=20=EC=84=A4=EC=A0=95=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/SpotListFilterViewController.swift | 119 ++++++++++++++---- 1 file changed, 97 insertions(+), 22 deletions(-) diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift index 90c79778..b563848b 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift @@ -21,6 +21,7 @@ class SpotListFilterViewController: BaseNavViewController { super.viewDidLoad() bindViewModel() + addTargets() } override func setHierarchy() { @@ -52,14 +53,28 @@ private extension SpotListFilterViewController { let spotType = spotType else { return } + print("spotType: \(spotType)") updateView(spotType) - print("updateView!") } } } +// MARK: - Add Target + +private extension SpotListFilterViewController { + + func addTargets() { + spotListFilterView.segmentedControl.addTarget( + self, + action: #selector(didChangeSpot), + for: .valueChanged) + } + +} + + // MARK: - UI Update private extension SpotListFilterViewController { @@ -74,34 +89,75 @@ private extension SpotListFilterViewController { private func updateFeatureStack(_ spotType: SpotType) { let featureStack = spotListFilterView.spotFeatureStackView - // clear stack + // NOTE: clear stack featureStack.clearStackView() - // add buttons - for feature in SpotListFilterModel.RestaurantFeature.firstLine { - let btn = FilterTagButton() + // TODO: Type으로 바꾸기 +// switch spotType { +// case .restaurant: +// let arr = SpotType.RestaurantFeatureType.allCases +// let arr1 = returnFirstArray(array: arr, firstLineCount: spotType.featureFirstLineCount) +// let arr2 = returnSecondArray(array: arr, firstLineCount: spotType.featureFirstLineCount) +// +// case .cafe: +// let arr = SpotType.CafeFeatureType.allCases +// let arr1 = returnFirstArray(array: arr, firstLineCount: spotType.featureFirstLineCount) +// let arr2 = returnSecondArray(array: arr, firstLineCount: spotType.featureFirstLineCount) +// } + + // NOTE: add buttons + switch spotType { + case .restaurant: + for feature in SpotListFilterModel.RestaurantFeature.firstLine { + let btn = FilterTagButton() + + btn.setAttributedTitle(text: feature.text, style: .b3) + btn.addTarget(self, + action: #selector(didTapFilterTagButton), + for: .touchUpInside) + + featureStack.addTagButton(to: .first, + button: btn) + } - btn.setAttributedTitle(text: feature.text, style: .b3) - btn.addTarget(self, - action: #selector(didTapFilterTagButton), - for: .touchUpInside) + for feature in SpotListFilterModel.RestaurantFeature.secondLine { + let btn = FilterTagButton() + btn.setAttributedTitle(text: feature.text, style: .b3) + btn.addTarget(self, + action: #selector(didTapFilterTagButton(_:)), + for: .touchUpInside) + + featureStack.addTagButton(to: .second, + button: btn) + } - featureStack.addTagButton(to: .first, - button: btn) - } - - for feature in SpotListFilterModel.RestaurantFeature.secondLine { - let btn = FilterTagButton() - btn.setAttributedTitle(text: feature.text, style: .b3) - btn.addTarget(self, - action: #selector(didTapFilterTagButton(_:)), - for: .touchUpInside) - featureStack.addTagButton(to: .second, - button: btn) + case .cafe: + for feature in SpotListFilterModel.CafeFeature.firstLine { + let btn = FilterTagButton() + + btn.setAttributedTitle(text: feature.text, style: .b3) + btn.addTarget(self, + action: #selector(didTapFilterTagButton), + for: .touchUpInside) + + featureStack.addTagButton(to: .first, + button: btn) + } + + for feature in SpotListFilterModel.CafeFeature.secondLine { + let btn = FilterTagButton() + btn.setAttributedTitle(text: feature.text, style: .b3) + btn.addTarget(self, + action: #selector(didTapFilterTagButton(_:)), + for: .touchUpInside) + + featureStack.addTagButton(to: .second, + button: btn) + } } - featureStack.addEmptyView() + featureStack.addEmptyView() // 오른쪽 여백 추가 } } @@ -116,4 +172,23 @@ private extension SpotListFilterViewController { sender.isSelected.toggle() } + @objc func didChangeSpot(segment: UISegmentedControl) { + print("didChangeSpot") + let index = segment.selectedSegmentIndex + viewModel.spotType.value = index == 0 ? .restaurant : .cafe + } +} + + +// MARK: - Assisting method + +extension SpotListFilterViewController { + + func returnFirstArray(array: T, firstLineCount: Int) -> T where T: RangeReplaceableCollection, T: RandomAccessCollection { + return T(array.prefix(firstLineCount)) + } + + func returnSecondArray(array: T, firstLineCount: Int) -> T where T: RangeReplaceableCollection, T: RandomAccessCollection { + return T(array.dropFirst(firstLineCount)) + } } From 207501451a6a4de2570bc27dbe26876a906e9f55 Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 09:51:21 +0900 Subject: [PATCH 21/44] =?UTF-8?q?[Chore]=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SpotListFilter/Type/SpotType.swift | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/SpotType.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/SpotType.swift index 9133ce38..63463b28 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/SpotType.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/SpotType.swift @@ -20,6 +20,13 @@ enum SpotType { } } + var featureFirstLineCount: Int { + switch self { + case .restaurant: return 5 + case .cafe: return 4 + } + } + // MARK: - 장소 상세 조건 @@ -40,10 +47,6 @@ enum SpotType { } } - var firstLineCount: Int { - return 5 - } - } enum CafeFeatureType { @@ -60,13 +63,9 @@ enum SpotType { } } - var firstLineCount: Int { - return 4 - } - } - enum CompanionType { + enum CompanionType: CaseIterable { case family, date, friend, alone, group @@ -82,7 +81,7 @@ enum SpotType { } - enum VisitPurposeType { + enum VisitPurposeType: CaseIterable { case meeting, study From 5fb082a88b6ed5db8ef656c33fdc62165a1583eb Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 09:52:20 +0900 Subject: [PATCH 22/44] =?UTF-8?q?[Chore]=20Todo=20=EC=82=AD=EC=A0=9C,=20?= =?UTF-8?q?=EC=A4=84=EB=B0=94=EA=BF=88=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Component/SpotFilterTagButtonStackView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/SpotFilterTagButtonStackView.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/SpotFilterTagButtonStackView.swift index 5c663125..681dae9a 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/SpotFilterTagButtonStackView.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/SpotFilterTagButtonStackView.swift @@ -67,12 +67,13 @@ extension SpotFilterTagButtonStackView { } func addEmptyView() { - // TODO: [Fix] priority low로 자동으로 작게 설정되지 않는 듯. [프랜차이즈 제외]가 2줄로 됨 let emptyView1 = UIView() let emptyView2 = UIView() + [emptyView1, emptyView2].forEach { $0.setContentHuggingPriority(.defaultLow, for: .horizontal) } + firstLineStackView.addArrangedSubview(emptyView1) secondLineStackView.addArrangedSubview(emptyView2) } From 211aba90285f99067d61bac7de6336715409a350 Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 09:54:38 +0900 Subject: [PATCH 23/44] =?UTF-8?q?[Chore]=20Todo=20=EC=9E=91=EC=84=B1=20(#2?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Component/CustomSegmentedControl.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/CustomSegmentedControl.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/CustomSegmentedControl.swift index d5fa5342..dcd765e8 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/CustomSegmentedControl.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/CustomSegmentedControl.swift @@ -7,6 +7,10 @@ import UIKit +// TODO: 커스텀 세그먼트컨트롤 만들기... +/// - 배경 insets +/// - attributed string + class CustomSegmentedControl: UISegmentedControl { override init(frame: CGRect) { From 4e2d6ff0d430f6c2f3c1d9bebd1e5c8fad6e1e46 Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 10:24:39 +0900 Subject: [PATCH 24/44] =?UTF-8?q?[Feat]=20=EC=9E=A5=EC=86=8C=20=EC=84=B9?= =?UTF-8?q?=EC=85=98=20=EA=B5=AC=ED=98=84=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Global/Literals/StringLiterals.swift | 6 ++ .../View/SpotListFilterView.swift | 73 ++++++++++++++++--- 2 files changed, 68 insertions(+), 11 deletions(-) diff --git a/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift b/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift index 86af1c3d..aff219ed 100644 --- a/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift +++ b/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift @@ -104,7 +104,13 @@ enum StringLiterals { enum SpotListFilter { static let restaurant = "음식점" + static let cafe = "카페" + + static let spotSection = "방문 장소" + + static let companionSection = "함께 하는 사람" + } } diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift index 041d2aab..58d4be9c 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift @@ -9,18 +9,32 @@ import UIKit class SpotListFilterView: BaseView { - // MARK: - UI Properties + // MARK: - Properties private let segmentItems = [StringLiterals.SpotListFilter.restaurant, StringLiterals.SpotListFilter.cafe] + + // MARK: - UI Properties + + // [Spot section]: 방문 장소 (restaurant + cafe) + + private let spotSectionStackView = UIStackView() + + private let spotSectionTitleLabel = UILabel() + lazy var segmentedControl = UISegmentedControl(items: segmentItems) private let segmentedControlBgView = UIView() let spotFeatureStackView = SpotFilterTagButtonStackView() - // TODO: 함께 하는 사람, 방문 목적, 도보 가능 거리, 가격대 + + // [Companion section]: 함께 하는 사람 (restaurant) + + let companionTypeStackView = SpotFilterTagButtonStackView() + + // TODO: 방문 목적, 도보 가능 거리, 가격대 // MARK: - Lifecycle @@ -28,18 +42,31 @@ class SpotListFilterView: BaseView { override func setHierarchy() { super.setHierarchy() - self.addSubviews(segmentedControlBgView, - spotFeatureStackView) + self.addSubviews(spotSectionStackView) + + // [Spot section] + + spotSectionStackView.addArrangedSubviews(spotSectionTitleLabel, + segmentedControlBgView, + spotFeatureStackView) segmentedControlBgView.addSubview(segmentedControl) + + + } override func setLayout() { super.setLayout() + // [Spot section] + + spotSectionStackView.snp.makeConstraints { + $0.top.equalToSuperview().offset(ScreenUtils.heightRatio * 41) + $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.widthRatio * 20) + } + segmentedControlBgView.snp.makeConstraints { - $0.top.equalToSuperview().offset(ScreenUtils.heightRatio * 17) - $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.heightRatio * 20) $0.height.equalTo(ScreenUtils.heightRatio * 37) } @@ -47,16 +74,19 @@ class SpotListFilterView: BaseView { $0.edges.equalToSuperview().inset(ScreenUtils.heightRatio * 3) } - spotFeatureStackView.snp.makeConstraints { - $0.top.equalTo(segmentedControl.snp.bottom).offset(ScreenUtils.heightRatio * 12) - $0.horizontalEdges.equalTo(segmentedControl) - } + + + } override func setStyle() { super.setStyle() - setSegmentControl() + // [Spot section] + + setSpotSectionUI() + + // TODO: 추후 추가 예정 @@ -69,6 +99,23 @@ class SpotListFilterView: BaseView { private extension SpotListFilterView { + // MARK: - (Spot section) + + func setSpotSectionUI() { + spotSectionStackView.do { + $0.axis = .vertical + $0.spacing = 12 + } + + spotSectionTitleLabel.do { + $0.setLabel(text: StringLiterals.SpotListFilter.spotSection, + style: .s2) + + setSegmentControl() + + } + } + func setSegmentControl() { segmentedControlBgView.do { // $0.backgroundColor = .gray8 @@ -86,3 +133,7 @@ private extension SpotListFilterView { } } + + + +// MARK: - UI Settings (Companion section) From 933f30b95ec48d2a4a75cba800eb4c3711f95664 Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 10:33:42 +0900 Subject: [PATCH 25/44] =?UTF-8?q?[Chore]=20=EC=A0=84=EC=B2=B4=20=EC=8A=A4?= =?UTF-8?q?=ED=83=9D=EB=B7=B0=EB=A1=9C=20=EB=AC=B6=EA=B8=B0=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/SpotListFilterView.swift | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift index 58d4be9c..7aa4f265 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift @@ -17,6 +17,9 @@ class SpotListFilterView: BaseView { // MARK: - UI Properties + private let stackView = UIStackView() + + // [Spot section]: 방문 장소 (restaurant + cafe) private let spotSectionStackView = UIStackView() @@ -27,12 +30,19 @@ class SpotListFilterView: BaseView { private let segmentedControlBgView = UIView() - let spotFeatureStackView = SpotFilterTagButtonStackView() + let spotTagStackView = SpotFilterTagButtonStackView() // [Companion section]: 함께 하는 사람 (restaurant) - let companionTypeStackView = SpotFilterTagButtonStackView() + private let companionSectionStackView = SpotFilterTagButtonStackView() + + private let companionSectionTitleLabel = UILabel() + + let companionTagStackView = SpotFilterTagButtonStackView() + + + // TODO: 방문 목적, 도보 가능 거리, 가격대 @@ -42,26 +52,32 @@ class SpotListFilterView: BaseView { override func setHierarchy() { super.setHierarchy() - self.addSubviews(spotSectionStackView) + self.addSubviews(stackView) + + stackView.addArrangedSubviews(spotSectionStackView, + companionSectionStackView) // [Spot section] spotSectionStackView.addArrangedSubviews(spotSectionTitleLabel, segmentedControlBgView, - spotFeatureStackView) + spotTagStackView) segmentedControlBgView.addSubview(segmentedControl) + // [Companion section] + + companionSectionStackView.addArrangedSubviews(companionSectionTitleLabel, + companionTagStackView) + } override func setLayout() { super.setLayout() - // [Spot section] - - spotSectionStackView.snp.makeConstraints { + stackView.snp.makeConstraints { $0.top.equalToSuperview().offset(ScreenUtils.heightRatio * 41) $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.widthRatio * 20) } @@ -75,6 +91,7 @@ class SpotListFilterView: BaseView { } + // [Companion section } @@ -82,6 +99,11 @@ class SpotListFilterView: BaseView { override func setStyle() { super.setStyle() + stackView.do { + $0.axis = .vertical + $0.spacing = 32 + } + // [Spot section] setSpotSectionUI() From a4fb0cfeba9f126848e42c61e12a28d794a91c76 Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 14:44:42 +0900 Subject: [PATCH 26/44] =?UTF-8?q?[Refactor]=20=ED=83=9C=EA=B7=B8=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=8A=A4=ED=83=9D=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20(#2?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj | 14 ++- .../View/Component/FilterTagButton.swift | 10 +- .../View/Component/PriorityLowEmptyView.swift | 23 ++++ .../SpotFilterTagButtonStackView.swift | 88 -------------- .../Component/SpotFilterTagStackView.swift | 74 ++++++++++++ .../View/SpotListFilterView.swift | 99 ++++++++++------ .../View/SpotListFilterViewController.swift | 110 +++--------------- 7 files changed, 195 insertions(+), 223 deletions(-) create mode 100644 ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/PriorityLowEmptyView.swift delete mode 100644 ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/SpotFilterTagButtonStackView.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/SpotFilterTagStackView.swift diff --git a/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj b/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj index 2f837349..8b39e7d1 100644 --- a/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj +++ b/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj @@ -19,7 +19,6 @@ 1547A88B2D3596B600E96616 /* SpotType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1547A88A2D3596B600E96616 /* SpotType.swift */; }; 1547A88E2D35B13700E96616 /* FilterTagButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1547A88D2D35B13700E96616 /* FilterTagButton.swift */; }; 1547A8902D35B30C00E96616 /* FilterTagButtonType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1547A88F2D35B30C00E96616 /* FilterTagButtonType.swift */; }; - 1547A8922D363E0000E96616 /* SpotFilterTagButtonStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1547A8912D363E0000E96616 /* SpotFilterTagButtonStackView.swift */; }; 1558BA1A2D318FFC00ECDEF8 /* ACTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1558BA192D318FFC00ECDEF8 /* ACTabBarController.swift */; }; 1558BADB2D31AAF900ECDEF8 /* ACTabBarItemType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1558BADA2D31AAF900ECDEF8 /* ACTabBarItemType.swift */; }; 1558BADE2D31AB6C00ECDEF8 /* SpotListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1558BADD2D31AB6C00ECDEF8 /* SpotListViewController.swift */; }; @@ -27,6 +26,8 @@ 15A3F6A62D36C49F00577E16 /* SpotListItemSizeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15A3F6A52D36C49F00577E16 /* SpotListItemSizeType.swift */; }; 15A3F6A82D36D5B900577E16 /* FilterTagButtonStackLineType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15A3F6A72D36D5B900577E16 /* FilterTagButtonStackLineType.swift */; }; 15A3F6E02D38724A00577E16 /* CustomSegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15A3F6DF2D38724A00577E16 /* CustomSegmentedControl.swift */; }; + 15A3F7A12D38C71900577E16 /* PriorityLowEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15A3F7A02D38C71900577E16 /* PriorityLowEmptyView.swift */; }; + 15A3F7A32D38C7FF00577E16 /* SpotFilterTagStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15A3F7A22D38C7FF00577E16 /* SpotFilterTagStackView.swift */; }; 71DE8AE7F1C4CF9051C631A3 /* Pods_ACON_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B28431BAB67B99CD7B85C705 /* Pods_ACON_iOS.framework */; }; 74054ECA2D32534200D1CDE4 /* MultitaskDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74054EC92D32533800D1CDE4 /* MultitaskDelegate.swift */; }; 74054ECE2D32549F00D1CDE4 /* ACLocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74054ECD2D32549800D1CDE4 /* ACLocationManager.swift */; }; @@ -100,7 +101,6 @@ 1547A88A2D3596B600E96616 /* SpotType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotType.swift; sourceTree = ""; }; 1547A88D2D35B13700E96616 /* FilterTagButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterTagButton.swift; sourceTree = ""; }; 1547A88F2D35B30C00E96616 /* FilterTagButtonType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterTagButtonType.swift; sourceTree = ""; }; - 1547A8912D363E0000E96616 /* SpotFilterTagButtonStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotFilterTagButtonStackView.swift; sourceTree = ""; }; 1558BA192D318FFC00ECDEF8 /* ACTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACTabBarController.swift; sourceTree = ""; }; 1558BADA2D31AAF900ECDEF8 /* ACTabBarItemType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACTabBarItemType.swift; sourceTree = ""; }; 1558BADD2D31AB6C00ECDEF8 /* SpotListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotListViewController.swift; sourceTree = ""; }; @@ -108,6 +108,8 @@ 15A3F6A52D36C49F00577E16 /* SpotListItemSizeType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotListItemSizeType.swift; sourceTree = ""; }; 15A3F6A72D36D5B900577E16 /* FilterTagButtonStackLineType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterTagButtonStackLineType.swift; sourceTree = ""; }; 15A3F6DF2D38724A00577E16 /* CustomSegmentedControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSegmentedControl.swift; sourceTree = ""; }; + 15A3F7A02D38C71900577E16 /* PriorityLowEmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriorityLowEmptyView.swift; sourceTree = ""; }; + 15A3F7A22D38C7FF00577E16 /* SpotFilterTagStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotFilterTagStackView.swift; sourceTree = ""; }; 50546F80D11F73C6A00B6C92 /* Pods-ACON-iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ACON-iOS.release.xcconfig"; path = "Target Support Files/Pods-ACON-iOS/Pods-ACON-iOS.release.xcconfig"; sourceTree = ""; }; 74054EC92D32533800D1CDE4 /* MultitaskDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultitaskDelegate.swift; sourceTree = ""; }; 74054ECD2D32549800D1CDE4 /* ACLocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACLocationManager.swift; sourceTree = ""; }; @@ -286,9 +288,10 @@ 1547A88C2D35AE5C00E96616 /* Component */ = { isa = PBXGroup; children = ( - 1547A88D2D35B13700E96616 /* FilterTagButton.swift */, - 1547A8912D363E0000E96616 /* SpotFilterTagButtonStackView.swift */, 15A3F6DF2D38724A00577E16 /* CustomSegmentedControl.swift */, + 1547A88D2D35B13700E96616 /* FilterTagButton.swift */, + 15A3F7A02D38C71900577E16 /* PriorityLowEmptyView.swift */, + 15A3F7A22D38C7FF00577E16 /* SpotFilterTagStackView.swift */, ); path = Component; sourceTree = ""; @@ -759,6 +762,7 @@ 1547A8902D35B30C00E96616 /* FilterTagButtonType.swift in Sources */, 748ECA712D31A72A00BBC981 /* LoginViewModel.swift in Sources */, 748D6F872D2BD247007690B4 /* UIView+.swift in Sources */, + 15A3F7A32D38C7FF00577E16 /* SpotFilterTagStackView.swift in Sources */, 1547A6EB2D337F4100E96616 /* SpotListView.swift in Sources */, 1547A6EF2D33854B00E96616 /* SpotListCollectionViewCell.swift in Sources */, 74CDCE562D310B1600E3A21A /* String+.swift in Sources */, @@ -774,7 +778,6 @@ 741A07592D355F1400778219 /* ReviewFinishedViewController.swift in Sources */, 748D6F892D2BD294007690B4 /* UIViewController+.swift in Sources */, 745C7E042D3599DF0074DBDB /* SpotSearchViewController.swift in Sources */, - 1547A8922D363E0000E96616 /* SpotFilterTagButtonStackView.swift in Sources */, 74B25C2C2D2FEE8E008BDCB7 /* Config.swift in Sources */, 74054ECA2D32534200D1CDE4 /* MultitaskDelegate.swift in Sources */, 748D6F8F2D2BD340007690B4 /* UIButton+.swift in Sources */, @@ -787,6 +790,7 @@ 1558BA1A2D318FFC00ECDEF8 /* ACTabBarController.swift in Sources */, 1547A8802D358E2700E96616 /* SpotListFilterView.swift in Sources */, 74CDCE542D310A1C00E3A21A /* ACFontStyleType.swift in Sources */, + 15A3F7A12D38C71900577E16 /* PriorityLowEmptyView.swift in Sources */, 748D6F802D2BCD94007690B4 /* ObservablePattern.swift in Sources */, 1558BADE2D31AB6C00ECDEF8 /* SpotListViewController.swift in Sources */, 1547A8732D34157700E96616 /* SpotListViewModel.swift in Sources */, diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/FilterTagButton.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/FilterTagButton.swift index 6ac87590..cd447e4d 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/FilterTagButton.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/FilterTagButton.swift @@ -13,7 +13,6 @@ class FilterTagButton: UIButton { override var isSelected: Bool { didSet { - print("button isSelected") configuration?.baseBackgroundColor = isSelected ? .subOrg35 : .gray8 configuration?.background.strokeColor = isSelected ? .org1 : .gray6 } @@ -26,6 +25,7 @@ class FilterTagButton: UIButton { super.init(frame: frame) configureButton() + addTarget() } required init?(coder: NSCoder) { @@ -55,4 +55,12 @@ private extension FilterTagButton { self.configuration = config } + func addTarget() { + self.addTarget(self, action: #selector(toggleSelf), for: .touchUpInside) + } + + @objc + func toggleSelf(_ sender: UIButton) { + isSelected.toggle() + } } diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/PriorityLowEmptyView.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/PriorityLowEmptyView.swift new file mode 100644 index 00000000..8ea8df0b --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/PriorityLowEmptyView.swift @@ -0,0 +1,23 @@ +// +// PriorityLowEmptyView.swift +// ACON-iOS +// +// Created by 김유림 on 1/16/25. +// + +import UIKit + +class PriorityLowEmptyView: UIView { + + override init(frame: CGRect) { + super.init(frame: frame) + + setContentHuggingPriority(.defaultLow, for: .horizontal) + setContentHuggingPriority(.defaultLow, for: .vertical) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/SpotFilterTagButtonStackView.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/SpotFilterTagButtonStackView.swift deleted file mode 100644 index 681dae9a..00000000 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/SpotFilterTagButtonStackView.swift +++ /dev/null @@ -1,88 +0,0 @@ -// -// RestaurantOptionStackView.swift -// ACON-iOS -// -// Created by 김유림 on 1/14/25. -// - -import UIKit - -class SpotFilterTagButtonStackView: UIStackView { - - // MARK: - UI Properties - - private let firstLineStackView = UIStackView() - private let secondLineStackView = UIStackView() - - - // MARK: - LifeCycle - - override init(frame: CGRect) { - super.init(frame: frame) - - setHierarchy() - setStyle() - } - - required init(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func setHierarchy() { - self.addArrangedSubviews(firstLineStackView, - secondLineStackView) - } - - private func setStyle() { - self.alignment = .leading - self.axis = .vertical - self.spacing = 5 - - firstLineStackView.axis = .horizontal - firstLineStackView.spacing = 5 - - secondLineStackView.axis = .horizontal - secondLineStackView.spacing = 5 - } - -} - - -// MARK: - StackView Control Methods - -extension SpotFilterTagButtonStackView { - - func addTagButton(to line: FilterTagButtonStackLineType, button: UIButton) { - switch line { - case .first: - firstLineStackView.addArrangedSubview(button) - case .second: - secondLineStackView.addArrangedSubview(button) - } - } - - func clearStackView() { - clearStackView(from: firstLineStackView) - clearStackView(from: secondLineStackView) - } - - func addEmptyView() { - let emptyView1 = UIView() - let emptyView2 = UIView() - - [emptyView1, emptyView2].forEach { - $0.setContentHuggingPriority(.defaultLow, for: .horizontal) - } - - firstLineStackView.addArrangedSubview(emptyView1) - secondLineStackView.addArrangedSubview(emptyView2) - } - - private func clearStackView(from stackView: UIStackView) { - stackView.arrangedSubviews.forEach { view in - stackView.removeArrangedSubview(view) - view.removeFromSuperview() - } - } - -} diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/SpotFilterTagStackView.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/SpotFilterTagStackView.swift new file mode 100644 index 00000000..267b8f51 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/SpotFilterTagStackView.swift @@ -0,0 +1,74 @@ +// +// SpotFilterTagStackView.swift +// ACON-iOS +// +// Created by 김유림 on 1/16/25. +// + +import UIKit + +class SpotFilterTagStackView: UIStackView { + + // MARK: - LifeCycle + + override init(frame: CGRect) { + super.init(frame: frame) + + setStyle() + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setStyle() { + self.axis = .horizontal + self.spacing = 5 + } + +} + + +// MARK: - StackView Control Methods + +extension SpotFilterTagStackView { + + // MARK: - Internal Methods + + func addTagButtons(titles: [String]) { + for title in titles { + let button = FilterTagButton() + button.setAttributedTitle(text: title, style: .b3) + self.addArrangedSubview(button) + } + + addEmptyView() + } + + func switchTagButtons(titles: [String]) { + clearStackView() + + for title in titles { + let button = FilterTagButton() + button.setAttributedTitle(text: title, style: .b3) + self.addArrangedSubview(button) + } + + addEmptyView() + } + + + // MARK: - Private Methods + + private func clearStackView() { + self.arrangedSubviews.forEach { view in + self.removeArrangedSubview(view) + view.removeFromSuperview() + } + } + + private func addEmptyView() { + self.addArrangedSubview(PriorityLowEmptyView()) + } + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift index 7aa4f265..fe4d6b5b 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift @@ -11,14 +11,14 @@ class SpotListFilterView: BaseView { // MARK: - Properties - private let segmentItems = [StringLiterals.SpotListFilter.restaurant, - StringLiterals.SpotListFilter.cafe] // MARK: - UI Properties private let stackView = UIStackView() + private let emptyView = PriorityLowEmptyView() + // [Spot section]: 방문 장소 (restaurant + cafe) @@ -26,20 +26,22 @@ class SpotListFilterView: BaseView { private let spotSectionTitleLabel = UILabel() - lazy var segmentedControl = UISegmentedControl(items: segmentItems) + lazy var segmentedControl = CustomSegmentedControl() + + private let spotTagStackView = UIStackView() - private let segmentedControlBgView = UIView() + private let firstLineSpotTagStackView = SpotFilterTagStackView() - let spotTagStackView = SpotFilterTagButtonStackView() + private let secondLineSpotTagStackView = SpotFilterTagStackView() // [Companion section]: 함께 하는 사람 (restaurant) - private let companionSectionStackView = SpotFilterTagButtonStackView() + private let companionSectionStackView = UIStackView() private let companionSectionTitleLabel = UILabel() - let companionTagStackView = SpotFilterTagButtonStackView() + let companionTagStackView = SpotFilterTagStackView() @@ -55,15 +57,17 @@ class SpotListFilterView: BaseView { self.addSubviews(stackView) stackView.addArrangedSubviews(spotSectionStackView, - companionSectionStackView) + companionSectionStackView, + emptyView) // [Spot section] spotSectionStackView.addArrangedSubviews(spotSectionTitleLabel, - segmentedControlBgView, + segmentedControl, spotTagStackView) - segmentedControlBgView.addSubview(segmentedControl) + spotTagStackView.addArrangedSubviews(firstLineSpotTagStackView, + secondLineSpotTagStackView) // [Companion section] @@ -82,18 +86,9 @@ class SpotListFilterView: BaseView { $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.widthRatio * 20) } - segmentedControlBgView.snp.makeConstraints { - $0.height.equalTo(ScreenUtils.heightRatio * 37) - } - segmentedControl.snp.makeConstraints { - $0.edges.equalToSuperview().inset(ScreenUtils.heightRatio * 3) + $0.height.equalTo(ScreenUtils.heightRatio * 37) } - - - // [Companion section - - } override func setStyle() { @@ -104,11 +99,12 @@ class SpotListFilterView: BaseView { $0.spacing = 32 } + // [Spot section] setSpotSectionUI() - + setCompanionSectionUI() // TODO: 추후 추가 예정 @@ -132,30 +128,61 @@ private extension SpotListFilterView { spotSectionTitleLabel.do { $0.setLabel(text: StringLiterals.SpotListFilter.spotSection, style: .s2) - - setSegmentControl() + } + spotTagStackView.do { + $0.alignment = .leading + $0.axis = .vertical + $0.spacing = 5 } } - func setSegmentControl() { - segmentedControlBgView.do { -// $0.backgroundColor = .gray8 - $0.layer.cornerRadius = ScreenUtils.heightRatio * 6 + + // MARK: - (Companion section) + + func setCompanionSectionUI() { + companionSectionStackView.do { + $0.axis = .vertical + $0.spacing = 12 } - // TODO: 배경 없애기 커스텀 - segmentedControl.do { - $0.selectedSegmentIndex = 0 - $0.selectedSegmentTintColor = .acWhite - $0.backgroundColor = .gray8 - $0.setTitleTextAttributes(String.ACStyle(.s2, .gray5), for: .normal) - $0.setTitleTextAttributes(String.ACStyle(.s2, .gray9), for: .selected) - } + companionSectionTitleLabel.setLabel( + text: StringLiterals.SpotListFilter.companionSection, + style: .s2) + + let tags: [String] = SpotListFilterModel.Companion.firstLine.map { return $0.text } + companionTagStackView.addTagButtons(titles: tags) } + + } +// MARK: - Internal Methods (Update UI) -// MARK: - UI Settings (Companion section) +extension SpotListFilterView { + + func updateSpotTagStack(_ spotType: SpotType) { + + // TODO: Model 대신 Type으로 바꾸기 + + // NOTE: add buttons + switch spotType { + case .restaurant: + let firstLine: [String] = SpotListFilterModel.RestaurantFeature.firstLine.map { return $0.text } + let secondLine: [String] = SpotListFilterModel.RestaurantFeature.secondLine.map { return $0.text } + + firstLineSpotTagStackView.switchTagButtons(titles: firstLine) + secondLineSpotTagStackView.switchTagButtons(titles: secondLine) + + case .cafe: + let firstLine: [String] = SpotListFilterModel.CafeFeature.firstLine.map { return $0.text } + let secondLine: [String] = SpotListFilterModel.CafeFeature.secondLine.map { return $0.text } + + firstLineSpotTagStackView.switchTagButtons(titles: firstLine) + secondLineSpotTagStackView.switchTagButtons(titles: secondLine) + } + } + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift index b563848b..02e25077 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift @@ -53,7 +53,6 @@ private extension SpotListFilterViewController { let spotType = spotType else { return } - print("spotType: \(spotType)") updateView(spotType) } } @@ -75,91 +74,19 @@ private extension SpotListFilterViewController { } -// MARK: - UI Update +// MARK: - Spot Type에 따른 UI Update private extension SpotListFilterViewController { // TODO: restaurant func updateView(_ spotType: SpotType) { - updateFeatureStack(spotType) +// updateSpotTagStack(spotType) + spotListFilterView.updateSpotTagStack(spotType) } - private func updateFeatureStack(_ spotType: SpotType) { - let featureStack = spotListFilterView.spotFeatureStackView - - // NOTE: clear stack - featureStack.clearStackView() - - // TODO: Type으로 바꾸기 -// switch spotType { -// case .restaurant: -// let arr = SpotType.RestaurantFeatureType.allCases -// let arr1 = returnFirstArray(array: arr, firstLineCount: spotType.featureFirstLineCount) -// let arr2 = returnSecondArray(array: arr, firstLineCount: spotType.featureFirstLineCount) -// -// case .cafe: -// let arr = SpotType.CafeFeatureType.allCases -// let arr1 = returnFirstArray(array: arr, firstLineCount: spotType.featureFirstLineCount) -// let arr2 = returnSecondArray(array: arr, firstLineCount: spotType.featureFirstLineCount) -// } - - // NOTE: add buttons - switch spotType { - case .restaurant: - for feature in SpotListFilterModel.RestaurantFeature.firstLine { - let btn = FilterTagButton() - - btn.setAttributedTitle(text: feature.text, style: .b3) - btn.addTarget(self, - action: #selector(didTapFilterTagButton), - for: .touchUpInside) - - featureStack.addTagButton(to: .first, - button: btn) - } - - for feature in SpotListFilterModel.RestaurantFeature.secondLine { - let btn = FilterTagButton() - btn.setAttributedTitle(text: feature.text, style: .b3) - btn.addTarget(self, - action: #selector(didTapFilterTagButton(_:)), - for: .touchUpInside) - - featureStack.addTagButton(to: .second, - button: btn) - } - - - case .cafe: - for feature in SpotListFilterModel.CafeFeature.firstLine { - let btn = FilterTagButton() - - btn.setAttributedTitle(text: feature.text, style: .b3) - btn.addTarget(self, - action: #selector(didTapFilterTagButton), - for: .touchUpInside) - - featureStack.addTagButton(to: .first, - button: btn) - } - - for feature in SpotListFilterModel.CafeFeature.secondLine { - let btn = FilterTagButton() - btn.setAttributedTitle(text: feature.text, style: .b3) - btn.addTarget(self, - action: #selector(didTapFilterTagButton(_:)), - for: .touchUpInside) - - featureStack.addTagButton(to: .second, - button: btn) - } - } - - featureStack.addEmptyView() // 오른쪽 여백 추가 - } - + } @@ -167,28 +94,25 @@ private extension SpotListFilterViewController { private extension SpotListFilterViewController { - @objc - func didTapFilterTagButton(_ sender: UIButton) { - sender.isSelected.toggle() - } - @objc func didChangeSpot(segment: UISegmentedControl) { print("didChangeSpot") let index = segment.selectedSegmentIndex viewModel.spotType.value = index == 0 ? .restaurant : .cafe } + } // MARK: - Assisting method - -extension SpotListFilterViewController { - - func returnFirstArray(array: T, firstLineCount: Int) -> T where T: RangeReplaceableCollection, T: RandomAccessCollection { - return T(array.prefix(firstLineCount)) - } - - func returnSecondArray(array: T, firstLineCount: Int) -> T where T: RangeReplaceableCollection, T: RandomAccessCollection { - return T(array.dropFirst(firstLineCount)) - } -} +// TODO: 메소드 수정 +//extension SpotListFilterViewController { +// +// func returnFirstArray(array: T, firstLineCount: Int) -> T where T: RangeReplaceableCollection, T: RandomAccessCollection { +// return T(array.prefix(firstLineCount)) +// } +// +// func returnSecondArray(array: T, firstLineCount: Int) -> T where T: RangeReplaceableCollection, T: RandomAccessCollection { +// return T(array.dropFirst(firstLineCount)) +// } +// +//} From 39282daa356d58ce4fc317202104db6a51a01441 Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 14:46:57 +0900 Subject: [PATCH 27/44] =?UTF-8?q?[Add]=20CustomSegment=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 미완성 --- .../Component/CustomSegmentedControl.swift | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/CustomSegmentedControl.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/CustomSegmentedControl.swift index dcd765e8..66a265d5 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/CustomSegmentedControl.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/CustomSegmentedControl.swift @@ -7,26 +7,34 @@ import UIKit +import Then + // TODO: 커스텀 세그먼트컨트롤 만들기... /// - 배경 insets /// - attributed string class CustomSegmentedControl: UISegmentedControl { - override init(frame: CGRect) { - super.init(frame: frame) - self.removeBackgroundAndDivider() - } + - override init(items: [Any]?) { - super.init(items: items) - self.removeBackgroundAndDivider() +// override init(frame: CGRect) { +// super.init(frame: frame) +//// self.removeBackgroundAndDivider() +// } + + init() { + let segmentItems = [StringLiterals.SpotListFilter.restaurant, + StringLiterals.SpotListFilter.cafe] + super.init(items: segmentItems) + + setStyle() } required init?(coder: NSCoder) { fatalError() } + // TODO: 커스텀 private func removeBackgroundAndDivider() { let image = UIImage() self.setBackgroundImage(image, for: .normal, barMetrics: .default) @@ -36,4 +44,13 @@ class CustomSegmentedControl: UISegmentedControl { self.setDividerImage(image, forLeftSegmentState: .selected, rightSegmentState: .normal, barMetrics: .default) } + private func setStyle() { + self.do { + $0.selectedSegmentIndex = 0 + $0.selectedSegmentTintColor = .acWhite + $0.backgroundColor = .gray8 + $0.setTitleTextAttributes(String.ACStyle(.s2, .gray5), for: .normal) + $0.setTitleTextAttributes(String.ACStyle(.s2, .gray9), for: .selected) + } + } } From b3d7c1182de9fe2179002e2181290d4074e9e463 Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 15:18:07 +0900 Subject: [PATCH 28/44] =?UTF-8?q?[Chore]=20=EC=A0=91=EA=B7=BC=EC=A0=9C?= =?UTF-8?q?=EC=96=B4=EC=9E=90,=20=EC=A4=84=EB=B0=94=EA=BF=88=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/SpotListFilterView.swift | 18 ++++++++++++------ .../View/SpotListFilterViewController.swift | 10 ++++++---- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift index fe4d6b5b..d6569423 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift @@ -30,9 +30,9 @@ class SpotListFilterView: BaseView { private let spotTagStackView = UIStackView() - private let firstLineSpotTagStackView = SpotFilterTagStackView() + let firstLineSpotTagStackView = SpotFilterTagStackView() - private let secondLineSpotTagStackView = SpotFilterTagStackView() + let secondLineSpotTagStackView = SpotFilterTagStackView() // [Companion section]: 함께 하는 사람 (restaurant) @@ -44,6 +44,9 @@ class SpotListFilterView: BaseView { let companionTagStackView = SpotFilterTagStackView() + // [ + + // TODO: 방문 목적, 도보 가능 거리, 가격대 @@ -154,8 +157,6 @@ private extension SpotListFilterView { companionTagStackView.addTagButtons(titles: tags) } - - } @@ -163,11 +164,10 @@ private extension SpotListFilterView { extension SpotListFilterView { - func updateSpotTagStack(_ spotType: SpotType) { + func switchSpotTagStack(_ spotType: SpotType) { // TODO: Model 대신 Type으로 바꾸기 - // NOTE: add buttons switch spotType { case .restaurant: let firstLine: [String] = SpotListFilterModel.RestaurantFeature.firstLine.map { return $0.text } @@ -185,4 +185,10 @@ extension SpotListFilterView { } } + func hideCompanionSection(isHidden: Bool) { + companionSectionStackView.isHidden = isHidden + } + + + } diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift index 02e25077..d23431a9 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift @@ -81,15 +81,17 @@ private extension SpotListFilterViewController { // TODO: restaurant func updateView(_ spotType: SpotType) { -// updateSpotTagStack(spotType) - spotListFilterView.updateSpotTagStack(spotType) + // NOTE: spot tag 바꾸기 + spotListFilterView.switchSpotTagStack(spotType) + + // NOTE: companion tag는 restaurant일 때만 보임 + spotListFilterView.hideCompanionSection(isHidden: spotType == .cafe) } - - } + // MARK: - @objc functions private extension SpotListFilterViewController { From d8603d663baf1e7860a0f6630ce90e287d8d16bd Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 15:34:52 +0900 Subject: [PATCH 29/44] =?UTF-8?q?[Feat]=20=EB=B0=A9=EB=AC=B8=20=EB=AA=A9?= =?UTF-8?q?=EC=A0=81=20=EC=84=B9=EC=85=98=20=EA=B5=AC=ED=98=84=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Global/Literals/StringLiterals.swift | 2 + .../View/SpotListFilterView.swift | 37 +++++++++++++++++-- .../View/SpotListFilterViewController.swift | 16 +++++--- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift b/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift index aff219ed..ca03bffe 100644 --- a/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift +++ b/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift @@ -111,6 +111,8 @@ enum StringLiterals { static let companionSection = "함께 하는 사람" + static let visitPurposeSection = "방문 목적" + } } diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift index d6569423..fc21ab42 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift @@ -20,7 +20,7 @@ class SpotListFilterView: BaseView { private let emptyView = PriorityLowEmptyView() - // [Spot section]: 방문 장소 (restaurant + cafe) + // [Spot section]: 방문 장소 (restaurant, cafe) private let spotSectionStackView = UIStackView() @@ -44,7 +44,13 @@ class SpotListFilterView: BaseView { let companionTagStackView = SpotFilterTagStackView() - // [ + // [Visit purpose]: 방문 목적 (cafe) + + private let visitPurposeSectionStackView = UIStackView() + + private let visitPurposeSectionTitleLabel = UILabel() + + let visitPurposeTagStackView = SpotFilterTagStackView() @@ -61,6 +67,7 @@ class SpotListFilterView: BaseView { stackView.addArrangedSubviews(spotSectionStackView, companionSectionStackView, + visitPurposeSectionStackView, emptyView) // [Spot section] @@ -79,6 +86,10 @@ class SpotListFilterView: BaseView { companionTagStackView) + // [Visit purpose section] + + visitPurposeSectionStackView.addArrangedSubviews(visitPurposeSectionTitleLabel, + visitPurposeTagStackView) } override func setLayout() { @@ -109,6 +120,7 @@ class SpotListFilterView: BaseView { setCompanionSectionUI() + setVisitPurposeSectionUI() // TODO: 추후 추가 예정 } @@ -157,6 +169,23 @@ private extension SpotListFilterView { companionTagStackView.addTagButtons(titles: tags) } + + // MARK: - (Visit purpose section) + + func setVisitPurposeSectionUI() { + visitPurposeSectionStackView.do { + $0.axis = .vertical + $0.spacing = 12 + } + + visitPurposeSectionTitleLabel.setLabel( + text: StringLiterals.SpotListFilter.visitPurposeSection, + style: .s2) + + let tags: [String] = SpotListFilterModel.VisitPurpose.firstLine.map { return $0.text } + visitPurposeTagStackView.addTagButtons(titles: tags) + } + } @@ -189,6 +218,8 @@ extension SpotListFilterView { companionSectionStackView.isHidden = isHidden } - + func hideVisitPurposeSection(isHidden: Bool) { + visitPurposeSectionStackView.isHidden = isHidden + } } diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift index d23431a9..216f7280 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift @@ -81,12 +81,17 @@ private extension SpotListFilterViewController { // TODO: restaurant func updateView(_ spotType: SpotType) { - // NOTE: spot tag 바꾸기 - spotListFilterView.switchSpotTagStack(spotType) - - // NOTE: companion tag는 restaurant일 때만 보임 - spotListFilterView.hideCompanionSection(isHidden: spotType == .cafe) + spotListFilterView.do { + // NOTE: spot tag 바꾸기 + $0.switchSpotTagStack(spotType) + + // NOTE: companion tag는 restaurant일 때만 보임 + $0.hideCompanionSection(isHidden: spotType == .cafe) + + // NOTE: visit purpose tag는 cafe일 때만 보임 + $0.hideVisitPurposeSection(isHidden: spotType == .restaurant) + } } } @@ -97,7 +102,6 @@ private extension SpotListFilterViewController { private extension SpotListFilterViewController { @objc func didChangeSpot(segment: UISegmentedControl) { - print("didChangeSpot") let index = segment.selectedSegmentIndex viewModel.spotType.value = index == 0 ? .restaurant : .cafe } From 02d6d26c7f7616929cde6790578cc600e430356a Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 15:37:09 +0900 Subject: [PATCH 30/44] =?UTF-8?q?[Chore]=20Todo=20=EC=B6=94=EA=B0=80=20(#2?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SpotListFilter/View/SpotListFilterViewController.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift index 216f7280..f7e3042a 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift @@ -82,6 +82,8 @@ private extension SpotListFilterViewController { func updateView(_ spotType: SpotType) { + // TODO: 세그먼트 바뀔 때 버튼 선택 전부 해제되는지 기획 확인 + spotListFilterView.do { // NOTE: spot tag 바꾸기 $0.switchSpotTagStack(spotType) From 937203eaed66adeef35aeb9c01bcfeee9428709e Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 16:25:13 +0900 Subject: [PATCH 31/44] =?UTF-8?q?[Chore]=20=EB=86=92=EC=9D=B4=20=EA=B3=A0?= =?UTF-8?q?=EC=A0=95=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Component/FilterTagButton.swift | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/FilterTagButton.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/FilterTagButton.swift index cd447e4d..e8de0917 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/FilterTagButton.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/FilterTagButton.swift @@ -25,7 +25,9 @@ class FilterTagButton: UIButton { super.init(frame: frame) configureButton() + setLayout() addTarget() + } required init?(coder: NSCoder) { @@ -51,16 +53,29 @@ private extension FilterTagButton { leading: 16, bottom: 7, trailing: 16) - self.configuration = config } + func setLayout() { + self.snp.makeConstraints { + $0.height.equalTo(32) + } + } + func addTarget() { self.addTarget(self, action: #selector(toggleSelf), for: .touchUpInside) } +} + + +// MARK: - @objc functions + +private extension FilterTagButton { + @objc func toggleSelf(_ sender: UIButton) { isSelected.toggle() } + } From 26791bc1f6f417be93cb9e3a22335d8603de7fb2 Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 16:25:30 +0900 Subject: [PATCH 32/44] =?UTF-8?q?[Fix]=20=EB=A0=88=EC=9D=B4=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/SpotListFilter/View/SpotListFilterView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift index fc21ab42..da6ec879 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift @@ -98,6 +98,7 @@ class SpotListFilterView: BaseView { stackView.snp.makeConstraints { $0.top.equalToSuperview().offset(ScreenUtils.heightRatio * 41) $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.widthRatio * 20) + $0.bottom.equalToSuperview() } segmentedControl.snp.makeConstraints { From 7f51315a3278f9b8138c4de5d273b15b4625367b Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 16:33:47 +0900 Subject: [PATCH 33/44] =?UTF-8?q?[Chore]=20String+=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ACON-iOS/Global/Extensions/String+.swift | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/ACON-iOS/ACON-iOS/Global/Extensions/String+.swift b/ACON-iOS/ACON-iOS/Global/Extensions/String+.swift index de88ef32..6ab9e608 100644 --- a/ACON-iOS/ACON-iOS/Global/Extensions/String+.swift +++ b/ACON-iOS/ACON-iOS/Global/Extensions/String+.swift @@ -26,21 +26,4 @@ extension String { return NSAttributedString(string: self, attributes: attributes) } - static func ACStyle(_ style: ACFontStyleType, _ color: UIColor = .acWhite) -> [NSAttributedString.Key: Any] { - let attributes: [NSAttributedString.Key: Any] = [ - .font: style.font, - .kern: style.kerning, - .paragraphStyle: { - let paragraphStyle = NSMutableParagraphStyle() - paragraphStyle.minimumLineHeight = style.lineHeight - paragraphStyle.maximumLineHeight = style.lineHeight - return paragraphStyle - }(), - .foregroundColor: color, - .baselineOffset: (style.lineHeight - style.font.lineHeight) / 2 - ] - - return attributes - } - } From 4556199239428712268ac60e1cee37812bed80fc Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 16:34:19 +0900 Subject: [PATCH 34/44] =?UTF-8?q?[Chore]=20=EC=84=B8=EA=B7=B8=EB=A8=BC?= =?UTF-8?q?=ED=8A=B8=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=20attributedString=20?= =?UTF-8?q?=EC=86=8D=EC=84=B1=20=EB=B0=98=ED=99=98=20=EB=A9=94=EC=86=8C?= =?UTF-8?q?=EB=93=9C=20=EA=B5=AC=ED=98=84=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Component/CustomSegmentedControl.swift | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/CustomSegmentedControl.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/CustomSegmentedControl.swift index 66a265d5..c3315ab9 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/CustomSegmentedControl.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/CustomSegmentedControl.swift @@ -49,8 +49,26 @@ class CustomSegmentedControl: UISegmentedControl { $0.selectedSegmentIndex = 0 $0.selectedSegmentTintColor = .acWhite $0.backgroundColor = .gray8 - $0.setTitleTextAttributes(String.ACStyle(.s2, .gray5), for: .normal) - $0.setTitleTextAttributes(String.ACStyle(.s2, .gray9), for: .selected) + $0.setTitleTextAttributes(ACStyle(.s2, .gray5), for: .normal) + $0.setTitleTextAttributes(ACStyle(.s2, .gray9), for: .selected) } } + + func ACStyle(_ style: ACFontStyleType, _ color: UIColor = .acWhite) -> [NSAttributedString.Key: Any] { + let attributes: [NSAttributedString.Key: Any] = [ + .font: style.font, + .kern: style.kerning, + .paragraphStyle: { + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.minimumLineHeight = style.lineHeight + paragraphStyle.maximumLineHeight = style.lineHeight + return paragraphStyle + }(), + .foregroundColor: color, + .baselineOffset: (style.lineHeight - style.font.lineHeight) / 2 + ] + + return attributes + } + } From ad91e196f5304d4daef0770cfd49e28db9970da6 Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 16:44:18 +0900 Subject: [PATCH 35/44] =?UTF-8?q?[Chore]=20=ED=95=84=EC=9A=94=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SpotListFilter/Model/SpotListFilterModel.swift | 1 - .../Presentation/SpotListFilter/Type/SpotType.swift | 7 ------- 2 files changed, 8 deletions(-) diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Model/SpotListFilterModel.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Model/SpotListFilterModel.swift index 82a795b8..6398b533 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Model/SpotListFilterModel.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Model/SpotListFilterModel.swift @@ -9,7 +9,6 @@ import Foundation struct SpotListFilterModel { - // TODO: Type에 포함시키는 방향으로 수정 struct RestaurantFeature { static let firstLine: [SpotType.RestaurantFeatureType] = [ diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/SpotType.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/SpotType.swift index 63463b28..1d94b085 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/SpotType.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/SpotType.swift @@ -20,13 +20,6 @@ enum SpotType { } } - var featureFirstLineCount: Int { - switch self { - case .restaurant: return 5 - case .cafe: return 4 - } - } - // MARK: - 장소 상세 조건 From bfb30995a1027c55f2e6a65af4872d04fdbbadce Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 17:11:31 +0900 Subject: [PATCH 36/44] =?UTF-8?q?[Chore]=20=ED=94=84=EB=A1=9C=ED=8D=BC?= =?UTF-8?q?=ED=8B=B0=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SpotListFilter/Model/SpotListFilterModel.swift | 8 ++------ .../SpotListFilter/View/SpotListFilterView.swift | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Model/SpotListFilterModel.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Model/SpotListFilterModel.swift index 6398b533..7b9fd23a 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Model/SpotListFilterModel.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Model/SpotListFilterModel.swift @@ -35,22 +35,18 @@ struct SpotListFilterModel { struct Companion { - static let firstLine: [SpotType.CompanionType] = [ + static let tags: [SpotType.CompanionType] = [ .family, .date, .friend, .alone, .group ] - static let secondLine: [SpotType.CompanionType] = [] - } struct VisitPurpose { - static let firstLine: [SpotType.VisitPurposeType] = [ + static let tags: [SpotType.VisitPurposeType] = [ .meeting, .study ] - static let secondLine: [SpotType.VisitPurposeType] = [] - } } diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift index da6ec879..5834d31e 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift @@ -166,7 +166,7 @@ private extension SpotListFilterView { text: StringLiterals.SpotListFilter.companionSection, style: .s2) - let tags: [String] = SpotListFilterModel.Companion.firstLine.map { return $0.text } + let tags: [String] = SpotListFilterModel.Companion.tags.map { return $0.text } companionTagStackView.addTagButtons(titles: tags) } @@ -183,7 +183,7 @@ private extension SpotListFilterView { text: StringLiterals.SpotListFilter.visitPurposeSection, style: .s2) - let tags: [String] = SpotListFilterModel.VisitPurpose.firstLine.map { return $0.text } + let tags: [String] = SpotListFilterModel.VisitPurpose.tags.map { return $0.text } visitPurposeTagStackView.addTagButtons(titles: tags) } From 50d70b817179012eb8421a67b300f73d77ca8352 Mon Sep 17 00:00:00 2001 From: cirtuare Date: Thu, 16 Jan 2025 18:52:11 +0900 Subject: [PATCH 37/44] =?UTF-8?q?[Chore]=20=EC=BD=94=EB=93=9C=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81=20(#21)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ACON-iOS/ACON-iOS/Application/AppDelegate.swift | 1 - .../View/LocalVerificationFinishedViewController.swift | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ACON-iOS/ACON-iOS/Application/AppDelegate.swift b/ACON-iOS/ACON-iOS/Application/AppDelegate.swift index 285d03c3..25f24b1c 100644 --- a/ACON-iOS/ACON-iOS/Application/AppDelegate.swift +++ b/ACON-iOS/ACON-iOS/Application/AppDelegate.swift @@ -14,7 +14,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. -// NMFAuthManager.shared().clientId = Config.nMapClientKey return true } diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedViewController.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedViewController.swift index eeaaa782..1c9a021d 100644 --- a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedViewController.swift +++ b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedViewController.swift @@ -18,6 +18,7 @@ class LocalVerificationFinishedViewController: BaseViewController { let localName = "동교동" + // MARK: - LifeCycle override func viewDidLoad() { @@ -74,7 +75,7 @@ private extension LocalVerificationFinishedViewController { @objc func startButtonTapped() { - closeView() + goToTabView() } } @@ -85,7 +86,7 @@ private extension LocalVerificationFinishedViewController { private extension LocalVerificationFinishedViewController { @objc - func closeView() { + func goToTabView() { if let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate { sceneDelegate.window?.rootViewController = ACTabBarController() } From f3b45915656b123bfd9d0d828e21d4f5f15f42cf Mon Sep 17 00:00:00 2001 From: sumin <86866423+cirtuare@users.noreply.github.com> Date: Thu, 16 Jan 2025 19:18:34 +0900 Subject: [PATCH 38/44] =?UTF-8?q?Revert=20"[FEAT]=20=EB=8F=99=EB=84=A4?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=ED=94=84=EB=A1=9C=EC=84=B8=EC=8A=A4=20UI?= =?UTF-8?q?=20(#21)"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj | 196 ++++++++++++++---- .../ACON-iOS/Application/AppDelegate.swift | 2 - .../Global/Extensions/UIButton+.swift | 30 --- .../Global/Extensions/UIViewController+.swift | 14 -- .../Global/Literals/StringLiterals.swift | 31 --- .../Contents.json | 10 +- .../icRadio.imageset/ic_radio_20.svg | 4 - .../ic_radio_pressed_20.svg | 4 - .../localAcorn.imageset/ic_local acon.svg | 39 ---- .../plainAcorn.imageset/ic_normal acon.svg | 21 -- .../Contents.json | 0 .../img_ex1.imageset}/Contents.json | 2 +- .../SpotDummy/img_ex1.imageset/img_ex1.svg | 14 ++ .../img_ex2.imageset}/Contents.json | 2 +- .../SpotDummy/img_ex2.imageset/img_ex2.svg | 14 ++ .../img_ex3.imageset}/Contents.json | 2 +- .../SpotDummy/img_ex3.imageset/img_ex3.jpg | Bin 0 -> 58739 bytes .../img_ex4.imageset}/Contents.json | 2 +- .../SpotDummy/img_ex4.imageset/img_ex4.svg | 14 ++ .../Assets.xcassets/SpotList/Contents.json | 6 + .../ic_walking_24.imageset/Contents.json | 12 ++ .../ic_walking_24.imageset/ic_walking_24.svg | 3 + .../Contents.json | 2 +- .../btn_bottomsheet bar.svg | 3 + .../Rectangle 34627096.svg | 3 - .../Global/Settings/Config/Config.swift | 8 +- ACON-iOS/ACON-iOS/Global/Settings/Info.plist | 4 +- .../ACON-iOS/Global/Utils/ScreenUtils.swift | 13 ++ .../ACON-iOS/Global/Utils/SheetUtils.swift | 9 - .../ACON-iOS/Presentation/Base/BaseView.swift | 22 -- .../LocalVerification/View/LocalMapView.swift | 72 ------- .../View/LocalMapViewController.swift | 108 ---------- .../View/LocalVerificationFinishedView.swift | 144 ------------- ...alVerificationFinishedViewController.swift | 95 --------- .../View/LocalVerificationView.swift | 121 ----------- .../LocalVerificationViewController.swift | 112 ---------- .../Login/View/LoginViewController.swift | 2 +- .../Place/SpotListViewController.swift | 18 -- .../SpotList/Model/SpotModel.swift | 81 ++++++++ .../Type/MatchingRateBgColorType.swift | 21 ++ .../SpotList/Type/SpotListItemSizeType.swift | 21 ++ .../Cell/SpotListCollectionViewCell.swift | 157 ++++++++++++++ .../SpotList/View/SpotListView.swift | 61 ++++++ .../View/SpotListViewController.swift | 192 +++++++++++++++++ .../ViewModel/SpotListViewModel.swift | 39 ++++ .../Model/SpotListFilterModel.swift | 57 +++++ .../Type/FilterTagButtonStackLineType.swift | 14 ++ .../Type/FilterTagButtonType.swift | 33 +++ .../SpotListFilter/Type/SpotType.swift | 98 +++++++++ .../View/Component/FilterTagButton.swift | 58 ++++++ .../SpotFilterTagButtonStackView.swift | 84 ++++++++ .../View/SpotListFilterView.swift | 45 ++++ .../View/SpotListFilterViewController.swift | 119 +++++++++++ .../ViewModel/SpotListFilterViewModel.swift | 18 ++ .../Upload/View/SpotSearchView.swift | 18 +- .../View/SpotSearchViewController.swift | 2 +- 56 files changed, 1363 insertions(+), 913 deletions(-) rename ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Colors/{dim_b_60.colorset => sub_org35.colorset}/Contents.json (55%) delete mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/ic_radio_20.svg delete mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/ic_radio_pressed_20.svg delete mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/localAcorn.imageset/ic_local acon.svg delete mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/plainAcorn.imageset/ic_normal acon.svg rename ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/{LocalVerification => SpotDummy}/Contents.json (100%) rename ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/{LocalVerification/icRadio.imageset => SpotDummy/img_ex1.imageset}/Contents.json (76%) create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotDummy/img_ex1.imageset/img_ex1.svg rename ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/{LocalVerification/localAcorn.imageset => SpotDummy/img_ex2.imageset}/Contents.json (75%) create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotDummy/img_ex2.imageset/img_ex2.svg rename ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/{LocalVerification/plainAcorn.imageset => SpotDummy/img_ex3.imageset}/Contents.json (75%) create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotDummy/img_ex3.imageset/img_ex3.jpg rename ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/{LocalVerification/icRadioSelected.imageset => SpotDummy/img_ex4.imageset}/Contents.json (72%) create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotDummy/img_ex4.imageset/img_ex4.svg create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotList/Contents.json create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotList/ic_walking_24.imageset/Contents.json create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotList/ic_walking_24.imageset/ic_walking_24.svg rename ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/{btn_bottomsheet_bar.imageset => btn_bottomsheet bar.imageset}/Contents.json (85%) create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet bar.imageset/btn_bottomsheet bar.svg delete mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet_bar.imageset/Rectangle 34627096.svg delete mode 100644 ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapView.swift delete mode 100644 ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift delete mode 100644 ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedView.swift delete mode 100644 ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedViewController.swift delete mode 100644 ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationView.swift delete mode 100644 ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationViewController.swift delete mode 100644 ACON-iOS/ACON-iOS/Presentation/Place/SpotListViewController.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/SpotList/Model/SpotModel.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/SpotList/Type/MatchingRateBgColorType.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/SpotList/Type/SpotListItemSizeType.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/SpotList/View/Cell/SpotListCollectionViewCell.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/SpotList/View/SpotListView.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/SpotList/View/SpotListViewController.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/SpotList/ViewModel/SpotListViewModel.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Model/SpotListFilterModel.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/FilterTagButtonStackLineType.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/FilterTagButtonType.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/SpotType.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/FilterTagButton.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/SpotFilterTagButtonStackView.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/SpotListFilter/ViewModel/SpotListFilterViewModel.swift diff --git a/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj b/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj index d8e54e17..ec45973c 100644 --- a/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj +++ b/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj @@ -7,10 +7,25 @@ objects = { /* Begin PBXBuildFile section */ + 1547A6EB2D337F4100E96616 /* SpotListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1547A6EA2D337F4100E96616 /* SpotListView.swift */; }; + 1547A6EF2D33854B00E96616 /* SpotListCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1547A6EE2D33854B00E96616 /* SpotListCollectionViewCell.swift */; }; + 1547A6F22D33AD4500E96616 /* SpotModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1547A6F12D33AD4500E96616 /* SpotModel.swift */; }; + 1547A8732D34157700E96616 /* SpotListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1547A8722D34157700E96616 /* SpotListViewModel.swift */; }; + 1547A8782D3550D900E96616 /* MatchingRateBgColorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1547A8772D3550D900E96616 /* MatchingRateBgColorType.swift */; }; + 1547A8802D358E2700E96616 /* SpotListFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1547A87F2D358E2700E96616 /* SpotListFilterView.swift */; }; + 1547A8822D358E3000E96616 /* SpotListFilterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1547A8812D358E3000E96616 /* SpotListFilterViewController.swift */; }; + 1547A8852D3595B600E96616 /* SpotListFilterModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1547A8842D3595B600E96616 /* SpotListFilterModel.swift */; }; + 1547A8882D3595E000E96616 /* SpotListFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1547A8872D3595E000E96616 /* SpotListFilterViewModel.swift */; }; + 1547A88B2D3596B600E96616 /* SpotType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1547A88A2D3596B600E96616 /* SpotType.swift */; }; + 1547A88E2D35B13700E96616 /* FilterTagButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1547A88D2D35B13700E96616 /* FilterTagButton.swift */; }; + 1547A8902D35B30C00E96616 /* FilterTagButtonType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1547A88F2D35B30C00E96616 /* FilterTagButtonType.swift */; }; + 1547A8922D363E0000E96616 /* SpotFilterTagButtonStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1547A8912D363E0000E96616 /* SpotFilterTagButtonStackView.swift */; }; 1558BA1A2D318FFC00ECDEF8 /* ACTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1558BA192D318FFC00ECDEF8 /* ACTabBarController.swift */; }; 1558BADB2D31AAF900ECDEF8 /* ACTabBarItemType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1558BADA2D31AAF900ECDEF8 /* ACTabBarItemType.swift */; }; 1558BADE2D31AB6C00ECDEF8 /* SpotListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1558BADD2D31AB6C00ECDEF8 /* SpotListViewController.swift */; }; 1558BAE12D31D41F00ECDEF8 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1558BAE02D31D41F00ECDEF8 /* ProfileViewController.swift */; }; + 15A3F6A62D36C49F00577E16 /* SpotListItemSizeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15A3F6A52D36C49F00577E16 /* SpotListItemSizeType.swift */; }; + 15A3F6A82D36D5B900577E16 /* FilterTagButtonStackLineType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15A3F6A72D36D5B900577E16 /* FilterTagButtonStackLineType.swift */; }; 71DE8AE7F1C4CF9051C631A3 /* Pods_ACON_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B28431BAB67B99CD7B85C705 /* Pods_ACON_iOS.framework */; }; 74054ECA2D32534200D1CDE4 /* MultitaskDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74054EC92D32533800D1CDE4 /* MultitaskDelegate.swift */; }; 74054ECE2D32549F00D1CDE4 /* ACLocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74054ECD2D32549800D1CDE4 /* ACLocationManager.swift */; }; @@ -67,21 +82,30 @@ 74B25C2E2D2FEFD3008BDCB7 /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 74B25C2D2D2FEFD3008BDCB7 /* Debug.xcconfig */; }; 74B25C302D2FEFE1008BDCB7 /* Release.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 74B25C2F2D2FEFE1008BDCB7 /* Release.xcconfig */; }; 74B25C322D2FF022008BDCB7 /* Shared.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 74B25C312D2FF022008BDCB7 /* Shared.xcconfig */; }; - 74CCB0802D36AAA000B254B7 /* LocalVerificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CCB07F2D36AA8400B254B7 /* LocalVerificationView.swift */; }; - 74CCB0822D36B3B200B254B7 /* LocalVerificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CCB0812D36B3AA00B254B7 /* LocalVerificationViewController.swift */; }; - 74CCB0842D36C43C00B254B7 /* LocalMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CCB0832D36C43400B254B7 /* LocalMapView.swift */; }; - 74CCB0862D36D4AA00B254B7 /* LocalMapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CCB0852D36D4A400B254B7 /* LocalMapViewController.swift */; }; - 74CCB0882D370B9100B254B7 /* LocalVerificationFinishedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CCB0872D370B6F00B254B7 /* LocalVerificationFinishedViewController.swift */; }; - 74CCB08A2D370BB100B254B7 /* LocalVerificationFinishedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CCB0892D370BAC00B254B7 /* LocalVerificationFinishedView.swift */; }; 74CDCE542D310A1C00E3A21A /* ACFontStyleType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CDCE532D310A1100E3A21A /* ACFontStyleType.swift */; }; 74CDCE562D310B1600E3A21A /* String+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CDCE552D310B1300E3A21A /* String+.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 1547A6EA2D337F4100E96616 /* SpotListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotListView.swift; sourceTree = ""; }; + 1547A6EE2D33854B00E96616 /* SpotListCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotListCollectionViewCell.swift; sourceTree = ""; }; + 1547A6F12D33AD4500E96616 /* SpotModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotModel.swift; sourceTree = ""; }; + 1547A8722D34157700E96616 /* SpotListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotListViewModel.swift; sourceTree = ""; }; + 1547A8772D3550D900E96616 /* MatchingRateBgColorType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchingRateBgColorType.swift; sourceTree = ""; }; + 1547A87F2D358E2700E96616 /* SpotListFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotListFilterView.swift; sourceTree = ""; }; + 1547A8812D358E3000E96616 /* SpotListFilterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotListFilterViewController.swift; sourceTree = ""; }; + 1547A8842D3595B600E96616 /* SpotListFilterModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotListFilterModel.swift; sourceTree = ""; }; + 1547A8872D3595E000E96616 /* SpotListFilterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotListFilterViewModel.swift; sourceTree = ""; }; + 1547A88A2D3596B600E96616 /* SpotType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotType.swift; sourceTree = ""; }; + 1547A88D2D35B13700E96616 /* FilterTagButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterTagButton.swift; sourceTree = ""; }; + 1547A88F2D35B30C00E96616 /* FilterTagButtonType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterTagButtonType.swift; sourceTree = ""; }; + 1547A8912D363E0000E96616 /* SpotFilterTagButtonStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotFilterTagButtonStackView.swift; sourceTree = ""; }; 1558BA192D318FFC00ECDEF8 /* ACTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACTabBarController.swift; sourceTree = ""; }; 1558BADA2D31AAF900ECDEF8 /* ACTabBarItemType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACTabBarItemType.swift; sourceTree = ""; }; 1558BADD2D31AB6C00ECDEF8 /* SpotListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotListViewController.swift; sourceTree = ""; }; 1558BAE02D31D41F00ECDEF8 /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; + 15A3F6A52D36C49F00577E16 /* SpotListItemSizeType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotListItemSizeType.swift; sourceTree = ""; }; + 15A3F6A72D36D5B900577E16 /* FilterTagButtonStackLineType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterTagButtonStackLineType.swift; sourceTree = ""; }; 50546F80D11F73C6A00B6C92 /* Pods-ACON-iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ACON-iOS.release.xcconfig"; path = "Target Support Files/Pods-ACON-iOS/Pods-ACON-iOS.release.xcconfig"; sourceTree = ""; }; 74054EC92D32533800D1CDE4 /* MultitaskDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultitaskDelegate.swift; sourceTree = ""; }; 74054ECD2D32549800D1CDE4 /* ACLocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACLocationManager.swift; sourceTree = ""; }; @@ -134,12 +158,6 @@ 74B25C2D2D2FEFD3008BDCB7 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; 74B25C2F2D2FEFE1008BDCB7 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 74B25C312D2FF022008BDCB7 /* Shared.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = ""; }; - 74CCB07F2D36AA8400B254B7 /* LocalVerificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalVerificationView.swift; sourceTree = ""; }; - 74CCB0812D36B3AA00B254B7 /* LocalVerificationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalVerificationViewController.swift; sourceTree = ""; }; - 74CCB0832D36C43400B254B7 /* LocalMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMapView.swift; sourceTree = ""; }; - 74CCB0852D36D4A400B254B7 /* LocalMapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMapViewController.swift; sourceTree = ""; }; - 74CCB0872D370B6F00B254B7 /* LocalVerificationFinishedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalVerificationFinishedViewController.swift; sourceTree = ""; }; - 74CCB0892D370BAC00B254B7 /* LocalVerificationFinishedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalVerificationFinishedView.swift; sourceTree = ""; }; 74CDCE532D310A1100E3A21A /* ACFontStyleType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACFontStyleType.swift; sourceTree = ""; }; 74CDCE552D310B1300E3A21A /* String+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+.swift"; sourceTree = ""; }; B28431BAB67B99CD7B85C705 /* Pods_ACON_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ACON_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -173,6 +191,105 @@ path = Type; sourceTree = ""; }; + 1547A6EC2D337FD000E96616 /* View */ = { + isa = PBXGroup; + children = ( + 1547A6ED2D33850600E96616 /* Cell */, + 1547A6EA2D337F4100E96616 /* SpotListView.swift */, + 1558BADD2D31AB6C00ECDEF8 /* SpotListViewController.swift */, + ); + path = View; + sourceTree = ""; + }; + 1547A6ED2D33850600E96616 /* Cell */ = { + isa = PBXGroup; + children = ( + 1547A6EE2D33854B00E96616 /* SpotListCollectionViewCell.swift */, + ); + path = Cell; + sourceTree = ""; + }; + 1547A6F02D33AD3600E96616 /* Model */ = { + isa = PBXGroup; + children = ( + 1547A6F12D33AD4500E96616 /* SpotModel.swift */, + ); + path = Model; + sourceTree = ""; + }; + 1547A8712D34156B00E96616 /* ViewModel */ = { + isa = PBXGroup; + children = ( + 1547A8722D34157700E96616 /* SpotListViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + 1547A8742D354E2900E96616 /* Type */ = { + isa = PBXGroup; + children = ( + 1547A8772D3550D900E96616 /* MatchingRateBgColorType.swift */, + 15A3F6A52D36C49F00577E16 /* SpotListItemSizeType.swift */, + ); + path = Type; + sourceTree = ""; + }; + 1547A87B2D358DBE00E96616 /* SpotListFilter */ = { + isa = PBXGroup; + children = ( + 1547A8892D3596A400E96616 /* Type */, + 1547A8832D3595AB00E96616 /* Model */, + 1547A87C2D358E0700E96616 /* View */, + 1547A8862D3595C800E96616 /* ViewModel */, + ); + path = SpotListFilter; + sourceTree = ""; + }; + 1547A87C2D358E0700E96616 /* View */ = { + isa = PBXGroup; + children = ( + 1547A88C2D35AE5C00E96616 /* Component */, + 1547A87F2D358E2700E96616 /* SpotListFilterView.swift */, + 1547A8812D358E3000E96616 /* SpotListFilterViewController.swift */, + ); + path = View; + sourceTree = ""; + }; + 1547A8832D3595AB00E96616 /* Model */ = { + isa = PBXGroup; + children = ( + 1547A8842D3595B600E96616 /* SpotListFilterModel.swift */, + ); + path = Model; + sourceTree = ""; + }; + 1547A8862D3595C800E96616 /* ViewModel */ = { + isa = PBXGroup; + children = ( + 1547A8872D3595E000E96616 /* SpotListFilterViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + 1547A8892D3596A400E96616 /* Type */ = { + isa = PBXGroup; + children = ( + 1547A88A2D3596B600E96616 /* SpotType.swift */, + 1547A88F2D35B30C00E96616 /* FilterTagButtonType.swift */, + 15A3F6A72D36D5B900577E16 /* FilterTagButtonStackLineType.swift */, + ); + path = Type; + sourceTree = ""; + }; + 1547A88C2D35AE5C00E96616 /* Component */ = { + isa = PBXGroup; + children = ( + 1547A88D2D35B13700E96616 /* FilterTagButton.swift */, + 1547A8912D363E0000E96616 /* SpotFilterTagButtonStackView.swift */, + ); + path = Component; + sourceTree = ""; + }; 1558BA182D318FDB00ECDEF8 /* TabBar */ = { isa = PBXGroup; children = ( @@ -182,12 +299,15 @@ path = TabBar; sourceTree = ""; }; - 1558BADC2D31AB5100ECDEF8 /* Place */ = { + 1558BADC2D31AB5100ECDEF8 /* SpotList */ = { isa = PBXGroup; children = ( - 1558BADD2D31AB6C00ECDEF8 /* SpotListViewController.swift */, + 1547A8742D354E2900E96616 /* Type */, + 1547A6F02D33AD3600E96616 /* Model */, + 1547A6EC2D337FD000E96616 /* View */, + 1547A8712D34156B00E96616 /* ViewModel */, ); - path = Place; + path = SpotList; sourceTree = ""; }; 1558BADF2D31D41400ECDEF8 /* Profile */ = { @@ -301,10 +421,10 @@ 748D6F702D2BCA46007690B4 /* Presentation */ = { isa = PBXGroup; children = ( - 74CCB07D2D36AA5000B254B7 /* LocalVerification */, 74220DD12D34361E000684BF /* Upload */, + 1547A87B2D358DBE00E96616 /* SpotListFilter */, 1558BADF2D31D41400ECDEF8 /* Profile */, - 1558BADC2D31AB5100ECDEF8 /* Place */, + 1558BADC2D31AB5100ECDEF8 /* SpotList */, 1558BA182D318FDB00ECDEF8 /* TabBar */, 748ECA692D31917A00BBC981 /* Login */, 748D6FA22D2C3C3D007690B4 /* Base */, @@ -480,27 +600,6 @@ name = Products; sourceTree = ""; }; - 74CCB07D2D36AA5000B254B7 /* LocalVerification */ = { - isa = PBXGroup; - children = ( - 74CCB07E2D36AA8000B254B7 /* View */, - ); - path = LocalVerification; - sourceTree = ""; - }; - 74CCB07E2D36AA8000B254B7 /* View */ = { - isa = PBXGroup; - children = ( - 74CCB0892D370BAC00B254B7 /* LocalVerificationFinishedView.swift */, - 74CCB0872D370B6F00B254B7 /* LocalVerificationFinishedViewController.swift */, - 74CCB0852D36D4A400B254B7 /* LocalMapViewController.swift */, - 74CCB0832D36C43400B254B7 /* LocalMapView.swift */, - 74CCB0812D36B3AA00B254B7 /* LocalVerificationViewController.swift */, - 74CCB07F2D36AA8400B254B7 /* LocalVerificationView.swift */, - ); - path = View; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -646,7 +745,6 @@ 748D6FAE2D2EBB9C007690B4 /* ACFont.swift in Sources */, 748D6F692D2BCA1C007690B4 /* AppDelegate.swift in Sources */, 74054ECE2D32549F00D1CDE4 /* ACLocationManager.swift in Sources */, - 74CCB08A2D370BB100B254B7 /* LocalVerificationFinishedView.swift in Sources */, 745C7E0D2D35A04A0074DBDB /* RelatedSearchCollectionViewCell.swift in Sources */, 748D6FA62D2C3F44007690B4 /* BaseView.swift in Sources */, 748D6F852D2BD230007690B4 /* DummyService.swift in Sources */, @@ -654,43 +752,53 @@ 741A06C82D3532E500778219 /* DropAcornView.swift in Sources */, 748D6FA82D2C3F93007690B4 /* BaseViewController.swift in Sources */, 748D6F6A2D2BCA1C007690B4 /* SceneDelegate.swift in Sources */, + 1547A8902D35B30C00E96616 /* FilterTagButtonType.swift in Sources */, 748ECA712D31A72A00BBC981 /* LoginViewModel.swift in Sources */, - 74CCB0862D36D4AA00B254B7 /* LocalMapViewController.swift in Sources */, 748D6F872D2BD247007690B4 /* UIView+.swift in Sources */, + 1547A6EB2D337F4100E96616 /* SpotListView.swift in Sources */, + 1547A6EF2D33854B00E96616 /* SpotListCollectionViewCell.swift in Sources */, 74CDCE562D310B1600E3A21A /* String+.swift in Sources */, + 1547A8852D3595B600E96616 /* SpotListFilterModel.swift in Sources */, 748D6F8B2D2BD31B007690B4 /* UIStackView+.swift in Sources */, - 74CCB0842D36C43C00B254B7 /* LocalMapView.swift in Sources */, 748D6F912D2BD450007690B4 /* UILabel+.swift in Sources */, + 1547A8822D358E3000E96616 /* SpotListFilterViewController.swift in Sources */, 748D6FAA2D2C3FF1007690B4 /* BaseCollectionViewCell.swift in Sources */, 748ECA6B2D31918D00BBC981 /* LoginView.swift in Sources */, + 1547A6F22D33AD4500E96616 /* SpotModel.swift in Sources */, 748D6F972D2BD544007690B4 /* DummyType.swift in Sources */, - 74CCB0822D36B3B200B254B7 /* LocalVerificationViewController.swift in Sources */, 74220DD42D34363B000684BF /* SpotUploadViewController.swift in Sources */, 741A07592D355F1400778219 /* ReviewFinishedViewController.swift in Sources */, 748D6F892D2BD294007690B4 /* UIViewController+.swift in Sources */, 745C7E042D3599DF0074DBDB /* SpotSearchViewController.swift in Sources */, + 1547A8922D363E0000E96616 /* SpotFilterTagButtonStackView.swift in Sources */, 74B25C2C2D2FEE8E008BDCB7 /* Config.swift in Sources */, 74054ECA2D32534200D1CDE4 /* MultitaskDelegate.swift in Sources */, 748D6F8F2D2BD340007690B4 /* UIButton+.swift in Sources */, 741A07572D3558FE00778219 /* ReviewFinishedView.swift in Sources */, + 1547A88E2D35B13700E96616 /* FilterTagButton.swift in Sources */, 748D6F7E2D2BCD72007690B4 /* StringLiterals.swift in Sources */, 1558BAE12D31D41F00ECDEF8 /* ProfileViewController.swift in Sources */, 746F39822D342523003F8498 /* SheetUtils.swift in Sources */, + 1547A8782D3550D900E96616 /* MatchingRateBgColorType.swift in Sources */, 1558BA1A2D318FFC00ECDEF8 /* ACTabBarController.swift in Sources */, + 1547A8802D358E2700E96616 /* SpotListFilterView.swift in Sources */, 74CDCE542D310A1C00E3A21A /* ACFontStyleType.swift in Sources */, 748D6F802D2BCD94007690B4 /* ObservablePattern.swift in Sources */, - 74CCB0882D370B9100B254B7 /* LocalVerificationFinishedViewController.swift in Sources */, 1558BADE2D31AB6C00ECDEF8 /* SpotListViewController.swift in Sources */, + 1547A8732D34157700E96616 /* SpotListViewModel.swift in Sources */, 748D6FA42D2C3C48007690B4 /* BaseNavViewController.swift in Sources */, 1558BADB2D31AAF900ECDEF8 /* ACTabBarItemType.swift in Sources */, 744BAF172D300AB900F95B4A /* DummyComponent.swift in Sources */, - 74CCB0802D36AAA000B254B7 /* LocalVerificationView.swift in Sources */, 741A06CA2D35415800778219 /* DropAcornViewController.swift in Sources */, + 15A3F6A62D36C49F00577E16 /* SpotListItemSizeType.swift in Sources */, 748D6F992D2BD54E007690B4 /* DummyProtocol.swift in Sources */, + 1547A8882D3595E000E96616 /* SpotListFilterViewModel.swift in Sources */, + 1547A88B2D3596B600E96616 /* SpotType.swift in Sources */, 748ECA6E2D3196F100BBC981 /* LoginViewController.swift in Sources */, 745C7E022D358E900074DBDB /* SpotSearchView.swift in Sources */, 745C7E152D35AEC10074DBDB /* SpotSearchViewModel.swift in Sources */, 748D6F6B2D2BCA1C007690B4 /* ViewController.swift in Sources */, + 15A3F6A82D36D5B900577E16 /* FilterTagButtonStackLineType.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ACON-iOS/ACON-iOS/Application/AppDelegate.swift b/ACON-iOS/ACON-iOS/Application/AppDelegate.swift index 25f24b1c..84c5c1f3 100644 --- a/ACON-iOS/ACON-iOS/Application/AppDelegate.swift +++ b/ACON-iOS/ACON-iOS/Application/AppDelegate.swift @@ -7,8 +7,6 @@ import UIKit -//import NMapsMap - @main class AppDelegate: UIResponder, UIApplicationDelegate { diff --git a/ACON-iOS/ACON-iOS/Global/Extensions/UIButton+.swift b/ACON-iOS/ACON-iOS/Global/Extensions/UIButton+.swift index b04c582d..5c9b0d72 100644 --- a/ACON-iOS/ACON-iOS/Global/Extensions/UIButton+.swift +++ b/ACON-iOS/ACON-iOS/Global/Extensions/UIButton+.swift @@ -30,34 +30,4 @@ extension UIButton { self.setAttributedTitle(attributedString, for: state) } - func setPartialTitle( - fullText: String, - textStyles: [(text: String, style: ACFontStyleType, color: UIColor)] - ) { - let attributedString = NSMutableAttributedString(string: fullText) - - textStyles.forEach { textStyle in - if let range = fullText.range(of: textStyle.text) { - let nsRange = NSRange(range, in: fullText) - let attributes: [NSAttributedString.Key: Any] = [ - .font: textStyle.style.font, - .kern: textStyle.style.kerning, - .paragraphStyle: { - let paragraphStyle = NSMutableParagraphStyle() - paragraphStyle.minimumLineHeight = textStyle.style.lineHeight - paragraphStyle.maximumLineHeight = textStyle.style.lineHeight - return paragraphStyle - }(), - .foregroundColor: textStyle.color - ] - - attributes.forEach { key, value in - attributedString.addAttribute(key, value: value, range: nsRange) - } - } - } - - self.setAttributedTitle(attributedString, for: state) - } - } diff --git a/ACON-iOS/ACON-iOS/Global/Extensions/UIViewController+.swift b/ACON-iOS/ACON-iOS/Global/Extensions/UIViewController+.swift index 43f4629d..090f0fd1 100644 --- a/ACON-iOS/ACON-iOS/Global/Extensions/UIViewController+.swift +++ b/ACON-iOS/ACON-iOS/Global/Extensions/UIViewController+.swift @@ -49,20 +49,6 @@ extension UIViewController { } } - func setMiddleSheetLayout() { - self.modalPresentationStyle = .pageSheet - - if let sheet = self.sheetPresentationController { - let sheetUtils = SheetUtils() - sheet.detents = [sheetUtils.acMiddleDetent] - sheet.prefersScrollingExpandsWhenScrolledToEdge = false - sheet.prefersGrabberVisible = false - - sheet.selectedDetentIdentifier = sheetUtils.middleDetentIdentifier - } - } - - func setLongSheetLayout() { self.modalPresentationStyle = .pageSheet diff --git a/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift b/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift index 30bf78ba..b7f9f270 100644 --- a/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift +++ b/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift @@ -55,8 +55,6 @@ enum StringLiterals { static let shortDetent = "acShortDetent" - static let middleDetent = "acMiddleDetent" - static let longDetent = "acLongDetent" } @@ -103,33 +101,4 @@ enum StringLiterals { } - enum LocalVerification { - - static let needLocalVerification = "로컬 맛집 추천을 위해\n지역 인증이 필요해요." - - static let doLocalVerification = "자주 가는 동네로 지역 인증을 해보세요" - - static let new = "새로운" - - static let verifyLocal = " 나의 동네 인증하기" - - static let next = "다음" - - static let finishVerification = "인증완료" - - static let letsStart = "시작하기" - - static let locateOnMap = "지도에서 위치 확인하기" - - static let now = "이제 " - - static let localAcornTitle = "에\n로컬 도토리를 떨어트릴 수 있어요!" - - static let localAcornExplaination = "로컬 도토리는 로컬 맛집을 보증하는 도토리에요.\n만족스러운 식사 후 리뷰에 사용해보세요!" - - static let localAcorn = "로컬 도토리" - - static let plainAcorn = "일반 도토리" - } - } diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Colors/dim_b_60.colorset/Contents.json b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Colors/sub_org35.colorset/Contents.json similarity index 55% rename from ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Colors/dim_b_60.colorset/Contents.json rename to ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Colors/sub_org35.colorset/Contents.json index c1bebfb9..68f84bef 100644 --- a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Colors/dim_b_60.colorset/Contents.json +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Colors/sub_org35.colorset/Contents.json @@ -2,12 +2,12 @@ "colors" : [ { "color" : { - "color-space" : "display-p3", + "color-space" : "srgb", "components" : { - "alpha" : "0.600", - "blue" : "0x00", - "green" : "0x00", - "red" : "0x00" + "alpha" : "0.350", + "blue" : "0.314", + "green" : "0.537", + "red" : "1.000" } }, "idiom" : "universal" diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/ic_radio_20.svg b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/ic_radio_20.svg deleted file mode 100644 index b51431c6..00000000 --- a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/ic_radio_20.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/ic_radio_pressed_20.svg b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/ic_radio_pressed_20.svg deleted file mode 100644 index 8c0ce618..00000000 --- a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/ic_radio_pressed_20.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/localAcorn.imageset/ic_local acon.svg b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/localAcorn.imageset/ic_local acon.svg deleted file mode 100644 index 900a2b8d..00000000 --- a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/localAcorn.imageset/ic_local acon.svg +++ /dev/null @@ -1,39 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/plainAcorn.imageset/ic_normal acon.svg b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/plainAcorn.imageset/ic_normal acon.svg deleted file mode 100644 index 99614451..00000000 --- a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/plainAcorn.imageset/ic_normal acon.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/Contents.json b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotDummy/Contents.json similarity index 100% rename from ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/Contents.json rename to ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotDummy/Contents.json diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/Contents.json b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotDummy/img_ex1.imageset/Contents.json similarity index 76% rename from ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/Contents.json rename to ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotDummy/img_ex1.imageset/Contents.json index e35b49fd..9bca2a65 100644 --- a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/Contents.json +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotDummy/img_ex1.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "ic_radio_20.svg", + "filename" : "img_ex1.svg", "idiom" : "universal" } ], diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotDummy/img_ex1.imageset/img_ex1.svg b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotDummy/img_ex1.imageset/img_ex1.svg new file mode 100644 index 00000000..8a92a80a --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotDummy/img_ex1.imageset/img_ex1.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/localAcorn.imageset/Contents.json b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotDummy/img_ex2.imageset/Contents.json similarity index 75% rename from ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/localAcorn.imageset/Contents.json rename to ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotDummy/img_ex2.imageset/Contents.json index 4ba1bb85..484106c2 100644 --- a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/localAcorn.imageset/Contents.json +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotDummy/img_ex2.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "ic_local acon.svg", + "filename" : "img_ex2.svg", "idiom" : "universal" } ], diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotDummy/img_ex2.imageset/img_ex2.svg b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotDummy/img_ex2.imageset/img_ex2.svg new file mode 100644 index 00000000..3954ac89 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotDummy/img_ex2.imageset/img_ex2.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/plainAcorn.imageset/Contents.json b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotDummy/img_ex3.imageset/Contents.json similarity index 75% rename from ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/plainAcorn.imageset/Contents.json rename to ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotDummy/img_ex3.imageset/Contents.json index 1751ef8d..58f4ae3c 100644 --- a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/plainAcorn.imageset/Contents.json +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotDummy/img_ex3.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "ic_normal acon.svg", + "filename" : "img_ex3.jpg", "idiom" : "universal" } ], diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotDummy/img_ex3.imageset/img_ex3.jpg b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotDummy/img_ex3.imageset/img_ex3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fae0a32bf8b49c74e867a9dcd706d3e58132152d GIT binary patch literal 58739 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 + + + + + + + + + + + + + diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotList/Contents.json b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotList/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotList/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotList/ic_walking_24.imageset/Contents.json b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotList/ic_walking_24.imageset/Contents.json new file mode 100644 index 00000000..8c32ce66 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotList/ic_walking_24.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_walking_24.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotList/ic_walking_24.imageset/ic_walking_24.svg b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotList/ic_walking_24.imageset/ic_walking_24.svg new file mode 100644 index 00000000..0e706273 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/SpotList/ic_walking_24.imageset/ic_walking_24.svg @@ -0,0 +1,3 @@ + + + diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet_bar.imageset/Contents.json b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet bar.imageset/Contents.json similarity index 85% rename from ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet_bar.imageset/Contents.json rename to ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet bar.imageset/Contents.json index f19f00a2..153852f9 100644 --- a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet_bar.imageset/Contents.json +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet bar.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Rectangle 34627096.svg", + "filename" : "btn_bottomsheet bar.svg", "idiom" : "universal", "scale" : "1x" }, diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet bar.imageset/btn_bottomsheet bar.svg b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet bar.imageset/btn_bottomsheet bar.svg new file mode 100644 index 00000000..d055efee --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet bar.imageset/btn_bottomsheet bar.svg @@ -0,0 +1,3 @@ + + + diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet_bar.imageset/Rectangle 34627096.svg b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet_bar.imageset/Rectangle 34627096.svg deleted file mode 100644 index c0f290cb..00000000 --- a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Upload/btn_bottomsheet_bar.imageset/Rectangle 34627096.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ACON-iOS/ACON-iOS/Global/Settings/Config/Config.swift b/ACON-iOS/ACON-iOS/Global/Settings/Config/Config.swift index 53ca9275..91547faa 100644 --- a/ACON-iOS/ACON-iOS/Global/Settings/Config/Config.swift +++ b/ACON-iOS/ACON-iOS/Global/Settings/Config/Config.swift @@ -19,7 +19,7 @@ enum Config { static let googleWebClientID = "GOOGLE_WEB_CLIENT_ID" - static let nMapClientKey = "NMFClientId" + static let nmapAPIKey = "NMAP_API_KEY" } @@ -58,9 +58,9 @@ extension Config { return key }() - static let nMapClientKey: String = { - guard let key = Config.infoDictionary[Keys.Plist.nMapClientKey] as? String else { - fatalError("nMapClientKey is not set in plist for this configuration") + static let nmapAPIKey: String = { + guard let key = Config.infoDictionary[Keys.Plist.nmapAPIKey] as? String else { + fatalError("nmapAPIKey is not set in plist for this configuration") } return key }() diff --git a/ACON-iOS/ACON-iOS/Global/Settings/Info.plist b/ACON-iOS/ACON-iOS/Global/Settings/Info.plist index 538e3704..46c6b042 100644 --- a/ACON-iOS/ACON-iOS/Global/Settings/Info.plist +++ b/ACON-iOS/ACON-iOS/Global/Settings/Info.plist @@ -23,8 +23,8 @@ ${GOOGLE_CLIENT_ID} GOOGLE_WEB_CLIENT_ID ${GOOGLE_WEB_CLIENT_ID} - NMFClientId - ${NMAP_CLIENT_KEY} + NMAP_API_KEY + ${NMAP_API_KEY} NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/ACON-iOS/ACON-iOS/Global/Utils/ScreenUtils.swift b/ACON-iOS/ACON-iOS/Global/Utils/ScreenUtils.swift index 4e688272..5fc0c380 100644 --- a/ACON-iOS/ACON-iOS/Global/Utils/ScreenUtils.swift +++ b/ACON-iOS/ACON-iOS/Global/Utils/ScreenUtils.swift @@ -22,4 +22,17 @@ struct ScreenUtils { return UIScreen.main.bounds.height } + + // MARK: - 비율 프로퍼티 + + static var widthRatio: CGFloat { + let figmaWidth: CGFloat = 360 + return width / figmaWidth + } + + static var heightRatio: CGFloat { + let figmaHeight: CGFloat = 780 + return height / figmaHeight + } + } diff --git a/ACON-iOS/ACON-iOS/Global/Utils/SheetUtils.swift b/ACON-iOS/ACON-iOS/Global/Utils/SheetUtils.swift index 25525557..947997ae 100644 --- a/ACON-iOS/ACON-iOS/Global/Utils/SheetUtils.swift +++ b/ACON-iOS/ACON-iOS/Global/Utils/SheetUtils.swift @@ -10,7 +10,6 @@ import UIKit struct SheetUtils { let shortDetentIdentifier = UISheetPresentationController.Detent.Identifier(StringLiterals.SheetUtils.shortDetent) - let middleDetentIdentifier = UISheetPresentationController.Detent.Identifier(StringLiterals.SheetUtils.middleDetent) let longDetentIdentifier = UISheetPresentationController.Detent.Identifier(StringLiterals.SheetUtils.longDetent) var acShortDetent: UISheetPresentationController.Detent { @@ -21,14 +20,6 @@ struct SheetUtils { } } - var acMiddleDetent: UISheetPresentationController.Detent { - return UISheetPresentationController.Detent.custom(identifier: longDetentIdentifier) { _ in - let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene - let safeAreaBottom = windowScene?.windows.first?.safeAreaInsets.bottom ?? 0 - return ScreenUtils.height*558/780 - safeAreaBottom - } - } - var acLongDetent: UISheetPresentationController.Detent { return UISheetPresentationController.Detent.custom(identifier: longDetentIdentifier) { _ in let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene diff --git a/ACON-iOS/ACON-iOS/Presentation/Base/BaseView.swift b/ACON-iOS/ACON-iOS/Presentation/Base/BaseView.swift index 073f5dc9..c071ac53 100644 --- a/ACON-iOS/ACON-iOS/Presentation/Base/BaseView.swift +++ b/ACON-iOS/ACON-iOS/Presentation/Base/BaseView.swift @@ -12,8 +12,6 @@ import Then class BaseView: UIView { - private let handlerImageView: UIImageView = UIImageView() - // MARK: - Initializer override init(frame: CGRect) { @@ -37,23 +35,3 @@ class BaseView: UIView { } } - -extension BaseView { - - func setHandlerImageView() { - self.addSubview(handlerImageView) - - handlerImageView.snp.makeConstraints { - $0.top.equalToSuperview().inset(ScreenUtils.height*4/780) - $0.centerX.equalToSuperview() - $0.width.equalTo(ScreenUtils.height*36/780) - $0.height.equalTo(ScreenUtils.height*3/780) - } - - handlerImageView.do { - $0.image = .btnBottomsheetBar - $0.contentMode = .scaleAspectFit - } - } - -} diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapView.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapView.swift deleted file mode 100644 index 856509a6..00000000 --- a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapView.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// LocalMapView.swift -// ACON-iOS -// -// Created by 이수민 on 1/15/25. -// - -import UIKit - -import NMapsMap -import SnapKit -import Then - -final class LocalMapView: BaseView { - - // MARK: - UI Properties - - let nMapView: NMFNaverMapView = NMFNaverMapView() - - var finishVerificationButton: UIButton = UIButton() - - // MARK: - Lifecycle - - override func setHierarchy() { - super.setHierarchy() - - self.addSubviews(nMapView, - finishVerificationButton) - } - - override func setLayout() { - super.setLayout() - - nMapView.snp.makeConstraints { - $0.top.horizontalEdges.equalToSuperview() - $0.height.equalTo(ScreenUtils.height*564/780) - } - - finishVerificationButton.snp.makeConstraints { - $0.bottom.equalToSuperview().inset(ScreenUtils.height*36/780) - $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) - $0.height.equalTo(52) - } - - } - - override func setStyle() { - super.setStyle() - - nMapView.do { - $0.showLocationButton = true - $0.showZoomControls = false - $0.showScaleBar = false - $0.showCompass = false - $0.mapView.positionMode = .normal - $0.mapView.zoomLevel = 17 - $0.mapView.minZoomLevel = 14 - $0.mapView.maxZoomLevel = 18 - } - - finishVerificationButton.do { - $0.setAttributedTitle(text: StringLiterals.LocalVerification.finishVerification, - style: .h8, - color: .acWhite, - for: .normal) - $0.backgroundColor = .gray5 - $0.roundedButton(cornerRadius: 6, maskedCorners: [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner]) - } - } - -} - diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift deleted file mode 100644 index 848aa64b..00000000 --- a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift +++ /dev/null @@ -1,108 +0,0 @@ -// -// LocalMapViewController.swift -// ACON-iOS -// -// Created by 이수민 on 1/15/25. -// - -import UIKit - -import NMapsMap -import SnapKit -import Then - -class LocalMapViewController: BaseNavViewController { - - // MARK: - UI Properties - - private let localMapView = LocalMapView() - - private var viewBlurEffect: UIVisualEffectView = UIVisualEffectView() - - private let coordinate: CLLocationCoordinate2D - - // MARK: - LifeCycle - - override func viewDidLoad() { - super.viewDidLoad() - - self.setXButton() - addTarget() - } - - init(coordinate: CLLocationCoordinate2D) { - self.coordinate = coordinate - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(false) - - self.tabBarController?.tabBar.isHidden = true - moveCameraToLocation(latitude: coordinate.latitude, longitude: coordinate.longitude) - } - - override func setHierarchy() { - super.setHierarchy() - - self.contentView.addSubview(localMapView) - } - - override func setLayout() { - super.setLayout() - - localMapView.snp.makeConstraints { - $0.edges.equalToSuperview() - } - } - - override func setStyle() { - super.setStyle() - - self.setXButton() - self.setSecondTitleLabelStyle(title: StringLiterals.LocalVerification.locateOnMap) - } - - func addTarget() { - localMapView.finishVerificationButton.addTarget(self, - action: #selector(finishVerificationButtonTapped), - for: .touchUpInside) - } - -} - - -// MARK: - @objc functions - -private extension LocalMapViewController { - - @objc - func finishVerificationButtonTapped() { - let vc = LocalVerificationFinishedViewController() - vc.dismissCompletion = { [weak self] in - self?.removeBlurView() - } - - vc.setMiddleSheetLayout() - self.addBlurView() - self.present(vc, animated: true) - } - -} - - -// MARK: - Map Functions - -extension LocalMapViewController { - - func moveCameraToLocation(latitude: Double, longitude: Double) { - let position = NMGLatLng(lat: latitude, lng: longitude) - let cameraUpdate = NMFCameraUpdate(scrollTo: position, zoomTo: 17) - localMapView.nMapView.mapView.moveCamera(cameraUpdate) - } - -} diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedView.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedView.swift deleted file mode 100644 index 45443ea4..00000000 --- a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedView.swift +++ /dev/null @@ -1,144 +0,0 @@ -// -// LocalVerificationFinishedView.swift -// ACON-iOS -// -// Created by 이수민 on 1/15/25. -// - -import UIKit - -import SnapKit -import Then - -final class LocalVerificationFinishedView: BaseView { - - // MARK: - UI Properties - - var titleLabel: UILabel = UILabel() - - private let explainationLabel: UILabel = UILabel() - - private let localAcornImageView: UIImageView = UIImageView() - - private let plainAcornImageView: UIImageView = UIImageView() - - private let localAcornLabel: UILabel = UILabel() - - private let plainAcornLabel: UILabel = UILabel() - - var startButton: UIButton = UIButton() - - - // MARK: - Lifecycle - - override func setHierarchy() { - super.setHierarchy() - - self.addSubviews(titleLabel, - explainationLabel, - localAcornImageView, - plainAcornImageView, - localAcornLabel, - plainAcornLabel, - startButton) - } - - override func setLayout() { - super.setLayout() - - titleLabel.snp.makeConstraints { - $0.top.equalToSuperview().inset(ScreenUtils.height*32/780) - $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) - $0.height.equalTo(56) - } - - explainationLabel.snp.makeConstraints { - $0.top.equalToSuperview().inset(ScreenUtils.height*96/780) - $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) - $0.height.equalTo(36) - } - - localAcornImageView.snp.makeConstraints { - $0.top.equalToSuperview().inset(ScreenUtils.height*258/780) - $0.leading.equalToSuperview().inset(ScreenUtils.width*73/360) - $0.width.height.equalTo(80) - } - - plainAcornImageView.snp.makeConstraints { - $0.top.equalToSuperview().inset(ScreenUtils.height*258/780) - $0.trailing.equalToSuperview().inset(ScreenUtils.width*73/360) - $0.width.height.equalTo(80) - } - - localAcornLabel.snp.makeConstraints { - $0.top.equalToSuperview().inset(ScreenUtils.height*346/780) - $0.leading.equalToSuperview().inset(ScreenUtils.width*66/360) - $0.width.equalTo(94) - } - - plainAcornLabel.snp.makeConstraints { - $0.top.equalToSuperview().inset(ScreenUtils.height*346/780) - $0.trailing.equalToSuperview().inset(ScreenUtils.width*66/360) - $0.width.equalTo(94) - } - - startButton.snp.makeConstraints { - $0.bottom.equalToSuperview().inset(ScreenUtils.height*36/780) - $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) - $0.height.equalTo(52) - } - - } - - override func setStyle() { - super.setStyle() - - self.setHandlerImageView() - self.backgroundColor = .dimB60 - self.backgroundColor?.withAlphaComponent(0.8) - - explainationLabel.do { - $0.setLabel(text: StringLiterals.LocalVerification.localAcornExplaination, - style: .b3, - color: .gray3) - } - - localAcornImageView.do { - $0.image = .localAcorn - $0.contentMode = .scaleAspectFit - } - - plainAcornImageView.do { - $0.image = .plainAcorn - $0.contentMode = .scaleAspectFit - } - - localAcornLabel.do { - $0.setLabel(text: StringLiterals.LocalVerification.localAcorn, - style: .s1, - color: .acWhite, - alignment: .center) - } - - plainAcornLabel.do { - $0.setLabel(text: StringLiterals.LocalVerification.plainAcorn, - style: .s1, - color: .acWhite, - alignment: .center) - } - - startButton.do { - $0.setAttributedTitle(text: StringLiterals.LocalVerification.letsStart, - style: .h8, - color: .gray6, - for: .disabled) - $0.setAttributedTitle(text: StringLiterals.LocalVerification.letsStart, - style: .h8, - color: .acWhite, - for: .normal) - $0.backgroundColor = .gray8 - $0.roundedButton(cornerRadius: 6, maskedCorners: [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner]) - } - } - -} diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedViewController.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedViewController.swift deleted file mode 100644 index 1c9a021d..00000000 --- a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedViewController.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// LocalVerificationFinishedViewController.swift -// ACON-iOS -// -// Created by 이수민 on 1/15/25. -// - -import UIKit - -import SnapKit -import Then - -class LocalVerificationFinishedViewController: BaseViewController { - - // MARK: - UI Properties - - private let localVerificationFinishedView = LocalVerificationFinishedView() - - let localName = "동교동" - - - // MARK: - LifeCycle - - override func viewDidLoad() { - super.viewDidLoad() - - addTarget() - } - - var dismissCompletion: (() -> Void)? - - override func viewDidDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - if isBeingDismissed { - dismissCompletion?() - } - } - - override func setHierarchy() { - super.setHierarchy() - - self.view.addSubview(localVerificationFinishedView) - } - - override func setLayout() { - super.setLayout() - - localVerificationFinishedView.snp.makeConstraints { - $0.edges.equalToSuperview() - } - } - - override func setStyle() { - super.setStyle() - - localVerificationFinishedView.titleLabel.do { - $0.setLabel(text: StringLiterals.LocalVerification.now + localName + StringLiterals.LocalVerification.localAcornTitle, - style: .h6, - color: .acWhite) - } - } - - func addTarget() { - localVerificationFinishedView.startButton.addTarget(self, - action: #selector(startButtonTapped), - for: .touchUpInside) - } - -} - - -// MARK: - @objc functions - -private extension LocalVerificationFinishedViewController { - - @objc - func startButtonTapped() { - goToTabView() - } - -} - - -// MARK: - Close View - -private extension LocalVerificationFinishedViewController { - - @objc - func goToTabView() { - if let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate { - sceneDelegate.window?.rootViewController = ACTabBarController() - } - } - -} diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationView.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationView.swift deleted file mode 100644 index 785f8423..00000000 --- a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationView.swift +++ /dev/null @@ -1,121 +0,0 @@ -// -// LocalVerificationView.swift -// ACON-iOS -// -// Created by 이수민 on 1/14/25. -// - -import UIKit - -import SnapKit -import Then - -final class LocalVerificationView: BaseView { - - // MARK: - UI Properties - - private let weNeedYourAddressLabel: UILabel = UILabel() - - private let doLocalVerificationLabel: UILabel = UILabel() - - var verifyNewLocalButton: UIButton = UIButton() - - var nextButton: UIButton = UIButton() - - var verifyNewLocalButtonConfiguration: UIButton.Configuration = { - var configuration = UIButton.Configuration.plain() - configuration.imagePlacement = .leading - configuration.imagePadding = 8 - configuration.titleAlignment = .leading - configuration.preferredSymbolConfigurationForImage = UIImage.SymbolConfiguration(pointSize: 20) - configuration.contentInsets = NSDirectionalEdgeInsets(top: 16, - leading: 16, - bottom: 16, - trailing: 139) - return configuration - }() - - // MARK: - Lifecycle - - override func setHierarchy() { - super.setHierarchy() - - self.addSubviews(weNeedYourAddressLabel, - doLocalVerificationLabel, - verifyNewLocalButton, - nextButton) - } - - override func setLayout() { - super.setLayout() - - weNeedYourAddressLabel.snp.makeConstraints { - $0.top.equalToSuperview().inset(ScreenUtils.height*32/780) - $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) - $0.height.equalTo(56) - } - - doLocalVerificationLabel.snp.makeConstraints { - $0.top.equalToSuperview().inset(ScreenUtils.height*96/780) - $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) - $0.height.equalTo(18) - } - - verifyNewLocalButton.snp.makeConstraints { - $0.top.equalToSuperview().inset(ScreenUtils.height*146/780) - $0.centerX.equalToSuperview() - $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) - $0.height.equalTo(ScreenUtils.height*52/780) - } - - nextButton.snp.makeConstraints { - $0.bottom.equalToSuperview().inset(ScreenUtils.height*36/780) - $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) - $0.height.equalTo(52) - } - - } - - override func setStyle() { - super.setStyle() - - weNeedYourAddressLabel.do { - $0.setLabel(text: StringLiterals.LocalVerification.needLocalVerification, - style: .h6, - color: .acWhite) - } - - doLocalVerificationLabel.do { - $0.setLabel(text: StringLiterals.LocalVerification.doLocalVerification, - style: .b3, - color: .gray3) - } - - verifyNewLocalButton.do { - $0.configuration = verifyNewLocalButtonConfiguration - $0.backgroundColor = .gray9 - $0.roundedButton(cornerRadius: 4, maskedCorners: [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner]) - $0.layer.borderWidth = 1 - $0.layer.borderColor = UIColor(resource: .gray8).cgColor - $0.setImage(.icRadio, for: .normal) - $0.setImage(.icRadioSelected, for: .selected) - $0.setPartialTitle(fullText: StringLiterals.LocalVerification.new + StringLiterals.LocalVerification.verifyLocal, - textStyles: [(StringLiterals.LocalVerification.new, .s2, .org1), (StringLiterals.LocalVerification.verifyLocal, .s2, .acWhite)]) - } - - nextButton.do { - $0.setAttributedTitle(text: StringLiterals.LocalVerification.next, - style: .h8, - color: .gray6, - for: .disabled) - $0.setAttributedTitle(text: StringLiterals.LocalVerification.next, - style: .h8, - color: .acWhite, - for: .normal) - $0.backgroundColor = .gray8 - $0.roundedButton(cornerRadius: 6, maskedCorners: [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner]) - } - } - -} - diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationViewController.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationViewController.swift deleted file mode 100644 index e3c2614a..00000000 --- a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationViewController.swift +++ /dev/null @@ -1,112 +0,0 @@ -// -// LocalVerificationViewController.swift -// ACON-iOS -// -// Created by 이수민 on 1/15/25. -// - -import UIKit -import CoreLocation - -import SnapKit -import Then - -class LocalVerificationViewController: BaseNavViewController { - - // MARK: - UI Properties - - private let localVerificationView = LocalVerificationView() - - private var userCoordinate: CLLocationCoordinate2D? - - // MARK: - LifeCycle - - override func viewDidLoad() { - super.viewDidLoad() - - self.setXButton() - addTarget() - ACLocationManager.shared.addDelegate(self) - } - - deinit { - ACLocationManager.shared.removeDelegate(self) - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(false) - - self.tabBarController?.tabBar.isHidden = true - } - - override func setHierarchy() { - super.setHierarchy() - - self.contentView.addSubview(localVerificationView) - } - - override func setLayout() { - super.setLayout() - - localVerificationView.snp.makeConstraints { - $0.edges.equalToSuperview() - } - } - - override func setStyle() { - super.setStyle() - - self.localVerificationView.nextButton.isEnabled = false - } - - func addTarget() { - localVerificationView.verifyNewLocalButton.addTarget(self, - action: #selector(verifyLocationButtonTapped), - for: .touchUpInside) - localVerificationView.nextButton.addTarget(self, - action: #selector(nextButtonTapped), - for: .touchUpInside) - } - -} - - -// MARK: - @objc functions - -private extension LocalVerificationViewController { - - @objc - func verifyLocationButtonTapped() { - localVerificationView.verifyNewLocalButton.isSelected.toggle() - let isSelected = localVerificationView.verifyNewLocalButton.isSelected - localVerificationView.verifyNewLocalButton.configuration?.baseBackgroundColor = isSelected ? .gray7 : .gray9 - localVerificationView.nextButton.isEnabled = isSelected - localVerificationView.nextButton.backgroundColor = isSelected ? .gray5 : .gray8 - } - - @objc - func nextButtonTapped() { - ACLocationManager.shared.checkUserDeviceLocationServiceAuthorization() - } - -} - -extension LocalVerificationViewController: ACLocationManagerDelegate { - - func locationManager(_ manager: ACLocationManager, didUpdateLocation coordinate: CLLocationCoordinate2D) { - print("성공 - 위도: \(coordinate.latitude), 경도: \(coordinate.longitude)") - self.userCoordinate = coordinate - pushToLocalMapVC() - } - -} - -extension LocalVerificationViewController { - - func pushToLocalMapVC() { - guard let coordinate = userCoordinate else { return } - let vc = LocalMapViewController(coordinate: coordinate) - navigationController?.pushViewController(vc, animated: false) - } - -} diff --git a/ACON-iOS/ACON-iOS/Presentation/Login/View/LoginViewController.swift b/ACON-iOS/ACON-iOS/Presentation/Login/View/LoginViewController.swift index d5cf9041..0071105a 100644 --- a/ACON-iOS/ACON-iOS/Presentation/Login/View/LoginViewController.swift +++ b/ACON-iOS/ACON-iOS/Presentation/Login/View/LoginViewController.swift @@ -90,7 +90,7 @@ extension LoginViewController { // TODO: - 나중에 서버 로그인 Success 시 이동하는 것으로 변경 (ObservablePattern) func navigateToLocalVerificationVC() { - let vc = LocalVerificationViewController() + let vc = ViewController() self.navigationController?.pushViewController(vc, animated: false) } diff --git a/ACON-iOS/ACON-iOS/Presentation/Place/SpotListViewController.swift b/ACON-iOS/ACON-iOS/Presentation/Place/SpotListViewController.swift deleted file mode 100644 index 932742e9..00000000 --- a/ACON-iOS/ACON-iOS/Presentation/Place/SpotListViewController.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// SpotListViewController.swift -// ACON-iOS -// -// Created by 김유림 on 1/11/25. -// - -import UIKit - -class SpotListViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - - view.backgroundColor = .acBlack - } - -} diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotList/Model/SpotModel.swift b/ACON-iOS/ACON-iOS/Presentation/SpotList/Model/SpotModel.swift new file mode 100644 index 00000000..590b08d9 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/SpotList/Model/SpotModel.swift @@ -0,0 +1,81 @@ +// +// SpotModel.swift +// ACON-iOS +// +// Created by 김유림 on 1/12/25. +// + +import UIKit + +// MARK: - Spot + +struct Spot: Equatable { + + let image: UIImage? + let matchingRate: Int + let type: String + let name: String + let walkingTime: Int + +} + + +// MARK: - Spots + +struct Spots { + + let array: [Spot] + +} + + +// MARK: - Dummy data (삭제 예정) + +extension Spots { + + static let dummy: [Spot] = [ + Spot( + image: .imgEx1, + matchingRate: 98, + type: "CAFE", + name: "카페1", + walkingTime: 5 + ), + Spot( + image: .imgEx2, + matchingRate: 88, + type: "CAFE", + name: "카페2", + walkingTime: 5 + ), + Spot( + image: .imgEx3, + matchingRate: 80, + type: "RESTAURANT", + name: "햄버거 가게3", + walkingTime: 5 + ), + Spot( + image: .imgEx4, + matchingRate: 50, + type: "CAFE", + name: "OO카페4", + walkingTime: 5 + ), + Spot( + image: .imgEx1, + matchingRate: 98, + type: "CAFE", + name: "카페5", + walkingTime: 5 + ), + Spot( + image: .imgEx2, + matchingRate: 88, + type: "CAFE", + name: "카페6", + walkingTime: 5 + ) + ] + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotList/Type/MatchingRateBgColorType.swift b/ACON-iOS/ACON-iOS/Presentation/SpotList/Type/MatchingRateBgColorType.swift new file mode 100644 index 00000000..e68e0e3e --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/SpotList/Type/MatchingRateBgColorType.swift @@ -0,0 +1,21 @@ +// +// SpotListCollectionViewType.swift +// ACON-iOS +// +// Created by 김유림 on 1/13/25. +// + +import UIKit + +enum MatchingRateBgColorType { + + case dark, light + + var color: UIColor { + switch self { + case .dark: return .gray9 + case .light: return .glaB30 + } + } + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotList/Type/SpotListItemSizeType.swift b/ACON-iOS/ACON-iOS/Presentation/SpotList/Type/SpotListItemSizeType.swift new file mode 100644 index 00000000..54c0202e --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/SpotList/Type/SpotListItemSizeType.swift @@ -0,0 +1,21 @@ +// +// SpotListItemSizeType.swift +// ACON-iOS +// +// Created by 김유림 on 1/15/25. +// + +import Foundation + +enum SpotListItemSizeType { + + case minimumLineSpacing, itemWidth + + var value: CGFloat { + switch self { + case .minimumLineSpacing: return 12 + case .itemWidth: return ScreenUtils.width - 40 + } + } + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotList/View/Cell/SpotListCollectionViewCell.swift b/ACON-iOS/ACON-iOS/Presentation/SpotList/View/Cell/SpotListCollectionViewCell.swift new file mode 100644 index 00000000..d5d5b2bb --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/SpotList/View/Cell/SpotListCollectionViewCell.swift @@ -0,0 +1,157 @@ +// +// SpotListCollectionViewCell.swift +// ACON-iOS +// +// Created by 김유림 on 1/12/25. +// + +import UIKit + +class SpotListCollectionViewCell: BaseCollectionViewCell { + + // TODO: bgImage dim 처리 + + // MARK: - UI Properties + + private let bgImage = UIImageView() + + private let matchingRateView = UIView() + private let matchingRateLabel = UILabel() + + private let stackView = UIStackView() + + private let titleStackView = UIStackView() + private let typeLabel = UILabel() + private let nameLabel = UILabel() + + private let timeInfoStackView = UIStackView() + private let walkingIcon = UIImageView() + private let walkingTimeLabel = UILabel() + + + // MARK: - Life Cycle + + override func setHierarchy() { + super.setHierarchy() + + self.addSubviews(bgImage, + matchingRateView, + stackView) + + matchingRateView.addSubview(matchingRateLabel) + + stackView.addArrangedSubviews(titleStackView, + timeInfoStackView) + + titleStackView.addArrangedSubviews(typeLabel, + nameLabel) + + timeInfoStackView.addArrangedSubviews(walkingIcon, + walkingTimeLabel) + } + + override func setLayout() { + super.setLayout() + + bgImage.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + matchingRateView.snp.makeConstraints { + $0.leading.top.equalToSuperview().offset(16) + $0.width.equalTo(96) + $0.height.equalTo(22) + } + + matchingRateLabel.snp.makeConstraints { + $0.center.equalToSuperview() + } + + stackView.snp.makeConstraints { + $0.leading.bottom.equalToSuperview().inset(16) + } + + walkingIcon.snp.makeConstraints { + $0.size.equalTo(16) + } + } + + override func setStyle() { + backgroundColor = .clear + + bgImage.do { + $0.contentMode = .scaleAspectFill + $0.layer.cornerRadius = 6 + $0.clipsToBounds = true + } + + matchingRateView.do { + $0.backgroundColor = .gray9 + $0.layer.contents = 2 + } + + matchingRateLabel.do { + $0.setText(.b4, .acWhite) + } + + stackView.do { + $0.axis = .vertical + $0.spacing = 4 + } + + titleStackView.do { + $0.axis = .vertical + $0.spacing = 0 + } + + timeInfoStackView.do { + $0.spacing = 0 + } + + walkingIcon.do { + $0.image = .icWalking24 + $0.contentMode = .scaleAspectFit + } + } + +} + + +// MARK: - Binding + +extension SpotListCollectionViewCell { + + func bind(spot: Spot, matchingRateBgColor: MatchingRateBgColorType) { + bgImage.image = spot.image + + changeMatchingRateBgColor(matchingRateBgColor) + + + let matchingRateHead = StringLiterals.SpotList.matchingRate + let matchingRateStringSet = matchingRateHead + " " + String(spot.matchingRate) + "%" + matchingRateLabel.setLabel(text: matchingRateStringSet, + style: .b4) + + typeLabel.setLabel(text: spot.type, + style: .b4) + + nameLabel.setLabel(text: spot.name, + style: .h7) + + walkingTimeLabel.setLabel(text: "\(String(spot.walkingTime))분", + style: .b4, + color: .gray3) + } + +} + + +// MARK: - UI Change Methods + +extension SpotListCollectionViewCell { + + private func changeMatchingRateBgColor(_ matchingRateBgColor: MatchingRateBgColorType) { + matchingRateView.backgroundColor = matchingRateBgColor.color + } + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotList/View/SpotListView.swift b/ACON-iOS/ACON-iOS/Presentation/SpotList/View/SpotListView.swift new file mode 100644 index 00000000..36f73398 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/SpotList/View/SpotListView.swift @@ -0,0 +1,61 @@ +// +// SpotListView.swift +// ACON-iOS +// +// Created by 김유림 on 1/12/25. +// + +import UIKit + +class SpotListView: BaseView { + + // MARK: - UI Properties + + let collectionView = UICollectionView( + frame: .zero, + collectionViewLayout: UICollectionViewFlowLayout() + ) + + + // MARK: - LifeCycles + + override func setHierarchy() { + super.setHierarchy() + + self.addSubviews(collectionView) + } + + override func setLayout() { + super.setLayout() + + collectionView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + } + + override func setStyle() { + super.setStyle() + + setCollectionView() + } + +} + + +// MARK: - UI Settings + +extension SpotListView { + + func setCollectionView() { + let flowLayout = UICollectionViewFlowLayout() + // NOTE: itemSize는 Controller에서 설정합니다. (collectionView의 height이 필요하기 때문) + flowLayout.minimumLineSpacing = SpotListItemSizeType.minimumLineSpacing.value + flowLayout.scrollDirection = .vertical + + collectionView.do { + $0.backgroundColor = .gray9 + $0.setCollectionViewLayout(flowLayout, animated: true) + } + } + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotList/View/SpotListViewController.swift b/ACON-iOS/ACON-iOS/Presentation/SpotList/View/SpotListViewController.swift new file mode 100644 index 00000000..bde0a592 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/SpotList/View/SpotListViewController.swift @@ -0,0 +1,192 @@ +// +// SpotListViewController.swift +// ACON-iOS +// +// Created by 김유림 on 1/11/25. +// + +import UIKit + +class SpotListViewController: BaseNavViewController { + + // MARK: - Properties + + private let spotListView = SpotListView() + private let spotListViewModel = SpotListViewModel() + + + // MARK: - LifeCycle + + override func setHierarchy() { + super.setHierarchy() + + contentView.addSubview(spotListView) + } + + override func setLayout() { + super.setLayout() + + spotListView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + } + + override func setStyle() { + super.setStyle() + + self.setTitleLabelStyle(title: "동네 인증") + } + + override func viewDidLoad() { + super.viewDidLoad() + + setCollectionView() + } + +} + + +// MARK: - @objc functions + +private extension SpotListViewController { + + @objc + func handleRefreshControl() { + print("refresh control 실행됨") + + if !spotListViewModel.secondSpotList.isEmpty { + spotListViewModel.isFirstPage.value?.toggle() + } else { + // TODO: 네트워크 요청 + } + + DispatchQueue.main.async { + // NOTE: 데이터 리로드 전 애니메이션 + UIView.animate(withDuration: 0.25, animations: { + self.spotListView.collectionView.alpha = 0.5 // 투명도 낮춤 + }) { _ in + // NOTE: 데이터 리로드 + self.spotListView.collectionView.reloadData() + + // NOTE: 데이터 리로드 후 애니메이션 (천천히 올라오는 애니메이션) + UIView.animate(withDuration: 1.0, delay: 0, options: .curveEaseOut) { + self.spotListView.collectionView.setContentOffset(.zero, animated: true) + self.spotListView.collectionView.alpha = 1.0 // 투명도 복원 + } completion: { _ in + // NOTE: 리프레시 종료 + self.spotListView.collectionView.refreshControl?.endRefreshing() + } + } + } + } + +} + + +// MARK: - CollectionView Settings + +private extension SpotListViewController { + + func setCollectionView() { + setDelegate() + registerCells() + setRefreshControl() + } + + func setDelegate() { + spotListView.collectionView.dataSource = self + spotListView.collectionView.delegate = self + } + + func registerCells() { + spotListView.collectionView.register( + SpotListCollectionViewCell.self, + forCellWithReuseIdentifier: SpotListCollectionViewCell.cellIdentifier + ) + } + + func setRefreshControl() { + // TODO: Refresh control 디자인 변경 + + let control = UIRefreshControl() + + control.addTarget(self, + action: #selector(handleRefreshControl), + for: .valueChanged + ) + + spotListView.collectionView.refreshControl = control + } + +} + + +// MARK: - CollectionViewDataSource + +extension SpotListViewController: UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + guard let isFirstPage = spotListViewModel.isFirstPage.value else { return 0 } + + return isFirstPage ? spotListViewModel.firstSpotList.count : spotListViewModel.secondSpotList.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let isFirstPage = spotListViewModel.isFirstPage.value, + let item = collectionView.dequeueReusableCell( + withReuseIdentifier: SpotListCollectionViewCell.cellIdentifier, + for: indexPath + ) as? SpotListCollectionViewCell + else { return UICollectionViewCell() } + + let spot = isFirstPage ? spotListViewModel.firstSpotList[indexPath.row] : spotListViewModel.secondSpotList[indexPath.row] + + // NOTE: 1번페이지 1번째 셀만 취향 일치율 배경색 어둡게 + let bgColor: MatchingRateBgColorType = isFirstPage && indexPath.item == 0 ? .dark : .light + + item.bind(spot: spot, matchingRateBgColor: bgColor) + + return item + } + +} + + +// MARK: - CollectionViewDelegate + +extension SpotListViewController: UICollectionViewDelegateFlowLayout { + + func collectionView(_ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + sizeForItemAt indexPath: IndexPath) -> CGSize { + // NOTE: 1번페이지 1번째 셀만 크게 + guard let isFirstPage = spotListViewModel.isFirstPage.value + else { return .zero } + + let itemWidth: CGFloat = SpotListItemSizeType.itemWidth.value + let collectionViewHeight: CGFloat = collectionView.frame.height + let shortHeight: CGFloat = shortItemHeight(collectionViewHeight) + let longHeight: CGFloat = longItemHeight(collectionViewHeight) + let itemHeight = isFirstPage && indexPath.item == 0 ? longHeight : shortHeight + + return CGSize(width: itemWidth, height: itemHeight) + } + +} + + +// MARK: - CollectionView ItemSize Method + +extension SpotListViewController { + + func longItemHeight(_ collectionViewHeight: CGFloat) -> CGFloat { + let shortHeight = shortItemHeight(collectionViewHeight) + return collectionViewHeight - shortHeight - 12 + } + + func shortItemHeight(_ collectionViewHeight: CGFloat) -> CGFloat { + let lineSpacing = SpotListItemSizeType.minimumLineSpacing.value + return (collectionViewHeight - lineSpacing * 3) / 4 + } + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotList/ViewModel/SpotListViewModel.swift b/ACON-iOS/ACON-iOS/Presentation/SpotList/ViewModel/SpotListViewModel.swift new file mode 100644 index 00000000..b7480e0a --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/SpotList/ViewModel/SpotListViewModel.swift @@ -0,0 +1,39 @@ +// +// SpotListViewModel.swift +// ACON-iOS +// +// Created by 김유림 on 1/13/25. +// + +import Foundation + +class SpotListViewModel { + + // MARK: - Properties + + var isFirstPage: ObservablePattern = ObservablePattern(true) + + private var givenSpotList: ObservablePattern<[Spot]> = ObservablePattern(Spots.dummy) + + var firstSpotList: [Spot] = [] + var secondSpotList: [Spot] = [] + + + // MARK: - Methods + + init() { + givenSpotList.bind { [weak self] spotList in + guard let self = self, + let spotList = spotList else { return } + + splitSpotList(spotList) + } + } + + // TODO: 서버와 논의 후 변경 예정 + private func splitSpotList(_ spotList: [Spot]) { + firstSpotList = Array(spotList.prefix(2)) + secondSpotList = Array(spotList.dropFirst(2)) + } + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Model/SpotListFilterModel.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Model/SpotListFilterModel.swift new file mode 100644 index 00000000..82a795b8 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Model/SpotListFilterModel.swift @@ -0,0 +1,57 @@ +// +// SpotListFilterModel.swift +// ACON-iOS +// +// Created by 김유림 on 1/14/25. +// + +import Foundation + +struct SpotListFilterModel { + + // TODO: Type에 포함시키는 방향으로 수정 + struct RestaurantFeature { + + static let firstLine: [SpotType.RestaurantFeatureType] = [ + .korean, .western, .chinese, .japanese, .snack + ] + + static let secondLine: [SpotType.RestaurantFeatureType] = [ + .asian, .bar, .excludeFranchise + ] + + } + + struct CafeFeature { + + static let firstLine: [SpotType.CafeFeatureType] = [ + .large, .goodView, .dessert, .terace + ] + + static let secondLine: [SpotType.CafeFeatureType] = [ + .excludeFranchise + ] + + } + + struct Companion { + + static let firstLine: [SpotType.CompanionType] = [ + .family, .date, .friend, .alone, .group + ] + + static let secondLine: [SpotType.CompanionType] = [] + + } + + struct VisitPurpose { + + static let firstLine: [SpotType.VisitPurposeType] = [ + .meeting, .study + ] + + static let secondLine: [SpotType.VisitPurposeType] = [] + + } + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/FilterTagButtonStackLineType.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/FilterTagButtonStackLineType.swift new file mode 100644 index 00000000..3459f900 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/FilterTagButtonStackLineType.swift @@ -0,0 +1,14 @@ +// +// FilterTagButtonStackLineType.swift +// ACON-iOS +// +// Created by 김유림 on 1/15/25. +// + +import Foundation + +enum FilterTagButtonStackLineType { + + case first, second + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/FilterTagButtonType.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/FilterTagButtonType.swift new file mode 100644 index 00000000..6aa75047 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/FilterTagButtonType.swift @@ -0,0 +1,33 @@ +// +// FilterTagButtonType.swift +// ACON-iOS +// +// Created by 김유림 on 1/14/25. +// + +import UIKit + +enum FilterTagButtonType { + + case selected, unselected + + var bgColor: UIColor { + switch self { + case .selected: return .subOrg35 + case .unselected: return .gray8 + } + } + + var strokeColor: UIColor { + switch self { + case .selected: return .org1 + case .unselected: return .gray6 + } + } + + var textColor: UIColor { + return .acWhite + } + +} + diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/SpotType.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/SpotType.swift new file mode 100644 index 00000000..9133ce38 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/SpotType.swift @@ -0,0 +1,98 @@ +// +// SpotType.swift +// ACON-iOS +// +// Created by 김유림 on 1/14/25. +// + +import Foundation + +enum SpotType { + + // MARK: - 장소 종류 + + case restaurant, cafe + + var text: String { + switch self { + case .restaurant: return "음식점" + case .cafe: return "카페" + } + } + + + // MARK: - 장소 상세 조건 + + enum RestaurantFeatureType { + + case korean, western, chinese, japanese, snack, asian, bar, excludeFranchise + + var text: String { + switch self { + case .korean: return "한식" + case .western: return "양식" + case .chinese: return "중식" + case .japanese: return "일식" + case .snack: return "간식" + case .asian: return "아시안" + case .bar: return "술/bar" + case .excludeFranchise: return "프랜차이즈 제외" + } + } + + var firstLineCount: Int { + return 5 + } + + } + + enum CafeFeatureType { + + case large, goodView, dessert, terace, excludeFranchise + + var text: String { + switch self { + case .large: return "대형" + case .goodView: return "뷰 좋은 곳" + case .dessert: return "디저트" + case .terace: return "테라스" + case .excludeFranchise: return "프랜차이즈 제외" + } + } + + var firstLineCount: Int { + return 4 + } + + } + + enum CompanionType { + + case family, date, friend, alone, group + + var text: String { + switch self { + case .family: return "가족" + case .date: return "연인" + case .friend: return "친구" + case .alone: return "혼자" + case .group: return "단체" + } + } + + } + + enum VisitPurposeType { + + case meeting, study + + var text: String { + switch self { + case .meeting: return "만남용" + case .study: return "공부용" + } + } + + } + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/FilterTagButton.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/FilterTagButton.swift new file mode 100644 index 00000000..6ac87590 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/FilterTagButton.swift @@ -0,0 +1,58 @@ +// +// FilterTagButton.swift +// ACON-iOS +// +// Created by 김유림 on 1/14/25. +// + +import UIKit + +class FilterTagButton: UIButton { + + // MARK: - Basic Properties + + override var isSelected: Bool { + didSet { + print("button isSelected") + configuration?.baseBackgroundColor = isSelected ? .subOrg35 : .gray8 + configuration?.background.strokeColor = isSelected ? .org1 : .gray6 + } + } + + + // MARK: - Initializing + + override init(frame: CGRect) { + super.init(frame: frame) + + configureButton() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + +} + + +// MARK: - UI Settings + +private extension FilterTagButton { + + func configureButton() { + var config = UIButton.Configuration.filled() + config.baseBackgroundColor = .gray8 + config.baseForegroundColor = .acWhite + config.background.strokeColor = .gray6 + config.background.strokeWidth = 1 + config.cornerStyle = .capsule + config.titleAlignment = .center + config.contentInsets = NSDirectionalEdgeInsets(top: 7, + leading: 16, + bottom: 7, + trailing: 16) + + self.configuration = config + } + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/SpotFilterTagButtonStackView.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/SpotFilterTagButtonStackView.swift new file mode 100644 index 00000000..c30ef910 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/SpotFilterTagButtonStackView.swift @@ -0,0 +1,84 @@ +// +// RestaurantOptionStackView.swift +// ACON-iOS +// +// Created by 김유림 on 1/14/25. +// + +import UIKit + +class SpotFilterTagButtonStackView: UIStackView { + + // MARK: - UI Properties + + private let firstLineStackView = UIStackView() + private let secondLineStackView = UIStackView() + + + // MARK: - LifeCycle + + override init(frame: CGRect) { + super.init(frame: frame) + + setHierarchy() + setStyle() + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setHierarchy() { + self.addArrangedSubviews(firstLineStackView, + secondLineStackView) + } + + private func setStyle() { + self.alignment = .leading + self.axis = .vertical + self.spacing = 5 + + firstLineStackView.axis = .horizontal + firstLineStackView.spacing = 5 + + secondLineStackView.axis = .horizontal + secondLineStackView.spacing = 5 + } + +} + + +// MARK: - StackView Control Methods + +extension SpotFilterTagButtonStackView { + + func addTagButton(to line: FilterTagButtonStackLineType, button: UIButton) { + switch line { + case .first: + firstLineStackView.addArrangedSubview(button) + case .second: + secondLineStackView.addArrangedSubview(button) + } + } + + func clearStackView() { + clearStackView(from: firstLineStackView) + clearStackView(from: secondLineStackView) + } + + func addEmptyView() { + // TODO: [Fix] priority low로 자동으로 작게 설정되지 않는 듯. [프랜차이즈 제외]가 2줄로 됨 + let emptyView = UIView() + emptyView.setContentHuggingPriority(.defaultLow, for: .horizontal) + firstLineStackView.addArrangedSubview(emptyView) + secondLineStackView.addArrangedSubview(emptyView) + } + + private func clearStackView(from stackView: UIStackView) { + stackView.arrangedSubviews.forEach { view in + stackView.removeArrangedSubview(view) + view.removeFromSuperview() + } + } + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift new file mode 100644 index 00000000..3689af76 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift @@ -0,0 +1,45 @@ +// +// SpotListFilterView.swift +// ACON-iOS +// +// Created by 김유림 on 1/14/25. +// + +import UIKit + +class SpotListFilterView: BaseView { + + // MARK: - UI Properties + + let spotTypeSegmentControl = UISegmentedControl() + + let spotFeatureStackView = SpotFilterTagButtonStackView() + + // TODO: 함께 하는 사람, 방문 목적, 도보 가능 거리, 가격대 + + + // MARK: - Lifecycle + + override func setHierarchy() { + super.setHierarchy() + + self.addSubviews(spotTypeSegmentControl, + spotFeatureStackView) + } + + override func setLayout() { + super.setLayout() + + spotFeatureStackView.snp.makeConstraints { + $0.top.equalTo(self.safeAreaLayoutGuide).offset(17) + $0.horizontalEdges.equalToSuperview().inset(20) + } + } + + override func setStyle() { + super.setStyle() + + // TODO: 추후 추가 예정 + } + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift new file mode 100644 index 00000000..90c79778 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterViewController.swift @@ -0,0 +1,119 @@ +// +// SpotListFilterViewController.swift +// ACON-iOS +// +// Created by 김유림 on 1/14/25. +// + +import UIKit + +class SpotListFilterViewController: BaseNavViewController { + + // MARK: - Properties + + let spotListFilterView = SpotListFilterView() + let viewModel = SpotListFilterViewModel() + + + // MARK: - LifeCycles + + override func viewDidLoad() { + super.viewDidLoad() + + bindViewModel() + } + + override func setHierarchy() { + super.setHierarchy() + + contentView.addSubview(spotListFilterView) + } + + override func setLayout() { + super.setLayout() + + spotListFilterView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + } + +} + + +// MARK: - Binding ViewModel + +private extension SpotListFilterViewController { + + func bindViewModel() { + viewModel.spotType.value = .restaurant + + viewModel.spotType.bind { [weak self] spotType in + guard let self = self, + let spotType = spotType + else { return } + + updateView(spotType) + print("updateView!") + } + } + +} + + +// MARK: - UI Update + +private extension SpotListFilterViewController { + + // TODO: restaurant + + func updateView(_ spotType: SpotType) { + updateFeatureStack(spotType) + + } + + private func updateFeatureStack(_ spotType: SpotType) { + let featureStack = spotListFilterView.spotFeatureStackView + + // clear stack + featureStack.clearStackView() + + // add buttons + for feature in SpotListFilterModel.RestaurantFeature.firstLine { + let btn = FilterTagButton() + + btn.setAttributedTitle(text: feature.text, style: .b3) + btn.addTarget(self, + action: #selector(didTapFilterTagButton), + for: .touchUpInside) + + featureStack.addTagButton(to: .first, + button: btn) + } + + for feature in SpotListFilterModel.RestaurantFeature.secondLine { + let btn = FilterTagButton() + btn.setAttributedTitle(text: feature.text, style: .b3) + btn.addTarget(self, + action: #selector(didTapFilterTagButton(_:)), + for: .touchUpInside) + + featureStack.addTagButton(to: .second, + button: btn) + } + + featureStack.addEmptyView() + } + +} + + +// MARK: - @objc functions + +private extension SpotListFilterViewController { + + @objc + func didTapFilterTagButton(_ sender: UIButton) { + sender.isSelected.toggle() + } + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/ViewModel/SpotListFilterViewModel.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/ViewModel/SpotListFilterViewModel.swift new file mode 100644 index 00000000..6bc41607 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/ViewModel/SpotListFilterViewModel.swift @@ -0,0 +1,18 @@ +// +// SpotListFilterViewModel.swift +// ACON-iOS +// +// Created by 김유림 on 1/14/25. +// + +import Foundation + +class SpotListFilterViewModel { + + // MARK: - Properties + + var spotType: ObservablePattern = ObservablePattern(.restaurant) + + + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchView.swift b/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchView.swift index c6aa73b0..41443995 100644 --- a/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchView.swift +++ b/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchView.swift @@ -14,6 +14,8 @@ final class SpotSearchView: BaseView { // MARK: - UI Properties + private let handlerImageView: UIImageView = UIImageView() + private let spotUploadLabel: UILabel = UILabel() let searchView: UIView = UIView() @@ -53,7 +55,8 @@ final class SpotSearchView: BaseView { override func setHierarchy() { super.setHierarchy() - self.addSubviews(spotUploadLabel, + self.addSubviews(handlerImageView, + spotUploadLabel, searchView, doneButton, recommendedSpotScrollView, @@ -69,6 +72,13 @@ final class SpotSearchView: BaseView { override func setLayout() { super.setLayout() + handlerImageView.snp.makeConstraints { + $0.top.equalToSuperview().inset(ScreenUtils.height*4/780) + $0.centerX.equalToSuperview() + $0.width.equalTo(ScreenUtils.height*64/780) + $0.height.equalTo(ScreenUtils.height*3/780) + } + spotUploadLabel.snp.makeConstraints { $0.top.equalToSuperview().inset(ScreenUtils.height*19/780) $0.centerX.equalToSuperview() @@ -152,7 +162,11 @@ final class SpotSearchView: BaseView { self.backgroundColor = .glaW10 self.backgroundColor?.withAlphaComponent(0.95) - self.setHandlerImageView() + + handlerImageView.do { + $0.image = .btnBottomsheetBar + $0.contentMode = .scaleAspectFit + } spotUploadLabel.do { $0.setLabel(text: StringLiterals.Upload.spotUpload2, diff --git a/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchViewController.swift b/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchViewController.swift index 7ae205d3..ff71e4fd 100644 --- a/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchViewController.swift +++ b/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchViewController.swift @@ -42,7 +42,7 @@ class SpotSearchViewController: BaseViewController { var dismissCompletion: (() -> Void)? override func viewWillDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) + super.viewWillDisappear(animated) if isBeingDismissed { dismissCompletion?() } From e5430f15a0cae8ba18865ecd3c82360712e68651 Mon Sep 17 00:00:00 2001 From: cirtuare Date: Thu, 16 Jan 2025 20:05:03 +0900 Subject: [PATCH 39/44] =?UTF-8?q?[Chore]=20LocalVerification=20=EC=99=B8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20=EB=B0=98=EC=98=81?= =?UTF-8?q?=20(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Global/Extensions/UIButton+.swift | 30 ++++++++++++++++++ .../Global/Extensions/UIViewController+.swift | 13 ++++++++ .../Global/Literals/StringLiterals.swift | 31 +++++++++++++++++++ .../Colors/dim_b_60.colorset/Contents.json | 20 ++++++++++++ .../Global/Settings/Config/Config.swift | 8 ++--- ACON-iOS/ACON-iOS/Global/Settings/Info.plist | 4 +-- .../ACON-iOS/Global/Utils/SheetUtils.swift | 9 ++++++ .../ACON-iOS/Presentation/Base/BaseView.swift | 25 +++++++++++++++ .../Login/View/LoginViewController.swift | 2 +- .../Upload/View/SpotSearchView.swift | 20 ++---------- .../View/SpotSearchViewController.swift | 2 +- 11 files changed, 139 insertions(+), 25 deletions(-) create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Colors/dim_b_60.colorset/Contents.json diff --git a/ACON-iOS/ACON-iOS/Global/Extensions/UIButton+.swift b/ACON-iOS/ACON-iOS/Global/Extensions/UIButton+.swift index 5c9b0d72..5b4bad84 100644 --- a/ACON-iOS/ACON-iOS/Global/Extensions/UIButton+.swift +++ b/ACON-iOS/ACON-iOS/Global/Extensions/UIButton+.swift @@ -30,4 +30,34 @@ extension UIButton { self.setAttributedTitle(attributedString, for: state) } + func setPartialTitle( + fullText: String, + textStyles: [(text: String, style: ACFontStyleType, color: UIColor)] + ) { + let attributedString = NSMutableAttributedString(string: fullText) + + textStyles.forEach { textStyle in + if let range = fullText.range(of: textStyle.text) { + let nsRange = NSRange(range, in: fullText) + let attributes: [NSAttributedString.Key: Any] = [ + .font: textStyle.style.font, + .kern: textStyle.style.kerning, + .paragraphStyle: { + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.minimumLineHeight = textStyle.style.lineHeight + paragraphStyle.maximumLineHeight = textStyle.style.lineHeight + return paragraphStyle + }(), + .foregroundColor: textStyle.color + ] + + attributes.forEach { key, value in + attributedString.addAttribute(key, value: value, range: nsRange) + } + } + } + + self.setAttributedTitle(attributedString, for: state) + } + } diff --git a/ACON-iOS/ACON-iOS/Global/Extensions/UIViewController+.swift b/ACON-iOS/ACON-iOS/Global/Extensions/UIViewController+.swift index 090f0fd1..8c2ddc96 100644 --- a/ACON-iOS/ACON-iOS/Global/Extensions/UIViewController+.swift +++ b/ACON-iOS/ACON-iOS/Global/Extensions/UIViewController+.swift @@ -49,6 +49,19 @@ extension UIViewController { } } + func setMiddleSheetLayout() { + self.modalPresentationStyle = .pageSheet + + if let sheet = self.sheetPresentationController { + let sheetUtils = SheetUtils() + sheet.detents = [sheetUtils.acMiddleDetent] + sheet.prefersScrollingExpandsWhenScrolledToEdge = false + sheet.prefersGrabberVisible = false + + sheet.selectedDetentIdentifier = sheetUtils.middleDetentIdentifier + } + } + func setLongSheetLayout() { self.modalPresentationStyle = .pageSheet diff --git a/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift b/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift index b7f9f270..d7a88c38 100644 --- a/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift +++ b/ACON-iOS/ACON-iOS/Global/Literals/StringLiterals.swift @@ -55,6 +55,8 @@ enum StringLiterals { static let shortDetent = "acShortDetent" + static let middleDetent = "acMiddleDetent" + static let longDetent = "acLongDetent" } @@ -101,4 +103,33 @@ enum StringLiterals { } + enum LocalVerification { + + static let needLocalVerification = "로컬 맛집 추천을 위해\n지역 인증이 필요해요." + + static let doLocalVerification = "자주 가는 동네로 지역 인증을 해보세요" + + static let new = "새로운" + + static let verifyLocal = " 나의 동네 인증하기" + + static let next = "다음" + + static let finishVerification = "인증완료" + + static let letsStart = "시작하기" + + static let locateOnMap = "지도에서 위치 확인하기" + + static let now = "이제 " + + static let localAcornTitle = "에\n로컬 도토리를 떨어트릴 수 있어요!" + + static let localAcornExplaination = "로컬 도토리는 로컬 맛집을 보증하는 도토리에요.\n만족스러운 식사 후 리뷰에 사용해보세요!" + + static let localAcorn = "로컬 도토리" + + static let plainAcorn = "일반 도토리" + } + } diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Colors/dim_b_60.colorset/Contents.json b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Colors/dim_b_60.colorset/Contents.json new file mode 100644 index 00000000..c1bebfb9 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/Colors/dim_b_60.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "0.600", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ACON-iOS/ACON-iOS/Global/Settings/Config/Config.swift b/ACON-iOS/ACON-iOS/Global/Settings/Config/Config.swift index 91547faa..53ca9275 100644 --- a/ACON-iOS/ACON-iOS/Global/Settings/Config/Config.swift +++ b/ACON-iOS/ACON-iOS/Global/Settings/Config/Config.swift @@ -19,7 +19,7 @@ enum Config { static let googleWebClientID = "GOOGLE_WEB_CLIENT_ID" - static let nmapAPIKey = "NMAP_API_KEY" + static let nMapClientKey = "NMFClientId" } @@ -58,9 +58,9 @@ extension Config { return key }() - static let nmapAPIKey: String = { - guard let key = Config.infoDictionary[Keys.Plist.nmapAPIKey] as? String else { - fatalError("nmapAPIKey is not set in plist for this configuration") + static let nMapClientKey: String = { + guard let key = Config.infoDictionary[Keys.Plist.nMapClientKey] as? String else { + fatalError("nMapClientKey is not set in plist for this configuration") } return key }() diff --git a/ACON-iOS/ACON-iOS/Global/Settings/Info.plist b/ACON-iOS/ACON-iOS/Global/Settings/Info.plist index 46c6b042..538e3704 100644 --- a/ACON-iOS/ACON-iOS/Global/Settings/Info.plist +++ b/ACON-iOS/ACON-iOS/Global/Settings/Info.plist @@ -23,8 +23,8 @@ ${GOOGLE_CLIENT_ID} GOOGLE_WEB_CLIENT_ID ${GOOGLE_WEB_CLIENT_ID} - NMAP_API_KEY - ${NMAP_API_KEY} + NMFClientId + ${NMAP_CLIENT_KEY} NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/ACON-iOS/ACON-iOS/Global/Utils/SheetUtils.swift b/ACON-iOS/ACON-iOS/Global/Utils/SheetUtils.swift index 947997ae..f581349c 100644 --- a/ACON-iOS/ACON-iOS/Global/Utils/SheetUtils.swift +++ b/ACON-iOS/ACON-iOS/Global/Utils/SheetUtils.swift @@ -10,6 +10,7 @@ import UIKit struct SheetUtils { let shortDetentIdentifier = UISheetPresentationController.Detent.Identifier(StringLiterals.SheetUtils.shortDetent) + let middleDetentIdentifier = UISheetPresentationController.Detent.Identifier(StringLiterals.SheetUtils.middleDetent) let longDetentIdentifier = UISheetPresentationController.Detent.Identifier(StringLiterals.SheetUtils.longDetent) var acShortDetent: UISheetPresentationController.Detent { @@ -20,6 +21,14 @@ struct SheetUtils { } } + var acMiddleDetent: UISheetPresentationController.Detent { + return UISheetPresentationController.Detent.custom(identifier: longDetentIdentifier) { _ in + let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene + let safeAreaBottom = windowScene?.windows.first?.safeAreaInsets.bottom ?? 0 + return ScreenUtils.height*558/780 - safeAreaBottom + } + } + var acLongDetent: UISheetPresentationController.Detent { return UISheetPresentationController.Detent.custom(identifier: longDetentIdentifier) { _ in let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene diff --git a/ACON-iOS/ACON-iOS/Presentation/Base/BaseView.swift b/ACON-iOS/ACON-iOS/Presentation/Base/BaseView.swift index c071ac53..6b414a69 100644 --- a/ACON-iOS/ACON-iOS/Presentation/Base/BaseView.swift +++ b/ACON-iOS/ACON-iOS/Presentation/Base/BaseView.swift @@ -12,6 +12,9 @@ import Then class BaseView: UIView { + private let handlerImageView: UIImageView = UIImageView() + + // MARK: - Initializer override init(frame: CGRect) { @@ -35,3 +38,25 @@ class BaseView: UIView { } } + +// MARK: setHandlerImage for Modal + +extension BaseView { + + func setHandlerImageView() { + self.addSubview(handlerImageView) + + handlerImageView.snp.makeConstraints { + $0.top.equalToSuperview().inset(ScreenUtils.height*4/780) + $0.centerX.equalToSuperview() + $0.width.equalTo(ScreenUtils.height*36/780) + $0.height.equalTo(ScreenUtils.height*3/780) + } + + handlerImageView.do { + $0.image = .btnBottomsheetBar + $0.contentMode = .scaleAspectFit + } + } + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/Login/View/LoginViewController.swift b/ACON-iOS/ACON-iOS/Presentation/Login/View/LoginViewController.swift index 0071105a..d5cf9041 100644 --- a/ACON-iOS/ACON-iOS/Presentation/Login/View/LoginViewController.swift +++ b/ACON-iOS/ACON-iOS/Presentation/Login/View/LoginViewController.swift @@ -90,7 +90,7 @@ extension LoginViewController { // TODO: - 나중에 서버 로그인 Success 시 이동하는 것으로 변경 (ObservablePattern) func navigateToLocalVerificationVC() { - let vc = ViewController() + let vc = LocalVerificationViewController() self.navigationController?.pushViewController(vc, animated: false) } diff --git a/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchView.swift b/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchView.swift index 41443995..f4df8941 100644 --- a/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchView.swift +++ b/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchView.swift @@ -14,8 +14,6 @@ final class SpotSearchView: BaseView { // MARK: - UI Properties - private let handlerImageView: UIImageView = UIImageView() - private let spotUploadLabel: UILabel = UILabel() let searchView: UIView = UIView() @@ -55,8 +53,7 @@ final class SpotSearchView: BaseView { override func setHierarchy() { super.setHierarchy() - self.addSubviews(handlerImageView, - spotUploadLabel, + self.addSubviews(spotUploadLabel, searchView, doneButton, recommendedSpotScrollView, @@ -71,14 +68,7 @@ final class SpotSearchView: BaseView { override func setLayout() { super.setLayout() - - handlerImageView.snp.makeConstraints { - $0.top.equalToSuperview().inset(ScreenUtils.height*4/780) - $0.centerX.equalToSuperview() - $0.width.equalTo(ScreenUtils.height*64/780) - $0.height.equalTo(ScreenUtils.height*3/780) - } - + spotUploadLabel.snp.makeConstraints { $0.top.equalToSuperview().inset(ScreenUtils.height*19/780) $0.centerX.equalToSuperview() @@ -162,11 +152,7 @@ final class SpotSearchView: BaseView { self.backgroundColor = .glaW10 self.backgroundColor?.withAlphaComponent(0.95) - - handlerImageView.do { - $0.image = .btnBottomsheetBar - $0.contentMode = .scaleAspectFit - } + self.setHandlerImageView() spotUploadLabel.do { $0.setLabel(text: StringLiterals.Upload.spotUpload2, diff --git a/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchViewController.swift b/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchViewController.swift index ff71e4fd..04ea644d 100644 --- a/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchViewController.swift +++ b/ACON-iOS/ACON-iOS/Presentation/Upload/View/SpotSearchViewController.swift @@ -41,7 +41,7 @@ class SpotSearchViewController: BaseViewController { var dismissCompletion: (() -> Void)? - override func viewWillDisappear(_ animated: Bool) { + override func viewDidDisappear(_ animated: Bool) { super.viewWillDisappear(animated) if isBeingDismissed { dismissCompletion?() From 9aa980c41a31e9b7ee57d58d443c0e864cf53e2d Mon Sep 17 00:00:00 2001 From: cirtuare Date: Thu, 16 Jan 2025 20:10:26 +0900 Subject: [PATCH 40/44] =?UTF-8?q?[Chore]=20LocalVerification=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EB=93=A4=20Restore=20(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj | 40 +++++ .../LocalVerification/Contents.json | 6 + .../icRadio.imageset/Contents.json | 12 ++ .../icRadio.imageset/ic_radio_20.svg | 4 + .../icRadioSelected.imageset/Contents.json | 12 ++ .../ic_radio_pressed_20.svg | 4 + .../localAcorn.imageset/Contents.json | 12 ++ .../localAcorn.imageset/ic_local acon.svg | 39 +++++ .../plainAcorn.imageset/Contents.json | 12 ++ .../plainAcorn.imageset/ic_normal acon.svg | 21 +++ .../LocalVerification/View/LocalMapView.swift | 72 +++++++++ .../View/LocalMapViewController.swift | 108 +++++++++++++ .../View/LocalVerificationFinishedView.swift | 144 ++++++++++++++++++ ...alVerificationFinishedViewController.swift | 95 ++++++++++++ .../View/LocalVerificationView.swift | 121 +++++++++++++++ .../LocalVerificationViewController.swift | 112 ++++++++++++++ 16 files changed, 814 insertions(+) create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/Contents.json create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/Contents.json create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/ic_radio_20.svg create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/Contents.json create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/ic_radio_pressed_20.svg create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/localAcorn.imageset/Contents.json create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/localAcorn.imageset/ic_local acon.svg create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/plainAcorn.imageset/Contents.json create mode 100644 ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/plainAcorn.imageset/ic_normal acon.svg create mode 100644 ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapView.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedView.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedViewController.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationView.swift create mode 100644 ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationViewController.swift diff --git a/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj b/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj index ec45973c..efc59053 100644 --- a/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj +++ b/ACON-iOS/ACON-iOS.xcodeproj/project.pbxproj @@ -82,6 +82,12 @@ 74B25C2E2D2FEFD3008BDCB7 /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 74B25C2D2D2FEFD3008BDCB7 /* Debug.xcconfig */; }; 74B25C302D2FEFE1008BDCB7 /* Release.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 74B25C2F2D2FEFE1008BDCB7 /* Release.xcconfig */; }; 74B25C322D2FF022008BDCB7 /* Shared.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 74B25C312D2FF022008BDCB7 /* Shared.xcconfig */; }; + 74BF92102D391FFE00B923E3 /* LocalVerificationFinishedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74BF920A2D391FFE00B923E3 /* LocalVerificationFinishedView.swift */; }; + 74BF92112D391FFE00B923E3 /* LocalVerificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74BF920D2D391FFE00B923E3 /* LocalVerificationViewController.swift */; }; + 74BF92122D391FFE00B923E3 /* LocalMapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74BF92092D391FFE00B923E3 /* LocalMapViewController.swift */; }; + 74BF92132D391FFE00B923E3 /* LocalVerificationFinishedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74BF920B2D391FFE00B923E3 /* LocalVerificationFinishedViewController.swift */; }; + 74BF92142D391FFE00B923E3 /* LocalMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74BF92082D391FFE00B923E3 /* LocalMapView.swift */; }; + 74BF92152D391FFE00B923E3 /* LocalVerificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74BF920C2D391FFE00B923E3 /* LocalVerificationView.swift */; }; 74CDCE542D310A1C00E3A21A /* ACFontStyleType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CDCE532D310A1100E3A21A /* ACFontStyleType.swift */; }; 74CDCE562D310B1600E3A21A /* String+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74CDCE552D310B1300E3A21A /* String+.swift */; }; /* End PBXBuildFile section */ @@ -158,6 +164,12 @@ 74B25C2D2D2FEFD3008BDCB7 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; 74B25C2F2D2FEFE1008BDCB7 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 74B25C312D2FF022008BDCB7 /* Shared.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = ""; }; + 74BF92082D391FFE00B923E3 /* LocalMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMapView.swift; sourceTree = ""; }; + 74BF92092D391FFE00B923E3 /* LocalMapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMapViewController.swift; sourceTree = ""; }; + 74BF920A2D391FFE00B923E3 /* LocalVerificationFinishedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalVerificationFinishedView.swift; sourceTree = ""; }; + 74BF920B2D391FFE00B923E3 /* LocalVerificationFinishedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalVerificationFinishedViewController.swift; sourceTree = ""; }; + 74BF920C2D391FFE00B923E3 /* LocalVerificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalVerificationView.swift; sourceTree = ""; }; + 74BF920D2D391FFE00B923E3 /* LocalVerificationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalVerificationViewController.swift; sourceTree = ""; }; 74CDCE532D310A1100E3A21A /* ACFontStyleType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACFontStyleType.swift; sourceTree = ""; }; 74CDCE552D310B1300E3A21A /* String+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+.swift"; sourceTree = ""; }; B28431BAB67B99CD7B85C705 /* Pods_ACON_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ACON_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -421,6 +433,7 @@ 748D6F702D2BCA46007690B4 /* Presentation */ = { isa = PBXGroup; children = ( + 74BF920F2D391FFE00B923E3 /* LocalVerification */, 74220DD12D34361E000684BF /* Upload */, 1547A87B2D358DBE00E96616 /* SpotListFilter */, 1558BADF2D31D41400ECDEF8 /* Profile */, @@ -600,6 +613,27 @@ name = Products; sourceTree = ""; }; + 74BF920E2D391FFE00B923E3 /* View */ = { + isa = PBXGroup; + children = ( + 74BF92082D391FFE00B923E3 /* LocalMapView.swift */, + 74BF92092D391FFE00B923E3 /* LocalMapViewController.swift */, + 74BF920A2D391FFE00B923E3 /* LocalVerificationFinishedView.swift */, + 74BF920B2D391FFE00B923E3 /* LocalVerificationFinishedViewController.swift */, + 74BF920C2D391FFE00B923E3 /* LocalVerificationView.swift */, + 74BF920D2D391FFE00B923E3 /* LocalVerificationViewController.swift */, + ); + path = View; + sourceTree = ""; + }; + 74BF920F2D391FFE00B923E3 /* LocalVerification */ = { + isa = PBXGroup; + children = ( + 74BF920E2D391FFE00B923E3 /* View */, + ); + path = LocalVerification; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -785,6 +819,12 @@ 74CDCE542D310A1C00E3A21A /* ACFontStyleType.swift in Sources */, 748D6F802D2BCD94007690B4 /* ObservablePattern.swift in Sources */, 1558BADE2D31AB6C00ECDEF8 /* SpotListViewController.swift in Sources */, + 74BF92102D391FFE00B923E3 /* LocalVerificationFinishedView.swift in Sources */, + 74BF92112D391FFE00B923E3 /* LocalVerificationViewController.swift in Sources */, + 74BF92122D391FFE00B923E3 /* LocalMapViewController.swift in Sources */, + 74BF92132D391FFE00B923E3 /* LocalVerificationFinishedViewController.swift in Sources */, + 74BF92142D391FFE00B923E3 /* LocalMapView.swift in Sources */, + 74BF92152D391FFE00B923E3 /* LocalVerificationView.swift in Sources */, 1547A8732D34157700E96616 /* SpotListViewModel.swift in Sources */, 748D6FA42D2C3C48007690B4 /* BaseNavViewController.swift in Sources */, 1558BADB2D31AAF900ECDEF8 /* ACTabBarItemType.swift in Sources */, diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/Contents.json b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/Contents.json b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/Contents.json new file mode 100644 index 00000000..e35b49fd --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_radio_20.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/ic_radio_20.svg b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/ic_radio_20.svg new file mode 100644 index 00000000..b51431c6 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadio.imageset/ic_radio_20.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/Contents.json b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/Contents.json new file mode 100644 index 00000000..3598d078 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_radio_pressed_20.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/ic_radio_pressed_20.svg b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/ic_radio_pressed_20.svg new file mode 100644 index 00000000..8c0ce618 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/icRadioSelected.imageset/ic_radio_pressed_20.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/localAcorn.imageset/Contents.json b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/localAcorn.imageset/Contents.json new file mode 100644 index 00000000..4ba1bb85 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/localAcorn.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_local acon.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/localAcorn.imageset/ic_local acon.svg b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/localAcorn.imageset/ic_local acon.svg new file mode 100644 index 00000000..900a2b8d --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/localAcorn.imageset/ic_local acon.svg @@ -0,0 +1,39 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/plainAcorn.imageset/Contents.json b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/plainAcorn.imageset/Contents.json new file mode 100644 index 00000000..1751ef8d --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/plainAcorn.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_normal acon.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/plainAcorn.imageset/ic_normal acon.svg b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/plainAcorn.imageset/ic_normal acon.svg new file mode 100644 index 00000000..99614451 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Global/Resources/Assets.xcassets/LocalVerification/plainAcorn.imageset/ic_normal acon.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapView.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapView.swift new file mode 100644 index 00000000..856509a6 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapView.swift @@ -0,0 +1,72 @@ +// +// LocalMapView.swift +// ACON-iOS +// +// Created by 이수민 on 1/15/25. +// + +import UIKit + +import NMapsMap +import SnapKit +import Then + +final class LocalMapView: BaseView { + + // MARK: - UI Properties + + let nMapView: NMFNaverMapView = NMFNaverMapView() + + var finishVerificationButton: UIButton = UIButton() + + // MARK: - Lifecycle + + override func setHierarchy() { + super.setHierarchy() + + self.addSubviews(nMapView, + finishVerificationButton) + } + + override func setLayout() { + super.setLayout() + + nMapView.snp.makeConstraints { + $0.top.horizontalEdges.equalToSuperview() + $0.height.equalTo(ScreenUtils.height*564/780) + } + + finishVerificationButton.snp.makeConstraints { + $0.bottom.equalToSuperview().inset(ScreenUtils.height*36/780) + $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) + $0.height.equalTo(52) + } + + } + + override func setStyle() { + super.setStyle() + + nMapView.do { + $0.showLocationButton = true + $0.showZoomControls = false + $0.showScaleBar = false + $0.showCompass = false + $0.mapView.positionMode = .normal + $0.mapView.zoomLevel = 17 + $0.mapView.minZoomLevel = 14 + $0.mapView.maxZoomLevel = 18 + } + + finishVerificationButton.do { + $0.setAttributedTitle(text: StringLiterals.LocalVerification.finishVerification, + style: .h8, + color: .acWhite, + for: .normal) + $0.backgroundColor = .gray5 + $0.roundedButton(cornerRadius: 6, maskedCorners: [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner]) + } + } + +} + diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift new file mode 100644 index 00000000..848aa64b --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalMapViewController.swift @@ -0,0 +1,108 @@ +// +// LocalMapViewController.swift +// ACON-iOS +// +// Created by 이수민 on 1/15/25. +// + +import UIKit + +import NMapsMap +import SnapKit +import Then + +class LocalMapViewController: BaseNavViewController { + + // MARK: - UI Properties + + private let localMapView = LocalMapView() + + private var viewBlurEffect: UIVisualEffectView = UIVisualEffectView() + + private let coordinate: CLLocationCoordinate2D + + // MARK: - LifeCycle + + override func viewDidLoad() { + super.viewDidLoad() + + self.setXButton() + addTarget() + } + + init(coordinate: CLLocationCoordinate2D) { + self.coordinate = coordinate + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(false) + + self.tabBarController?.tabBar.isHidden = true + moveCameraToLocation(latitude: coordinate.latitude, longitude: coordinate.longitude) + } + + override func setHierarchy() { + super.setHierarchy() + + self.contentView.addSubview(localMapView) + } + + override func setLayout() { + super.setLayout() + + localMapView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + } + + override func setStyle() { + super.setStyle() + + self.setXButton() + self.setSecondTitleLabelStyle(title: StringLiterals.LocalVerification.locateOnMap) + } + + func addTarget() { + localMapView.finishVerificationButton.addTarget(self, + action: #selector(finishVerificationButtonTapped), + for: .touchUpInside) + } + +} + + +// MARK: - @objc functions + +private extension LocalMapViewController { + + @objc + func finishVerificationButtonTapped() { + let vc = LocalVerificationFinishedViewController() + vc.dismissCompletion = { [weak self] in + self?.removeBlurView() + } + + vc.setMiddleSheetLayout() + self.addBlurView() + self.present(vc, animated: true) + } + +} + + +// MARK: - Map Functions + +extension LocalMapViewController { + + func moveCameraToLocation(latitude: Double, longitude: Double) { + let position = NMGLatLng(lat: latitude, lng: longitude) + let cameraUpdate = NMFCameraUpdate(scrollTo: position, zoomTo: 17) + localMapView.nMapView.mapView.moveCamera(cameraUpdate) + } + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedView.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedView.swift new file mode 100644 index 00000000..45443ea4 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedView.swift @@ -0,0 +1,144 @@ +// +// LocalVerificationFinishedView.swift +// ACON-iOS +// +// Created by 이수민 on 1/15/25. +// + +import UIKit + +import SnapKit +import Then + +final class LocalVerificationFinishedView: BaseView { + + // MARK: - UI Properties + + var titleLabel: UILabel = UILabel() + + private let explainationLabel: UILabel = UILabel() + + private let localAcornImageView: UIImageView = UIImageView() + + private let plainAcornImageView: UIImageView = UIImageView() + + private let localAcornLabel: UILabel = UILabel() + + private let plainAcornLabel: UILabel = UILabel() + + var startButton: UIButton = UIButton() + + + // MARK: - Lifecycle + + override func setHierarchy() { + super.setHierarchy() + + self.addSubviews(titleLabel, + explainationLabel, + localAcornImageView, + plainAcornImageView, + localAcornLabel, + plainAcornLabel, + startButton) + } + + override func setLayout() { + super.setLayout() + + titleLabel.snp.makeConstraints { + $0.top.equalToSuperview().inset(ScreenUtils.height*32/780) + $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) + $0.height.equalTo(56) + } + + explainationLabel.snp.makeConstraints { + $0.top.equalToSuperview().inset(ScreenUtils.height*96/780) + $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) + $0.height.equalTo(36) + } + + localAcornImageView.snp.makeConstraints { + $0.top.equalToSuperview().inset(ScreenUtils.height*258/780) + $0.leading.equalToSuperview().inset(ScreenUtils.width*73/360) + $0.width.height.equalTo(80) + } + + plainAcornImageView.snp.makeConstraints { + $0.top.equalToSuperview().inset(ScreenUtils.height*258/780) + $0.trailing.equalToSuperview().inset(ScreenUtils.width*73/360) + $0.width.height.equalTo(80) + } + + localAcornLabel.snp.makeConstraints { + $0.top.equalToSuperview().inset(ScreenUtils.height*346/780) + $0.leading.equalToSuperview().inset(ScreenUtils.width*66/360) + $0.width.equalTo(94) + } + + plainAcornLabel.snp.makeConstraints { + $0.top.equalToSuperview().inset(ScreenUtils.height*346/780) + $0.trailing.equalToSuperview().inset(ScreenUtils.width*66/360) + $0.width.equalTo(94) + } + + startButton.snp.makeConstraints { + $0.bottom.equalToSuperview().inset(ScreenUtils.height*36/780) + $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) + $0.height.equalTo(52) + } + + } + + override func setStyle() { + super.setStyle() + + self.setHandlerImageView() + self.backgroundColor = .dimB60 + self.backgroundColor?.withAlphaComponent(0.8) + + explainationLabel.do { + $0.setLabel(text: StringLiterals.LocalVerification.localAcornExplaination, + style: .b3, + color: .gray3) + } + + localAcornImageView.do { + $0.image = .localAcorn + $0.contentMode = .scaleAspectFit + } + + plainAcornImageView.do { + $0.image = .plainAcorn + $0.contentMode = .scaleAspectFit + } + + localAcornLabel.do { + $0.setLabel(text: StringLiterals.LocalVerification.localAcorn, + style: .s1, + color: .acWhite, + alignment: .center) + } + + plainAcornLabel.do { + $0.setLabel(text: StringLiterals.LocalVerification.plainAcorn, + style: .s1, + color: .acWhite, + alignment: .center) + } + + startButton.do { + $0.setAttributedTitle(text: StringLiterals.LocalVerification.letsStart, + style: .h8, + color: .gray6, + for: .disabled) + $0.setAttributedTitle(text: StringLiterals.LocalVerification.letsStart, + style: .h8, + color: .acWhite, + for: .normal) + $0.backgroundColor = .gray8 + $0.roundedButton(cornerRadius: 6, maskedCorners: [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner]) + } + } + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedViewController.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedViewController.swift new file mode 100644 index 00000000..1c9a021d --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationFinishedViewController.swift @@ -0,0 +1,95 @@ +// +// LocalVerificationFinishedViewController.swift +// ACON-iOS +// +// Created by 이수민 on 1/15/25. +// + +import UIKit + +import SnapKit +import Then + +class LocalVerificationFinishedViewController: BaseViewController { + + // MARK: - UI Properties + + private let localVerificationFinishedView = LocalVerificationFinishedView() + + let localName = "동교동" + + + // MARK: - LifeCycle + + override func viewDidLoad() { + super.viewDidLoad() + + addTarget() + } + + var dismissCompletion: (() -> Void)? + + override func viewDidDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + if isBeingDismissed { + dismissCompletion?() + } + } + + override func setHierarchy() { + super.setHierarchy() + + self.view.addSubview(localVerificationFinishedView) + } + + override func setLayout() { + super.setLayout() + + localVerificationFinishedView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + } + + override func setStyle() { + super.setStyle() + + localVerificationFinishedView.titleLabel.do { + $0.setLabel(text: StringLiterals.LocalVerification.now + localName + StringLiterals.LocalVerification.localAcornTitle, + style: .h6, + color: .acWhite) + } + } + + func addTarget() { + localVerificationFinishedView.startButton.addTarget(self, + action: #selector(startButtonTapped), + for: .touchUpInside) + } + +} + + +// MARK: - @objc functions + +private extension LocalVerificationFinishedViewController { + + @objc + func startButtonTapped() { + goToTabView() + } + +} + + +// MARK: - Close View + +private extension LocalVerificationFinishedViewController { + + @objc + func goToTabView() { + if let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate { + sceneDelegate.window?.rootViewController = ACTabBarController() + } + } + +} diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationView.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationView.swift new file mode 100644 index 00000000..785f8423 --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationView.swift @@ -0,0 +1,121 @@ +// +// LocalVerificationView.swift +// ACON-iOS +// +// Created by 이수민 on 1/14/25. +// + +import UIKit + +import SnapKit +import Then + +final class LocalVerificationView: BaseView { + + // MARK: - UI Properties + + private let weNeedYourAddressLabel: UILabel = UILabel() + + private let doLocalVerificationLabel: UILabel = UILabel() + + var verifyNewLocalButton: UIButton = UIButton() + + var nextButton: UIButton = UIButton() + + var verifyNewLocalButtonConfiguration: UIButton.Configuration = { + var configuration = UIButton.Configuration.plain() + configuration.imagePlacement = .leading + configuration.imagePadding = 8 + configuration.titleAlignment = .leading + configuration.preferredSymbolConfigurationForImage = UIImage.SymbolConfiguration(pointSize: 20) + configuration.contentInsets = NSDirectionalEdgeInsets(top: 16, + leading: 16, + bottom: 16, + trailing: 139) + return configuration + }() + + // MARK: - Lifecycle + + override func setHierarchy() { + super.setHierarchy() + + self.addSubviews(weNeedYourAddressLabel, + doLocalVerificationLabel, + verifyNewLocalButton, + nextButton) + } + + override func setLayout() { + super.setLayout() + + weNeedYourAddressLabel.snp.makeConstraints { + $0.top.equalToSuperview().inset(ScreenUtils.height*32/780) + $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) + $0.height.equalTo(56) + } + + doLocalVerificationLabel.snp.makeConstraints { + $0.top.equalToSuperview().inset(ScreenUtils.height*96/780) + $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) + $0.height.equalTo(18) + } + + verifyNewLocalButton.snp.makeConstraints { + $0.top.equalToSuperview().inset(ScreenUtils.height*146/780) + $0.centerX.equalToSuperview() + $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) + $0.height.equalTo(ScreenUtils.height*52/780) + } + + nextButton.snp.makeConstraints { + $0.bottom.equalToSuperview().inset(ScreenUtils.height*36/780) + $0.horizontalEdges.equalToSuperview().inset(ScreenUtils.width*20/360) + $0.height.equalTo(52) + } + + } + + override func setStyle() { + super.setStyle() + + weNeedYourAddressLabel.do { + $0.setLabel(text: StringLiterals.LocalVerification.needLocalVerification, + style: .h6, + color: .acWhite) + } + + doLocalVerificationLabel.do { + $0.setLabel(text: StringLiterals.LocalVerification.doLocalVerification, + style: .b3, + color: .gray3) + } + + verifyNewLocalButton.do { + $0.configuration = verifyNewLocalButtonConfiguration + $0.backgroundColor = .gray9 + $0.roundedButton(cornerRadius: 4, maskedCorners: [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner]) + $0.layer.borderWidth = 1 + $0.layer.borderColor = UIColor(resource: .gray8).cgColor + $0.setImage(.icRadio, for: .normal) + $0.setImage(.icRadioSelected, for: .selected) + $0.setPartialTitle(fullText: StringLiterals.LocalVerification.new + StringLiterals.LocalVerification.verifyLocal, + textStyles: [(StringLiterals.LocalVerification.new, .s2, .org1), (StringLiterals.LocalVerification.verifyLocal, .s2, .acWhite)]) + } + + nextButton.do { + $0.setAttributedTitle(text: StringLiterals.LocalVerification.next, + style: .h8, + color: .gray6, + for: .disabled) + $0.setAttributedTitle(text: StringLiterals.LocalVerification.next, + style: .h8, + color: .acWhite, + for: .normal) + $0.backgroundColor = .gray8 + $0.roundedButton(cornerRadius: 6, maskedCorners: [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner]) + } + } + +} + diff --git a/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationViewController.swift b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationViewController.swift new file mode 100644 index 00000000..e3c2614a --- /dev/null +++ b/ACON-iOS/ACON-iOS/Presentation/LocalVerification/View/LocalVerificationViewController.swift @@ -0,0 +1,112 @@ +// +// LocalVerificationViewController.swift +// ACON-iOS +// +// Created by 이수민 on 1/15/25. +// + +import UIKit +import CoreLocation + +import SnapKit +import Then + +class LocalVerificationViewController: BaseNavViewController { + + // MARK: - UI Properties + + private let localVerificationView = LocalVerificationView() + + private var userCoordinate: CLLocationCoordinate2D? + + // MARK: - LifeCycle + + override func viewDidLoad() { + super.viewDidLoad() + + self.setXButton() + addTarget() + ACLocationManager.shared.addDelegate(self) + } + + deinit { + ACLocationManager.shared.removeDelegate(self) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(false) + + self.tabBarController?.tabBar.isHidden = true + } + + override func setHierarchy() { + super.setHierarchy() + + self.contentView.addSubview(localVerificationView) + } + + override func setLayout() { + super.setLayout() + + localVerificationView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + } + + override func setStyle() { + super.setStyle() + + self.localVerificationView.nextButton.isEnabled = false + } + + func addTarget() { + localVerificationView.verifyNewLocalButton.addTarget(self, + action: #selector(verifyLocationButtonTapped), + for: .touchUpInside) + localVerificationView.nextButton.addTarget(self, + action: #selector(nextButtonTapped), + for: .touchUpInside) + } + +} + + +// MARK: - @objc functions + +private extension LocalVerificationViewController { + + @objc + func verifyLocationButtonTapped() { + localVerificationView.verifyNewLocalButton.isSelected.toggle() + let isSelected = localVerificationView.verifyNewLocalButton.isSelected + localVerificationView.verifyNewLocalButton.configuration?.baseBackgroundColor = isSelected ? .gray7 : .gray9 + localVerificationView.nextButton.isEnabled = isSelected + localVerificationView.nextButton.backgroundColor = isSelected ? .gray5 : .gray8 + } + + @objc + func nextButtonTapped() { + ACLocationManager.shared.checkUserDeviceLocationServiceAuthorization() + } + +} + +extension LocalVerificationViewController: ACLocationManagerDelegate { + + func locationManager(_ manager: ACLocationManager, didUpdateLocation coordinate: CLLocationCoordinate2D) { + print("성공 - 위도: \(coordinate.latitude), 경도: \(coordinate.longitude)") + self.userCoordinate = coordinate + pushToLocalMapVC() + } + +} + +extension LocalVerificationViewController { + + func pushToLocalMapVC() { + guard let coordinate = userCoordinate else { return } + let vc = LocalMapViewController(coordinate: coordinate) + navigationController?.pushViewController(vc, animated: false) + } + +} From 0538a64919cffe0ba7ff5e54a6fa39a3d9c0a06d Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 20:39:49 +0900 Subject: [PATCH 41/44] =?UTF-8?q?[Chore]=20CaseIterable=20=EC=B1=84?= =?UTF-8?q?=ED=83=9D=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ACON-iOS/Presentation/SpotListFilter/Type/SpotType.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/SpotType.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/SpotType.swift index 1d94b085..1914cfa7 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/SpotType.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Type/SpotType.swift @@ -23,7 +23,7 @@ enum SpotType { // MARK: - 장소 상세 조건 - enum RestaurantFeatureType { + enum RestaurantFeatureType: CaseIterable { case korean, western, chinese, japanese, snack, asian, bar, excludeFranchise @@ -42,7 +42,7 @@ enum SpotType { } - enum CafeFeatureType { + enum CafeFeatureType: CaseIterable { case large, goodView, dessert, terace, excludeFranchise From ff86308d2d370105e74456b1b2492df66fddcc49 Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 20:59:07 +0900 Subject: [PATCH 42/44] =?UTF-8?q?[Chore]=20Model=20=EC=82=AD=EC=A0=9C,=20T?= =?UTF-8?q?ype=EC=9C=BC=EB=A1=9C=20=EB=8C=80=EC=B2=B4=20(Supsequence=20?= =?UTF-8?q?=ED=99=9C=EC=9A=A9)=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Model/SpotListFilterModel.swift | 40 +------------------ .../View/SpotListFilterView.swift | 14 ++++--- 2 files changed, 9 insertions(+), 45 deletions(-) diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Model/SpotListFilterModel.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Model/SpotListFilterModel.swift index 7b9fd23a..2d1751e7 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Model/SpotListFilterModel.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Model/SpotListFilterModel.swift @@ -9,44 +9,6 @@ import Foundation struct SpotListFilterModel { - struct RestaurantFeature { - - static let firstLine: [SpotType.RestaurantFeatureType] = [ - .korean, .western, .chinese, .japanese, .snack - ] - - static let secondLine: [SpotType.RestaurantFeatureType] = [ - .asian, .bar, .excludeFranchise - ] - - } - - struct CafeFeature { - - static let firstLine: [SpotType.CafeFeatureType] = [ - .large, .goodView, .dessert, .terace - ] - - static let secondLine: [SpotType.CafeFeatureType] = [ - .excludeFranchise - ] - - } - - struct Companion { - - static let tags: [SpotType.CompanionType] = [ - .family, .date, .friend, .alone, .group - ] - - } - - struct VisitPurpose { - - static let tags: [SpotType.VisitPurposeType] = [ - .meeting, .study - ] - - } + // TODO: 서버 통신 관련 모델 구현 } diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift index 5834d31e..15cfa7c9 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/SpotListFilterView.swift @@ -166,7 +166,7 @@ private extension SpotListFilterView { text: StringLiterals.SpotListFilter.companionSection, style: .s2) - let tags: [String] = SpotListFilterModel.Companion.tags.map { return $0.text } + let tags: [String] = SpotType.CompanionType.allCases.map { return $0.text } companionTagStackView.addTagButtons(titles: tags) } @@ -183,7 +183,7 @@ private extension SpotListFilterView { text: StringLiterals.SpotListFilter.visitPurposeSection, style: .s2) - let tags: [String] = SpotListFilterModel.VisitPurpose.tags.map { return $0.text } + let tags: [String] = SpotType.VisitPurposeType.allCases.map { return $0.text } visitPurposeTagStackView.addTagButtons(titles: tags) } @@ -200,15 +200,17 @@ extension SpotListFilterView { switch spotType { case .restaurant: - let firstLine: [String] = SpotListFilterModel.RestaurantFeature.firstLine.map { return $0.text } - let secondLine: [String] = SpotListFilterModel.RestaurantFeature.secondLine.map { return $0.text } + let tagTexts: [String] = SpotType.RestaurantFeatureType.allCases.map { return $0.text } + let firstLine: [String] = Array(tagTexts[0..<5]) + let secondLine: [String] = Array(tagTexts[5..<7]) firstLineSpotTagStackView.switchTagButtons(titles: firstLine) secondLineSpotTagStackView.switchTagButtons(titles: secondLine) case .cafe: - let firstLine: [String] = SpotListFilterModel.CafeFeature.firstLine.map { return $0.text } - let secondLine: [String] = SpotListFilterModel.CafeFeature.secondLine.map { return $0.text } + let tagTexts: [String] = SpotType.CafeFeatureType.allCases.map { return $0.text } + let firstLine: [String] = Array(tagTexts[0..<4]) + let secondLine: [String] = [tagTexts[4]] firstLineSpotTagStackView.switchTagButtons(titles: firstLine) secondLineSpotTagStackView.switchTagButtons(titles: secondLine) From 38f976642240211bff834daccddaa590ef079832 Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Thu, 16 Jan 2025 22:03:16 +0900 Subject: [PATCH 43/44] =?UTF-8?q?[Chore]=20Todo=20=EC=88=98=EC=A0=95=20(#2?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/SpotListFilter/Model/SpotListFilterModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Model/SpotListFilterModel.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Model/SpotListFilterModel.swift index 2d1751e7..4f3435b3 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Model/SpotListFilterModel.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/Model/SpotListFilterModel.swift @@ -9,6 +9,6 @@ import Foundation struct SpotListFilterModel { - // TODO: 서버 통신 관련 모델 구현 + // TODO: 데이터 모델 구현 } From efaccb3f47d2054d6b0cefd0626b461f6734ae02 Mon Sep 17 00:00:00 2001 From: Yurim Kim Date: Fri, 17 Jan 2025 00:29:16 +0900 Subject: [PATCH 44/44] =?UTF-8?q?[Chore]=20truncate=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SpotListFilter/View/Component/FilterTagButton.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/FilterTagButton.swift b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/FilterTagButton.swift index e8de0917..5d159161 100644 --- a/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/FilterTagButton.swift +++ b/ACON-iOS/ACON-iOS/Presentation/SpotListFilter/View/Component/FilterTagButton.swift @@ -49,6 +49,7 @@ private extension FilterTagButton { config.background.strokeWidth = 1 config.cornerStyle = .capsule config.titleAlignment = .center + config.titleLineBreakMode = .byTruncatingTail config.contentInsets = NSDirectionalEdgeInsets(top: 7, leading: 16, bottom: 7,