Apple地图框架MapKit
Apple地图框架MapKit

Apple地图框架MapKit

MapKit 是 Apple 提供的一个框架,用于在 iOS、macOS、watchOS 和 iPadOS 应用中嵌入地图视图,支持地图显示、导航、地理编码等功能。

本文主要使用 Swift + MapKit 实现地图显示,与之前的 UIKit + MapKit 实现的方式不同,SwiftUI 的 MapKit 是现代化的封装,旨在提升开发效率,但功能还不够完善,某些复杂功能可能依赖 UIKit 实现

主要功能

1、地图显示

在应用中嵌入交互式地图。

支持平移、缩放、旋转和俯视等多种手势操作。

添加自定义标注(Annotations)和覆盖物(Overlays),如地理区域、高亮路径等。

3、地理编码和反向地理编码

将地址转换为地理坐标(纬度和经度)。

将地理坐标转换为地址。

4、导航和方向

计算两点之间的路线。

支持步行、驾车和骑行等多种导航模式。

5、地点搜索

搜索附近的兴趣点(POI),如餐馆、加油站等。

显示地图

// 导入框架
import MapKit 

struct ContentView: View {
    var body: some View {
        // SwiftUI视图放置地图
        Map()
    }
}

修改地图外观

Map()
    .mapStyle(.imagery)

// 创建一个3D地图
Map()
    .mapStyle(.hybrid(elevation: .realistic))

MapStyle地图样式

1、hybrid

表示混合视图,包括卫星图像和道路名称的叠加。

2、imagery

表示纯卫星视图(没有任何叠加信息)。

3、standard

表示标准地图视图(包括道路、地标和其他兴趣点信息)。

可选参数

某些样式(如 hybrid 和 standard)可能还提供可选参数,用于自定义地图样式的细节,例如:

1)elevation

是否显示地形。

.automatic:该选项表示自动选择地形样式,由系统根据设备性能、用户设置和当前环境决定是使用 2D 还是 3D 样式。

.flat:使用平面化的 2D 样式,即不渲染任何真实的地形高度或深度。

.realistic:使用真实的 3D 地形样式,包括山脉、丘陵、峡谷等自然地形的真实高度和深度。

示例:

Map()
    .mapStyle(.hybrid(elevation: .realistic))

2)pointsOfInterest

是否显示兴趣点。

.all:显示所有兴趣点。

.including(_:):仅显示指定类别的兴趣点。

.excluding(_:):显示所有兴趣点,除了某些指定类别的兴趣点。

.excludingAll:隐藏所有兴趣点。

代码示例:

Map()
    // 兴趣点显示只银行
    .mapStyle(.standard(pointsOfInterest: .including(.bank)))

常见的兴趣点枚举值

.restaurant: 餐厅

.cafe: 咖啡馆

.museum: 博物馆

.bank: 银行

.hotel: 酒店

.gasStation: 加油站

.groceryStore: 超市

3)showsTraffic

是否显示交通信息。

true: 地图会显示交通拥堵、车流等信息;

false: 不会显示。

限制操作

interactionModes

作用是初始化一个 Map 视图,并限制用户可以对地图进行的交互操作。

// 允许用户旋转(.rotate)和缩放(.zoom)地图
Map(interactionModes: [.rotate, .zoom])

MapInteractionModes 中其他常见选项

.all:启用所有交互模式,包括旋转、缩放和平移。

.pan:允许用户平移(拖动)地图。

.pitch:允许用户改变地图的倾斜角度

.rotate:允许用户旋转地图

.zoom:允许用户缩放地图

示例

1、启用所有交互模式

Map(interactionModes: .all)

用户可以自由平移、缩放和旋转地图。

2、仅允许平移

Map(interactionModes: [.pan])

用户只能拖动地图,无法缩放或旋转。

3、禁用所有交互模式

Map(interactionModes: [])

用户无法进行任何交互,地图显示为静态。

MapCameraPosition

MapCameraPosition 是 SwiftUI 中更现代和灵活的地图位置管理方式。

let position = MapCameraPosition.region(
    MKCoordinateRegion(
        center: CLLocationCoordinate2D(latitude: 51.507222, longitude: -0.1275),
        span: MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1)
    )
)

var body: some View {
    Map(initialPosition: position)
        .edgesIgnoringSafeArea(.all)
}

