-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
[Feat/Style] MapView 뷰 및 기능을 일부 구현했습니다.
- Loading branch information
Showing
7 changed files
with
432 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Self> { | ||
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 | ||
} | ||
} | ||
} | ||
} |
51 changes: 51 additions & 0 deletions
51
Score/Score/Source/View/Record/TodayWorkOut/LocationManager.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// | ||
// LocationManager.swift | ||
// Score | ||
// | ||
// Created by sole on 8/14/24. | ||
// | ||
|
||
import CoreLocation | ||
import os.log | ||
import ComposableArchitecture | ||
|
||
final class LocationManager: CLLocationManager { | ||
private var store: StoreOf<MapFeature> | ||
|
||
init(store: StoreOf<MapFeature>) { | ||
self.store = store | ||
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] | ||
) { | ||
store.send(.updatingLocations(locations: locations)) | ||
} | ||
} | ||
|
||
fileprivate let logger = Logger(subsystem: "sole.Score", category: "LocationManager") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() | ||
} |
125 changes: 125 additions & 0 deletions
125
Score/Score/Source/View/Record/TodayWorkOut/MapViewController.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
// | ||
// MapViewController.swift | ||
// Score | ||
// | ||
// 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<MapFeature> = .init(initialState: .init(), reducer: { MapFeature() }) | ||
private var cancellable: Set<AnyCancellable> = .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.setUpMapView() | ||
self.setUpCamera() | ||
self.setUpMapMarker() | ||
view.addSubview(naverMapView) | ||
|
||
store.publisher.locations | ||
.sink{ [weak self] in | ||
self?.overlayMapPath(locations: $0) | ||
} | ||
.store(in: &cancellable) | ||
} | ||
|
||
/// locationManager 관련 설정을 세팅합니다. | ||
func setUpLocationManager() { | ||
self.locationManager = .init(store: store) | ||
self.locationManager.requestAuthorization() | ||
self.locationManager.startUpdatingLocation() | ||
} | ||
|
||
/// 지도 관련 설정을 세팅합니다. | ||
func setUpMapView() { | ||
self.naverMapView = .init(frame: view.frame) | ||
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) | ||
} | ||
|
||
// 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) | ||
} | ||
} |
Oops, something went wrong.