获取MapKit本地搜索结果类型及解决限流问题
地址自动补全:无需MKLocalSearch获取结果类型与POI分类的优化方案
问题场景
我正在实现地址自动补全功能,已配置MKLocalSearchCompleter并设置了结果类型(包含地址和兴趣点),还指定了允许的POI分类。需要构建自定义MapSearchResult结构体,包含结果类型(.address或.pointOfInterest),如果是兴趣点则需获取对应的MKPointOfInterestCategory。
现有自定义结构体:
struct MapSearchResult: Identifiable { let id = UUID() let locationResult: MKLocalSearchCompletion let locationResultType: MKLocalSearchCompleter.ResultType let locationResultTypeCategory: MKPointOfInterestCategory? }
Completer配置:
private var searchCompleter = MKLocalSearchCompleter() private let includedPOITypes: [MKPointOfInterestCategory] = [.fitnessCenter, .foodMarket, .store] self.searchCompleter.resultTypes = MKLocalSearchCompleter.ResultType([.address, .pointOfInterest]) self.searchCompleter.pointOfInterestFilter = MKPointOfInterestFilter(including: includedPOITypes)
当前的实现是通过MKLocalSearch创建mapItem来提取类型和分类,但即使限制用户输入至少3个字符,仍因请求量过大被Apple限流。希望找到仅用Apple原生API、无需调用MKLocalSearch的解决方案。
当前触发限流的实现代码:
func createMapSearchResults(from locations: [MKLocalSearchCompletion]) async throws -> [MapSearchResult] { var mapSearchResults: [MapSearchResult] = [] try await withThrowingTaskGroup(of: MapSearchResult.self) { group in for location in locations { group.addTask { let localSearch = MKLocalSearch(request: MKLocalSearch.Request(completion: location)) do { let response = try await localSearch.start() guard let mapItem = response.mapItems.first else { throw NSError(domain: "com.example", code: 1, userInfo: [NSLocalizedDescriptionKey: "Map item not found"]) } let resultType: MKLocalSearchCompleter.ResultType = { if mapItem.pointOfInterestCategory != nil { return .pointOfInterest } else { return .address } }() let resultTypeCategory = mapItem.pointOfInterestCategory let mapSearchResult = MapSearchResult( locationResult: location, locationResultType: resultType, locationResultTypeCategory: resultType == .address ? nil : resultTypeCategory ) return mapSearchResult } catch { throw error } } } mapSearchResults = try await group.reduce(into: []) { $0.append($1) } } return mapSearchResults }
优化解决方案
方案一:iOS 16+ 原生属性直接获取(最优)
从iOS 16开始,MKLocalSearchCompletion新增了pointOfInterestCategory属性,可以直接获取POI分类,完全无需网络请求,彻底避免限流问题:
func createMapSearchResults(from locations: [MKLocalSearchCompletion]) -> [MapSearchResult] { locations.map { completion in let resultType: MKLocalSearchCompleter.ResultType let category: MKPointOfInterestCategory? if let poiCategory = completion.pointOfInterestCategory { resultType = .pointOfInterest category = poiCategory } else { resultType = .address category = nil } return MapSearchResult( locationResult: completion, locationResultType: resultType, locationResultTypeCategory: category ) } }
方案二:iOS 15及以下 解析Subtitle推断类型
对于iOS 15及更早版本,无法直接获取POI分类,但可以结合MKLocalSearchCompletion的subtitle字段与已配置的includedPOITypes进行推断:
首先建立POI分类名称与类型的映射(需根据App支持的语言补充对应本地化名称):
private let poiCategoryNameMap: [String: MKPointOfInterestCategory] = [ "健身中心": .fitnessCenter, "食品市场": .foodMarket, "商店": .store // 其他支持的语言名称可在此补充,比如英文环境下添加"Fitness Center": .fitnessCenter等 ]
然后修改结果创建函数:
func createMapSearchResults(from locations: [MKLocalSearchCompletion]) -> [MapSearchResult] { locations.map { completion in let resultType: MKLocalSearchCompleter.ResultType let category: MKPointOfInterestCategory? // 检查subtitle是否包含目标POI分类名称 let subtitleLowercased = completion.subtitle.lowercased() let matchedCategory = poiCategoryNameMap.first { key, _ in subtitleLowercased.contains(key.lowercased()) }?.value if matchedCategory != nil { resultType = .pointOfInterest category = matchedCategory } else { resultType = .address category = nil } return MapSearchResult( locationResult: completion, locationResultType: resultType, locationResultTypeCategory: category ) } }
方案说明
- 方案一完全规避了网络请求,性能和可靠性最优,建议优先针对iOS 16+版本适配
- 方案二依赖本地化文本匹配,准确性略低于原生属性,但能满足低版本系统需求,且不会触发限流
- 由于已配置
pointOfInterestFilter,返回的POI结果仅包含指定分类,因此推断的准确性会进一步提升
内容的提问来源于stack exchange,提问作者kittonian
相关产品推荐
相关产品推荐

