SwiftUI - interacting with UIKit - part1: map view
SwiftUI comes with good integration with existing UIKit framework. It is possible to achieve SwiftUI interaction with UIKit by wrapping UIViewControllers into SwiftUI views and the other way around, embed SwiftUI views into view controllers. In this post, I will show you an example of how to include the MapKit functionality in your SwiftUI app, wrapping a view controller.
The purpose of this demo is to create a view, containing a search bar and a map view. The user types in a place in the search bar; the first result in the list of results retrieved by the map search is added to the mapas a pin. Finnaly , I will show how to interact with the outside view by creating a delegate for the controller.
programatically include all the elements that I want to show in my view: a search bar and a map view:
savedPin action.
searchBar : UISearchBar) method; this is executed when the user presses Enter after typing a text in the search bar. The search function uses MKLocalSearch for performing a search using the apple map kit and returns a list of map items. Subsequently, I am taking the first one from the list and add a pin to the map containing the placemark embedded in this map item. addPin and addAnnotation methods are just helpers for creating an annotation from a placemark and adding it to the map. Finally, showAlert is used to display an alert if the search did not lead to any results.
subsequently, add a delegate variable in my Controller:
and finally , implement the savedPin() action method to use this delegate:
saveLocation method (just prints for now) . makeUIViewController is creating the controller from above and return it. After that, updateUIViewController is attaching the Coordinator as a delegate to the MapViewController.
Here is how the controls are looking like in my sample application:
I hope you enjoyed this tutorial and start including MapKit in your SwiftUI applications. You can read more SwiftUI posts here.
The purpose of this demo is to create a view, containing a search bar and a map view. The user types in a place in the search bar; the first result in the list of results retrieved by the map search is added to the map
1. Start with a view controller
I create a view controller and I class MapViewController: UIViewController {
var mapView: MKMapView!
fileprivate var searchBar: UISearchBar!
fileprivate var localSearchRequest: MKLocalSearch.Request!
fileprivate var localSearch: MKLocalSearch!
fileprivate var localSearchResponse: MKLocalSearch.Response!
fileprivate var annotation: MKAnnotation!
fileprivate var isCurrentLocation: Bool = false
var selectedPin: MKPlacemark?
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
}
}
2. Map delegate
The MKMapView object requires that the view controller conforms to MKMapViewDelegate. In this example, I will only implement the mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) method, used for adding annotations to the map. In addition, the annotation will show a small + button; I can, for example, use it for adding the placemark to a placemark repository. I will talk about it later. For the moment, I just keep an empty implementation for 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(){
}
}
3. SearchBar delegate
The Search bar needs the controller to conform with UISearchBarDelegate. I only implement the searchBarSearchButtonClicked(_ extension MapViewController: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)
}
}
4. interact with outside world
Obviously, I cannot contain the functionality online inside this controller. The views that will wrap and later embed this controller need to be triggered when various actions are performed inside the controller. Therefore, I am implementing a delegate protocol for interacting with my MapViewController: protocol MapViewDelegate{
func saveLocation(placemark: MKPlacemark)
}
subsequently, add a delegate variable in my Controller:
var delegate:MapViewDelegate!
and finally , implement the savedPin() action method to use this delegate:
@objc func savedPin(){
guard let delegate = delegate, let placemark = selectedPin else { return}
delegate.saveLocation(placemark: placemark)
}
5. Final implementation of the controller
import UIKit
import MapKit
protocol MapViewDelegate{
func saveLocation(placemark: MKPlacemark)
}
class MapViewController: UIViewController {
var delegate:MapViewDelegate!
var mapView: MKMapView!
fileprivate var searchBar: UISearchBar!
fileprivate var localSearchRequest: MKLocalSearch.Request!
fileprivate var localSearch: MKLocalSearch!
fileprivate var localSearchResponse: MKLocalSearch.Response!
fileprivate var annotation: MKAnnotation!
fileprivate var isCurrentLocation: Bool = false
var selectedPin: MKPlacemark?
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{
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)
}
}
6. wrap the controller
Now let’s wrap the previously created view controller into a UIViewControllerRepresentable to be used later in SwiftUI views. This is a standard implementation, following the apple example. A coordinator class conforming to MapViewDelegate protocol is implementing the 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
}
}
7. Usage
Now lets use this view in a sample SwiftUI application: struct ContentView: View {
var body: some View {
MapSearchView()
}
}
Here is how the controls are looking like in my sample application:
I hope you enjoyed this tutorial and start including MapKit in your SwiftUI applications. You can read more SwiftUI posts here.
Betway Casino Sports Betting Odds - Online Gaming
ReplyDeleteBetway Mobile Casino - Online Gaming. Betway Mobile Casino. Betway Mobile jordan 18 retro clearance Casino. Betway Mobile Casino. betway Mobile 골인 벳 먹튀 Casino. 출장샵 Betway Mobile air jordan 20 shoes clearance Casino. Betway 바카라 양빵 Mobile