它相比传统的 MKCoordinateRegion 提供了更多功能,适合用于高级地图交互和 3D 显示。

相关知识可以进一步阅读《Swift控制地图类型MapCamerPosition

注意

CLLocationCoordinate2D(latitude:longitude:) 的经纬度值必须合法:

纬度范围:-90 到 90

经度范围:-180 到 180

关于经纬度知识可以查看《地图知识《经纬度》》这篇文章。

CLLocationCoordinate2D

CLLocationCoordinate2D 是一个结构体,用来表示一个地理坐标点(经纬度)。

struct CLLocationCoordinate2D {
    var latitude: CLLocationDegrees
    var longitude: CLLocationDegrees
}

latitude: 纬度,表示地理位置的南北方向。

longitude: 经度,表示地理位置的东西方向。

类型为 CLLocationDegrees(实际上是 Double 类型)。

let coordinate = CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194)
// 表示美国旧金山的地理坐标:纬度 37.7749°,经度 -122.4194°
MKCoordinateSpan

MKCoordinateSpan 是一个结构体,用来表示地图显示范围的跨度(东西和南北方向的覆盖范围)。

struct MKCoordinateSpan {
    var latitudeDelta: CLLocationDegrees
    var longitudeDelta: CLLocationDegrees
}

latitudeDelta: 纬度跨度,表示地图南北方向的覆盖范围。

longitudeDelta: 经度跨度,表示地图东西方向的覆盖范围。

单位是 度数(degrees)。

跨度越小,地图显示的区域范围越小,放大效果更明显;跨度越大,地图显示的区域范围越大,缩小效果更明显。

let span = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
// 表示地图将显示一个南北和东西方向分别为 0.1 度范围的区域

Swift实际应用

动态切换MapCameraPosition.region,改变地图的显示区域:

import SwiftUI
import MapKit
struct ContentView: View {
    @State private var position = MapCameraPosition.region(
        MKCoordinateRegion(
            center: CLLocationCoordinate2D(latitude: 51.507222, longitude: -0.1275),
            span: MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1)
        )
    )
    
    var body: some View {
        Map(position: $position)
            .onMapCameraChange { context in
                print(context.region)
            }
        HStack(spacing: 50) {
            Button("Paris") {
                position = MapCameraPosition.region(
                    MKCoordinateRegion(
                        center: CLLocationCoordinate2D(latitude: 48.8566, longitude: 2.3522),
                        span: MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1)
                    )
                )
            }

            Button("Tokyo") {
                position = MapCameraPosition.region(
                    MKCoordinateRegion(
                        center: CLLocationCoordinate2D(latitude: 35.6897, longitude: 139.6922),
                        span: MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1)
                    )
                )
            }
        }
    }
}

Map组件修饰符

在上面的代码中涉及onMapCameraChange修饰符,用于监听地图相机位置的变化,onMapCameraChange监听的内容为:

MKCoordinateRegion(center: __C.CLLocationCoordinate2D(latitude: 48.85660000000001, longitude: 2.3522000000000536), span: __C.MKCoordinateSpan(latitudeDelta: 1.2305118256316732, longitudeDelta: 1.0004438526408777))

onMapCameraChange 是 SwiftUI 中 Map 组件的修饰符,用于监听 地图相机位置的变化。它可以捕获地图显示区域、中心点、缩放级别等信息在变化时触发的事件。常见场景包括用户交互(如拖动地图、缩放)或程序性更新地图视图。

onMapCameraChange主要有三种用法:

// 交互结束后触发
.onMapCameraChange { context in
    print(context.region)
}
// 交互结束后触发,与上面等同
.onMapCameraChange(frequency: .onEnd) { context in
    print(context.region)
}
// 相机更新时触发,频率更高,如实时更新
.onMapCameraChange(frequency: .continuous) { context in
    print(context.region)
}

初始地图的用法

1、Map(initialPosition: position)

Map(initialPosition: position)

initialPosition:这是一个只读的参数,用来设置地图的初始显示位置。它会在视图第一次加载时设置地图的初始坐标和缩放级别(通过 MapCameraPosition 或 MKCoordinateRegion)。

不会更新:这个参数仅在视图初始化时生效,一旦地图加载完成,initialPosition 不会再有任何变化。如果想动态更新位置,initialPosition 不会起作用。

用途:适用于只需要设置地图的初始显示位置,并且不打算根据用户的操作或者其他因素动态更新地图的显示位置。

