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

SwiftUI对接WordPress REST API时的HTML解码问题求助

解决SwiftUI接入WordPress API时的HTML解析与更新错误问题

问题根源分析

你遇到的precondition failure: setting value during update: 52280错误,主要来自三个方面:

  1. Model的ID不稳定:你自定义了UUID()作为Identifiable的id,每次解析新的帖子数组时,所有元素的id都会重新生成,导致SwiftUI认为列表元素全部更新,触发异常的视图刷新。
  2. 强制解包风险:ViewModel里的try!和data!会在网络或数据异常时直接崩溃,同时也可能在数据未完全加载时引发提前解包问题。
  3. 视图布局与状态管理缺失:原View没有处理加载状态,且用Section作为导航链接的标签不符合布局规范,可能加剧更新冲突。

分步解决方案

1. 修正Model的ID定义

用WordPress API返回的原生id(整数类型)替代自定义的UUID,保证列表元素的id稳定:

struct Rendered: Codable {
    var rendered: String
}

struct JSONuser: Codable, Identifiable {
    let id: Int // 直接使用WordPress返回的帖子ID
    let title: Rendered
    let content: Rendered
}

2. 重构ViewModel,移除强制解包

改用Result类型处理成功/失败状态,避免崩溃:

class ApiCall {
    func getUsers(completion:@escaping (Result<[JSONuser], Error>) -> ()) {
        guard let url = URL(string: "https://www.db-professional.com/wp-json/wp/v2/posts") else {
            completion(.failure(NSError(domain: "无效URL", code: -1, userInfo: nil)))
            return
        }
        URLSession.shared.dataTask(with: url) { data, _, error in
            if let error = error {
                completion(.failure(error))
                return
            }
            guard let data = data else {
                completion(.failure(NSError(domain: "未获取到数据", code: -2, userInfo: nil)))
                return
            }
            do {
                let posts = try JSONDecoder().decode([JSONuser].self, from: data)
                DispatchQueue.main.async {
                    completion(.success(posts))
                }
            } catch {
                completion(.failure(error))
                print("JSON解析错误:\(error)")
            }
        }.resume()
    }
}

3. 完善View的状态管理与布局

添加加载状态、错误提示,修正导航链接的标签布局:

struct BlogView: View {
    @State private var posts: [JSONuser] = []
    @State private var isLoading = true
    @State private var errorMessage: String?

    var body: some View {
        NavigationStack {
            Group {
                if isLoading {
                    ProgressView("加载中...")
                } else if let error = errorMessage {
                    Text(error)
                        .foregroundColor(.red)
                        .padding()
                } else {
                    List(posts) { post in
                        NavigationLink {
                            PostDetailView(post: post)
                        } label: {
                            Text(post.title.rendered.stripOutHtml() ?? "标题不可用")
                                .bold()
                                .padding(.vertical)
                        }
                    }
                    .listStyle(InsetGroupedListStyle())
                    .padding(.bottom, 20)
                }
            }
            .navigationTitle("新闻博客")
        }
        .onAppear {
            let api = ApiCall()
            api.getUsers { result in
                switch result {
                case .success(let fetchedPosts):
                    posts = fetchedPosts
                    isLoading = false
                case .failure(let error):
                    errorMessage = "加载失败:\(error.localizedDescription)"
                    isLoading = false
                }
            }
        }
    }
}

// 单独抽离详情页视图,让代码更清晰
struct PostDetailView: View {
    let post: JSONuser
    
    var body: some View {
        ScrollView {
            Text(post.title.rendered.stripOutHtml() ?? "标题不可用")
                .bold()
                .font(.title)
                .padding([.top, .horizontal], 10)
            Divider().modifier(dividerViewStyle())
            Text(post.content.rendered.stripOutHtml() ?? "内容不可用")
                .padding(.bottom, 30)
                .padding(.horizontal)
        }
    }
}

4. 确保HTML解析扩展无副作用

检查你的stripOutHtml扩展,确保它是纯字符串处理,不会触发状态变化:

extension String {
    func stripOutHtml() -> String? {
        guard let data = self.data(using: .utf8) else { return nil }
        do {
            let attributedString = try NSAttributedString(
                data: data,
                options: [
                    .documentType: NSAttributedString.DocumentType.html,
                    .characterEncoding: String.Encoding.utf8.rawValue
                ],
                documentAttributes: nil
            )
            return attributedString.string
        } catch {
            print("HTML解析失败:\(error)")
            return nil
        }
    }
}

关键注意点

  • 永远不要在视图的body闭包里执行会修改状态的操作,所有数据更新都要放在onAppear、异步回调等合适的时机。
  • 避免使用强制解包(!),改用可选绑定或Result类型处理异常情况。
  • List的元素id必须稳定,否则会引发不必要的视图刷新,甚至出现更新错误。

内容的提问来源于stack exchange,提问作者Fabian H.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 19:34:58