示例代码均为匿名化改写,用于说明模式;地图与地理编码依赖由调用方注入。
地图页存在多份候选状态
地图位置选择页面同时处理已有地区、地图中心、附近候选、搜索结果和用户点选项。地图移动可能触发附近查询;搜索期间的镜头变化不应覆盖搜索结果;切换城市则需要清理旧地区的候选和搜索状态。
每次异步查询都分配新的请求标识。只有仍属于当前请求、且没有取消的响应才能更新候选列表。附近查询和关键词搜索使用不同依赖入口,便于分别处理加载状态、错误和结果。
@MainActor
final class SearchResultsModel<Item: Sendable> {
private(set) var items: [Item] = []
private(set) var error: Error?
private(set) var isLoading = false
private var requestID = UUID()
func search(
_ query: String,
using lookup: (String) async throws -> [Item]
) async {
let currentID = UUID()
requestID = currentID
error = nil
isLoading = true
defer {
if requestID == currentID { isLoading = false }
}
do {
let result = try await lookup(query)
guard !Task.isCancelled, requestID == currentID else { return }
items = result
} catch let lookupError {
guard !Task.isCancelled, requestID == currentID else { return }
error = lookupError
}
}
}
程序移动与用户拖动要区分
代码把镜头移动到定位点、地区中心或候选地址后,地图也会回报一次区域变化。如果把程序变化当作用户拖动,就会立即再发一遍附近查询。一次性跳过标记能消除这类重复请求。
页面还可以记录上次附近查询坐标,只有地图移动超过业务阈值才刷新。模型负责响应顺序和选中状态,视图层负责地图相机与交互节流,避免两类职责混在一个状态对象中。
程序调用地图相机也可能触发区域变化回调;需要识别预期中的程序更新,再决定是否查询附近内容。搜索态和浏览态最好用互斥状态表达,避免旧的附近结果覆盖关键词结果。
确认结果应成为明确值
点选候选时先使旧请求失效,再保存选择。确认后向上层返回名称、地区、地址和坐标组成的值对象。定位成功、无数据、取消和失败应分别表达,让页面根据权限状态显示合适的提示。
这种结构既能让地图和列表保持同步,也便于不启动地图 UI 就测试搜索切换、旧结果丢弃和候选确认。
