SwiftUI - MKMapView - Display map center coordinate in a #EnvironmentObject variable - swiftui

can someone please help me with my problem?
I have a SwiftUI Project and wrote a MKMapView File. I would like to store the Center coordinates in a #EnvironmentObject variable.
Unfortunately, I can't manage to pack the center coordinates into the EnvironmentObject variable.
my code looks like this:
struct MapView: UIViewRepresentable {
#EnvironmentObject var messpunkt_manager: Messpunkt_Manager
func makeUIView(context: Context) -> MKMapView {
let map = MKMapView()
map.showsUserLocation = true // zeigt den aktuellen Standort des Users
map.delegate = context.coordinator // ermöglich die Bedienung
// shows annotations already in array
for location in messpunkt_manager.messpunkte {
let annotation = MKPointAnnotation()
annotation.title = location.title
annotation.coordinate = CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude)
map.addAnnotation(annotation)
}
return map
}
func updateUIView(_ uiView: MKMapView, context: UIViewRepresentableContext<MapView>) {
// start coordinates
let coordinate = CLLocationCoordinate2D(latitude: messpunkt_manager.centerCoordinate.latitude, longitude: messpunkt_manager.centerCoordinate.longitude)
let span = MKCoordinateSpan(latitudeDelta: messpunkt_manager.centerZoom.latitudeDelta, longitudeDelta: messpunkt_manager.centerZoom.longitudeDelta)
let region = MKCoordinateRegion(center: coordinate, span: span)
uiView.setRegion(region, animated: true)
// Adds annotation to the map
for location in messpunkt_manager.messpunkte {
let annotation = MKPointAnnotation()
annotation.title = location.title
annotation.coordinate = CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude)
uiView.addAnnotation(annotation)
}
if messpunkt_manager.karteAusgeaehlt == 0 {
uiView.mapType = .standard
}
if messpunkt_manager.karteAusgeaehlt == 1 {
uiView.mapType = .hybrid
}
if messpunkt_manager.karteAusgeaehlt == 2 {
uiView.mapType = .satellite
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
the center coordinates should then be put into this #EnvironmentObject variable
#Published var position = CLLocationCoordinate2D()
thanks for your help!
And sorry for my naming. For me its easier to name the variables, functions ... in my native language German. I hope its not a big deal.

In your class Coordinator you need to set the value. Assuming your coordinator is set up like this:
class Coordinator: NSObject, MKMapViewDelegate {
var parent: MapView
init(_ parent: MapView) {
self.parent = parent
}
...
}
Then you would add this function:
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
parent. messpunkt_manager.position = mapView.region
}
It is really helpful to post a Minimal Reproducible Example (MRE). As you can see, you left out an important piece

Related

Binding array is empty when accessed through Coordinator

