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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 13:11:13