SwiftUI对接WordPress REST API时的HTML解码问题求助
解决SwiftUI接入WordPress API时的HTML解析与更新错误问题
问题根源分析
你遇到的precondition failure: setting value during update: 52280错误,主要来自三个方面:
- Model的ID不稳定:你自定义了
UUID()作为Identifiable的id,每次解析新的帖子数组时,所有元素的id都会重新生成,导致SwiftUI认为列表元素全部更新,触发异常的视图刷新。 - 强制解包风险:ViewModel里的
try!和data!会在网络或数据异常时直接崩溃,同时也可能在数据未完全加载时引发提前解包问题。 - 视图布局与状态管理缺失:原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.
相关产品推荐
相关产品推荐

