@AppStorage变量首次未更新问题求助
问题:@AppStorage变量首次仅更新storedCityName,其余需二次操作才生效
3个@AppStorage变量中有2个首次不会更新。关闭视图后重新打开,第二次关闭时才全部更新,准确来说只有storedCityName首次会更新。尝试过用DispatchQueue.main.asyncAfter延迟dismiss,以为@AppStorage变量需要更新时间,但没有任何改变。
视图代码
import SwiftUI struct SearchView: View { @Environment(\.dismiss) private var dismiss @EnvironmentObject private var searchManager: SearchManager @AppStorage("storedUserLatitude") private var storedUserLatitude: Double? @AppStorage("storedUserLongitude") private var storedUserLongitude: Double? @AppStorage("storedCityName") private var storedCityName: String? var body: some View { VStack { TextField("", text: $searchManager.search) ForEach(searchManager.searchResults, id: \.self) { result in HStack { VStack { Text(result.title) Text(result.subtitle) } } .onTapGesture { update(result.title) } } } } private func update(_ city: String) { searchManager.searchLocation(city) storedUserLatitude = searchManager.userLocation?.coordinate.latitude storedUserLongitude = searchManager.userLocation?.coordinate.longitude storedCityName = city dismiss() } }
管理器代码
import Foundation import MapKit import Combine class SearchManager: NSObject, ObservableObject, MKLocalSearchCompleterDelegate { private let completer = MKLocalSearchCompleter() @Published var search = "" @Published var searchResults = [MKLocalSearchCompletion]() @Published var userLocation: CLLocation? private var publisher: AnyCancellable? override init() { super.init() completer.delegate = self completer.resultTypes = .address publisher = $search .receive(on: RunLoop.main) .sink(receiveValue: { string in self.completer.queryFragment = string }) } func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) { searchResults = completer.results } func searchLocation(_ city: String) { let geocoder = CLGeocoder() geocoder.geocodeAddressString(city) { placemarks, error in guard let placemark = placemarks?.first else { return } self.userLocation = placemark.location } } }
问题原因
核心问题是地理编码是异步操作:调用searchManager.searchLocation(city)后,代码会立刻执行后续的赋值和dismiss逻辑,但此时CLGeocoder的回调还没触发,searchManager.userLocation仍然是nil,导致storedUserLatitude和storedUserLongitude没有被赋值。
第二次操作时,userLocation已经保留了上一次地理编码的结果,所以此时赋值能成功,造成“第二次才生效”的错觉。
解决方法
需要等待地理编码完成后,再更新@AppStorage变量并关闭视图,这里提供两种实现方式:
方式1:用Async/Await(iOS 15+)
改造SearchManager的searchLocation方法为异步函数:
func searchLocation(_ city: String) async { let geocoder = CLGeocoder() do { let placemarks = try await geocoder.geocodeAddressString(city) if let placemark = placemarks.first { self.userLocation = placemark.location } } catch { print("地理编码失败:\(error)") } }
然后修改SearchView的update方法为异步,并在onTapGesture中通过Task调用:
private func update(_ city: String) async { await searchManager.searchLocation(city) storedUserLatitude = searchManager.userLocation?.coordinate.latitude storedUserLongitude = searchManager.userLocation?.coordinate.longitude storedCityName = city dismiss() } // 修改onTapGesture .onTapGesture { Task { await update(result.title) } }
方式2:用Combine监听userLocation变化
如果需要兼容iOS 15以下版本,可以通过Combine订阅userLocation的更新,等拿到有效位置后再执行赋值和dismiss:
private func update(_ city: String) { storedCityName = city searchManager.searchLocation(city) // 订阅userLocation的变化,取第一个非nil值 let cancellable = searchManager.$userLocation .compactMap { $0 } .first() .sink { location in self.storedUserLatitude = location.coordinate.latitude self.storedUserLongitude = location.coordinate.longitude self.dismiss() cancellable.cancel() // 完成后取消订阅 } }
这样就能保证地理编码完成后再更新坐标存储,首次操作即可生效。
内容的提问来源于stack exchange,提问作者Alexnnd
相关产品推荐
相关产品推荐

