You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

获取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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 19:12:38