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

SwiftUI新闻应用ViewModel数据更新失效,视图无内容显示求助

SwiftUI新闻应用ViewModel数据无法同步到视图的问题

我用SwiftUI开发简易新闻应用,引入ViewModel后视图无内容显示。最初ViewModel功能正常,现在数据变更无法同步到视图,已尝试多种方法未解决。


问题根源分析

  1. 卡片数据依赖错误:NewsView遍历newsModel.articles渲染卡片,但NewsCard却依赖全局的selectedNews(初始为nil),导致所有卡片无内容。
  2. ViewModel生命周期管理错误:App入口用@State持有NewsViewModel(类类型),未使用正确的@StateObject管理生命周期,可能导致状态丢失。
  3. 预览未注入必要环境/数据:预览无法正常渲染,影响开发调试。

修复步骤

1. 修正App入口的ViewModel声明

将@State替换为@StateObject,确保ViewModel的生命周期被SwiftUI正确管理:

@main
struct GameNewsApp: App {
    @StateObject var newsModel = NewsViewModel()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(newsModel)
        }
    }
}

2. 修改NewsCard,直接接收Article实例

移除对全局selectedNews的依赖,改为接收当前遍历的Article,确保每个卡片显示对应数据:

import SwiftUI
import CachedAsyncImage

struct NewsCard: View {
    let article: Articles
    
    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            CachedAsyncImage(url: URL(string: article.image?.original ?? "")) { image in
                switch image {
                case .empty:
                    HStack {
                        Spacer()
                        ProgressView()
                        Spacer()
                    }
                case .success(let image):
                    image
                        .resizable()
                        .clipShape(.rect(topLeadingRadius: 10, topTrailingRadius: 10))
                        .frame(height: 150)
                        .padding(.bottom, 10)
                        .overlay {
                            LinearGradient(stops: [
                                Gradient.Stop(color: .clear, location: 0.6),
                                Gradient.Stop(color: .black, location: 1)
                            ], startPoint: .top, endPoint: .bottom)
                        }
                case .failure:
                    HStack {
                        Spacer()
                        Image(systemName: "photo")
                            .imageScale(.large)
                        Spacer()
                    }
                @unknown default:
                    fatalError()
                }
            }
            .frame(maxHeight: 150)
            .background(Color.gray.opacity(0.3))
            .clipped()
            
            Spacer()

            Text(article.title ?? "")
                .font(.title3)
                .fontWeight(.bold)
                .lineLimit(3)
                .padding(.bottom, 10)
                .padding(.horizontal)
                .multilineTextAlignment(.leading)
            
            Text(article.deck ?? "")
                .font(.subheadline)
                .lineLimit(4)
                .padding(.bottom, 10)
                .padding(.horizontal)
                .multilineTextAlignment(.leading)
            
            Spacer()
            
            HStack {
                ForEach(article.categories) { category in
                    Text(category.name ?? "")
                }
            }
            .padding(.horizontal)
            .padding(.bottom, 5)
            
            Spacer()
        }
        .frame(height: 350)
        .overlay {
            RoundedRectangle(cornerRadius: 10)
                .stroke(.white)
        }
        .padding(.all, 10)
        .foregroundStyle(.white)
    }
}

#Preview {
    NewsCard(article: Articles(
        publish_date: "2024-01-01",
        authors: "Test Author",
        title: "Test News Title",
        image: Images(original: "https://example.com/image.jpg"),
        deck: "Test news deck description",
        body: "Test news body content",
        categories: [Category(id: 1, name: "Game")]
    ))
}

3. 更新NewsView,传递Article给NewsCard

在遍历数组时,将当前Article传入NewsCard,同时保留点击设置selectedNews的逻辑(用于详情页):

struct NewsView: View {
    @Environment(NewsViewModel.self) var newsModel
    
    var body: some View {
        NavigationStack {
            ScrollView(showsIndicators: false) {
                VStack {
                    ForEach(newsModel.articles) { article in
                        NavigationLink {
                            DetailNews()
                        } label: {
                            NewsCard(article: article)
                        }
                        .onTapGesture {
                            newsModel.selectedNews = article
                        }
                    }
                }
            }
            .navigationTitle("Game News")
        }
        .onAppear {
            newsModel.getNewsData()
        }
        .refreshable {
            newsModel.getNewsData()
        }
    }
}

#Preview {
    NewsView()
        .environment(NewsViewModel())
}

4. 验证数据获取逻辑

在ViewModel中添加调试打印,确认数据是否正确获取:

@Observable
class NewsViewModel {
    var articles = [Articles]()
    var dataService = DataService()
    var selectedNews: Articles?
    
    func getNewsData() {
        Task {
            articles = await dataService.articleSearch()
            print("Fetched \(articles.count) articles")
        }
    }
}

修复说明

  • 核心问题解决:卡片直接绑定当前Article,不再依赖初始为空的selectedNews,数据加载后即可直接渲染。
  • 生命周期修正:@StateObject确保ViewModel在App运行期间稳定存在,避免状态意外丢失。
  • 预览优化:添加模拟数据和环境注入,方便开发时实时查看卡片效果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 21:27:03