「这是我参加2022首次更文应战的第3天,活动详情检查:2022首次更文应战」

前言

ios中能够直接运用苹果官方提供的map——MapKit。在SwiftUI中怎么运用MapKit网上有也有不少文章,可是大部分不具体,大部分只是简略的展现出地图。所以本文来具体的解说一下怎么运用MapKit的各项功用。

官方地址:developer.apple.com/documentati…

1、Map

SwiftUI中能够直接运用Map组件,如下:

import SwiftUI
import MapKit
struct ContentView: View {
    @State var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 39.915, longitude: 116.397), span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05))
    @State var trackingMode = MapUserTrackingMode.follow
    var body: some View {
        Map(coordinateRegion: $region, interactionModes: .all, showsUserLocation: true, userTrackingMode: $trackingMode)
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

这样能够直接显现地图,其中coordinateRegion就是当时的地图区域(中心点,经纬度的跨度等等)。

可是地图的各种功用怎么运用?答案是不能运用。经过官网的介绍:

Map

A view that displays an embedded map interface.

Overview

A map view displays a region. Use this native SwiftUI view to optionally configure user-allowed interactions, display the user’s location, and track location.

Also create maps that display annotations at specific locations.

These annotated maps use one of the following types of annotation views:

MapPin

MapMarker

MapAnnotation

Maps only show annotation views of the same type, backed by a single collection.

能够看到这个view只是一个简略版别,能够展现一些预界说好的annotation,其他功用都无法运用。

所以一般情况下咱们不运用这个,而是运用MKMapView。

2、MKMapView

MKMapView就无法直接像Map那样运用了,由于它并不承继View,而是承继UIView,所以需求用UIViewRepresentable来包装,代码如下:

import SwiftUI
import MapKit
struct MapView: UIViewRepresentable {
    typealias UIViewType = MKMapView
    func makeUIView(context: Context) -> MKMapView {
        return MKMapView()
    }
    func updateUIView(_ uiView: MKMapView, context: Context) {
        uiView.showsUserLocation = true
        let loc = CLLocationCoordinate2D(latitude: 39.915352, longitude: 116.397105)
        let region = MKCoordinateRegion(center: loc, span: MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02))
        uiView.setRegion(uiView.regionThatFits(region), animated: true)
    }
}
struct MapView_Previews: PreviewProvider {
    static var previews: some View {
        MapView()
    }
}

相同为MKMapView设置了初始的Region。然后咱们就能够直接在SwiftUI中运用MapView了。

3、MKMapView的一些设置

下面是一些比较常用的设置

  • mapType:地图类型。包含标准、卫星等,具体看MKMapType这个枚举即可。
  • isZoomEnabled:答应缩放
  • isScrollEnabled:地图是否能够拖动
  • isRotateEnabled:地图是否能够旋转
  • isPitchEnabled:是否能够调整仰望视角
  • showsScale:是否展现比例尺(只有当缩放地图的时分才显现,缩放完结后会自动隐藏)
  • showsCompass:是否展现罗盘(当正上方是正北的时分罗盘自动隐藏)
  • showsUserLocation:显现当时方位(需求有定位权限并且用户现已答应该权限,否则不显现)
  • showsTraffic:显现交通信息
  • showsBuildings:是否显现建筑(当camera视角不在正上方时,假如这个为true则会显现建筑物。视角在正上方则无差别)
  • showsPointsOfInterest:是否显现POI(这个方法现已不推荐运用了)

这儿简略说一下ios simulator的运用,由于缩放、旋转、仰望需求双指操作,在simulator中需求按住option键能够进行双指相对操作(即两个点相对运动),比方缩放、旋转;同时按住option+shift键才能够进行双指同向操作,比方仰望,或许比方在桌面上切屏。

4、处理Map上的操作

前面咱们展现了地图,假如要在地图上的进行操作,比方点击,获取中心点等,这就需求运用UIViewRepresentable的Coordinator——和谐器。

咱们创立一个类,承继NSObject和MKMapViewDelegate,这儿MKMapViewDelegate是一个protocol,界说了一些地图的操作有关的函数,比方

optional func mapViewDidChangeVisibleRegion(_ mapView: MKMapView)

是地图可见规模改变时回调,比方拖动、缩放等行为。在这儿咱们作一些更新地图的操作,比方能够获取地图的中心点,代码如下:

class MapCoordinator : NSObject, MKMapViewDelegate{
    func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
        print(mapView.centerCoordinate)
    }
}

MKMapViewDelegate还有很多函数对应不同的回调,这儿就不一一列举了。

然后咱们需求重写UIViewRepresentable的makeCoordinator函数,新建一个MapCoordinator类的目标并回来,这样就能够经过context.coordinator来获取这个目标了。

然后将这个和谐器绑定到map上,代码如下:

import SwiftUI
import MapKit
struct MapView: UIViewRepresentable {
  func makeCoordinator() -> MapCoordinator {
    MapCoordinator()
  }
 
  typealias UIViewType = MKMapView
 
  func makeUIView(context: Context) -> MKMapView {
    let mapView = MKMapView()
    mapView.delegate = context.coordinator  //绑定和谐器到map
    return mapView
  }
 
