From 5e4d500c5bc7f9237e2a8a28fb94a870eeb8faae Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Thu, 15 Aug 2024 00:53:24 +0900 Subject: [PATCH 01/11] =?UTF-8?q?#79=20[Add]=20Naver=20clientID=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Score/Score/Source/AppDelegate.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Score/Score/Source/AppDelegate.swift b/Score/Score/Source/AppDelegate.swift index d421c79..4095ad6 100644 --- a/Score/Score/Source/AppDelegate.swift +++ b/Score/Score/Source/AppDelegate.swift @@ -9,6 +9,7 @@ import Foundation import GoogleSignIn import KakaoSDKAuth import KakaoSDKCommon +import NMapsMap //MARK: - AppDelegate @@ -21,16 +22,19 @@ class AppDelegate: NSObject, _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil ) -> Bool { - /// KakaoSDK 초기화 + // KakaoSDK 초기화 KakaoSDK.initSDK( appKey: self.apiKeys.Kakao.appID.getValueFromBundle() ?? "none" ) - /// Google Client ID 초기화 + // Google Client ID 초기화 GIDSignIn.sharedInstance.configuration = .init( clientID: self.apiKeys.Google.clientID.getValueFromBundle() ?? "none" ) + // Naver Client ID 지정 + NMFAuthManager.shared().clientId = self.apiKeys.Naver.clientID.getValueFromBundle() ?? "none" + return false } From 71735bfc781933537fb03e8c0a858bce3bfa83ce Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Thu, 15 Aug 2024 00:54:12 +0900 Subject: [PATCH 02/11] =?UTF-8?q?#79=20[Feat]=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EB=90=9C=20=EC=9C=84=EC=B9=98=20=EC=A0=95=EB=B3=B4=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Record/TodayWorkOut/LocationManager.swift | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 Score/Score/Source/View/Record/TodayWorkOut/LocationManager.swift diff --git a/Score/Score/Source/View/Record/TodayWorkOut/LocationManager.swift b/Score/Score/Source/View/Record/TodayWorkOut/LocationManager.swift new file mode 100644 index 0000000..d030474 --- /dev/null +++ b/Score/Score/Source/View/Record/TodayWorkOut/LocationManager.swift @@ -0,0 +1,49 @@ +// +// LocationManager.swift +// Score +// +// Created by sole on 8/14/24. +// + +import CoreLocation +import os.log + +final class LocationManager: CLLocationManager { + private(set) var locations: [CLLocation] = [] + + override init() { + super.init() + self.delegate = self + } + + /// 위치 접근 권한이 없으면 위치 접근 권한을 요청하는 메서드입니다. + func requestAuthorization() { + switch self.authorizationStatus { + case .notDetermined: + self.requestWhenInUseAuthorization() + logger.debug("\(#function) 위치 접근 권한이 설정되지 않았습니다.") + case .restricted: + logger.debug("\(#function) 위치 접근 권한이 제한적으로 설정되었습니다.") + case .denied: + // go to setting + logger.debug("\(#function) 위치 접근 권한이 거부되었습니다.") + case .authorizedAlways: + logger.debug("\(#function) 위치 접근 권한이 항상 허용되었습니다.") + case .authorizedWhenInUse: + logger.debug("\(#function) 위치 접근 권한이 허용되었습니다.") + @unknown default: + logger.debug("\(#function) 알 수 없는 위치 권한입니다.") + } + } +} + +extension LocationManager: CLLocationManagerDelegate { + func locationManager( + _ manager: CLLocationManager, + didUpdateLocations locations: [CLLocation] + ) { + self.locations += locations + } +} + +fileprivate let logger = Logger(subsystem: "sole.Score", category: "LocationManager") From 32b1531f4c67026720056450449194b96221493f Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Thu, 15 Aug 2024 00:55:20 +0900 Subject: [PATCH 03/11] =?UTF-8?q?#79=20[Style]=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=20=ED=98=84=EC=9E=AC=20=EC=9C=84=EC=B9=98=20=EA=B8=B0?= =?UTF-8?q?=EC=A4=80=20MapViewController=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TodayWorkOut/MapViewController.swift | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 Score/Score/Source/View/Record/TodayWorkOut/MapViewController.swift diff --git a/Score/Score/Source/View/Record/TodayWorkOut/MapViewController.swift b/Score/Score/Source/View/Record/TodayWorkOut/MapViewController.swift new file mode 100644 index 0000000..64a5a92 --- /dev/null +++ b/Score/Score/Source/View/Record/TodayWorkOut/MapViewController.swift @@ -0,0 +1,62 @@ +// +// MapViewController.swift +// Score +// +// Created by sole on 8/14/24. +// + +import NMapsMap +import UIKit + +final class MapViewController: UIViewController { + private var naverMapView: NMFNaverMapView! + private var locationManager: LocationManager! + + override func viewDidLoad() { + super.viewDidLoad() + self.setUpLocationManager() + self.naverMapView = .init(frame: view.frame) + self.setUpMapView() + self.setUpCamera() + view.addSubview(naverMapView) + } + + /// locationManager 관련 설정을 세팅합니다. + func setUpLocationManager() { + self.locationManager = .init() + self.locationManager.requestAuthorization() + self.locationManager.startUpdatingLocation() + } + + /// 지도 interaction 관련 설정을 세팅합니다. + func setUpMapView() { + self.naverMapView.showCompass = false + self.naverMapView.showZoomControls = false + self.naverMapView.mapView.isScrollGestureEnabled = false + self.naverMapView.mapView.isRotateGestureEnabled = false + self.naverMapView.mapView.minZoomLevel = 5 + self.naverMapView.mapView.maxZoomLevel = 18 + self.naverMapView.mapView.positionMode = .direction + } + + /// camera 위치를 현재 유저의 위치로 설정합니다. + func setUpCamera() { + guard let currentLocation = locationManager.location + else { return } + + let position: NMFCameraPosition = .init( + .init( + lat: currentLocation.coordinate.latitude, + lng: currentLocation.coordinate.longitude + ), + zoom: 18 + ) + let cameraUpdate: NMFCameraUpdate = .init(position: position) + self.naverMapView.mapView.moveCamera(cameraUpdate) + } + + func requestLocations() -> [CLLocationCoordinate2D] { + self.locationManager.locations.map{ $0.coordinate } + } +} + From 105bc374c7d485c749e89d49fdb7553a635200ed Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Thu, 15 Aug 2024 00:55:42 +0900 Subject: [PATCH 04/11] =?UTF-8?q?#79=20[Style]=20MapView=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Record/TodayWorkOut/MapView.swift | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 Score/Score/Source/View/Record/TodayWorkOut/MapView.swift diff --git a/Score/Score/Source/View/Record/TodayWorkOut/MapView.swift b/Score/Score/Source/View/Record/TodayWorkOut/MapView.swift new file mode 100644 index 0000000..ecb8a49 --- /dev/null +++ b/Score/Score/Source/View/Record/TodayWorkOut/MapView.swift @@ -0,0 +1,28 @@ +// +// MapView.swift +// Score +// +// Created by sole on 8/14/24. +// + +import SwiftUI +import NMapsMap + +struct MapView: UIViewControllerRepresentable { + func makeUIViewController( + context: Context + ) -> some UIViewController { + return MapViewController() + } + + func updateUIViewController( + _ uiViewController: UIViewControllerType, + context: Context + ) { + + } +} + +#Preview { + MapView() +} From 6ddeef1fbfd68be602b1476f2decd0f35bea23c0 Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Thu, 15 Aug 2024 00:56:14 +0900 Subject: [PATCH 05/11] =?UTF-8?q?#79=20[Chore]=20.pbxproj=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Score/Score.xcodeproj/project.pbxproj | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Score/Score.xcodeproj/project.pbxproj b/Score/Score.xcodeproj/project.pbxproj index e31f1f5..5957e6e 100644 --- a/Score/Score.xcodeproj/project.pbxproj +++ b/Score/Score.xcodeproj/project.pbxproj @@ -77,6 +77,7 @@ FD8B63A12BD2016D000500BF /* ServicePolicyFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8B63A02BD2016D000500BF /* ServicePolicyFeature.swift */; }; FD8B63A32BD201CC000500BF /* UnregisterFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8B63A22BD201CC000500BF /* UnregisterFeature.swift */; }; FD8B63A62BD21802000500BF /* ContactFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8B63A52BD21802000500BF /* ContactFeature.swift */; }; + FD8D001C2C6BB391001C126B /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8D001B2C6BB391001C126B /* MapView.swift */; }; FD8D1C012C09C91700B87C43 /* GoogleAuthStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8D1C002C09C91700B87C43 /* GoogleAuthStore.swift */; }; FD8D1C042C09C92600B87C43 /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = FD8D1C032C09C92600B87C43 /* GoogleSignIn */; }; FD8D1C062C09C92600B87C43 /* GoogleSignInSwift in Frameworks */ = {isa = PBXBuildFile; productRef = FD8D1C052C09C92600B87C43 /* GoogleSignInSwift */; }; @@ -100,6 +101,8 @@ FD9E070D2BCB4E66000109FD /* SCNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9E070C2BCB4E66000109FD /* SCNavigationBar.swift */; }; FD9E070F2BCB58BC000109FD /* SCLineTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9E070E2BCB58BC000109FD /* SCLineTabBar.swift */; }; FD9E07112BCC99DE000109FD /* DismissButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9E07102BCC99DE000109FD /* DismissButton.swift */; }; + FDA1AF212C6CD4FA008A1312 /* MapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1AF202C6CD4F6008A1312 /* MapViewController.swift */; }; + FDA1AF252C6CD6AB008A1312 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1AF242C6CD6AB008A1312 /* LocationManager.swift */; }; FDC09D352C18327B00D5CF5C /* TermsAgreementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC09D342C18327B00D5CF5C /* TermsAgreementView.swift */; }; FDC09D372C18522E00D5CF5C /* TermsAgreementFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC09D362C18522E00D5CF5C /* TermsAgreementFeature.swift */; }; FDC09D392C18644000D5CF5C /* SelectProfileImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC09D382C18644000D5CF5C /* SelectProfileImageView.swift */; }; @@ -223,6 +226,7 @@ FD8B63A02BD2016D000500BF /* ServicePolicyFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicePolicyFeature.swift; sourceTree = ""; }; FD8B63A22BD201CC000500BF /* UnregisterFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnregisterFeature.swift; sourceTree = ""; }; FD8B63A52BD21802000500BF /* ContactFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactFeature.swift; sourceTree = ""; }; + FD8D001B2C6BB391001C126B /* MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = ""; }; FD8D1C002C09C91700B87C43 /* GoogleAuthStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleAuthStore.swift; sourceTree = ""; }; FD8D1C092C09C9A300B87C43 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; FD8FC3AF2BC8251700B80B3D /* CalendarFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarFeature.swift; sourceTree = ""; }; @@ -244,6 +248,8 @@ FD9E070C2BCB4E66000109FD /* SCNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SCNavigationBar.swift; sourceTree = ""; }; FD9E070E2BCB58BC000109FD /* SCLineTabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SCLineTabBar.swift; sourceTree = ""; }; FD9E07102BCC99DE000109FD /* DismissButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissButton.swift; sourceTree = ""; }; + FDA1AF202C6CD4F6008A1312 /* MapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewController.swift; sourceTree = ""; }; + FDA1AF242C6CD6AB008A1312 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = ""; }; FDC09D342C18327B00D5CF5C /* TermsAgreementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsAgreementView.swift; sourceTree = ""; }; FDC09D362C18522E00D5CF5C /* TermsAgreementFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsAgreementFeature.swift; sourceTree = ""; }; FDC09D382C18644000D5CF5C /* SelectProfileImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectProfileImageView.swift; sourceTree = ""; }; @@ -432,6 +438,9 @@ isa = PBXGroup; children = ( FD1C54422C40493000419364 /* TodayWorkOutRecordView.swift */, + FD8D001B2C6BB391001C126B /* MapView.swift */, + FDA1AF202C6CD4F6008A1312 /* MapViewController.swift */, + FDA1AF242C6CD6AB008A1312 /* LocationManager.swift */, ); path = TodayWorkOut; sourceTree = ""; @@ -1115,6 +1124,7 @@ FD9E06FF2BC97DF6000109FD /* ProfileEditView.swift in Sources */, FD3428F92BDC24AE0021E542 /* CGFloat+.swift in Sources */, FD2A260A2BAC837000F7B317 /* SCRadioButton.swift in Sources */, + FD8D001C2C6BB391001C126B /* MapView.swift in Sources */, FD1C54462C40529A00419364 /* AttributedText.swift in Sources */, FDE5C8C52C11F31D0021C8E3 /* TypeUserInfoMainView.swift in Sources */, FD995E6D2C3AE1A200376823 /* SelectSchoolNameSheet.swift in Sources */, @@ -1207,6 +1217,7 @@ FD443C232C4DF1AC00C2E62E /* CameraPreview.swift in Sources */, FD2E8CE12BD93238001957F3 /* MyPageMainView.swift in Sources */, FDFD3D082BD85E8900629B8C /* SchoolGroupMainFeature.swift in Sources */, + FDA1AF252C6CD6AB008A1312 /* LocationManager.swift in Sources */, FD1C54402C40393800419364 /* CameraView.swift in Sources */, FDEDB9642BFC17DC0040E313 /* AuthToken.swift in Sources */, FD38E98E2BD5297E00D3BDB7 /* PolicyView.swift in Sources */, @@ -1220,6 +1231,7 @@ FD2E8CE72BD94F37001957F3 /* SCIconButton.swift in Sources */, FD208FC52BB9C45700DD4D3B /* SCNumberIcon.swift in Sources */, FD208FCB2BB9EDCD00DD4D3B /* SCInformationCard.swift in Sources */, + FDA1AF212C6CD4FA008A1312 /* MapViewController.swift in Sources */, FD408AA12C11D12200DD8EAF /* TypeUserInfoFeature.swift in Sources */, FD443C252C4DF4A000C2E62E /* Camera.swift in Sources */, FD208FBA2BB634AF00DD4D3B /* SCTextField.swift in Sources */, @@ -1377,6 +1389,7 @@ INFOPLIST_FILE = Score/Resource/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "스코어"; INFOPLIST_KEY_NSCameraUsageDescription = "스코어에서 운동 기록 촬영을 위해 카메라에 접근합니다."; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "스코어에서 운동 기록을 위해 사용자의 위치 정보에 접근합니다."; INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "스코어에서 운동 기록 사진 저장을 위해 갤러리에 접근합니다."; INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "스코어에서 운동 기록 사진 저장을 위해 갤러리에 접근합니다."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; @@ -1421,6 +1434,7 @@ INFOPLIST_FILE = Score/Resource/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "스코어"; INFOPLIST_KEY_NSCameraUsageDescription = "스코어에서 운동 기록 촬영을 위해 카메라에 접근합니다."; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "스코어에서 운동 기록을 위해 사용자의 위치 정보에 접근합니다."; INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "스코어에서 운동 기록 사진 저장을 위해 갤러리에 접근합니다."; INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "스코어에서 운동 기록 사진 저장을 위해 갤러리에 접근합니다."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; From 2d628d0854800574b026b1b230a6d21617091975 Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Fri, 16 Aug 2024 03:48:28 +0900 Subject: [PATCH 06/11] =?UTF-8?q?#79=20[Feat]=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Source/Reducer/Record/MapFeature.swift | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Score/Score/Source/Reducer/Record/MapFeature.swift diff --git a/Score/Score/Source/Reducer/Record/MapFeature.swift b/Score/Score/Source/Reducer/Record/MapFeature.swift new file mode 100644 index 0000000..1d25e92 --- /dev/null +++ b/Score/Score/Source/Reducer/Record/MapFeature.swift @@ -0,0 +1,33 @@ +// +// MapFeature.swift +// Score +// +// Created by sole on 8/16/24. +// + +import CoreLocation +import ComposableArchitecture +import NMapsMap + +@Reducer +struct MapFeature { + struct State: Equatable { + var locations: [NMGLatLng] = [] + } + + enum Action { + case updatingLocations(locations: [CLLocation]) + } + + + + var body: some ReducerOf { + Reduce { state, action in + switch action { + case .updatingLocations(let locations): + state.locations += locations.map{ .init(lat: $0.coordinate.latitude, lng: $0.coordinate.longitude)} + return .none + } + } + } +} From d008d30285aa851f8c287aa3700b92daadef74bb Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Fri, 16 Aug 2024 03:48:46 +0900 Subject: [PATCH 07/11] =?UTF-8?q?#79=20[Feat]=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20sto?= =?UTF-8?q?re=EC=99=80=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Source/View/Record/TodayWorkOut/LocationManager.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Score/Score/Source/View/Record/TodayWorkOut/LocationManager.swift b/Score/Score/Source/View/Record/TodayWorkOut/LocationManager.swift index d030474..9ca3b43 100644 --- a/Score/Score/Source/View/Record/TodayWorkOut/LocationManager.swift +++ b/Score/Score/Source/View/Record/TodayWorkOut/LocationManager.swift @@ -7,11 +7,13 @@ import CoreLocation import os.log +import ComposableArchitecture final class LocationManager: CLLocationManager { - private(set) var locations: [CLLocation] = [] + private var store: StoreOf - override init() { + init(store: StoreOf) { + self.store = store super.init() self.delegate = self } @@ -42,7 +44,7 @@ extension LocationManager: CLLocationManagerDelegate { _ manager: CLLocationManager, didUpdateLocations locations: [CLLocation] ) { - self.locations += locations + store.send(.updatingLocations(locations: locations)) } } From 96372a8ae7605ed5367dac366147eb18b1f70674 Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Fri, 16 Aug 2024 03:49:03 +0900 Subject: [PATCH 08/11] =?UTF-8?q?#79=20[Feat]=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=EA=B2=BD=EB=A1=9C=20=EA=B7=B8=EB=A6=AC?= =?UTF-8?q?=EA=B8=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TodayWorkOut/MapViewController.swift | 72 +++++++++++++++++-- 1 file changed, 67 insertions(+), 5 deletions(-) diff --git a/Score/Score/Source/View/Record/TodayWorkOut/MapViewController.swift b/Score/Score/Source/View/Record/TodayWorkOut/MapViewController.swift index 64a5a92..1a1782d 100644 --- a/Score/Score/Source/View/Record/TodayWorkOut/MapViewController.swift +++ b/Score/Score/Source/View/Record/TodayWorkOut/MapViewController.swift @@ -5,31 +5,58 @@ // Created by sole on 8/14/24. // +import ComposableArchitecture import NMapsMap import UIKit +import SwiftUI +import Combine final class MapViewController: UIViewController { + private let store: StoreOf = .init(initialState: .init(), reducer: { MapFeature() }) + private var cancellable: Set = .init() + private var naverMapView: NMFNaverMapView! private var locationManager: LocationManager! + private let pathOverlay: NMFPath = { + let pathOverlay: NMFPath = .init() + pathOverlay.color = UIColor(.brandColor(color: .main)) + pathOverlay.outlineWidth = 0 + return pathOverlay + }() + + init() { + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func viewDidLoad() { super.viewDidLoad() self.setUpLocationManager() - self.naverMapView = .init(frame: view.frame) self.setUpMapView() self.setUpCamera() + self.setUpMapMarker() view.addSubview(naverMapView) + + store.publisher + .map{ $0.locations } + .sink{ self.overlayMapPath(locations: $0) } + .store(in: &cancellable) } /// locationManager 관련 설정을 세팅합니다. func setUpLocationManager() { - self.locationManager = .init() + self.locationManager = .init(store: store) self.locationManager.requestAuthorization() self.locationManager.startUpdatingLocation() } - /// 지도 interaction 관련 설정을 세팅합니다. + /// 지도 관련 설정을 세팅합니다. func setUpMapView() { + self.naverMapView = .init(frame: view.frame) self.naverMapView.showCompass = false self.naverMapView.showZoomControls = false self.naverMapView.mapView.isScrollGestureEnabled = false @@ -55,8 +82,43 @@ final class MapViewController: UIViewController { self.naverMapView.mapView.moveCamera(cameraUpdate) } - func requestLocations() -> [CLLocationCoordinate2D] { - self.locationManager.locations.map{ $0.coordinate } + // FIXME: UIImage로 Rendering이 제대로 되지 않는 문제 해결 해야 함. + func setUpMapMarker() { + let locationOverlay = self.naverMapView.mapView.locationOverlay + let markerVC = SCMapMarker(isFocused: true).asUIViewController() +// let markerUIImage: UIImage = markerVC.view.asUIImage(bounds: self.view.bounds) + let markerUIImage: UIImage = .apple + let overlayImage: NMFOverlayImage = .init(image: markerUIImage) + locationOverlay.icon = overlayImage + } + + /// 유저가 지나온 경로를 그립니다. + func overlayMapPath(locations: [NMGLatLng]) { + // point가 2개 이상인 경우에만 실행됩니다. + guard locations.count > 1 + else { return } + self.pathOverlay.path = .init(points: locations) + self.pathOverlay.mapView = self.naverMapView.mapView + } + + deinit { + cancellable.forEach{ $0.cancel() } } } +extension UIView { + func asUIImage(bounds: CGRect) -> UIImage { + let renderer = UIGraphicsImageRenderer(bounds: bounds) + let uiImage = renderer.image { context in + self.layer.render(in: context.cgContext) + } + return uiImage + } +} + + +extension View { + func asUIViewController() -> UIViewController { + UIHostingController(rootView: self) + } +} From e85237996c3dae1eadba31e6d9d0bf552f75fee4 Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Fri, 16 Aug 2024 03:51:30 +0900 Subject: [PATCH 09/11] =?UTF-8?q?#79=20[Style]=20SCMapMarker=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UIComponent/MapMarker/SCMapMarker.swift | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 Score/Score/Source/View/UIComponent/MapMarker/SCMapMarker.swift diff --git a/Score/Score/Source/View/UIComponent/MapMarker/SCMapMarker.swift b/Score/Score/Source/View/UIComponent/MapMarker/SCMapMarker.swift new file mode 100644 index 0000000..f6776c5 --- /dev/null +++ b/Score/Score/Source/View/UIComponent/MapMarker/SCMapMarker.swift @@ -0,0 +1,159 @@ +// +// SCMapMarker.swift +// Score +// +// Created by sole on 8/15/24. +// + +import SwiftUI + +struct SCMapMarker: View { + let isFocused: Bool + let hour: Int = 0 + let minute: Int = 0 + var image: Image? + + var formattedTimeString: String { + let hourString = String(format: "%02d", hour) + let minuteString = String(format: "%02d", minute) + return "\(hourString):\(minuteString)" + } + + var body: some View { + VStack(spacing: 4) { + VStack(spacing: -12) { + timeChipBuilder() + .zIndex(1) + + UnevenRoundedRectangle( + topLeadingRadius: 28, + bottomLeadingRadius: 5, + bottomTrailingRadius: 28, + topTrailingRadius: 28 + ) + .rectFrame(size: 56) + .foregroundStyle(.white) + .shadow(radius: 5) + .rotationEffect(.degrees(-45)) + .overlay { + Image(.apple) + .imagePlaceHolder(size: 48) + } + } + .zIndex(1) + + userPositionMarkerBuilder() + } + } + + @ViewBuilder + private func timeChipBuilder() -> some View { + Text(formattedTimeString) + .pretendard(weight: .bold, size: .xs) + .padding(.vertical, 3) + .padding(.horizontal, 10) + .foregroundStyle(isFocused ? .white : .brandColor(color: .text2)) + .background(isFocused ? .black : .brandColor(color: .gray2)) + .clipShape(RoundedRectangle(cornerRadius: 12)) + } + + @ViewBuilder + private func userPositionMarkerBuilder() -> some View { + Circle() + .foregroundStyle(.white) + .rectFrame(size: 15) + .overlay { + Circle() + .rectFrame(size: 12) + .foregroundStyle( + isFocused ? + Color.brandColor(color: .main) : Color.brandColor(color: .gray1) + ) + } + } +} + +//final class SCMarkerUIView: UIView { +// let isFocusedUser: Bool +// var hour: Int +// var minute: Int +// +// private var formattedTimeString: String { +// let hourString = String(format: "%02d", hour) +// let minuteString = String(format: "%02d", minute) +// return "\(hourString):\(minuteString)" +// } +// +// private lazy var timeLabel: UILabel = { +// let timeLabel: UILabel = .init(frame: .zero) +// timeLabel.text = formattedTimeString +// timeLabel.backgroundColor = isFocusedUser ? .black : UIColor(.brandColor(color: .gray2)) +// timeLabel.textColor = isFocusedUser ? .white : UIColor(.brandColor(color: .text2)) +// timeLabel.layer.cornerRadius = 12 +// return timeLabel +// }() +// +// init( +// isFocusedUser: Bool, +// hour: Int, +// minute: Int +// ) { +// self.isFocusedUser = isFocusedUser +// self.hour = hour +// self.minute = minute +// super.init(frame: .zero) +// self.setUI() +// } +// +// +// required init?(coder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +// +// +// private func setUI() { +// self.backgroundColor = .white +// let unevenRectangle: UIView = .init() +// unevenRectangle.backgroundColor = .white +// +// unevenRectangle.layer.cornerRadius = 28 +// unevenRectangle.layer.maskedCorners = [.layerMinXMinYCorner] +// +// [timeLabel].forEach{ self.addSubview($0) } +// +// +// timeLabel.translatesAutoresizingMaskIntoConstraints = false +// +// NSLayoutConstraint.activate([ +// timeLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor), +// timeLabel.topAnchor.constraint(equalTo: self.topAnchor), +// timeLabel.heightAnchor.constraint(equalToConstant: 61) +// ]) +// timeLabel.layoutMargins.left = 1000 +// } +// +// private func updateUI() { +// +// } +//} + +//struct Test: UIViewRepresentable { +// func makeUIView(context: Context) -> some UIView { +// SCMarkerUIView(isFocusedUser: true, hour: 0, minute: 0) +// } +// func updateUIView(_ uiView: UIViewType, context: Context) { +// +// } +//} + +#Preview { + VStack(spacing: 20) { + Spacer() + SCMapMarker(isFocused: true) + SCMapMarker(isFocused: false) + Spacer() +// Test() + } + .frame(maxWidth: .infinity) + .background(.blue) +} From f6e927590119b7edcd8a8a0ef01d0653a72870cf Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Fri, 16 Aug 2024 03:51:51 +0900 Subject: [PATCH 10/11] =?UTF-8?q?#79=20[Chore]=20.pbxproj=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Score/Score.xcodeproj/project.pbxproj | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Score/Score.xcodeproj/project.pbxproj b/Score/Score.xcodeproj/project.pbxproj index 5957e6e..c667b93 100644 --- a/Score/Score.xcodeproj/project.pbxproj +++ b/Score/Score.xcodeproj/project.pbxproj @@ -103,6 +103,8 @@ FD9E07112BCC99DE000109FD /* DismissButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9E07102BCC99DE000109FD /* DismissButton.swift */; }; FDA1AF212C6CD4FA008A1312 /* MapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1AF202C6CD4F6008A1312 /* MapViewController.swift */; }; FDA1AF252C6CD6AB008A1312 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1AF242C6CD6AB008A1312 /* LocationManager.swift */; }; + FDA1AF272C6D0B3A008A1312 /* SCMapMarker.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1AF262C6D0B3A008A1312 /* SCMapMarker.swift */; }; + FDA1AF2A2C6E64B4008A1312 /* MapFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1AF292C6E64AF008A1312 /* MapFeature.swift */; }; FDC09D352C18327B00D5CF5C /* TermsAgreementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC09D342C18327B00D5CF5C /* TermsAgreementView.swift */; }; FDC09D372C18522E00D5CF5C /* TermsAgreementFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC09D362C18522E00D5CF5C /* TermsAgreementFeature.swift */; }; FDC09D392C18644000D5CF5C /* SelectProfileImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC09D382C18644000D5CF5C /* SelectProfileImageView.swift */; }; @@ -250,6 +252,8 @@ FD9E07102BCC99DE000109FD /* DismissButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissButton.swift; sourceTree = ""; }; FDA1AF202C6CD4F6008A1312 /* MapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewController.swift; sourceTree = ""; }; FDA1AF242C6CD6AB008A1312 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = ""; }; + FDA1AF262C6D0B3A008A1312 /* SCMapMarker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SCMapMarker.swift; sourceTree = ""; }; + FDA1AF292C6E64AF008A1312 /* MapFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapFeature.swift; sourceTree = ""; }; FDC09D342C18327B00D5CF5C /* TermsAgreementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsAgreementView.swift; sourceTree = ""; }; FDC09D362C18522E00D5CF5C /* TermsAgreementFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsAgreementFeature.swift; sourceTree = ""; }; FDC09D382C18644000D5CF5C /* SelectProfileImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectProfileImageView.swift; sourceTree = ""; }; @@ -409,6 +413,7 @@ FD4A3B3F2BBADBDE00D2DFFA /* Card */, FD4A3B3E2BBADBCE00D2DFFA /* Label */, FD4A3B3D2BBADBBE00D2DFFA /* Button */, + FDA1AF282C6D0DAC008A1312 /* MapMarker */, ); path = UIComponent; sourceTree = ""; @@ -793,6 +798,14 @@ path = Navigation; sourceTree = ""; }; + FDA1AF282C6D0DAC008A1312 /* MapMarker */ = { + isa = PBXGroup; + children = ( + FDA1AF262C6D0B3A008A1312 /* SCMapMarker.swift */, + ); + path = MapMarker; + sourceTree = ""; + }; FDA9FDC12BFF3B1A00AC4EB7 /* DTO */ = { isa = PBXGroup; children = ( @@ -961,6 +974,7 @@ FDFD3D032BD85E5C00629B8C /* Record */ = { isa = PBXGroup; children = ( + FDA1AF292C6E64AF008A1312 /* MapFeature.swift */, FDFD3D042BD85E6800629B8C /* RecordMainFeature.swift */, FD584E422C4285AC00E81E07 /* CameraFeature.swift */, FD443C242C4DF49C00C2E62E /* Camera.swift */, @@ -1188,6 +1202,8 @@ FD208FBC2BB6F2A600DD4D3B /* EdgeBorder.swift in Sources */, FD8B639F2BD20124000500BF /* PrivacyPolicyFeature.swift in Sources */, FDEDB94A2BFBBA6D0040E313 /* APIProtocol.swift in Sources */, + FDA1AF2A2C6E64B4008A1312 /* MapFeature.swift in Sources */, + FDA1AF272C6D0B3A008A1312 /* SCMapMarker.swift in Sources */, FDC09D372C18522E00D5CF5C /* TermsAgreementFeature.swift in Sources */, FD38E9932BD53D0900D3BDB7 /* SignOutFeature.swift in Sources */, FD408A9F2C11CFEB00DD8EAF /* TypeNickNameView.swift in Sources */, From b652f35052859a818cf9b57f8c4ad19f534c445f Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Tue, 20 Aug 2024 00:29:41 +0900 Subject: [PATCH 11/11] =?UTF-8?q?[Feat]=20sink=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=ED=98=B8=EC=B6=9C=EB=B6=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Record/TodayWorkOut/MapViewController.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Score/Score/Source/View/Record/TodayWorkOut/MapViewController.swift b/Score/Score/Source/View/Record/TodayWorkOut/MapViewController.swift index 1a1782d..d29b76b 100644 --- a/Score/Score/Source/View/Record/TodayWorkOut/MapViewController.swift +++ b/Score/Score/Source/View/Record/TodayWorkOut/MapViewController.swift @@ -41,9 +41,10 @@ final class MapViewController: UIViewController { self.setUpMapMarker() view.addSubview(naverMapView) - store.publisher - .map{ $0.locations } - .sink{ self.overlayMapPath(locations: $0) } + store.publisher.locations + .sink{ [weak self] in + self?.overlayMapPath(locations: $0) + } .store(in: &cancellable) }