I have a SearchBar that updates a certain Binding string that is the geocoded into a list of potential location matches. Sometimes as I type in a location, I get the following:
libswiftCore.dylib`_swift_runtime_on_report:
-> 0x1054f7180 <+0>: pushq %rbp //Error on this line says "= Thread 1: Fatal Error: Duplicate keys
of type 'GeocodedPlacemark' were found in a Dictionary. This usually means either that the type
violates Hashable's requirements, or that members of the dictionary were mutated after
insertion."
0x1054f7181 <+1>: movq %rsp, %rbp
0x1054f7184 <+4>: popq %rbp
0x1054f7185 <+5>: retq
0x1054f7186 <+6>: nopw %cs:(%rax,%rax)
Only problem is, it gives me no clue as to where the source of the error is...is there some clue in all those numbers or the 'pushq' keyword found on that line that could direct me to the dictionary it is referring to?
Side Note: This error happens maybe once in every 15 or so searches, so it's quite rare.
Search bar code is as follows:
import SwiftUI
import Mapbox
import MapboxGeocoder
struct SearchBar: View {
var annotation: AnnotationsVM
@State var searchText: String = ""
//@State var typing: Bool = false
@State private var showCancelButton: Bool = false
@ObservedObject var locationManager = LocationManager()
@ObservedObject var VModel : ViewModel
@Binding var searchedText: String
@Binding var showResults: Bool
@Binding var showMoreDetails: Bool
var mapStyle: URL
var body: some View {
let binding = Binding<String>(get: {
self.searchText
}, set: {
self.searchText = $0
self.searchedText = self.searchText
self.VModel.findResults(address: self.searchedText)
if self.VModel.searchResults.count >= 0 {
self.showResults = true
self.showMoreDetails = false
} else {
self.showResults = false
}
}
)
return VStack {
// Search view
HStack {
Image(systemName: "magnifyingglass")
TextField("search", text: binding, onEditingChanged: { isEditing in
self.showCancelButton = true
self.showMoreDetails = false
}, onCommit: {
if self.VModel.searchResults.first != nil {
self.annotation.addNextAnnotation(address: self.rowText(result: self.VModel.searchResults.first!).label)
self.searchedText = "\(self.rowText(result: self.VModel.searchResults.first!).label)"
}
self.showMoreDetails = false
self.showResults = false
})
Button(action: {
self.searchText = ""
self.showResults = false
}) {
Image(systemName: "xmark.circle.fill").opacity(searchText == "" ? 0.0 : 1.0)
}
}
.padding(EdgeInsets(top: 8, leading: 6, bottom: 8, trailing: 6))
}
if showCancelButton {
Button("Cancel") {
UIApplication.shared.endEditing(true) // this must be placed before the other commands here
self.searchText = ""
self.showResults = false
self.showCancelButton = false
}
}
.padding(.horizontal)
}
}
private func rowText(result: GeocodedPlacemark) -> (view: Text, label: String) {
// city is not nil
// state is not nil
// country is not nil
if result.postalAddress != nil && result.postalAddress?.city != "" && result.postalAddress?.state != "" && result.postalAddress?.country != "" {
return (Text("\(result.formattedName), \(result.postalAddress!.city), \(result.postalAddress!.state), \(result.postalAddress!.country)"), "\(result.formattedName), \(result.postalAddress!.city), \(result.postalAddress!.state), \(result.postalAddress!.country)")
}
// city is not nil
// state is not nil
// country is nil
else if result.postalAddress != nil && result.postalAddress?.city != "" && result.postalAddress?.state != "" && result.postalAddress?.country == "" {
return (Text("\(result.formattedName), \(result.postalAddress!.city), \(result.postalAddress!.state)"), "\(result.formattedName), \(result.postalAddress!.city), \(result.postalAddress!.state)")
}
// city is not nil
// state is nil
// country is nil
else if result.postalAddress != nil && result.postalAddress?.city != "" && result.postalAddress?.state == "" && result.postalAddress?.country == "" {
return (Text("\(result.formattedName), \(result.postalAddress!.city)"), "\(result.formattedName), \(result.postalAddress!.city)")
}
//More if statements to cover all the different states, this section essentially just returns the way to format the different search results in the search results view (that results view works fine btw)
}
extension UIApplication {
func endEditing(_ force: Bool) {
self.windows
.filter{$0.isKeyWindow}
.first?
.endEditing(force)
}
}
struct ResignKeyboardOnDragGesture: ViewModifier {
var gesture = DragGesture().onChanged{_ in
UIApplication.shared.endEditing(true)
}
func body(content: Content) -> some View {
content.gesture(gesture)
}
}
extension View {
func resignKeyboardOnDragGesture() -> some View {
return modifier(ResignKeyboardOnDragGesture())
}
}
The VModel class is as follows:
class ViewModel: ObservableObject {
@ObservedObject var locationManager = LocationManager()
@Published var lat: Double?
@Published var lon: Double?
@Published var location: CLLocationCoordinate2D?
@Published var name: CLPlacemark?
@Published var searchResults: [GeocodedPlacemark] = []
var userLatitude: CLLocationDegrees {
return (locationManager.lastLocation?.latitude ?? 0)
}
var userLongitude: CLLocationDegrees {
return (locationManager.lastLocation?.longitude ?? 0)
}
func getLocation(from address: String, completion: @escaping (_ location: CLLocationCoordinate2D?)-> Void) {
//let geocoder = CLGeocoder()
let geocoder = Geocoder(accessToken: "pk.eyJ1Ijoibmlja2JyaW5zbWFkZSIsImEiOiJjazh4Y2dzcW4wbnJyM2ZtY2V1d20yOW4wIn0.LY1H3cf7Uz4BhAUz6JmMww")
let foptions = ForwardGeocodeOptions(query: address)
print("hit this point")
foptions.focalLocation = CLLocation(latitude: userLatitude, longitude: userLongitude)
geocoder.geocode(foptions) { (placemarks, attribution ,error) in
guard let placemarks = placemarks,
let location = placemarks.first?.location?.coordinate
else {
completion(nil)
return
}
completion(location)
}
}
func fetchCoords(address: String, completion: @escaping (Double, Double) -> Void){
self.getLocation(from: address) { coordinates in
print(coordinates ?? 0) // Print here
self.location = coordinates // Assign to a local variable for further processing
if let lat = coordinates?.latitude, let lon = coordinates?.longitude {
completion(lat, lon)
}
}
}
func findResults(address: String) {
let geocoder = Geocoder(accessToken: "pk.eyJ1Ijoibmlja2JyaW5zbWFkZSIsImEiOiJjazh4Y2dzcW4wbnJyM2ZtY2V1d20yOW4wIn0.LY1H3cf7Uz4BhAUz6JmMww")
let foptions = ForwardGeocodeOptions(query: address)
foptions.focalLocation = CLLocation(latitude: userLatitude, longitude: userLongitude)
foptions.maximumResultCount = 10
geocoder.geocode(foptions) { (placemarks, attribution ,error) in
guard let placemarks = placemarks
else {
return
}
self.searchResults = []
for placemark in placemarks {
self.searchResults.append(placemark)
}
}
}
}
After setting a Swift Error Breakpoint, it stopped the search in this function (I guess this is a backend MapBox function, because I certainly didn't write it; maybe it comes with the framework?):
fileprivate func dataTaskWithURL(_ url: URL, completionHandler: @escaping (_ data: Data?) -> Void, errorHandler: @escaping (_ error: NSError) -> Void) -> URLSessionDataTask {
var request = URLRequest(url: url)
request.setValue(userAgent, forHTTPHeaderField: "User-Agent")
return URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else {
DispatchQueue.main.async {
if let e = error as NSError? {
errorHandler(e)
} else {
let unexpectedError = NSError(domain: MBGeocoderErrorDomain, code: -1024, userInfo: [NSLocalizedDescriptionKey : "unexpected error", NSDebugDescriptionErrorKey : "this error happens when data task return nil data and nil error, which typically is not possible"])
errorHandler(unexpectedError)
}
}
return
}
let decoder = JSONDecoder()
do {
// Handle multiple batch geocoding queries, THE ERROR IS ON THE LINE BELOW and says 'Thread 19: breakpoint 1.1'
let result = try decoder.decode([GeocodeAPIResult].self, from: data)
// Check if any of the batch geocoding queries failed
if let failedResult = result.first(where: { $0.message != nil }) {
let apiError = Geocoder.descriptiveError(["message": failedResult.message!], response: response, underlyingError: error as NSError?)
DispatchQueue.main.async {
errorHandler(apiError)
}
return
}
DispatchQueue.main.async {
completionHandler(data)
}
} catch {
// Handle single & single batch geocoding queries
do {
let result = try decoder.decode(GeocodeAPIResult.self, from: data)
// Check if geocoding query failed
if let message = result.message {
let apiError = Geocoder.descriptiveError(["message": message], response: response, underlyingError: error as NSError?)
DispatchQueue.main.async {
errorHandler(apiError)
}
return
}
DispatchQueue.main.async {
completionHandler(data)
}
} catch {
// Handle errors that don't return a message (such as a server/network error)
DispatchQueue.main.async {
errorHandler(error as NSError)
}
}
}
}
}
I'm going to recommend first to update the iOS Mapbox
and MapBox Geocoder
SDKs to the latest versions - sometimes these updates fix outstanding bugs in the frameworks.
Next, I'd recommend to wrap the error-causing geocode
lines in synchronous DispatchQueue
blocks, like this:
func getLocation(from address: String, completion: @escaping (_ location: CLLocationCoordinate2D?)-> Void) {
//let geocoder = CLGeocoder()
let geocoder = Geocoder(accessToken: "pk.eyJ1Ijoibmlja2JyaW5zbWFkZSIsImEiOiJjazh4Y2dzcW4wbnJyM2ZtY2V1d20yOW4wIn0.LY1H3cf7Uz4BhAUz6JmMww")
let foptions = ForwardGeocodeOptions(query: address)
print("hit this point")
foptions.focalLocation = CLLocation(latitude: userLatitude, longitude: userLongitude)
DispatchQueue.global().sync {
geocoder.geocode(foptions) { (placemarks, attribution ,error) in
guard let placemarks = placemarks,
let location = placemarks.first?.location?.coordinate
else {
completion(nil)
return
}
completion(location)
}
}
}
func findResults(address: String) {
let geocoder = Geocoder(accessToken: "pk.eyJ1Ijoibmlja2JyaW5zbWFkZSIsImEiOiJjazh4Y2dzcW4wbnJyM2ZtY2V1d20yOW4wIn0.LY1H3cf7Uz4BhAUz6JmMww")
let foptions = ForwardGeocodeOptions(query: address)
foptions.focalLocation = CLLocation(latitude: userLatitude, longitude: userLongitude)
foptions.maximumResultCount = 10
DispatchQueue.global().sync {
geocoder.geocode(foptions) { (placemarks, attribution ,error) in
guard let placemarks = placemarks else {
return
}
self.searchResults = []
for placemark in placemarks {
self.searchResults.append(placemark)
}
}
}
}
If this doesn't fix the issue, then I'd recommend viewing the various threads in the stack frame in Xcode when the Swift Error breakpoint is raised - you can do this on the left hand panel by tapping on the different thread names. See this:
How to select a thread:
Once you can see the individual lines of code for each thread (tapping each thread on the left, shows this for each of your files), you can add the same DispatchQueue.global().sync { }
blocks around the conflicting access lines in your code for every single relevant thread. I outline how to select where to place these blocks now.
If you see in the image, for each thread, the call stack is listed from bottom to top. You only need to add the DispatchQueue.global().sync { }
block around one line where the data
variable is being accessed. But, if you are accessing the data
variable inside a completion block (like your geocode functions) then the DispatchQueue.global().sync { }
needs to go around the whole function.
How to select an error line in Thread 1
:
Hope this helps! :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With