diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 60e76aaf..1f23c44b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -13,6 +13,9 @@ ## πŸ“Έ μŠ€ν¬λ¦°μƒ· + | κ΅¬ν˜„ λ‚΄μš© | μŠ€ν¬λ¦°μƒ· | | :-------------: | :----------: | diff --git a/Runnect-iOS/.swiftlint.yml b/Runnect-iOS/.swiftlint.yml index bbe4a629..a1bc6612 100644 --- a/Runnect-iOS/.swiftlint.yml +++ b/Runnect-iOS/.swiftlint.yml @@ -14,5 +14,6 @@ excluded: # AppDelegate, SceneDelegate file λ¬΄μ‹œ - Runnect-iOS/Global/Supports/AppDelegate.swift - Runnect-iOS/Global/Supports/SceneDelegate.swift + - Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift force_cast: warning diff --git a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj index f513f5f4..09b9b28d 100644 --- a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj +++ b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj @@ -1703,7 +1703,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2024.0312.0041; + CURRENT_PROJECT_VERSION = 2024.0319.2120; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8Q4H7X3Q58; GENERATE_INFOPLIST_FILE = NO; @@ -1747,7 +1747,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2024.0312.0041; + CURRENT_PROJECT_VERSION = 2024.0319.2120; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8Q4H7X3Q58; GENERATE_INFOPLIST_FILE = NO; diff --git a/Runnect-iOS/Runnect-iOS/Global/Literal/ImageLiterals.swift b/Runnect-iOS/Runnect-iOS/Global/Literal/ImageLiterals.swift index 9c926c77..6cf8c1ff 100644 --- a/Runnect-iOS/Runnect-iOS/Global/Literal/ImageLiterals.swift +++ b/Runnect-iOS/Runnect-iOS/Global/Literal/ImageLiterals.swift @@ -40,7 +40,8 @@ enum ImageLiterals { static var icTime: UIImage { .load(named: "ic_time") } static var icLocationPoint: UIImage { .load(named: "ic_location_point") } static var icAlert: UIImage { .load(named: "ic_alert") } - static var icLocationOverlay: UIImage { .load(named: "ic_location_overlay") } + static var icLocationOverlayDirection: UIImage { .load(named: "ic_location_overlay_direction") } + static var icLocationOverlayNormal: UIImage { .load(named: "ic_location_overlay_normal") } static var icLogoCircle: UIImage { .load(named: "ic_logo_circle") } static var icMore: UIImage { .load(named: "ic_more") } static var icPlus: UIImage { .load(named: "ic_plus") } diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay.imageset/Contents.json b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay_direction.imageset/Contents.json similarity index 100% rename from Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay.imageset/Contents.json rename to Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay_direction.imageset/Contents.json diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay.imageset/point 1.png b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay_direction.imageset/point 1.png similarity index 100% rename from Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay.imageset/point 1.png rename to Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay_direction.imageset/point 1.png diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay.imageset/point 1@2x.png b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay_direction.imageset/point 1@2x.png similarity index 100% rename from Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay.imageset/point 1@2x.png rename to Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay_direction.imageset/point 1@2x.png diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay.imageset/point 1@3x.png b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay_direction.imageset/point 1@3x.png similarity index 100% rename from Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay.imageset/point 1@3x.png rename to Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay_direction.imageset/point 1@3x.png diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay_normal.imageset/Contents.json b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay_normal.imageset/Contents.json new file mode 100644 index 00000000..c7f7c9a6 --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay_normal.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Group 9581.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group 9581@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group 9581@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay_normal.imageset/Group 9581.png b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay_normal.imageset/Group 9581.png new file mode 100644 index 00000000..a33a4259 Binary files /dev/null and b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay_normal.imageset/Group 9581.png differ diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay_normal.imageset/Group 9581@2x.png b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay_normal.imageset/Group 9581@2x.png new file mode 100644 index 00000000..ce0a010e Binary files /dev/null and b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay_normal.imageset/Group 9581@2x.png differ diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay_normal.imageset/Group 9581@3x.png b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay_normal.imageset/Group 9581@3x.png new file mode 100644 index 00000000..3dacf1da Binary files /dev/null and b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_location_overlay_normal.imageset/Group 9581@3x.png differ diff --git a/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift b/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift index 5c836b75..6e2cb552 100644 --- a/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift +++ b/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift @@ -25,7 +25,7 @@ final class RNMapView: UIView { private let screenHeight = UIScreen.main.bounds.height let pathImage = PassthroughSubject() - private var cancelBag = Set() + private var cancelBag = CancelBag() private let locationManager = CLLocationManager() private var isDrawMode: Bool = false @@ -40,7 +40,8 @@ final class RNMapView: UIView { [self.startMarker.position] + self.markers.map { $0.position } } private var bottomPadding: CGFloat = 0 - private let locationOverlayIcon = NMFOverlayImage(image: ImageLiterals.icLocationOverlay) + private let locationOverlayIconDirection = NMFOverlayImage(image: ImageLiterals.icLocationOverlayDirection) + private let locationOverlayIconNormal = NMFOverlayImage(image: ImageLiterals.icLocationOverlayNormal) // MARK: - UI Components @@ -59,16 +60,17 @@ final class RNMapView: UIView { override init(frame: CGRect) { super.init(frame: frame) self.mapInit() - setLocationOverlay() } private func mapInit() { setUI() setLayout() + setBind() setDelegate() setMap() getLocationAuth() setPathOverlay() + observePositionModeChanges() } required init?(coder: NSCoder) { @@ -90,7 +92,12 @@ extension RNMapView { @discardableResult func setPositionMode(mode: NMFMyPositionMode) -> Self { map.mapView.positionMode = mode - setLocationOverlay() + switch mode { + case .direction: + setDirectionModeLocationOverlay() + default: + setNormalModeLocationOverLay() + } return self } @@ -154,26 +161,6 @@ extension RNMapView { return self } - /// 캑처λ₯Ό μœ„ν•œ μ’Œν‘œ μ„€μ • 및 카메라 이동 - func makeCameraMoveForCapture(at locations: [NMGLatLng]) { - map.mapView.contentInset = UIEdgeInsets(top: screenHeight/4, left: 0, bottom: screenHeight/4, right: 0) - let bounds = makeMBR(at: locations) - let cameraUpdate = NMFCameraUpdate(fit: bounds, padding: 100) - cameraUpdate.animation = .none - LoadingIndicator.showLoading() - map.mapView.moveCamera(cameraUpdate) { isCancelled in - if isCancelled { - print("카메라 이동 μ·¨μ†Œ") - LoadingIndicator.hideLoading() - } else { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self.makePathImage() - LoadingIndicator.hideLoading() - } - } - } - } - /// μ‚¬μš©μž μœ„μΉ˜λ‘œ 카메라 이동 @discardableResult func moveToUserLocation() -> Self { @@ -285,6 +272,26 @@ extension RNMapView { } } + /// 캑처λ₯Ό μœ„ν•œ μ’Œν‘œ μ„€μ • 및 카메라 이동 + private func makeCameraMoveForCapture(at locations: [NMGLatLng]) { + map.mapView.contentInset = UIEdgeInsets(top: screenHeight/4, left: 0, bottom: screenHeight/4, right: 0) + let bounds = makeMBR(at: locations) + let cameraUpdate = NMFCameraUpdate(fit: bounds, padding: 100) + cameraUpdate.animation = .none + LoadingIndicator.showLoading() + map.mapView.moveCamera(cameraUpdate) { isCancelled in + if isCancelled { + print("카메라 이동 μ·¨μ†Œ") + LoadingIndicator.hideLoading() + } else { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.makePathImage() + LoadingIndicator.hideLoading() + } + } + } + } + // 두 지점 μ‚¬μ΄μ˜ 거리(m) μΆ”κ°€ private func addDistance(with newLocation: NMGLatLng) { let lastCLLoc = markersLatLngs.last?.toCLLocation() @@ -331,29 +338,62 @@ extension RNMapView { map.mapView.touchDelegate = self } - private func setPathOverlay() { + private func setPathOverlay() { // μ½”μŠ€ path UI μ„€μ • pathOverlay.width = 4 pathOverlay.outlineWidth = 0 pathOverlay.color = .m1 } - private func setLocationOverlay() { + private func setDirectionModeLocationOverlay() { + let locationOverlay = map.mapView.locationOverlay + locationOverlay.icon = locationOverlayIconDirection + } + + private func setNormalModeLocationOverLay() { let locationOverlay = map.mapView.locationOverlay - locationOverlay.icon = locationOverlayIcon + locationOverlay.icon = locationOverlayIconNormal + } + + private func observePositionModeChanges() { + map.mapView.addObserver(self, forKeyPath: "positionMode", options: [.new, .old], context: nil) + } + + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { + // positionModeκ°€ 변경될 λ•Œ ν˜ΈμΆœλ©λ‹ˆλ‹€. + if keyPath == "positionMode", object is NMFMapView { + DispatchQueue.main.async { [weak self] in + self?.updateStateName() + } + } + } + + private func updateStateName() { + let stateStr: String + switch map.mapView.positionMode { + case .normal: + stateStr = "NoFollow" + setNormalModeLocationOverLay() + case .direction: + stateStr = "Follow" + setDirectionModeLocationOverlay() + default: + stateStr = "otherAction" + } + + print("Position Mode: \(stateStr)") } } // MARK: - UI & Layout extension RNMapView { - private func setUI() { + private func setUI() { // ν˜„μž¬ μœ„μΉ˜λ₯Ό μ°λŠ” μ•„μ΄μ½˜μ˜ UI κ΅¬ν˜„ self.backgroundColor = .white self.locationButton.setImage(ImageLiterals.icMapLocation, for: .normal) self.locationButton.isHidden = true - self.locationButton.addTarget(self, action: #selector(locationButtonDidTap), for: .touchUpInside) } - private func setLayout() { + private func setLayout() { // 지도와 ν˜„μž¬μœ„μΉ˜ 이동 λ²„νŠΌ 의 λ ˆμ΄μ•„μ›ƒ ꡬ성 addSubviews(map, locationButton) map.snp.makeConstraints { @@ -373,13 +413,11 @@ extension RNMapView { } } } -} - -// MARK: - @objc Function - -extension RNMapView { - @objc func locationButtonDidTap() { - self.setPositionMode(mode: .direction) + + private func setBind() { + locationButton.tapPublisher.sink { [weak self] _ in + self?.setPositionMode(mode: .direction) + }.store(in: cancelBag) } } @@ -388,11 +426,9 @@ extension RNMapView { extension RNMapView: NMFMapViewCameraDelegate, NMFMapViewTouchDelegate { // 지도 νƒ­ 이벀트 func mapView(_ mapView: NMFMapView, didTapMap latlng: NMGLatLng, point: CGPoint) { - guard isDrawMode && markers.count < 19 else { return } + guard isDrawMode && markers.count < 25 else { return } self.makeMarker(at: latlng) - } - - func mapView(_ mapView: NMFMapView, cameraWillChangeByReason reason: Int, animated: Bool) { + } // 지도 이동 λ©ˆμ·„μ„ λ•Œ ν˜ΈμΆœλ˜λŠ” λ©”μ„œλ“œ diff --git a/Runnect-iOS/Runnect-iOS/Info.plist b/Runnect-iOS/Runnect-iOS/Info.plist index 104548fc..d5e25111 100644 --- a/Runnect-iOS/Runnect-iOS/Info.plist +++ b/Runnect-iOS/Runnect-iOS/Info.plist @@ -2,6 +2,8 @@ + ITSAppUsesNonExemptEncryption + CFBundleIconName AppIcon CFBundleDevelopmentRegion @@ -19,9 +21,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 2.0.1 - ITSAppUsesNonExemptEncryption - + 2.0.2 CFBundleURLTypes @@ -46,7 +46,7 @@ CFBundleVersion - 2024.0312.0041 + 2024.0319.2120 LSApplicationQueriesSchemes kakaokompassauth diff --git a/Runnect-iOS/Runnect-iOS/Network/Dto/CourseDrawingDto/ResponseDto/TmapAddressSearchingResponseDto.swift b/Runnect-iOS/Runnect-iOS/Network/Dto/CourseDrawingDto/ResponseDto/TmapAddressSearchingResponseDto.swift index 185464fb..00a23648 100644 --- a/Runnect-iOS/Runnect-iOS/Network/Dto/CourseDrawingDto/ResponseDto/TmapAddressSearchingResponseDto.swift +++ b/Runnect-iOS/Runnect-iOS/Network/Dto/CourseDrawingDto/ResponseDto/TmapAddressSearchingResponseDto.swift @@ -8,23 +8,30 @@ import Foundation // MARK: - TmapAddressSearchingResponseDto + struct TmapAddressSearchingResponseDto: Codable { let addressInfo: AddressInfo func toDepartureLocationModel(latitude: Double, longitude: Double) -> DepartureLocationModel { - let buildingName = self.addressInfo.buildingName.isEmpty ? "λ‚΄κ°€ μ„€μ •ν•œ μΆœλ°œμ§€" : self.addressInfo.buildingName + let buildingName = self.addressInfo.buildingName.isEmpty ? "μ£Όμ†Œλ₯Ό μ•Œ 수 μ—†λŠ” μΆœλ°œμ§€" : self.addressInfo.buildingName - return DepartureLocationModel(departureName: buildingName, departureAddress: addressInfo.fullAddress, latitude: String(latitude), longitude: String(longitude)) + return DepartureLocationModel( + departureName: buildingName, + departureAddress: addressInfo.fullAddress, + latitude: String(latitude), + longitude: String(longitude) + ) } } // MARK: - AddressInfo + struct AddressInfo: Codable { let fullAddress, addressType, cityDo, guGun: String let eupMyun, adminDong, adminDongCode, legalDong: String let legalDongCode, ri, bunji, roadName: String let buildingIndex, buildingName, mappingDistance, roadCode: String - + enum CodingKeys: String, CodingKey { case fullAddress, addressType case cityDo = "city_do" diff --git a/Runnect-iOS/Runnect-iOS/Network/Router/CourseDrawingRouter/DepartureSearchingRouter.swift b/Runnect-iOS/Runnect-iOS/Network/Router/CourseDrawingRouter/DepartureSearchingRouter.swift index f4851389..c7ef4517 100644 --- a/Runnect-iOS/Runnect-iOS/Network/Router/CourseDrawingRouter/DepartureSearchingRouter.swift +++ b/Runnect-iOS/Runnect-iOS/Network/Router/CourseDrawingRouter/DepartureSearchingRouter.swift @@ -69,7 +69,7 @@ extension DepartureSearchingRouter: TargetType { switch self { case .getAddress(let keyword): return .requestParameters(parameters: ["query": keyword], encoding: URLEncoding.default) - case .getLocationAddress(let latitude, let longitude): + case .getLocationAddress(let latitude, let longitude): // μ‚¬μš©ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. return .requestParameters(parameters: ["x": longitude, "y": latitude], encoding: URLEncoding.default) case .getLocationTmapAddress(let latitude, let longitude): return .requestParameters(parameters: ["lat": latitude, "lon": longitude, "addressType": "A04", "appKey": Config.tmapAPIKey], encoding: URLEncoding.default) diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CourseDrawingVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CourseDrawingVC.swift index 8b3e93ba..c4b3c037 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CourseDrawingVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CourseDrawingVC.swift @@ -286,9 +286,17 @@ extension CourseDrawingVC { private func setLayout() { setHiddenViewsLayout() - self.view.addSubviews(naviBarContainerStackView, mapView, departureInfoContainerView) + self.view.addSubviews( + naviBarContainerStackView, + mapView, + departureInfoContainerView + ) self.view.addSubview(startMarkStackView) - self.departureInfoContainerView.addSubviews(departureLocationLabel, departureDetailLocationLabel, decideDepartureButton) + self.departureInfoContainerView.addSubviews( + departureLocationLabel, + departureDetailLocationLabel, + decideDepartureButton + ) view.bringSubviewToFront(naviBarContainerStackView) setNotchCoverViewLayout() @@ -300,7 +308,13 @@ extension CourseDrawingVC { } private func setHiddenViewsLayout() { - view.addSubviews(naviBarForEditing, guideView, distanceContainerView, completeButton, undoButton) + view.addSubviews( + naviBarForEditing, + guideView, + distanceContainerView, + completeButton, + undoButton + ) view.sendSubviewToBack(naviBarForEditing) naviBarForEditing.snp.makeConstraints { @@ -483,8 +497,17 @@ extension CourseDrawingVC { } private func searchLocationTmapAddress(latitude: Double, longitude: Double) { - departureSearchingProvider.request(target: .getLocationTmapAddress(latitude: latitude, longitude: longitude), instance: TmapAddressSearchingResponseDto.self, vc: self) { data in - self.updateData(model: data.toDepartureLocationModel(latitude: latitude, longitude: longitude)) + departureSearchingProvider.request( + target: .getLocationTmapAddress(latitude: latitude, longitude: longitude), + instance: TmapAddressSearchingResponseDto.self, + vc: self + ) { data in + self.updateData( + model: data.toDepartureLocationModel( + latitude: latitude, + longitude: longitude + ) + ) } } } diff --git a/Runnect-iOS/Runnect-iOS/Presentation/Running/VC/RunningWaitingVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/Running/VC/RunningWaitingVC.swift index 9c8de928..5e63c1e4 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/Running/VC/RunningWaitingVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/Running/VC/RunningWaitingVC.swift @@ -120,8 +120,8 @@ extension RunningWaitingVC { self.shareButton.addTarget(self, action: #selector(shareButtonDidTap), for: .touchUpInside) } + // λ‚΄ μ½”μŠ€μΈμ§€ μ•„λ‹Œμ§€ κ΅¬λ³„ν•˜λŠ” ν•¨μˆ˜μž…λ‹ˆλ‹€. private func isMyCourse(courseOwner: Bool) { - print("πŸ’ͺπŸ’ͺπŸ’ͺπŸ’ͺπŸ’ͺπŸ’ͺπŸ’ͺπŸ’ͺπŸ’ͺπŸ’ͺπŸ’ͺπŸ’ͺπŸ’ͺπŸ’ͺπŸ’ͺπŸ’ͺπŸ’ͺπŸ’ͺ") self.shareButton.isHidden = !courseOwner self.moreButton.isHidden = !courseOwner }