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

SwiftUI新闻应用如何正确设置模型与ViewModel加载NewsAPI数据

问题根因
  • 数据模型和API返回结构不匹配:newsapi.org的返回根节点是单个对象,不是数组;且根节点下的articles字段是文章数组,你当前定义为单个Article类型,是解析失败打印failed的核心原因
  • Source结构体的id字段要设为可选型:部分新闻来源的id为null,不设可选会触发解析失败
  • ViewModel属性定义错误:你当前@Published的是[APIResponse]数组,实际应该直接存储文章数组[Article]更适配列表渲染
  • 列表遍历逻辑错误:当前遍历的是API响应数组,应该直接遍历文章数组简化取值逻辑
修正后代码

1. 数据模型(Model)

import Foundation

struct APIResponse: Codable {
    // 改为Article数组
    let articles: [Article]
}

struct Article: Codable {
    let source: Source
    let title: String
    let description: String? // 部分文章描述为空,建议也设为可选
}

struct Source: Codable, Identifiable {
    // id改为可选型,适配null值
    let id: String?
    let name: String
}

2. 视图模型(ViewModel)

import Foundation

class NewsViewModel: ObservableObject {
    // 改为存储Article数组
    @Published var articles = [Article]()
    
    init() {
        fetchNews()
    }
        
    func fetchNews() {
        guard let url = URL(string: "https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=4b6cfa9b54c74b4db7d7d8a2120718d3") else {
            return
        }
        
        let task = URLSession.shared.dataTask(with: url) { data, _, error in
            guard let data = data, error == nil else {
                return
            }
            
            do {
                // 解析类型改为APIResponse.self,根节点是单个对象不是数组
                let response = try JSONDecoder().decode(APIResponse.self, from: data)
                DispatchQueue.main.async {
                    // 直接赋值文章数组
                    self.articles = response.articles
                }
            }
            catch {
                print("解析失败: \(error)") // 打印具体错误信息方便后续定位
            }
        }
        task.resume()
    }
}

3. ContentView

import SwiftUI

struct ContentView: View {
    @ObservedObject var viewModel = NewsViewModel()
            
    var body: some View {
        NavigationView {
            List(viewModel.articles, id: \.title) { article in
                VStack(alignment: .leading) {
                    // 直接取article的属性即可,不需要多一层.articles
                    Text(article.source.name)
                        .font(.headline)
                    Text(article.title)
                        .font(.subheadline)
                    if let desc = article.description {
                        Text(desc)
                            .font(.caption)
                            .foregroundColor(.secondary)
                    }
                }
            }
            .navigationTitle("News List")
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.14 12:45:05