I am displaying a map in a view showing a pin location of an address.
Here's what I've got:
import Combine
import CoreLocation
import MapKit
import SwiftUI
struct MapViewLocation: Identifiable {
let id = UUID()
var coordinate: CLLocationCoordinate2D
}
struct MapViewSmall: View {
@State var address: String
@State private var mapCoordinate: CLLocationCoordinate2D? = nil
@State private var mapIsLoading: Bool = true
@State private var mapLocations: [MapViewLocation] = []
@State private var mapRegion: MKCoordinateRegion = MKCoordinateRegion()
@State private var mapSuccess: Bool = false
var body: some View {
ZStack {
if mapIsLoading {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
.scaleEffect(0.8)
} else {
if self.mapSuccess {
Map(
coordinateRegion: $mapRegion,
interactionModes: [.pan],
annotationItems: mapLocations,
annotationContent: { location in
MapPin(
coordinate: self.mapCoordinate ?? CLLocationCoordinate2D(),
tint: .red
)
}
)
.frame(width: 100, height: 100)
.cornerRadius(9)
.onTapGesture {
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = self.address
let search = MKLocalSearch(request: request)
search.start { response, error in
if let response = response {
let mapItem = MKMapItem(placemark: response.mapItems[0].placemark)
mapItem.name = self.address
mapItem.openInMaps(launchOptions: [MKLaunchOptionsDirectionsModeKey : MKLaunchOptionsDirectionsModeDriving])
}
}
}
} else {
Image(systemName: "mappin.slash.circle")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 100, height: 100)
.foregroundColor(.red)
}
}
}
.onAppear {
self.mapIsLoading = true
Task {
await self.makeMap()
}
}
}
func getLocation(address: String, completion: @escaping (_ location: CLLocationCoordinate2D?) -> Void) {
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(address) { (placemarks, error) in
if let error = error {
print(error.localizedDescription)
completion(nil)
return
}
guard let placemarks = placemarks,
let location = placemarks.first?.location?.coordinate else {
completion(nil)
return
}
completion(location)
}
}
func makeMap() async {
// Convert the address
self.getLocation(address: self.address) { location in
// Handle Bad Address Errors
if location == nil {
self.mapSuccess = false
self.mapIsLoading = false
return
}
self.mapCoordinate = CLLocationCoordinate2D(
latitude: location?.latitude ?? 0.0,
longitude: location?.longitude ?? 0.0
)
// Set the Region
self.mapRegion = MKCoordinateRegion(
center: self.mapCoordinate ?? CLLocationCoordinate2D(),
span: MKCoordinateSpan(
latitudeDelta: 0.01,
longitudeDelta: 0.01
)
)
// Create the MapViewLocation
self.mapLocations.append(
MapViewLocation(
coordinate: self.mapCoordinate ?? CLLocationCoordinate2D()
)
)
// Set the Loading State
self.mapSuccess = true
self.mapIsLoading = false
}
}
}
struct TestView: View {
var body: some View {
MapViewSmall(address: "1 Infinite Loop, Cupertino CA 95014, United States")
}
}
The errors in the console when I go to the view are:
2023-01-05 16:22:05.512925-0600 BLAH[14113:3111741] [Client] {"msg":"#NullIsland Received a latitude or longitude from getLocationForBundleID that was exactly zero", "latIsZero":0, "lonIsZero":0, "location":{"floor":2147483647,"lifespan":-1,"rawLat":0,"integrity":0,"referenceFrame":"Unknown","lon":0,"speed":-1,"type":"Unknown","altitude":0,"rawCourse":-1,"confidence":0,"suitability":{"type":"decode failure","raw value":1797990240,"expected type":"CLClientLocationSuitability"},"ellipsoidalAltitude":0,"timestamp":-1,"rawReferenceFrame":"Unknown","lat":4.9406564584124654e-324,"verticalAccuracy":-1,"rawLon":0,"horizontalAccuracy":-1,"speedAccuracy":-1,"courseAccuracy":-1,"fromSimulationController":false,"course":-1}}
2023-01-05 16:22:05.588983-0600 BLAH[14113:3111741] [core] "Error returned from daemon: Error Domain=com.apple.accounts Code=7 "(null)""
2023-01-05 16:22:05.875299-0600 BLAH[14113:3111397] [PipelineLibrary] Mapping the pipeline data cache failed, errno 22
I also tried rewriting the functions using Combine to see if that helped and nothing changed so I reverted back.
This all works and the user experience is great, but I would very much like to resolve the errors and understand what I'm doing wrong.
CodePudding user response:
Remove the Task wrapper around the makeMap() function, because it is not necessary.
Add a onLongPressGesture modifier to the Map view, so that it can handle long press gestures.
Wrap the makeMap() function in a DispatchQueue.main.async block, to ensure that it is called on the main thread. This should fix the "Mapping the pipeline data cache failed, errno 22" error.
.onAppear {
self.mapIsLoading = true
DispatchQueue.main.async {
self.makeMap()
}
}
CodePudding user response:
The error you're seeing is nothing to do with displaying a map, it's generated when doing the geocoding. It seems like the geocoder likes to know where you are when it is geocoding, presumably so it can order results somehow. If your app asks for in-use location permissions, the warning goes away.
If you get console warnings you don't understand, it's always a good idea to set a breakpoint and step through the code to find out exactly where the error is generated.
Incidentally, you can rewrite getLocation
like this:
func getLocation(address: String) async -> CLLocationCoordinate2D? {
let geocoder = CLGeocoder()
do {
let marks = try await geocoder.geocodeAddressString(address)
return marks.first?.location?.coordinate
} catch {
return nil
}
}
And makeMap()
like this:
@MainActor
func makeMap() async {
// Convert the address
let location = await getLocation(address: self.address)
// Handle Bad Address Errors
guard let location = location else {
self.mapSuccess = false
self.mapIsLoading = false
return
}
self.mapCoordinate = location
// Set the Region
self.mapRegion = MKCoordinateRegion(
center: location,
span: MKCoordinateSpan(
latitudeDelta: 0.01,
longitudeDelta: 0.01
)
)
// Create the MapViewLocation
self.mapLocations.append(MapViewLocation(coordinate: location))
// Set the Loading State
self.mapSuccess = true
self.mapIsLoading = false
}
Call it all from .task
instead of onAppear
:
.task {
await self.makeMap()
}
Which makes your code a little cleaner.