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

@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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 13:20:58