I am having an issue with my Coordinator. I am interacting with a MKMapView via SwiftUI. I am passing in a Binding to the UIViewRepresentable and need to access that same Binding in the Coordinator. Inside the Coordinator I determine what strokeColor to use for my polyline. When I try to access the routes Binding from my Coordinator it is always empty. When I set a breakpoint inside the MapView on the updateUIView function the binding is indeed populated.
Heres the code:
import SwiftUI
import MapKit
struct MapView: UIViewRepresentable {
var region: MKCoordinateRegion
#Binding var routes: [RouteData]
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
mapView.showsUserLocation = true
mapView.setRegion(region, animated: true)
addOverlays(mapView)
return mapView
}
func updateUIView(_ view: MKMapView, context: Context) {
addOverlays(view)
removeOverlays(view)
}
private func addOverlays(_ view: MKMapView) {
for route in routes {
for point in route.points {
let waypoints = point.waypoints
let polyline = MKPolyline(coordinates: waypoints, count: waypoints.count)
polyline.title = route.routeID
view.addOverlay(polyline)
}
}
}
private func removeOverlays(_ view: MKMapView) {
for overlay in view.overlays {
if let routeID = overlay.title!, routes.first(where: { $0.routeID == routeID }) == nil {
view.removeOverlay(overlay)
}
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
class Coordinator: NSObject, MKMapViewDelegate {
let parent: MapView
init(_ parent: MapView) {
self.parent = parent
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if let routePolyline = overlay as? MKPolyline {
let renderer = MKPolylineRenderer(polyline: routePolyline)
// Always prints route is empty even though I set a break point inside the parents' updateUIView func and the route is populated.
print("parents routes: \(self.parent.routes)")
if let title = routePolyline.title, let route = self.parent.routes.first(where: { $0.routeID == title }) {
renderer.strokeColor = UIColor(convertRGBStringToColor(color: route.route.rtclr))
} else {
renderer.strokeColor = UIColor.blue
}
renderer.lineWidth = 5
return renderer
}
return MKOverlayRenderer()
}
}
A few mistakes
#Binding var routes: [RouteData] should be let routes: [RouteData] because you don’t change it so don’t need the write access.
Coordinator(self) Should be Coordinator(), self is an old value the Coordinator should not hang on to.
Subclass MKPolyline to add your colour properties eg https://stackoverflow.com/a/44294417/259521
makeUIView Should return context.coordinator.mapView
addOverlays should only add ones that are not already added. You need to essentially implement a diff in updateUIView.
Update is called after make so no need to add overlays in make.

SwiftUI and UIKit Interoperability with displaying multiple views

Overview: I'm using SwiftUI, but wanted to use UIKit-MapKit. I used UIViewRepresentable to be able to wrap the UIKit feature.
Problem: I'm learning about swiftui-uikit-interoperability and I'm getting stuck on being able to display multiple SwiftUI views.
Code Snippet:
ContentView
struct ContentView: View {
#ObservedObject var viewModel: MapView.PinViewModel
init() {
self.viewModel = MapView.PinViewModel()
}
var body: some View {
NavigationView {
MapView()
.sheet(isPresented: $viewModel.showPinForm) {
PinForm()
}
.navigationTitle("SwiftUI UIKit Interop").scaledToFill()
}
}
}
MapView
struct MapView: UIViewRepresentable {
class PinViewModel: ObservableObject {
#Published var showPinForm: Bool
init() {
self.showPinForm = false
}
func updateShowPinVar() {
self.showPinForm = true
}
}
func showPinForm() {
pinViewModel.updateShowPinVar()
}
func makeCoordinator() -> MapViewCoordinator {
let coordinator = MapViewCoordinator()
coordinator.delegate = self
return coordinator
}
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
let coordinate = CLLocationCoordinate2D(latitude: 40.7209, longitude: -74.0007)
let span = MKCoordinateSpan(latitudeDelta: 0.03, longitudeDelta: 0.03)
let mapRegion = MKCoordinateRegion(center: coordinate, span: span)
mapView.setRegion(mapRegion, animated: true)
return mapView
}
func updateUIView(_ uiView: MKMapView, context: Context) {
}
}
In this I have a #Published var showPinForm that gets toggled in MapView. ContentView is supposed to watch this variable and when it is true it will cause the sheet to pull up. However, I believe when I enter MapView() from ContentView() then I no longer recognize ContentView.
Using the UIViewRepresentable, what is the best way to display another swiftui view? Does not have to use .sheet (Although, it would be nice)
I have tried to simplify the code to show the main problem, so I left out a lot of additional info and took out basic patterns that I used (MVVM)
Please let me know if you need any clarifications
try to follow this pattern, you can toggle the flag both inside and outside your MapView
struct MapView: UIViewRepresentable {
#Binding var switcher: Bool // -> use binding
func makeUIView(context: Context) -> MKMapView { MKMapView() }
func updateUIView(_ uiView: MKMapView, context: Context) { }
}
struct MainView: View {
#ObservedObject var viewModel = MainViewModel()
var body: some View {
MapView(switcher: $viewModel.flag)
.sheet(isPresented: $viewModel.flag) {
Text("Pin pin")
}
}
}
class MainViewModel: ObservableObject {
#Published var flag: Bool = false
}

Unexpected acting of MapKit in SwiftUI

I'm stuck with the really strange problem. I'm implementing map into my SwiftUI app. It should act like a normal map (drag, scroll and so on). When changing position (that is binding point) the app gets an address via geocoder.
Also user can click "Change" button and enter address manually (with autocompletion). After selecting the address, the map should move to the reverse geocoded point.
Built-in SwiftUI Map() is a good thing, but... it's unreal to make it show buildings. And in the app it's something that matters. So, going with UIViewRepresentable gives me another strange problem.
If I set the center coordinate in UpdateUIView, the map stops any interactivity. Otherwise changing the address manually doesn't work.
What could be wrong with this?
struct MapView: UIViewRepresentable {
#Binding var point: CLLocationCoordinate2D
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.showsBuildings = true
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ uiView: MKMapView, context: Context) {
// uiView.setCenter(point, animated: true)
}
func makeCoordinator() -> MapView.Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, MKMapViewDelegate {
var parent: MapView
init(_ parent: MapView) {
self.parent = parent
}
func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
self.parent.point = mapView.centerCoordinate
}
}
}
I tried wrapping everything into DispatchQueue.main.async {} - not working (and honestly I don't think it could)
I also tried this solution, but it worked neither: https://www.reddit.com/r/SwiftUI/comments/kti9r9/uiviewrepresentable_how_to_update_bindings/
I also had the same problem. I solved this using #state. So every time the mapView changes, the corresponding function of the coordinator is definitely called. Hope it helps.
struct YourView: View {
#State mapView: MKMapView = .init()
#State var point: CLLocationCoordinate2D = [...]
var body: some View {
MapView(mapView: $mapView, point: $point)
...
}
struct MapView: UIViewRepresentable {
#Binding var mapView: MKMapView
#Binding var point: CLLocationCoordinate2D
func makeUIView(context: Context) -> MKMapView {
self.mapView = MKMapView()
self.mapView.showsBuildings = true
self.mapView.delegate = context.coordinator
return self.mapView
}
...
func updateUIView(_ uiView: MKMapView, context: Context) {
// uiView.setCenter(point, animated: true)
}
func makeCoordinator() -> MapView.Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, MKMapViewDelegate {
var parent: MapView
init(_ parent: MapView) {
self.parent = parent
}
func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
self.parent.point = self.mapView.centerCoordinate
}
}
}

Show user coordinates SwiftUI

I have custom Map with pin on user location. When i need to show user location. I tried use CLLocationManager to show coordinates but they turns 0.0
struct MapView : UIViewRepresentable {
func makeCoordinator() -> MapView.Coordinator {
return MapView.Coordinator(parent1: self)
}
#Binding var title : String
#Binding var subtitle : String
func makeUIView(context: UIViewRepresentableContext<MapView>) -> MKMapView {
let map = MKMapView()
let coordinate = CLLocationCoordinate2D(latitude: 55.757485,
longitude: 37.632179)
map.region = MKCoordinateRegion(center: coordinate,
latitudinalMeters: 100,
longitudinalMeters: 100)
let annotation = MKPointAnnotation()
annotation.coordinate = coordinate
map.delegate = context.coordinator
map.addAnnotation(annotation)
return map
}
func updateUIView(_ uiView: MKMapView, context: UIViewRepresentableContext<MapView>) {}
class Coordinator: NSObject , MKMapViewDelegate {
var parent : MapView
init(parent1: MapView) {
parent = parent1
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let pin = MKPinAnnotationView(annotation: annotation,
reuseIdentifier: "pin")
pin.isDraggable = true
pin.pinTintColor = .blue
pin.animatesDrop = true
return pin
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, didChange newState: MKAnnotationView.DragState, fromOldState oldState: MKAnnotationView.DragState) {
CLGeocoder().reverseGeocodeLocation(CLLocation(latitude: (view.annotation?.coordinate.latitude)!,
longitude: (view.annotation?.coordinate.longitude)!)){ (places, err) in
if err != nil {
print((err?.localizedDescription)!)
return
}
self.parent.title = (places?.first?.name ?? places?.first?.postalCode)!
self.parent.subtitle = (places?.first?.locality ?? places?.first?.country ?? "None")
}
}
}
}
There is my start point
let coordinate = CLLocationCoordinate2D(latitude: 55.757485,
longitude: 37.632179)
So i need to add user coordinates in this stroke without asking permit as i have already asked it
All you have to do is make the showsUserLocation var true.(Look at the docs)
func makeUIView(context: UIViewRepresentableContext<MapView>) -> MKMapView {
let map = MKMapView()
let coordinate = CLLocationCoordinate2D(latitude: 55.757485, longitude: 37.632179)
map.region = MKCoordinateRegion(center: coordinate, latitudinalMeters: 100, longitudinalMeters: 100)
let annotation = MKPointAnnotation()
annotation.coordinate = coordinate
// new code...
map.showsUserLocation = true
map.delegate = context.coordinator
map.addAnnotation(annotation)
return map
}
As of iOS 14 you can use the native SwiftUI Map. This might be easier to work with than bridging to UIKit

Mapbox iOS Navigation SDK Not Working With SwiftUI

I'm getting the error:
Type 'NavView' does not conform to protocol 'UIViewRepresentable'
Here is my code. What should I do? Thanks. Using SwiftUI
struct NavView: UIViewRepresentable{
func makeUIView(context: Context) -> NavigationViewController {
var viewController: NavigationViewController
// Define two waypoints to travel between
let origin = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 38.9131752, longitude: -77.0324047), name: "Mapbox")
let destination = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 38.8977, longitude: -77.0365), name: "White House")
// Set options
let options = NavigationRouteOptions(waypoints: [origin, destination])
// Request a route using MapboxDirections.swift
Directions.shared.calculate(options) { (waypoints, routes, error) in
guard let route = routes?.first else { return }
// Pass the generated route to the the NavigationViewController
viewController = NavigationViewController(for: route)
viewController.modalPresentationStyle = .fullScreen
}
return viewController
}
func updateUIView(_ uiView: NavigationViewController, context: Context) {
}
}
For this case it should be used UIViewControllerRepresentable, like below
struct NavView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> NavigationViewController {
var viewController: NavigationViewController
// Define two waypoints to travel between
let origin = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 38.9131752, longitude: -77.0324047), name: "Mapbox")
let destination = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 38.8977, longitude: -77.0365), name: "White House")
// Set options
let options = NavigationRouteOptions(waypoints: [origin, destination])
// Request a route using MapboxDirections.swift
Directions.shared.calculate(options) { (waypoints, routes, error) in
guard let route = routes?.first else { return }
// Pass the generated route to the the NavigationViewController
viewController = NavigationViewController(for: route)
viewController.modalPresentationStyle = .fullScreen
}
return viewController
}
func updateUIViewController(_ uiViewController: NavigationViewController, context: Context) {
}
}