I need to implement search functionality for addresses based on user input. For address suggestions I am using MKLocalSearchCompleter
. After suggestions are retrieved I need to get placemarks based on the title of the address. To get the placemarks I use CLGeocoder
function geocodeAddressString
. Since this is an async function and I need to do multiple requests, calling it separately with each address one after another is too slow, therefore I need to combine the requests and perform them at the same time. withTaskGroup
seems like a perfect way to do this, but the problem occurs that for some reason the task group simply stops without any errors or exceptions. Only the first task goes through and the Task keeps hanging forever.
I tried rewriting the same thing in multiple different ways but nothing helped. In order to make this as simple as possible I created a separate Playgrounds project just to keep this as isolated as possible. To my surprise this issue still persists! Here is the code from Playgrounds:
import CoreLocation
import MapKit
class MapManager: NSObject, MKLocalSearchCompleterDelegate {
let geocoder = CLGeocoder()
private lazy var localSearchCompleter: MKLocalSearchCompleter = {
let completer = MKLocalSearchCompleter()
completer.delegate = self
return completer
}()
func searchAddress(_ query: String) {
localSearchCompleter.queryFragment = query
}
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
Task {
let placemarks = try! await withThrowingTaskGroup(of: [CLPlacemark].self) { group in
var placemarks: [CLPlacemark] = []
print(completer.results.count) // 15
for result in completer.results {
print("Task was added to the group")
group.addTask { try! await self.geocoder.geocodeAddressString(result.title) }
}
for try await placemark in group {
print("Task appended to a list")
placemarks.append(contentsOf: placemark)
}
// This is never called
return placemarks
}
print(placemarks)
}
}
}
let manager = MapManager()
manager.searchAddress("wall street")
When searching for addresses based on query "wall street", completer returns 15 completions. I need to add 15 tasks to the task group, one for each address. Then each of those addresses are geocoded to get their placemarks. The problem is that all tasks are added to the group but only the first one is appended to the list. Here is the console output:
15
Task was added to the group
Task was added to the group
Task was added to the group
Task was added to the group
Task was added to the group
Task was added to the group
Task was added to the group
Task was added to the group
Task was added to the group
Task was added to the group
Task was added to the group
Task was added to the group
Task was added to the group
Task was added to the group
Task was added to the group
Task appended to a list
I wonder if this is an issue with CLGeocoder, MKLocalSearchCompleter, TaskGroup or I am simply doing something wrong. This seems like a really simple thing to do and it works perfectly with other mocked types and values, yet for some reason the combination of Location services and task group completely breaks the project.
Thanks for the help!
You are performing geocoding requests in parallel. Apple’s geocoding API is not intended for parallel queries. If you run them consecutively, it does not hang:
FYI, the documentation warns us:
So, having shown you how to get around the geocoding tasks not completing, you really should not be geocoding all the individual
MKLocalSearchCompleter
results
at all. As the user types, we debounce the input and then call the completer to present some text field completion options. But that is it. We should not geocode all of these.So, the user is typing and we call the completer (probably after a little debouncing). The user sees the completer UI (with no additional geocoding requests), either picks one or keeps typing, and only when they hit enter or pick one should the app then do an actual
MKLocalSearch
on the one they picked.And when you perform that final
MKLocalSearch
, the results include the detailedmapItems
with all the necessary geocoded data.Bottom line, one should decouple the completer UX from the searching UX.