SwiftUI中MVVM模式下地图搜索建议的状态同步问题
SwiftUI MVVM架构下MKLocalSearchCompletion结果的状态同步方案
问题背景
在SwiftUI应用中遵循MVVM原则开发地图地点搜索界面时,遇到了MKLocalSearchCompleter的结果无法正确同步到视图的问题:
- 定义了结构体类型的Model
SearchState,包含currentLocation、currentText、suggestedLocations: [MKLocalSearchCompletion]等属性 - ViewModel持有
SearchState并通过@Published发布给视图 - 已实现
AutocompleteLocations类处理MKLocalSearchCompleter逻辑,但无法让其内部的建议列表变更触发视图更新
现有AutocompleteLocations代码
import MapKit class AutocompleteLocations: NSObject, MKLocalSearchCompleterDelegate { var completer: MKLocalSearchCompleter var suggestions: [MKLocalSearchCompletion] init(suggestions: inout [MKLocalSearchCompletion]) { self.completer = MKLocalSearchCompleter() self.suggestions = suggestions super.init() self.completer.delegate = self } // MKLocalSearchCompleterDelegate method func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) { suggestions = completer.results } func updateQuery(query: String) { self.completer.queryFragment = query } func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) { // Handle error } }
已尝试的无效方案
- 方案1:在
SearchState中实例化AutocompleteLocations作为属性,视图直接访问其suggestions。无效原因:嵌套引用类型的属性变更无法触发@Published的状态更新(SwiftUI仅监听值类型的整体替换或引用类型的引用变更)。 - 方案2:在ViewModel中手动调用
objectWillChange.send()。效果不佳:搜索结果返回时机不确定,无法精准触发更新。 - 方案3:让
AutocompleteLocations直接使用SearchState中的数组。无法实现:Swift数组是值类型,赋值时会产生拷贝,修改的是副本而非原数组。 - 方案4:将
AutocompleteLocations改为ObservableObject,单独发布suggestions再由ViewModel转发。违背MVVM原则:让服务类持有状态,破坏了Model纯数据、ViewModel管理状态的职责划分,且无法将建议列表统一存放在SearchState中。
符合MVVM的解决方案
核心思路:将AutocompleteLocations改为无状态的服务类,通过回调将搜索结果传递给ViewModel,由ViewModel负责更新SearchState,利用@Published的自动更新机制触发视图刷新。
步骤1:重构AutocompleteLocations
去掉自身的suggestions属性,改用回调传递结果:
import MapKit class AutocompleteLocations: NSObject, MKLocalSearchCompleterDelegate { private let completer: MKLocalSearchCompleter private let onResultsUpdated: ([MKLocalSearchCompletion]) -> Void // 初始化时传入结果更新的回调 init(onResultsUpdated: @escaping ([MKLocalSearchCompletion]) -> Void) { self.completer = MKLocalSearchCompleter() self.onResultsUpdated = onResultsUpdated super.init() completer.delegate = self } // 更新搜索关键词 func updateQuery(query: String) { completer.queryFragment = query } // MARK: - MKLocalSearchCompleterDelegate func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) { // 将结果通过回调传递给外部 onResultsUpdated(completer.results) } func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) { // 错误时可以清空结果或传递错误信息(根据需求扩展) onResultsUpdated([]) } }
步骤2:ViewModel中整合服务与状态更新
ViewModel持有AutocompleteLocations实例,在回调中更新SearchState的suggestedLocations:
import SwiftUI import MapKit // 自定义地点结构体(示例) struct CustomLocation { var latitude: Double var longitude: Double } struct SearchState { var currentLocation: CustomLocation var currentText: String = "" var suggestedLocations: [MKLocalSearchCompletion] = [] // 其他业务属性和方法 } class SearchViewModel: ObservableObject { @Published var searchState: SearchState private let autocompleteService: AutocompleteLocations init(initialState: SearchState) { self.searchState = initialState // 初始化服务并传入结果回调 self.autocompleteService = AutocompleteLocations { [weak self] suggestions in guard let self = self else { return } // 直接更新SearchState的建议列表,值类型的赋值会触发@Published的更新 self.searchState.suggestedLocations = suggestions } } // 对外暴露的搜索更新方法 func updateSearchQuery(_ query: String) { // 先更新搜索文本状态 searchState.currentText = query // 通知服务更新关键词 autocompleteService.updateQuery(query: query) } }
步骤3:视图中绑定状态
视图只需绑定ViewModel的searchState.suggestedLocations,即可自动响应更新:
struct SearchView: View { @StateObject private var viewModel: SearchViewModel init(initialLocation: CustomLocation) { let initialState = SearchState(currentLocation: initialLocation) _viewModel = StateObject(wrappedValue: SearchViewModel(initialState: initialState)) } var body: some View { VStack { TextField("搜索地点", text: $viewModel.searchState.currentText) .onChange(of: viewModel.searchState.currentText) { newText in viewModel.updateSearchQuery(newText) } List(viewModel.searchState.suggestedLocations, id: \.self) { completion in Text(completion.title) } } } }
方案优势
- 严格遵循MVVM职责划分:
SearchState作为纯数据模型,仅负责存储状态,不包含任何业务逻辑AutocompleteLocations作为专门的服务类,仅处理MKLocalSearchCompleter的底层逻辑,不持有状态SearchViewModel作为中间层,协调服务与模型,统一管理状态并发布给视图
- 自动触发视图更新:通过
@Published的特性,只要SearchState的属性发生变更,视图会自动刷新 - 代码解耦:服务类与ViewModel通过回调通信,便于后续测试和扩展
内容的提问来源于stack exchange,提问作者user22428147
相关产品推荐
相关产品推荐

