일모도원(日暮途遠) 개발자
[SwiftUI Map] MapKit사용하기 본문
SwiftUI에서 애플 지도를 사용할려면 MapKit을 사용하면 된다.
import MapKit
지도를 그려주기 위해서는 Map View를 이용하면 된다. 크게는 coordinateRegion과 mapRect가 있는데, 주로 coordinateRegion를 사용한다.
Map(coordinateRegion: Binding<MKCoordinateRegion>
Map(mapRect: Binding<MKMapRect>
아래코드는 서울지역을 보여주는 코드이다. MKCoordinateRegion으로 중심 경위도를 주고, span으로 반경을 주면 된다.
import SwiftUI
import MapKit
struct ContentView: View {
@State private var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 37.5665, longitude: 126.9780), // Seoul coordinates
span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
)
var body: some View {
Map(coordinateRegion: $region)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
폰의 화면을 꽉 채울려면 .ingoresSafeArea()를 사용하면 된다.
Map(coordinateRegion: $region)
.ignoresSafeArea()
아래처럼 MapConstants로 상수를 만들어서 사용하면 코드가 조금 더 깔끔해진다.
struct MapConstants {
static let seoulCenter = CLLocationCoordinate2D(latitude: 37.5665, longitude: 126.9780)
static let defaultSpan = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
}
struct ContentView: View {
@State private var region = MKCoordinateRegion(
center: MapConstants.seoulCenter,
span: MapConstants.defaultSpan
)
MapConstants에 필요하면 아래처럼 더 추가하면 돈다.
static let cityHallCoordinate = CLLocationCoordinate2D(latitude: 37.5663, longitude: 126.9778)
이제 지도위에 아이콘등을 표시해보자. MapMarker를 사용하면 되는데, CLLocationCoordinate2D로 좌표를 주고, 색상을 줄수 있다.
// Available when SwiftUI is imported with MapKit
@available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *)
public struct MapMarker : MapAnnotationProtocol {
public init(coordinate: CLLocationCoordinate2D, tint: Color? = nil)
}
실제 Map뷰에 MapMarker를 그려주기 위해서는 아래처럼 사용해야 한다.
coordinateRegion으로 지도화면을 보여줄 지역을 정해주고, annotationItems로 지도위에 뿌리 annotation들을 같이 줘야한다. 이 annotation들은 coordinateRegion을 벗어날수 있다.
Map(coordinateRegion: Binding<MKCoordinateRegion>,
annotationItems: RandomAccessCollection) { Identifiable in
code
}
annotation은 여러 정보를 담을수 있는데, 필수적인게 CLLocationCoordinate2D 좌표이다. 그리고 Identifiable을 구현해야 한다. 그래서 UUID를 사용했다. name은 부가 정보이다. 다른 정보가 필요하면 더 추가하면 된다.
struct AnnotationItem: Identifiable {
let id = UUID()
let coordinate: CLLocationCoordinate2D
let name: String
}
이제 만든 구조체에 화면에 뿌릴 annotation정보들을 주자. 실 사용예에서는 네트웍이나 DB등에서 읽어오면 된다.
let annotations: [AnnotationItem] = [
AnnotationItem(coordinate: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194), name: "San Francisco"),
AnnotationItem(coordinate: CLLocationCoordinate2D(latitude: 34.0522, longitude: -118.2437), name: "Los Angeles"),
AnnotationItem(coordinate: CLLocationCoordinate2D(latitude: 40.7128, longitude: -74.0060), name: "New York"),
AnnotationItem(coordinate: MapConstants.cityHallCoordinate, name: "Seoul City Hall")
]
이제 MapMarker를 이용해서 annotation들을 뿌려주자.
Map(coordinateRegion: $region, annotationItems: annotations) { item in
MapMarker(coordinate: item.coordinate, tint: .red)
}
.ignoresSafeArea()
에뮬레이터에서는 Option키를 누른상태에서 마우스 드래그를 하면 확대/축소가 가능하다.
참고로 MapPin은 사용 비권장으로 되었다. MapMarker를 사용하자. MapPin을 사용하면 아래처럼 진짜 Pin처럼 보인다.
MapPin(coordinate: item.coordinate, tint: .red)
'MapPin' was deprecated in iOS 16.0: Use MapMarker
MapMarker는 커스터마이징을 할수 없다. 그럴때는 MapAnnotation을 사용하자.
아래는 동그라미를 보여주거나 텍스트를 보여주는 예제이다.
Map(coordinateRegion: $region, showsUserLocation: false, userTrackingMode: .constant(.none), annotationItems: annotations) { item in
MapAnnotation(coordinate: item.coordinate) {
Circle()
.fill(Color.red.opacity(0.3))
.frame(width: 200, height: 200)
}
}
.ignoresSafeArea()
Map(coordinateRegion: $region, showsUserLocation: false, userTrackingMode: .constant(.none), annotationItems: annotations) { item in
MapAnnotation(coordinate: item.coordinate) {
Text(item.name)
.padding(.horizontal, 10)
.padding(.vertical, 10)
.background(Color.red.opacity(0.5))
.cornerRadius(20)
}
}
.ignoresSafeArea()
MapAnnotation을 이용하면 원하는 모양으로 자유자재로 아이콘을 만들수 있다.
MyAnnotationView를 만들어서 MapAnnotation을 보여주는 샘플.
Map(coordinateRegion: $region, annotationItems: annotations) { item in
MapAnnotation(coordinate: item.coordinate, content: {
MyAnnotationView(name: item.name)
})
}
.ignoresSafeArea()
struct MyAnnotationView: View {
let accentColor = Color.blue
let name: String
init(name: String) {
self.name = name
}
var body: some View {
VStack(spacing: 0) {
Text(name)
.padding(.horizontal, 10)
.padding(.vertical, 5)
.background(Color.red.opacity(0.5))
.cornerRadius(20)
Image(systemName: "map.circle.fill")
.resizable()
.scaledToFit()
.frame(width: 30, height: 30)
.font(.headline)
.foregroundColor(.white)
.padding(6)
.background(accentColor)
.cornerRadius(36)
Image(systemName: "triangle.fill")
.resizable()
.scaledToFit()
.foregroundColor(accentColor)
.frame(width: 10, height: 10)
.rotationEffect(Angle(degrees: 180))
.offset(y:-3)
.padding(.bottom, 40)
}
}
}
이제 annotation을 선택하면 화면 하단에 Sheet를 보여주고, 선택한 annotation의 이름을 보여주는 코드를 만들어 보자.
MyAnnotationView에 onTapGesture를 추가하고 showSheet을 변경하자. 그리고 showSheet가 true이면 쉬트 뷰를 보여주기 위해 ".sheet"를 추가하자.
@State private var showSheet = false
MyAnnotationView(name: item.name)
.onTapGesture {
showSheet.toggle()
}
.sheet(isPresented: $showSheet) {
BottomSheetContent(name: item.name)
}
BottomSheetContent는 각자 요구에 맞게 만들면 된다. 이제 annotation을 클릭하면 BottomSheetContent가 보이게 된다.
struct BottomSheetContent: View {
var name: String
var body: some View {
VStack {
Text("선택 : \(name)")
.font(.title)
}
.frame(maxHeight: .infinity)
.background(Color.white)
.cornerRadius(20)
.padding()
}
}
Privacy - Location When In Use Usage Description
지도를 사용할려면 권한이 필요합니다.
import SwiftUI
import CoreLocation
class MyLocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
private var locationManager = CLLocationManager()
static let shared = MyLocationManager()
override init() {
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
public func requestLocation() {
locationManager.requestWhenInUseAuthorization()
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
requestLocation()
print("Location authorization status: Not Determined")
case .restricted:
print("Location authorization status: Restricted")
case .denied:
print("Location authorization status: Denied")
case .authorizedAlways:
print("Location authorization status: Authorized Always")
case .authorizedWhenInUse:
print("Location authorization status: Authorized When In Use")
@unknown default:
print("Location authorization status: Unknown")
}
}
}
@ObservedObject var locationManager = MyLocationManager.shared
.ignoresSafeArea()
.onAppear {
locationManager.requestLocation()
}
Map(coordinateRegion: $region, showsUserLocation: true, annotationItems: annotations)
struct ContentView: View {
@ObservedObject var locationManager = MyLocationManager.shared
@State private var showSheet = false
@State private var region = MKCoordinateRegion(
center: MapConstants.seoulCenter,
span: MapConstants.defaultSpan
)
var body: some View {
Map(coordinateRegion: $region, showsUserLocation: true, annotationItems: annotations) { item in
MapAnnotation(coordinate: item.coordinate, content: {
MyAnnotationView(name: item.name)
.onTapGesture {
showSheet.toggle()
}
.sheet(isPresented: $showSheet) {
BottomSheetContent(name: item.name)
}
})
}
.ignoresSafeArea()
.onAppear {
locationManager.requestLocation()
}
}
}
'iOS개발 > SwiftUI' 카테고리의 다른 글
[SwiftUI] 앱 전체에 다크모드 적용하기 (0) | 2023.02.28 |
---|