Creating MapView from decoded coordinates - swift - ios

I am setting up a MapView with custom coordinates. Here is my code thus far
var coordModel: AirportModel?
struct MapView: View {
#Binding private var lat: Double
#Binding private var lon: Double
private let initialLatitudinalMetres: Double = coordModel?.airportLat ?? 0
private let initialLongitudinalMetres: Double = coordModel?.airportLong ?? 0
#State private var span: MKCoordinateSpan?
init(lat: Binding<Double>, lon: Binding<Double>) {
_lat = lat
_lon = lon
}
private var region: Binding<MKCoordinateRegion> {
Binding {
let centre = CLLocationCoordinate2D(latitude: lat, longitude: lon)
if let span = span {
return MKCoordinateRegion(center: centre, span: span)
} else {
return MKCoordinateRegion(center: centre, latitudinalMeters: initialLatitudinalMetres, longitudinalMeters: initialLongitudinalMetres)
}
} set: { region in
lat = region.center.latitude
lon = region.center.longitude
span = region.span
}
}
var body: some View {
Map(coordinateRegion: region)
}
}
In the AirportModel, there is a piece of data decoded in Double form called "latitude" and "longitude." I am simply trying to pass these data into the view after they are decoded. Here is my view body:
var body: some View {
ScrollView{
MapView(lat: <#Binding<Double>#>, lon: <#Binding<Double>#>)
.frame(height: 250)
.edgesIgnoringSafeArea(/*#START_MENU_TOKEN#*/.all/*#END_MENU_TOKEN#*/)
}
I have tried putting coordModel?.latitude and coordModel?.longitude in for lat and lon, as well as setting variables above the call. I'm not quite sure where to go now.
Note: I am decoding a bunch of JSON data and every user query results in a different lat/lon pull (over 10000 potential responses). Thanks again!

Use a model that is an ObservableObject:
import Combine
import MapKit
class MapModel: ObservableObject {
var region: MKCoordinateRegion = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 48.687330584, longitude: 9.219832454),
latitudinalMeters: 1000000,
longitudinalMeters: 1000000
) {
willSet {
self.objectWillChange.send()
}
}
// MARK: - test code change center evey 10 seconds
init() {
self.timer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in
self.testIndex += 1
self.testIndex %= self.testCoordinates.count
let newCenter = self.testCoordinates[self.testIndex]
let oldSpan = self.region.span
DispatchQueue.main.async {
self.region = MKCoordinateRegion(center: newCenter, span: oldSpan)
}
}
}
let testCoordinates = [
CLLocationCoordinate2D(latitude: 48.687330584, longitude: 9.219832454), // STR
CLLocationCoordinate2D(latitude: 41.297445, longitude: 2.0832941), // BCN
]
var testIndex: Int = 0
var timer: Timer? = nil
}
then create a View that observes your model
import SwiftUI
import MapKit
struct ContentView: View {
#ObservedObject private var mapModel = MapModel()
var body: some View {
Map(coordinateRegion: $mapModel.region)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
For test reasons I used a timer that simulates updated data from a server.
If you need Annotations, add them to the model and don't forget self.objectWillChange.send() if you change them.

Related

I can't print the api I pulled on the mapmarker

I pulled the data from the mapmarker, when I print, I see all of them, but I cannot get them from the example ( quake.latitude ). Error. Even if it was a list, I had taken it by typing "(quakes) { quake in" in parentheses, but now I don't know how to do it through mapview.
struct MapAnnotationsView: View {
#State var quakes: [EarthQuake] = []
#State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 38.9520281, longitude: 35.6980142), span: MKCoordinateSpan(latitudeDelta: 30, longitudeDelta: 10))
let placeArray: [Place] = [Place(title: {quake.latitude}, coordinate: CLLocationCoordinate2D(latitude: 37.8008, longitude: 27.2465))]
var body: some View {
Map(coordinateRegion: $region, annotationItems: placeArray) { annotation in
// This makes a generic annotation that takes a View
MapAnnotation(coordinate: annotation.coordinate) {
// This is your custom view
AnnotationView(placeName: annotation.title)
}
} .onAppear {
Api().getEarthQuake { (quakes) in
self.quakes = quakes
}
}
}
}
Error code screenshot
Try this:
struct MapAnnotationsView: View {
#State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 38.9520281, longitude: 35.6980142), span: MKCoordinateSpan(latitudeDelta: 30, longitudeDelta: 10))
#State private var placeArray: [Place] = []
var body: some View {
Map(coordinateRegion: $region, annotationItems: placeArray) { annotation in
// This makes a generic annotation that takes a View
MapAnnotation(coordinate: annotation.coordinate) {
// This is your custom view
AnnotationView(placeName: annotation.title)
}
} .onAppear {
Api().getEarthQuake { (quakes) in
let tempArray = quakes.map{ quake in
Place(title: "\(quake.latitude)", coordinate: CLLocationCoordinate2D(latitude: 37.8008, longitude: 27.2465))
}
self.placeArray = tempArray
}
}
}
}
As this is not a reproducible example there might be some typos involved here which you would have to address yourself.
Explenation:
Your property placeArray cannot depend on another property. To get an array of places to provide to your map you need to create these after you loaded the earthquakes from your API and assign them. Using a #State var ensures your View gets updated.

How can I make map route instructions clickable on SwiftUI?

I have a simple map made with "pure" SwiftUI. There is a search bar to search a place and when I click the "Go" button it shows the instructions of how to go that place from a particular location. It shows it below the map, on the "Enter a destination" field. What I want to do is, I want these instructions to be clickable. When I click each of the instructions it should zoom in that particular place where the instruction takes place. Right now it's only a list of text. Is it possible to do it without using UIViewRepresentable? And how can I do it?
I tried with
.onTapGesture {
region.span = MKCoordinateSpan(latitudeDelta: region.span.latitudeDelta/2, longitudeDelta: region.span.longitudeDelta/2)
}
but it zooms in the same location on every instruction I click.
ContentView
struct Location: Identifiable {
let id = UUID()
let name: String
let coordinate: CLLocationCoordinate2D
}
struct RouteSteps: Identifiable {
let id = UUID()
let step: String
}
struct ContentView: View {
#State private var searchBar: String = ""
#State private var home = CLLocationCoordinate2D(latitude: 39.90068, longitude: 32.86081)
#State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 39.90068, longitude: 32.86081), span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05))
#State var routeSteps: [RouteSteps] = [RouteSteps(step: "Enter a destination")]
#State var annotations = [Location(name: "Ankara", coordinate: CLLocationCoordinate2D(latitude: 39.90068, longitude: 32.86081))]
var body: some View {
VStack{
HStack {
TextField("", text: $searchBar)
Button("Go") {
findNewLocation()
}
.frame(width: 35, height: 35)
.foregroundColor(Color.white)
.background(Color.blue)
.cornerRadius(5)
}.textFieldStyle(.roundedBorder).colorInvert()
Map(coordinateRegion: $region, annotationItems: annotations){ item in
MapMarker(coordinate: item.coordinate)
}.frame(width: 400, height: 300)
List(routeSteps) { r in
Text(r.step)
}
route function in ContentView
func findNewLocation(){
let searchResult = searchBar
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(searchResult, completionHandler:
{(placemarks, error) -> Void in
if((error) != nil){
print("error at geocode")
}
if let placemark = placemarks?.first {
let coordinates : CLLocationCoordinate2D = placemark.location!.coordinate
region = MKCoordinateRegion(center: coordinates, span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05))
annotations.append(Location(name: placemark.name!, coordinate: coordinates))
let request = MKDirections.Request()
request.source = MKMapItem(placemark: MKPlacemark(coordinate: home, addressDictionary: nil))
request.destination = MKMapItem(placemark: MKPlacemark(coordinate: coordinates, addressDictionary: nil))
request.requestsAlternateRoutes = false
request.transportType = .automobile
let directions = MKDirections(request: request)
directions.calculate(completionHandler: { response, error in
for route in (response?.routes)! {
self.routeSteps = []
for step in route.steps {
self.routeSteps.append(RouteSteps(step: step.instructions))
}
}
})
}
})
}
Yes, but you have to stop throwing away the data. MKRoute.Steps actually gives you all of the information you need, so first, set up RouteSteps to accept MKRoute.Steps:
struct RouteSteps: Identifiable {
let id = UUID()
let step: MKRoute.Step
}
Then, in your findNewLocation() function:
func findNewLocation(){
...
directions.calculate(completionHandler: { response, error in
// I used a guard statement to get rid of your force unwrap.
// NEVER force unwrap in a situation like this.
// Implement error handling here
guard let response = response else { return }
for route in response.routes {
// Map the MKRoute.Steps to your struct
self.routeSteps = route.steps.map( { RouteSteps(step: $0) } )
}
})
...
}
Then in the main part of your ContentView:
struct ContentView: View {
...
var body: some View {
...
List(routeSteps) { routeStep in
// Pull your instructions out here
Text(routeStep.step.instructions)
.onTapGesture {
// The MKRoute.Step contains an overlay polyline for MKMapKit.
// We can't use that in SwiftUI maps yet, but the polyline contains a
// boundingMapRect that we can use to change the region shown on the map
// with the MKCoordinateRegion(_ MKMapRect) initializer. Setting the region
// to the insturction's polyline boundingMapRect does exactly what you want.
region = MKCoordinateRegion(routeStep.step.polyline.boundingMapRect)
}
}
...
}

Swift/iOS project map doesn't show location properly

Hello everyone I'm experiencing issues with my map implementation in test Swift App. So I added view for map just like in the code below, then as preview data I passed coordinates of a random city. Then I ran live preview, and (with lot of disappointment) saw blue rectangle instead of map. What have I messed up?
import MapKit
struct MapSection: View {
private var log: Log?
#State var region: MKCoordinateRegion = MKCoordinateRegion()
private var locationCoordinate: CLLocationCoordinate2D?
init(_ log: Log) {
self.log = log
if let coordinate = log.location?.coordinate {
let locationCoordinate = CLLocationCoordinate2D(latitude: coordinate.latitude, longitude: coordinate.longitude)
self.locationCoordinate = locationCoordinate
self.region = MKCoordinateRegion(center: locationCoordinate, span: MKCoordinateSpan(latitudeDelta: 0.075, longitudeDelta: 0.075))
} else {
self.log = nil
self.locationCoordinate = nil
}
}
var body: some View {
Section(header: Text("Position on map")) {
if log != nil && locationCoordinate != nil {
Map(coordinateRegion: $region, annotationItems: [self.log!]) {
_ in MapPin(coordinate: locationCoordinate!)
}
.frame(minHeight: 500)
}
else {
HStack {
Spacer()
Text("Map Error: Position Unknown")
Spacer()
}
}
}
}
}
struct LogDetail_Previews: PreviewProvider {
static var previews: some View {
let context = PersistenceController.preview.container.viewContext
let log = Log(context: context)
log.country = Locale.current.regionCode
log.date = Date(timeIntervalSince1970: TimeInterval(100000))
log.location = CLLocation(latitude: CLLocationDegrees(52.229676), longitude: CLLocationDegrees(21.012229))
return LogDetail(log)
}
}

adding a MapMarker to MapKit in swiftUI 2

I am trying to use the new Mapkit SwiftUI view and I am able to show a map with a certain region but I can't figure how to show a Map Marker.
This is what I have:
import SwiftUI
import MapKit
struct ContentView: View {
#State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 38.8977, longitude: -77.0365), span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))
var location1 = MapMarker(coordinate: CLLocationCoordinate2D(latitude: 38.8977, longitude: -77.0365), tint: .red)
var body: some View {
Map(coordinateRegion: $region, showsUserLocation: true).edgesIgnoringSafeArea(.all)
}
}
Does anyone know how to add location1 to the Map? I found this but I have not been able to make it work
Here is a simple demo. Tested with Xcode 12 / iOS 14
import CoreLocation
import MapKit
struct Marker: Identifiable {
let id = UUID()
var location: MapMarker
}
struct DemoView: View {
#State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 38.8977, longitude: -77.0365), span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))
let markers = [Marker(location: MapMarker(coordinate: CLLocationCoordinate2D(latitude: 38.8977, longitude: -77.0365), tint: .red))]
var body: some View {
Map(coordinateRegion: $region, showsUserLocation: true,
annotationItems: markers) { marker in
marker.location
}.edgesIgnoringSafeArea(.all)
}
}
You can use MapPin(), or MapMarker() as follows:
import SwiftUI
import MapKit
struct Place: Identifiable {
let id = UUID()
let name: String
let latitude: Double
let longitude: Double
var coordinate: CLLocationCoordinate2D {
CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
}
}
struct MapView: View {
let places = [
Place(name: "Position 1", latitude: 31.21, longitude: 120.50),
Place(name: "Position 2", latitude: 31.210205, longitude: 120.52301),
Place(name: "Position 3", latitude: 31.230006, longitude: 120.54002)
]
#State private var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 31.21, longitude: 120.50),
span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2)
)
var body: some View {
Map(coordinateRegion: $region, showsUserLocation: false, annotationItems: places){ place in
MapPin(coordinate: place.coordinate)
//MapMarker(coordinate: place.coordinate)
}
}
}
'MapPin' was deprecated in iOS 16.0: Use MapMarker or MapAnnotation
import Foundation
import MapKit
struct NationalParkLocation: Codable, Identifiable {
var id = UUID()
var latitude, longitude: Double
//Computed Property
var location: CLLocationCoordinate2D {
CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
}
}
import SwiftUI
import MapKit
struct MapView: View {
#State private var region: MKCoordinateRegion = {
var mapCoordinate = CLLocationCoordinate2D.init(latitude: 6.60, longitude: 16.43)
var mapZoomLevel = MKCoordinateSpan.init(latitudeDelta: 70.0, longitudeDelta: 70.0)
var mapRegion = MKCoordinateRegion.init(center: mapCoordinate, span: mapZoomLevel)
return mapRegion
}()
let locations: [NationalParkLocation] = [
NationalParkLocation.init(latitude: -2.3333333, longitude: 34.8333333),
NationalParkLocation.init(latitude: -23.9883848, longitude: 31.5525515)
]
var body: some View {
Map(coordinateRegion: $region,showsUserLocation: true ,annotationItems: locations) { item in
// (A) MARKER: (New Style) (always static)
MapMarker(coordinate: item.location)
// (B) Custom basic annotation (it could be iteractive)
// MapAnnotation(coordinate: item.location) {
// Button {
// print("Location is", item.location)
// } label: {
// Image("job")
// .resizable()
// .scaledToFit()
// .frame(width: 50, height: 50)
// }
// } // Annotation
}
}
}
(A)MARKER:
(B)Custom basic annotation

Add single pin to Mapkit with SwiftUI

How can I add a simple pin on my map with Xcode 11 GM / SwiftUI.
My code is as follows (here it shows me the map centered with the coordinates) but I want to show there only one pin of other coordinates.
import SwiftUI
import MapKit
struct ContentView: UIViewRepresentable {
func makeUIView(context: Context) -> MKMapView {
MKMapView(frame: .zero)
}
func updateUIView(_ view: MKMapView, context: Context) {
let coordinate = CLLocationCoordinate2D(
latitude: 34.011_286, longitude: -116.166_868)
let span = MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0)
let region = MKCoordinateRegion(center: coordinate, span: span)
view.setRegion(region, animated: true)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I would appreciate any suggestions, thanks.
Update your code:
struct ContentView: UIViewRepresentable {
func makeUIView(context: Context) -> MKMapView {
MKMapView(frame: .zero)
}
func updateUIView(_ view: MKMapView, context: Context) {
// 1
view.mapType = MKMapType.standard // (satellite)
// 2
let mylocation = CLLocationCoordinate2D(latitude: -6.863190,longitude: -79.818250)
// 3
let coordinate = CLLocationCoordinate2D(
latitude: -6.864138, longitude: -79.819634)
let span = MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)
let region = MKCoordinateRegion(center: coordinate, span: span)
view.setRegion(region, animated: true)
// 4
let annotation = MKPointAnnotation()
annotation.coordinate = mylocation
annotation.title = "My Location"
annotation.subtitle = "Visit us soon"
view.addAnnotation(annotation)
}
}
Found this post looking for how to add a simple pin/marker to XCode's MapKit and SwiftUI tutorials. Sharing a workable answer for using the new Map SwiftUI View rather than the more complex MKMapView in OP's question.
import SwiftUI
import MapKit
struct Marker: Identifiable {
let id = UUID()
var location: MapMarker
}
struct MapView: View {
var coordinate: CLLocationCoordinate2D
#State private var region = MKCoordinateRegion()
#State var markers = [Marker(location: MapMarker(coordinate: CLLocationCoordinate2D(latitude: -25.342863, longitude: 131.036974), tint: .blue))]
var body: some View {
Map(coordinateRegion: $region, annotationItems: markers) { marker in
marker.location }
.onAppear {
setRegion(coordinate)
}
}
private func setRegion(_ coordinate: CLLocationCoordinate2D) {
region = MKCoordinateRegion(
center: coordinate,
span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2)
)
markers = [Marker(location: MapMarker(coordinate: coordinate, tint: .blue))]
}
}
struct MapView_Previews: PreviewProvider {
static var previews: some View {
MapView(coordinate: CLLocationCoordinate2D(latitude: -25.342863, longitude: 131.036974))
}
}

Resources