如何在SwiftUI的Text中显示async/throw函数的返回值?
问题描述
需要调用经纬度转城市/国家名的函数,将返回值显示在SwiftUI的Text组件中。现有代码如下:
import SwiftUI import CoreLocation struct junoWeatherEntryView: View { @ObservedObject var cityVM = allCities() @State private var searchTerm = "San Francisco" var body: some View { VStack{ search Spacer() ForEach(cityVM.weather){ item in Text("\( try await reverseGeocode(lat: item.lat ,lan:item.lan).locality ?? "Unkown")") } } } func reverseGeocode(lat: Double, lan: Double) async throws -> CLPlacemark { let geoCoder = CLGeocoder() let location = CLLocation(latitude: lat, longitude: lan) // <- New York return try await withCheckedThrowingContinuation { continuation in geoCoder.reverseGeocodeLocation(location) { (placemarks, error) in guard error == nil, let placemark = placemarks?.first else { continuation.resume(throwing: error ?? CLError(.geocodeFoundNoResult)) return } continuation.resume(returning: placemark) } } } }
其中cityVM包含@Published var weather = [WeatherResponse](),运行时出现以下错误:
1.Cannot pass function of type '(WeatherResponse) async throws -> Text' to parameter expecting synchronous function type 2.Invalid conversion from throwing function of type '(WeatherResponse) async throws -> Text' to non-throwing function type '(Array<WeatherResponse>.Element) -> Text'
解决方案
错误原因
SwiftUI的View体是同步执行的,ForEach的闭包要求是同步、非抛出的函数,但你直接在Text构造中调用了async throws类型的reverseGeocode函数,违反了SwiftUI的视图渲染规则。必须将异步逻辑移到视图生命周期方法或ViewModel中,提前获取并缓存数据后再显示。
方法一:ViewModel统一处理(推荐)
将地理编码逻辑移到ViewModel中,批量处理并缓存结果,避免重复请求:
1. 修改ViewModel(allCities)
import Foundation import CoreLocation class allCities: ObservableObject { @Published var weather = [WeatherResponse]() // 用经纬度组合字符串作为键,缓存城市名 @Published var locationCache: [String: String] = [:] // 加载完weather数据后,调用此方法批量获取地名 func loadLocationNames() async { for weatherItem in weather { let cacheKey = "\(weatherItem.lat)_\(weatherItem.lan)" // 避免重复请求 guard locationCache[cacheKey] == nil else { continue } do { let placemark = try await reverseGeocode(lat: weatherItem.lat, lan: weatherItem.lan) locationCache[cacheKey] = placemark.locality ?? "Unknown" } catch { locationCache[cacheKey] = "Unknown" } } } private func reverseGeocode(lat: Double, lan: Double) async throws -> CLPlacemark { let geoCoder = CLGeocoder() let location = CLLocation(latitude: lat, longitude: lan) return try await withCheckedThrowingContinuation { continuation in geoCoder.reverseGeocodeLocation(location) { (placemarks, error) in guard error == nil, let placemark = placemarks?.first else { continuation.resume(throwing: error ?? CLError(.geocodeFoundNoResult)) return } continuation.resume(returning: placemark) } } } }
2. 修改View
import SwiftUI import CoreLocation struct junoWeatherEntryView: View { @ObservedObject var cityVM = allCities() @State private var searchTerm = "San Francisco" var body: some View { VStack{ search Spacer() ForEach(cityVM.weather){ item in let cacheKey = "\(item.lat)_\(item.lan)" Text(cityVM.locationCache[cacheKey] ?? "Loading...") } } .task { // 先加载weather数据(假设你有对应的fetch方法) // await cityVM.fetchWeather() // 再批量获取地名 await cityVM.loadLocationNames() } } // 示例搜索栏 private var search: some View { TextField("搜索城市", text: $searchTerm) .padding() .border(Color.gray) } }
方法二:子View独立处理
创建单独的子View,每个子View自己处理异步地理编码请求:
import SwiftUI import CoreLocation // 子View:负责单个天气条目的地名显示 struct WeatherLocationView: View { let lat: Double let lan: Double @State private var cityName = "Loading..." var body: some View { Text(cityName) .task { do { let placemark = try await reverseGeocode(lat: lat, lan: lan) cityName = placemark.locality ?? "Unknown" } catch { cityName = "Unknown" } } } private func reverseGeocode(lat: Double, lan: Double) async throws -> CLPlacemark { let geoCoder = CLGeocoder() let location = CLLocation(latitude: lat, longitude: lan) return try await withCheckedThrowingContinuation { continuation in geoCoder.reverseGeocodeLocation(location) { (placemarks, error) in guard error == nil, let placemark = placemarks?.first else { continuation.resume(throwing: error ?? CLError(.geocodeFoundNoResult)) return } continuation.resume(returning: placemark) } } } } // 父View struct junoWeatherEntryView: View { @ObservedObject var cityVM = allCities() @State private var searchTerm = "San Francisco" var body: some View { VStack{ search Spacer() ForEach(cityVM.weather){ item in WeatherLocationView(lat: item.lat, lan: item.lan) } } } private var search: some View { TextField("搜索城市", text: $searchTerm) .padding() .border(Color.gray) } }
这种方法适合条目较少的场景,但相同经纬度的条目会重复发起请求,不如方法一高效。
内容的提问来源于stack exchange,提问作者KARAN _186
相关产品推荐
相关产品推荐

