MapSearch
MapSearch 调用 MapKit 系统级地图关键字搜索。两个入口:
MapSearch.locate(options)— 一次性搜索,返回MapItem[]。MapSearch.createCompleter(options?)— 有状态的自动补全,用于输入框场景,通过 listener 回调下发建议。
两者都是纯查询 API,不需要任何系统权限。返回的坐标是 MapCoordinate,可直接
灌入视图层的 <Marker> / <Map>。
正/反向地理编码(地址 ↔ 坐标)仍走现有 Location namespace
(Location.geocodeAddress / Location.reverseGeocode)。
locate — 一次性搜索
选项
返回 — MapItem
MapItem 是顶层 opaque class(MapDirections 也返回它)。按字段名读取即可,
不要对实例做 JSON 序列化。
openInMaps(options?) — 跳转到 Apple Maps
返回值 true 表示系统已经接受了 launch 请求,当前应用切到后台,Apple Maps 接管。
JSON.stringify(item)/Object.keys(item)不会返回字段字典 ——MapItem是带 getter 的 class,不是普通对象。需要序列化时自行 spread 字段。
几何便利方法
底层转发 MapUtils.distance / MapUtils.bearing。
MapItem.forCurrentLocation()
Apple 的"当前位置"占位 MapItem。同步,不弹定位权限,也不读真实坐标 —— Apple Maps 拿到这个占位符后自己解析为用户位置。
返回的实例 isCurrentLocation === true。
选中 marker 与内置 POI —— <Map selection>
把一个 Observable<MapSelectionValue | null> 绑到 <Map selection>。observable
写入的值是一个 tagged-union:
通过 value.type 分支处理:
kind 取值为 "pointOfInterest" / "physicalFeature" / "territory" /
"unknown"。pointOfInterestCategory 跟 MapPointOfInterestCategory 同套词表
(例如 "restaurant" / "cafe"),无类别时为 null。
没有 tag 的 marker 不会参与选中。
iOS 17 限制
iOS 17 上只会触发 type: "feature" —— 点击带 tag 的 <Marker> 不会触发
selection。统一的 marker / feature 选中必须依赖 iOS 18+ 的 MapSelection<Value>。
脚本若要兼容 iOS 17,POI 选中走 feature 分支,marker 选中视为 iOS 18+ 才有的能力。
Item 选中 + Apple 原生 detail 卡 —— <Map itemSelection>
iOS 18+ 提供更上层的玩法:<Map itemSelection> 绑 Observable<MapItem | null>,
配合 <Marker item={mapItem}> + <Map itemDetailSelectionAccessory> /
<Map featureSelectionAccessory>,让 Apple 自动弹原生 detail 卡。
- 点
<Marker item>会把同一个MapItem实例写进 observable。JS 端用===比对 找选中项(SwiftUI Map 走对象身份)。 - Apple 自动弹卡:
itemDetailSelectionAccessory—— 点 item marker 时弹(地址 / 电话 / 路线按钮等)。featureSelectionAccessory—— 点 Apple 内置 POI label 时弹。
- 风格三选一:
"automatic"(MapKit 自己挑 callout / sheet)、"callout"、"sheet"; 传null或不传则不弹。
嵌套呈现注意:
"automatic"与"sheet"走 modal sheet 呈现 (MKPresentableSelectionAccessoryViewController)。在Navigation.present(...)这类已经是 modal 上下文里再嵌套 sheet,iOS 18 当前会抛Attempt to present ... which is already presenting,严重时会把外层 modal 一起 dismiss 掉。所以 Map 处于已 present 的页面(sheet / Navigation.present) 里时,用"callout"(inline 气泡,不走 modal 呈现链)更稳。
itemSelection 跟 selection 互斥 —— 都传时 itemSelection 优先,字符串-tag marker
不会触发 selection。
iOS 17 限制
itemSelection / itemDetailSelectionAccessory / featureSelectionAccessory 都是
iOS 18+ API。iOS 17 上这些 prop 静默忽略 —— 地图正常渲染,marker 显示,但点击
不会写 observable,也不弹 Apple 卡片。
取消语义
locate 不提供 cancel handle。输入框 typeahead 场景请用 createCompleter —
连续 locate 调用没有去重,快速输入会看到旧结果晚到覆盖新结果。
createCompleter — 自动补全
选项
方法
生命周期
一个 completer 对应一个输入框 — 跨字段复用会因为底层 queryFragment 共享导致
结果交叉污染。字段卸载时调 dispose()。
建议时效
MapSearchCompletion 的 id 仅在下一批建议产出前有效。对过期建议调用
resolve 会以 "unknown completion id" 拒绝。在 React-style UI 中,把整批
建议跟用户选中项一起存到 state 里,保证选中的 id 跟它所属的批次配对。
与 <Map> 联动
把整个 MapItem 直接传给 <Marker>,MapKit 自动用 item 的 name 当 title、
coordinate 当坐标、根据 POI 类别选默认 glyph:
如果再传了 title / systemImage / monogram,marker 会退回默认 pin 或者你
指定的 glyph,并使用你的覆盖值 —— auto-glyph 只在这些都没传时生效。item 与
coordinate 在类型层面互斥,同时传会编译报错。
让地图自动框住整组结果,配合 MapUtils.regionFromCoordinates:
错误
locate reject 条件:
query缺失或为空- 底层
MKLocalSearch失败(网络异常 / 无结果等)
completer.resolve reject 条件:
- completion id 已过期(下一批建议产出后)
- 底层 lookup 失败
completer 的 listener 不会同步拿到错误;底层失败时 listener 会收到空数组。