2、Map(position: $position)

Map(position: $position)

position:这里的 position 是一个可变绑定(Binding),即地图的视角和位置会与 position 变量保持同步。每当 position 更新时,地图视图会自动重新渲染并更新位置。

动态更新:通过 $position 传递绑定,意味着地图的坐标和视角可以根据代码中的更新动态改变。例如,可以通过用户操作、数据变化或手动更新 position 来改变地图视角。

用途:适用于需要动态控制地图位置的情况,比如根据用户的交互(如拖动、缩放)或外部因素(如位置更新)来改变地图显示的区域。

关键区别

初始位置 vs 可变位置:initialPosition 只用于设置初始显示位置,并且不会随着数据更新而改变。而 $position 是一个双向绑定,使得地图的视角和代码中的 position 保持同步,能够动态更新。

静态 vs 动态:initialPosition 适用于只设置一次初始位置,$position 适用于需要不断更新的位置或视角。

示例

使用 initialPosition 设置初始位置:

struct ContentView: View {
    let position = MapCameraPosition.region(
        MKCoordinateRegion(
            center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194),
            span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
        )
    )

    var body: some View {
        Map(initialPosition: position)  // 设置地图的初始位置
            .edgesIgnoringSafeArea(.all)
    }
}

这段代码会设置一个初始位置,但如果之后改变 position,地图的位置不会更新。

使用 $position 绑定位置:

struct ContentView: View {
    @State private var position = MKCoordinateRegion(
        center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194),
        span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
    )

    var body: some View {
        Map(coordinateRegion: $position)  // 绑定位置
            .edgesIgnoringSafeArea(.all)
    }
}

这里,$position 是一个绑定(Binding),意味着当 position 更新时,地图会自动反映这些更新。如果更改 position(例如响应用户交互或程序内部逻辑),地图会立即显示新的位置。

Map标记地标

Marker标记

在Map中可以通过Marker显示地标名称并定位到相应的坐标。

import SwiftUI
import MapKit

struct Location: Identifiable {
    let id = UUID()
    var name: String
    var coordinate: CLLocationCoordinate2D
}

struct ContentView: View {
    let locations = [
        Location(name: "Buckingham Palace", coordinate: CLLocationCoordinate2D(latitude: 51.501, longitude: -0.141)),
        Location(name: "Tower of London", coordinate: CLLocationCoordinate2D(latitude: 51.508, longitude: -0.076))
    ]
    
    var body: some View {
        Map {
            ForEach(locations) { location in
                Marker(location.name, coordinate: location.coordinate)
            }
        }
    }
}

Map的初始化方法支持闭包来定义地图的内容,因此可以在Map中使用ForEach循环。

关于Marker更多的知识,可以阅读《SwiftUI地图标记点Marker

Annotation标记

Annotation 是 SwiftUI 和 MapKit 提供的一种在地图上标记位置的方式。与 Marker 类似,它用于在地图上展示特定位置,但更灵活,可以自定义样式、内容和交互。

Annotation(location.name, coordinate: location.coordinate) {
    VStack {
        Image(systemName: "mappin.circle.fill")
            .foregroundColor(.red)
        Text(location.name)
            .font(.caption)
            .padding(4)
            .background(Color.white)
            .cornerRadius(8)
    }
}

关于Annotation更多的知识,可以阅读《SwiftUI地图标记点Marker

MapReader视图

MapReader { proxy in
    Map()
        .onTapGesture { position in
            if let coordinate = proxy.convert(position, from: .local) {
                print(coordinate)
            }
        }
}

通过MapReader包装后,可以实现点击获取地图的实际位置。

关于MapReader更多的知识,可以阅读《SwiftUI获取地图信息的MapReader视图

常见应用场景

1、显示用户当前位置。

2、标注地点(如商店、餐馆、景点)。

3、显示路径或路线导航。

4、搜索附近的兴趣点。

5、结合 CoreLocation 实现地理围栏或实时位置跟踪。

总结

MapKit 是一个强大的地图框架,适用于需要嵌入地图和地理功能的应用。通过结合 SwiftUI 的 Map 组件,开发者可以快速构建现代化的交互式地图界面,同时支持自定义标注和导航功能。

参考文章

Integrating MapKit with SwiftUI:https://www.hackingwithswift.com/books/ios-swiftui/integrating-mapkit-with-swiftui

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注