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

SwiftUI中NavigationList的ForEach循环API解码数据报错求助

问题分析与解决方案

核心错误原因

你遇到的ForEach错误,是因为直接把CompetitionGroup实例传给了它——CompetitionGroup是包含数组的结构体,本身并不是集合类型,而ForEach要求遍历的对象必须遵循RandomAccessCollection协议(比如数组)。

除此之外,你的数据加载逻辑还存在异步调用的问题:getComps里的URLSession.dataTask是异步执行的,函数直接返回competitions时,异步任务还没完成,返回的只会是初始的nil。


分步修复代码

1. 修正数据加载逻辑(改用ViewModel管理异步数据)

创建一个遵循ObservableObject的ViewModel类,用来管理异步加载的数据,确保UI能响应数据变化:

import Foundation
import Combine

class CompetitionViewModel: ObservableObject {
    @Published var competitionGroup: CompetitionGroup?
    
    func fetchCompetitions() {
        let headers = [
            "x-rapidapi-key": "你的API密钥",
            "x-rapidapi-host": "football-web-pages1.p.rapidapi.com"
        ]
        
        guard let url = URL(string: "https://football-web-pages1.p.rapidapi.com/competitions.json?include=rounds") else {
            print("无效的请求URL")
            return
        }
        
        var request = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)
        request.httpMethod = "GET"
        request.allHTTPHeaderFields = headers
        
        URLSession.shared.dataTask(with: request) { [weak self] data, _, error in
            if let error = error {
                print("请求错误: \(error)")
                return
            }
            
            guard let data = data else {
                print("未获取到返回数据")
                return
            }
            
            do {
                let decoder = JSONDecoder()
                let result = try decoder.decode(CompetitionGroup.self, from: data)
                // 回到主线程更新UI
                DispatchQueue.main.async {
                    self?.competitionGroup = result
                }
            } catch {
                print("JSON解码失败: \(error)")
            }
        }.resume()
    }
}

2. 修正ContentView的列表遍历逻辑

在视图中使用ViewModel,正确遍历CompetitionGroup里的competitions数组,同时处理可选值:

import SwiftUI

struct ContentView: View {
    // 使用StateObject托管ViewModel
    @StateObject private var viewModel = CompetitionViewModel()
    
    var body: some View {
        NavigationView {
            List {
                // 先解包CompetitionGroup和内部的competitions数组
                if let competitions = viewModel.competitionGroup?.competitions {
                    ForEach(competitions, id: \.id) { competition in
                        NavigationLink(
                            destination: Text(competition.genericname ?? "未命名赛事")
                        ) {
                            Image(systemName: "airplane")
                            Text(competition.genericname ?? "未命名赛事")
                        }
                        .padding()
                    }
                } else {
                    // 数据加载前显示占位文本
                    Text("加载中...")
                }
            }
            .navigationTitle("Destinations")
            .onAppear {
                // 视图出现时触发数据请求
                viewModel.fetchCompetitions()
            }
        }
    }
}

关键修复点说明

  • ForEach遍历对象修正:从遍历CompetitionGroup改为遍历它内部的competitions数组,满足RandomAccessCollection协议要求
  • 异步数据管理:用ObservableObject+@Published实现数据的响应式更新,确保异步请求完成后UI自动刷新
  • 可选值安全处理:用if let解包可选数组,用??处理genericname可能为nil的情况,避免运行时崩溃
  • 主线程更新UI:异步回调中必须切换到主线程修改UI相关属性,否则会触发线程错误

内容的提问来源于stack exchange,提问作者Paul

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 12:27:46