4

前言

SwiftUI 与 MapKit 的集成在今年发生了重大变化。在之前的 SwiftUI 版本中,我们将 MKMapView 的基本功能封装到名为 Map 的 SwiftUI 视图中。幸运的是,事情发生了变化,SwiftUI 引入了与 MapKit 集成的新 API。本篇文章我们将学习如何在 SwiftUI 的最新版本中使用可用的新功能丰富的 API 与 MapKit 集成。

正如我之前所说,在 SwiftUI 框架的早期版本中,我们有一个 Map 视图,为我们提供了 MapKit 的基本功能,该功能现在已被弃用。在面向较早 Apple 平台版本的情况下,仍然使用已弃用的 Map 视图是有意义的。

新 MapKit API 的引入

新的 MapKit API 引入了 MapContentBuilder 结果构建器,它看起来类似于 ViewBuilder,但是使用符合 MapContent 协议的类型。让我们从使用 SwiftUI 中最新迭代中提供的新 MapKit API 集成的基本示例开始。

import MapKit
import SwiftUI

extension CLLocationCoordinate2D {
    static let newYork: Self = .init(
        latitude: 40.730610,
        longitude: -73.935242
    )
    
    static let seattle: Self = .init(
        latitude: 47.608013,
        longitude: -122.335167
    )
    
    static let sanFrancisco: Self = .init(
        latitude: 37.733795,
        longitude: -122.446747
    )
}

struct ContentView: View {
    var body: some View {
        Map {
            Annotation("Seattle", coordinate: .seattle) {
                Image(systemName: "mappin")
                    .foregroundStyle(.black)
                    .padding()
                    .background(.red)
                    .clipShape(Circle())
            }
            
            Marker(coordinate: .newYork) {
                Label("New York", systemImage: "mappin")
            }
            
            Marker("San Francisco", monogram: Text("SF"), coordinate: .sanFrancisco)
        }
    }
}

正如你在上面的示例中看到的,我们通过使用 MapContentBuilder 闭包定义地图,并在其上放置内容。MapContentBuilder 类型与符合 MapContent 协议的任何类型一起使用。

在我们的示例中,我们使用了 Marker 和 Annotation 类型。Marker 是一个基本项,允许我们在地图上放置预定义的标记。Annotation 类型更先进,将使我们能够使用纬度和经度在地图上放置 SwiftUI 视图。

SwiftUI 为我们提供了许多符合 MapContent 协议的类型。我们已经使用了其中的两个:Marker 和 Annotation。其中许多包括 MapCircle、MapPolygon、MapPolyline、UserAnnotation 等。

struct ContentView: View {
    var body: some View {
        Map {
            Annotation("Seattle", coordinate: .seattle) {
                Image(systemName: "mappin")
                    .foregroundStyle(.black)
                    .padding()
                    .background(.red)
                    .clipShape(Circle())
            }
            
            Marker(coordinate: .newYork) {
                Label("New York", systemImage: "mappin")
            }
            
            UserAnnotation()
        }
    }
}

控制初始地图位置

你可以通过使用 Map 初始化器的另一个重载来控制地图的初始位置,该初始化器提供 initialPosition 参数。

struct ContentView: View {
    let initialPosition: MapCameraPosition = .userLocation(
        fallback: .camera(
            MapCamera(centerCoordinate: .newYork, distance: 0)
        )
    )
    
    var body: some View {
        Map(initialPosition: initialPosition) {
            Annotation("Seattle", coordinate: .seattle) {
                Image(systemName: "mappin")
                    .foregroundStyle(.black)
                    .padding()
                    .background(.red)
                    .clipShape(Circle())
            }
            
            Marker(coordinate: .newYork) {
                Label("New York", systemImage: "mappin")
            }
            
            Marker("San Francisco", monogram: Text("SF"), coordinate: .sanFrancisco)
        }
    }
}

initialPosition 参数接受 MapCameraPosition 类型的实例。MapCameraPosition 允许我们以几种方式定义地图位置。它可以是我们在示例中使用的用户位置,或者你可以使用 camera、region、rect 或 item 等静态函数将其指向地图上的任何区域。默认情况下,它使用 MapCameraPosition 类型的自动实例,该类型适合地图内容。

相机位置的双向绑定

每当你需要对相机位置有恒定的控制时,你可以使用 Map 初始化器的另一个重载,允许你提供与地图相机位置的双向绑定。

struct ContentView: View {
    @State private var position: MapCameraPosition = .userLocation(
        fallback: .camera(
            MapCamera(centerCoordinate: .newYork, distance: 0)
        )
    )
    
    var body: some View {
        Map(position: $position) {
            // ...
        }
    }
}

SwiftUI 在用户拖动地图时更新位置绑定。它还在你以编程方式更新 position 属性时立即更新地图相机位置。

struct ContentView: View {
    @State private var position: MapCameraPosition = .userLocation(
        fallback: .camera(
            MapCamera(centerCoordinate: .newYork, distance: 0)
        )
    )
    
    var body: some View {
        Map(position: $position, interactionModes: .pitch) {
            // ...
        }
    }
}

通过使用 interactionModes 参数,你可以控制与地图允许的交互类型。MapInteractionModes 类型定义了一组交互,如平移、俯仰、旋转和缩放。默认情况下,它启用所有可用的交互类型。

总结

今天,我们学习了在 SwiftUI 中集成 MapKit 的基础知识。在接下来的几周里,我们将继续讨论相机操作、地图控件和其他高级主题。希望你喜欢这篇文章。


Swift社区
16.3k 声望4.4k 粉丝

我们希望做一个最专业最权威的 Swift 中文社区,我们希望更多的人学习和使用Swift。我们会分享以 Swift 实战、SwiftUI、Swift 基础为核心的技术干货,欢迎您的关注与支持。