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
Related
I want to use MapKit along with MapAnnotation in SwiftUI. So I can have custom views as map pins and add tap gestures to them later.
But when I run the app, Xcode starts to show so many UI errors:
Publishing changes from within view updates is not allowed, this will cause undefined behavior.
The Code is as simple as we can find, even in apple documentation or the ones below from hacking with swift
import SwiftUI
import MapKit
struct Location: Identifiable {
let id = UUID()
let name: String
let coordinate: CLLocationCoordinate2D
}
struct MapView: View {
#State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 51.5, longitude: -0.12), span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2))
let locations = [
Location(name: "Buckingham Palace", coordinate: CLLocationCoordinate2D(latitude: 51.501, longitude: -0.141)),
Location(name: "Tower of London", coordinate: CLLocationCoordinate2D(latitude: 51.508, longitude: -0.076))
]
var body: some View {
Map(coordinateRegion: $region, annotationItems: locations) { location in
MapAnnotation(coordinate: location.coordinate) {
Circle()
.stroke(.red, lineWidth: 3)
.frame(width: 44, height: 44)
}
}
.navigationTitle("Map")
.edgesIgnoringSafeArea(.all)
}
}
Would you happen to have any suggestions or workaround?
Can we use MapKit with these huge errors in a Production Release?
UPDATE
Based on recent answer, As a workaround, using a specific Binding<MKCoordinateRegion>() for the coordinateRegion, removes the warning/error.
But if you want to move the map based on the selected pin, those errors/warnings will appear again.
struct MapView: View {
#State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 51.5, longitude: -0.12), span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2))
let locations = [
Location(name: "Buckingham Palace", coordinate: CLLocationCoordinate2D(latitude: 51.501, longitude: -0.141)),
Location(name: "Tower of London", coordinate: CLLocationCoordinate2D(latitude: 51.508, longitude: -0.076))
]
#State var selectedLocation: Location?
var body: some View {
ZStack {
Map(coordinateRegion: Binding<MKCoordinateRegion>(
get: { region },
set: { _ in }
), annotationItems: locations) { location in
MapAnnotation(coordinate: location.coordinate) {
Circle()
.stroke(.red, lineWidth: 3)
.frame(width: 44, height: 44)
.onTapGesture {
selectedLocation = location
}
}
}
}
.onChange(of: selectedLocation) { newValue in
if let location = newValue {
withAnimation {
region = MKCoordinateRegion(center: location.coordinate, span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2))
}
}
}
.edgesIgnoringSafeArea(.all)
}
}
As a workaround, using a specific Binding<MKCoordinateRegion>() for the coordinateRegion, removes the warning/error for me.
struct MapView: View {
#State var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 51.5, longitude: -0.12), span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2))
let locations = [
Location(name: "Buckingham Palace", coordinate: CLLocationCoordinate2D(latitude: 51.501, longitude: -0.141)),
Location(name: "Tower of London", coordinate: CLLocationCoordinate2D(latitude: 51.508, longitude: -0.076))
]
var body: some View {
Map(coordinateRegion: Binding<MKCoordinateRegion>(
get: { region },
set: { _ in }
), annotationItems: locations) { location in
MapAnnotation(coordinate: location.coordinate) {
Circle().stroke(.red, lineWidth: 3).frame(width: 44, height: 44)
}
}
.navigationTitle("Map")
.edgesIgnoringSafeArea(.all)
}
}
I'm new to swiftUI and I made this view where you see a maps with different pins on it. I wanted it so when I click the pin it shows a little box above it with the info about the place (name, address)
I found other threads about it but I couldn't really find what I needed.
Anybody who could help me?
import SwiftUI
import MapKit
struct MyAnnotationItem: Identifiable {
var coordinate: CLLocationCoordinate2D
let id = UUID()
}
struct MapView: View {
#State var coordinateRegion: MKCoordinateRegion = {
var newRegion = MKCoordinateRegion()
newRegion.center.latitude = 50.80580148204638
newRegion.center.longitude = 3.2842106137743308
newRegion.span.latitudeDelta = 0.2
newRegion.span.longitudeDelta = 0.2
return newRegion
}()
var annotationItems: [MyAnnotationItem] = [
//castel motors
MyAnnotationItem(coordinate: CLLocationCoordinate2D(latitude: 50.690042667142485, longitude: 3.1734146440963777)),
//leon saggaert
MyAnnotationItem(coordinate: CLLocationCoordinate2D(latitude: 50.94343953661561, longitude: 3.2940420541585396)),
//R4 motos
MyAnnotationItem(coordinate: CLLocationCoordinate2D(latitude: 51.09466176530236, longitude: 3.7763867274168774)),
//garden tools
MyAnnotationItem(coordinate: CLLocationCoordinate2D(latitude: 50.78567261592435, longitude: 3.2141870141679365)),
//desmet roland
MyAnnotationItem(coordinate: CLLocationCoordinate2D(latitude: 50.87668047058957, longitude: 3.442740743366995)),
//r trac
MyAnnotationItem(coordinate: CLLocationCoordinate2D(latitude: 50.853492889583116, longitude: 3.7401561581010645))
]
var body: some View {
VStack {
Map(coordinateRegion: $coordinateRegion,
annotationItems: annotationItems) {item in
MapMarker(coordinate: item.coordinate)
}
}
.ignoresSafeArea()
}
}
struct Previews_SwiftUIMap_Previews: PreviewProvider {
static var previews: some View {
MapView()
}
}
I made the view with the pins, now I only need them to be clickable
My app now
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.
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.
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))
}
}