Пример того, как включить функциональность MapKit в ваше приложение SwiftUI
SwiftUI имеет хорошую интеграцию с существующим фреймворком UIkit. Вы можете добиться взаимодействия SwiftUI с UIKit, заключив UIViewControllers
в представления SwiftUI и наоборот; встраивать представления SwiftUI в контроллеры представлений.
В этом посте я покажу вам пример того, как включить функцию MapKit
в ваше приложение SwiftUI, обернув контроллер представления.
Цель этой демонстрации - создать представление, содержащее панель поиска и вид карты. Пользователь вводит текст в строке поиска; первый результат в списке результатов, полученных при поиске по карте, добавляется к карте в виде булавки.
Наконец, я покажу, как взаимодействовать с внешним видом, создав делегата для контроллера.
Начните с View Controller
Создайте контроллер представления и программно включите все элементы, которые вы хотите отобразить в представлении; панель поиска и вид карты:
class MapViewController: UIViewController { var mapView: MKMapView! // MARK: - Search fileprivate var searchBar: UISearchBar! fileprivate var localSearchRequest: MKLocalSearch.Request! fileprivate var localSearch: MKLocalSearch! fileprivate var localSearchResponse: MKLocalSearch.Response! // MARK: - Map variables fileprivate var annotation: MKAnnotation! fileprivate var isCurrentLocation: Bool = false var selectedPin: MKPlacemark? // MARK: - UIViewController's methods override func viewDidLoad() { super.viewDidLoad() mapView = MKMapView() let leftMargin:CGFloat = 10 let topMargin:CGFloat = 60 let mapWidth:CGFloat = view.frame.size.width-20 let mapHeight:CGFloat = view.frame.size.height - 100 mapView.frame = CGRect(x: leftMargin, y: topMargin, width: mapWidth, height: mapHeight) mapView.isZoomEnabled = true mapView.isScrollEnabled = true self.view.addSubview(mapView) searchBar = UISearchBar(frame: CGRect(x: 10, y: 0, width: mapWidth, height: 60)) searchBar.delegate = self self.view.addSubview(searchBar) mapView.delegate = self mapView.mapType = .hybrid } }
Карта Делегат
Объект MKMapView
требует, чтобы контроллер представления соответствовал MKMapViewDelegate
.
В этом примере я буду реализовывать только метод mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation)
, используемый для добавления аннотаций к карте.
Кроме того, в аннотации будет отображаться небольшая кнопка +
. Я могу, например, использовать его для добавления метки в хранилище меток. Я расскажу об этом позже.
На данный момент я просто сохраняю пустую реализацию для действия savedPin
.
extension MapViewController:MKMapViewDelegate{ func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?{ guard !(annotation is MKUserLocation) else { return nil } let reuseId = "pin" var pinView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId) as? MKPinAnnotationView if pinView == nil { pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId) } pinView?.pinTintColor = UIColor.orange pinView?.canShowCallout = true let smallSquare = CGSize(width: 30, height: 30) let button = UIButton(frame: CGRect(origin: CGPoint.zero, size: smallSquare)) button.setBackgroundImage(UIImage(systemName: "plus.square"), for: .normal) button.addTarget(self, action: #selector(savedPin), for: .touchUpInside) pinView?.leftCalloutAccessoryView = button return pinView } @objc func savedPin(){ } }
SearchBar Delegate
Строке поиска необходимо, чтобы контроллер соответствовал UISearchBarDelegate
.
Я реализую только метод searchBarSearchButtonClicked(_ searchBar: UISearchBar)
; это выполняется, когда пользователь нажимает клавишу ВВОД после ввода текста в строке поиска.
Функция поиска использует MKLocalSearch
для выполнения поиска с помощью набора карт Apple и возвращает список элементов карты. Затем я беру первую из списка и добавляю на карту булавку, содержащую метку, встроенную в этот элемент карты.
Методы addPin
и addAnnotation
помогают создать аннотацию из метки и добавить ее на карту. Наконец, showAlert
используется для отображения предупреждения, если поиск не привел к каким-либо результатам.
// MARK: - UISearchBarDelegate func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { if self.mapView.annotations.count != 0 { annotation = self.mapView.annotations[0] self.mapView.removeAnnotation(annotation) } localSearchRequest = MKLocalSearch.Request() localSearchRequest.naturalLanguageQuery = searchBar.text localSearch = MKLocalSearch(request: localSearchRequest) localSearch.start { (localSearchResponse, error) -> Void in if localSearchResponse == nil { return self.showAlert() } guard let mapItem = localSearchResponse?.mapItems.first else { return self.showAlert() } let placemark = mapItem.placemark self.addPin( placemark: placemark) self.selectedPin = placemark } } func showAlert(){ let alert = UIAlertController(title: nil, message:"Place not found", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Try again", style: .default) { _ in }) self.present(alert, animated: true){} } func addPin(placemark: MKPlacemark){ let annotation = MKPointAnnotation() annotation.coordinate = placemark.coordinate annotation.title = placemark.name if let city = placemark.locality, let state = placemark.administrativeArea { annotation.subtitle = "\(city) \(state)" } addAnnotation(annotation: annotation) } func addAnnotation( annotation:MKPointAnnotation ){ mapView.addAnnotation(annotation) let span = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05) let region = MKCoordinateRegion(center: annotation.coordinate, span: span) mapView.setRegion(region, animated: true) } }
Взаимодействие с внешним миром
Очевидно, я не могу содержать функциональность онлайн внутри этого контроллера. Представления, которые будут обертывать, а затем встраивать этот контроллер, должны запускаться, когда внутри контроллера выполняются различные действия.
Поэтому я реализую протокол делегата для взаимодействия с моим MapViewController
:
protocol MapViewDelegate{ func saveLocation(placemark: MKPlacemark) }
Впоследствии добавьте в контроллер переменную делегата:
var delegate:MapViewDelegate!
И, наконец, реализуйте метод действия savedPin()
для использования этого делегата:
@objc func savedPin(){ guard let delegate = delegate, let placemark = selectedPin else { return} delegate.saveLocation(placemark: placemark) }
Окончательная реализация контроллера
import UIKit import MapKit protocol MapViewDelegate{ func saveLocation(placemark: MKPlacemark) } class MapViewController: UIViewController { var delegate:MapViewDelegate! var mapView: MKMapView! // MARK: - Search fileprivate var searchBar: UISearchBar! fileprivate var localSearchRequest: MKLocalSearch.Request! fileprivate var localSearch: MKLocalSearch! fileprivate var localSearchResponse: MKLocalSearch.Response! // MARK: - Map variables fileprivate var annotation: MKAnnotation! fileprivate var isCurrentLocation: Bool = false var selectedPin: MKPlacemark? // MARK: - UIViewController's methods override func viewDidLoad() { super.viewDidLoad() mapView = MKMapView() let leftMargin:CGFloat = 10 let topMargin:CGFloat = 60 let mapWidth:CGFloat = view.frame.size.width-20 let mapHeight:CGFloat = view.frame.size.height - 100 mapView.frame = CGRect(x: leftMargin, y: topMargin, width: mapWidth, height: mapHeight) mapView.isZoomEnabled = true mapView.isScrollEnabled = true self.view.addSubview(mapView) searchBar = UISearchBar(frame: CGRect(x: 10, y: 0, width: mapWidth, height: 60)) searchBar.delegate = self self.view.addSubview(searchBar) mapView.delegate = self mapView.mapType = .hybrid } } extension MapViewController:MKMapViewDelegate{ func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?{ guard !(annotation is MKUserLocation) else { return nil } let reuseId = "pin" var pinView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId) as? MKPinAnnotationView if pinView == nil { pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId) } pinView?.pinTintColor = UIColor.orange pinView?.canShowCallout = true let smallSquare = CGSize(width: 30, height: 30) let button = UIButton(frame: CGRect(origin: CGPoint.zero, size: smallSquare)) button.setBackgroundImage(UIImage(systemName: "plus.square"), for: .normal) button.addTarget(self, action: #selector(savedPin), for: .touchUpInside) pinView?.leftCalloutAccessoryView = button return pinView } @objc func savedPin(){ guard let delegate = delegate, let placemark = selectedPin else { return} delegate.saveLocation(placemark: placemark) } } extension MapViewController:UISearchBarDelegate{ // MARK: - UISearchBarDelegate func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { if self.mapView.annotations.count != 0 { annotation = self.mapView.annotations[0] self.mapView.removeAnnotation(annotation) } localSearchRequest = MKLocalSearch.Request() localSearchRequest.naturalLanguageQuery = searchBar.text localSearch = MKLocalSearch(request: localSearchRequest) localSearch.start { (localSearchResponse, error) -> Void in if localSearchResponse == nil { return self.showAlert() } guard let mapItem = localSearchResponse?.mapItems.first else { return self.showAlert() } let placemark = mapItem.placemark self.addPin( placemark: placemark) self.selectedPin = placemark } } func showAlert(){ let alert = UIAlertController(title: nil, message:"Place not found", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Try again", style: .default) { _ in }) self.present(alert, animated: true){} } func addPin(placemark: MKPlacemark){ let annotation = MKPointAnnotation() annotation.coordinate = placemark.coordinate annotation.title = placemark.name if let city = placemark.locality, let state = placemark.administrativeArea { annotation.subtitle = "\(city) \(state)" } addAnnotation(annotation: annotation) } func addAnnotation( annotation:MKPointAnnotation ){ mapView.addAnnotation(annotation) let span = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05) let region = MKCoordinateRegion(center: annotation.coordinate, span: span) mapView.setRegion(region, animated: true) } }
Оберните контроллер
Теперь давайте обернем ранее созданный контроллер представления в UIViewControllerRepresentable
, который будет использоваться позже в представлениях SwiftUI. Это стандартная реализация, по примеру Apple.
Класс coordinator
, соответствующий протоколу MapViewDelegate
, реализует метод saveLocation
(пока только печатает). makeUIViewController
создает контроллер сверху и возвращает его.
После этого updateUIViewController
присоединяет Coordinator
в качестве делегата к MapViewController
.
struct MapSearchView: UIViewControllerRepresentable { class Coordinator: NSObject, MapViewDelegate { func saveLocation(placemark: MKPlacemark) { print("add placemark" ) } } func makeCoordinator() -> Coordinator { return Coordinator() } func makeUIViewController(context: UIViewControllerRepresentableContext<MapSearchView>) -> MapViewController { let mapController = MapViewController() return mapController } func updateUIViewController(_ uiViewController: MapViewController, context: UIViewControllerRepresentableContext<MapSearchView>) { uiViewController.delegate = context.coordinator }
использование
Теперь давайте воспользуемся этим представлением в примере приложения SwiftUI:
struct ContentView: View { var body: some View { MapSearchView() } }
Вот как выглядят элементы управления в моем примере приложения:
Надеюсь, вам понравилось это руководство, и вы начнете включать MapKit
в свои приложения SwiftUI.