  func updateUIView(_ uiView: MKMapView, context: Context) {
    uiView.showsUserLocation = true
    uiView.showsScale = true
    uiView.showsBuildings = false
   
    let loc = CLLocationCoordinate2D(latitude: 39.915352, longitude: 116.397105)
    let region = MKCoordinateRegion(center: loc, span: MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02))
   
    uiView.setRegion(uiView.regionThatFits(region), animated: true)
  }
 
  class MapCoordinator : NSObject, MKMapViewDelegate{
   
    func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
      print(mapView.centerCoordinate)
    }
  }
}
struct MapView_Previews: PreviewProvider {
  static var previews: some View {
    MapView()
  }
}

这样当拖动或缩放地图的时分,就会回调到mapViewDidChangeVisibleRegion,然后打印出当时中心点的经纬度。

点击操作

MKMapViewDelegate只能被迫的接受地图的回调,假如咱们主动操作怎么办?比方点击地图获取点击方位的经纬度(或许增加地图掩盖物),这时分需求为地图设置GestureRecognizer,并经过和谐器进行处理。

首先创立一个UITapGestureRecognizer,并增加到map上,如下:

let gRecognizer = UITapGestureRecognizer(target: context.coordinator, action: #selector(MapCoordinator.touch(gestureReconizer:)))
mapView.addGestureRecognizer(gRecognizer)

这儿UITapGestureRecognizer的action履行的是MapCoordinator的touch函数,所以咱们需求给MapCoordinator增加一个touch函数:

class MapCoordinator : NSObject, MKMapViewDelegate{
    func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
        print(mapView.centerCoordinate)
    }
    @objc func touch(gestureReconizer: UITapGestureRecognizer) {
        print('click')
    }
}

这个函数有必要增加@objc标识,表示它能够供OC进行调用,由于终究实际上是OC调用的UITapGestureRecognizer的action。

然后咱们点击地图,就能够看到打印出click了。

注意:只能在MKMapViewDelegate中运用@objc标识,假如咱们直接给MapView增加一个touch函数,然后赋值给UITapGestureRecognizer,就会报错,由于UITapGestureRecognizer的action有必要是一个被@objc标识的函数,而在MapView中不能给函数增加@objc。所以咱们要经过MKMapViewDelegate来完成。

现在咱们能够响应点击了,可是怎么获取点击方位的经纬度?touch函数只传入了一个UITapGestureRecognizer目标,经过它能够获取到点击的方位,如下:

let point = gestureReconizer.location(in: gestureReconizer.view)

可是这个方位是屏幕方位,假如想换成经纬度还需求MKMapView才行,代码如下:

let point = gestureReconizer.location(in: gestureReconizer.view)
let loc = mMapView?.convert(point, toCoordinateFrom: mMapView)

可是在MapCoordinator中无法得到MKMapView目标,这儿我增加了一个initMap(_ mapView : MKMapView)函数,在makeUIView阶段履行这个函数,将MKMapView目标传入,这样就能够获取经纬度了,终究整体代码如下:

import SwiftUI
import MapKit
struct MapView: UIViewRepresentable {
  func makeCoordinator() -> MapCoordinator {
    MapCoordinator()
  }
 
  typealias UIViewType = MKMapView
 
  func makeUIView(context: Context) -> MKMapView {
    let mapView = MKMapView()
        //增加GestureRecognizer
    let gRecognizer = UITapGestureRecognizer(target: context.coordinator, action: #selector(MapCoordinator.touch(gestureReconizer:)))
    mapView.addGestureRecognizer(gRecognizer)
        //绑定和谐器
    mapView.delegate = context.coordinator
        //传入MKMapView目标
    context.coordinator.initMap(mapView)
    return mapView
  }
 
  func updateUIView(_ uiView: MKMapView, context: Context) {
    uiView.showsUserLocation = true
    uiView.showsScale = true
    uiView.showsBuildings = false
   
    let loc = CLLocationCoordinate2D(latitude: 39.915352, longitude: 116.397105)
    let region = MKCoordinateRegion(center: loc, span: MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02))
   
    uiView.setRegion(uiView.regionThatFits(region), animated: true)
  }
 
  class MapCoordinator : NSObject, MKMapViewDelegate{
   
    var mMapView : MKMapView?
    func initMap(_ mapView : MKMapView) {
      mMapView = mapView
    }
   
    func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
      print(mapView.centerCoordinate)
    }
   
    @objc func touch(gestureReconizer: UITapGestureRecognizer) {
      let point = gestureReconizer.location(in: gestureReconizer.view)
      let loc = mMapView?.convert(point, toCoordinateFrom: mMapView)
      print(loc!)
    }
  }
}
struct MapView_Previews: PreviewProvider {
  static var previews: some View {
    MapView()
  }
}

5、总结

经过体验,感觉自带的MapKit运用起来并不是很便捷,并且我期望能够点击选中地图上的爱好点,可是经过查找并没有发现任何可用的api。于是请教了ios的大佬,大佬说MapKit很少运用,由于MapKit功用不全(比方之前底子不支持路线,是近期才新增的),所以国内开发一般还是运用百度或高德。再联想这几天运用MapKit的各种问题,我决断放弃继续深化的主意。