Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error when I geocode an address (rare) in Swift

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)
                }
            }
        }
    }
}
like image 315
nickcoding Avatar asked Nov 06 '22 07:11

nickcoding


1 Answers

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: select different 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: error line selection

Hope this helps! :)

like image 136
Pranav Kasetti Avatar answered Nov 15 '22 06:11

Pranav Kasetti