“This is the third day of my participation in the First Challenge 2022. For details: First Challenge 2022”
preface
You can directly use the map Kit provided by Apple in ios. There are many articles on how to use MapKit in SwiftUI, but most of them are not detailed, and most of them simply show maps. So this article will explain in detail how to use MapKit functions.
The official address: developer.apple.com/documentati…
1, the Map
You can use the Map component directly in SwiftUI as follows:
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()}}Copy the code
This allows the map to be displayed directly, where the coordinate coordinate is the current map area (center point, latitude and longitude span, etc.).
But what about maps? The answer is no. Through the introduction of the official website:
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.
As you can see, this view is just a simple version that shows predefined annotations, and nothing else works.
So normally we don’t use that, we use MKMapView.
2, MKMapView
MKMapView cannot be used directly like Map because it does not inherit from View but from UIView, so it needs to be wrapped in UIViewRepresentable, as follows:
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()}}Copy the code
Also set the initial Region for MKMapView. Then we can use MapView directly in SwiftUI.
3, MKMapView some Settings
Here are some of the more common Settings
- MapType: indicates the mapType. Include standard, satellite, etc., see MKMapType enumeration.
- IsZoomEnabled: Allows zooming
- IsScrollEnabled: Whether the map can be dragged
- IsRotateEnabled: Whether the map can be rotated
- IsPitchEnabled: Whether the top view can be adjusted
- ShowsScale: Whether to display scale (only when scaling the map, it will be hidden automatically after scaling)
- ShowsCompass: Whether to display compass (compass is automatically hidden when it is due north directly above)
- ShowsUserLocation: Displays the current location (the location permission must be granted and not displayed otherwise)
- ShowsTraffic: Displays the traffic information
- ShowsBuildings: whether to display buildings (if this is true, buildings will be displayed when the camera view is not directly above. It makes no difference if the Angle is directly above)
- ShowsPointsOfInterest: Whether to show the POI (this method is no longer recommended)
Here is a brief description of the use of ios simulator, because zooming, rotating and overlooking require two-finger operation. In the simulator, you need to hold down the Option key to conduct two-finger relative operations (i.e. two points moving relative), such as zooming and rotating. Hold down the Option + Shift key to perform double-fingered operations, such as looking down or cutting the screen on the desktop.
4. Process operations on Map
Previously we showed the map, and if you want to operate on the map, such as clicking and getting a center point, you need to use a UIViewRepresentable Coordinator.
We’re going to create a class that inherits NSObject and MKMapViewDelegate, where MKMapViewDelegate is a protocol that defines some of the map manipulation functions, like
optional func mapViewDidChangeVisibleRegion(_ mapView: MKMapView)
Copy the code
Is a callback when the map’s visible range changes, such as dragging, zooming, etc. Here we make some operations to update the map, such as to obtain the center point of the map, the code is as follows:
class MapCoordinator : NSObject.MKMapViewDelegate{
func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
print(mapView.centerCoordinate)
}
}
Copy the code
MKMapViewDelegate also has a bunch of functions that correspond to different callbacks, which I’m not going to list here.
We then need to override the makeCoordinator function of UIViewRepresentable to create an object of the MapCoordinator class and return it so that it can be retrieved through context.Coordinator.
Then bind the coordinator to the map as follows:
import SwiftUI
import MapKit
struct MapView: UIViewRepresentable {
func makeCoordinator(a) -> MapCoordinator {
MapCoordinator()}typealias UIViewType = MKMapView
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator // Bind the coordinator to the 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()}}Copy the code
So when dragging or zoom will callback to mapViewDidChangeVisibleRegion, then print out the center of the current latitude and longitude.
Click on the operation
MKMapViewDelegate can only passively accept callbacks from the map, what if we do it actively? For example, if you click on a map to get the latitude and longitude of the click location (or add a map overlay), set GestureRecognizer for the map and use the coordinator to handle it.
Create a UITapGestureRecognizer and add it to the map as follows:
let gRecognizer = UITapGestureRecognizer(target: context.coordinator, action: #selector(MapCoordinator.touch(gestureReconizer:)))
mapView.addGestureRecognizer(gRecognizer)
Copy the code
The UITapGestureRecognizer action executes the MapCoordinator touch function, so we need to add a touch function to the MapCoordinator:
class MapCoordinator : NSObject.MKMapViewDelegate{
func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
print(mapView.centerCoordinate)
}
@objc func touch(gestureReconizer: UITapGestureRecognizer) {
print('click')
}
}
Copy the code
This function must add the @objc flag to indicate that it can be called by the OC, because ultimately it is actually the action of the UITapGestureRecognizer that the OC calls.
Then we click on the map and see the click printed.
Note: The @objc flag can only be used in MKMapViewDelegate. If we just add a touch function to MapView and assign it to UITapGestureRecognizer, we’ll get an error. Because UITapGestureRecognizer’s action must be a function identified by @objc, you can’t add @objc to a function in MapView. So we’re going to do it with MKMapViewDelegate.
Now we can respond to the click, but how do we get the latitude and longitude of the click? The touch function only passes in a UITapGestureRecognizer object, which gets the click location as follows:
let point = gestureReconizer.location(in: gestureReconizer.view)
Copy the code
But this position is the screen position, if you want to change the longitude and latitude also need to MKMapView, code as follows:
let point = gestureReconizer.location(in: gestureReconizer.view)
let loc = mMapView?.convert(point, toCoordinateFrom: mMapView)
Copy the code
A MKMapView object cannot be found ina MapCoordinator. I have added an initMap(_ mapView: MKMapView) function, execute this function in the makeUIView stage, pass in the MKMapView object, so that we can get the latitude and longitude, the final overall code is as follows:
import SwiftUI import MapKit struct MapView: UIViewRepresentable { func makeCoordinator() -> MapCoordinator { MapCoordinator() } typealias UIViewType = MKMapView func makeUIView(context: Context) -> MKMapView {let mapView = MKMapView() let gRecognizer = UITapGestureRecognizer context.coordinator, action: # the selector (MapCoordinator. Touch (gestureReconizer:))). MapView addGestureRecognizer (gRecognizer) / / bind coordinator mapView. Delegate = The context. The coordinator / / afferent MKMapView object context. The 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 = MKcoordinateegion (Center: LOC, SPAN: MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02) UIView.setregion (UIView.regionthatfits (region), Crystal: 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() } }Copy the code
5, summary
Through experience, I feel that the built-in MapKit is not very convenient to use, and I hope I can click to select the point of interest on the map, but I did not find any available API after searching. So I consulted the boss of ios, who said that MapKit is rarely used, because MapKit is not full of functions (for example, it does not support routes before, it is newly added recently), so the domestic development is generally using Baidu or Autonavi. Thinking about the problems with MapKit these days, I gave up the idea of going further.