如何使用 StateObject 并与 JSON loader 结合?
Posted
技术标签:
【中文标题】如何使用 StateObject 并与 JSON loader 结合?【英文标题】:How to use StateObject and Combine with JSON loader? 【发布时间】:2022-01-18 01:57:12 【问题描述】:我目前正在尝试在 Swift 中加载 JSON 以在我的 UI 中使用它。我想我已经设法让 JSON 正确加载,但由于代码中出现多个错误,我无法对其进行测试。
JSONReader.swift:
import Foundation
struct DatabaseObject: Decodable
let name: String
let books: Books
let memoryVerses: MemoryVerses
struct Books: Codable
let Romans: Book
let James: Book
struct Book: Codable
let abbreviation: String
let chapters: [Chapter]
struct Chapter: Codable
let sections: [Section]
let footnotes: Footnotes
struct Section: Codable
let title: String
let verses: [String]
struct Footnotes: Codable
let a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z: String
struct MemoryVerses: Codable
let singles: [String]
let multiples: [String]
public class JSONReaderSuperclass
@Published var contentData: (status: String, result: DatabaseObject?)
init()
contentData = (status: "loading", result: nil)
public class JSONReader: JSONReaderSuperclass, ObservableObject
private func parse(jsonData: Data) -> (status: String, result: DatabaseObject?)
do
let decodedData = try JSONDecoder().decode(DatabaseObject.self, from: jsonData)
print(decodedData)
return (status: "success", result: decodedData)
catch
return (status: "fail", result: nil)
private func loadJson(fromURLString urlString: String,
completion: @escaping (Result<Data, Error>) -> Void)
if let url = URL(string: urlString)
let urlSession = URLSession(configuration: .default).dataTask(with: url) (data, response, error) in
if let error = error
completion(.failure(error))
if let data = data
completion(.success(data))
urlSession.resume()
override init()
super.init()
DispatchQueue.main.async
self.loadJson(fromURLString: "redacted for anonymity") result in
switch result
case .success(let data):
self.contentData = self.parse(jsonData: data)
case .failure:
self.contentData = (status: "fail", result: nil)
ContentView.swift:
import SwiftUI
struct ContentView: View
@StateObject var databaseObject = JSONReader()
var body: some View
switch ($databaseObject.status)
case "loading":
Text("Loading...")
case "success":
VersePicker(databaseObject: $databaseObject.result)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.navigationTitle("Content Judge")
.navigationBarTitleDisplayMode(.inline)
case "fail":
Text("An unknown error occured. Check your internet connection and try again.")
struct ContentView_Previews: PreviewProvider
static var previews: some View
ContentView()
VersePicker.swift:
import SwiftUI
enum Book: String, CaseIterable, Identifiable
case romans
case james
var id: String self.rawValue
struct VersePicker: View
var databaseObject: DatabaseObject
@State private var selectedBook = Book.romans
@State private var selectedChapter: Int = 1
@State private var selectedVerse: Int = 1
var body: some View
VStack
Picker("Book", selection: $selectedBook)
ForEach(Book.allCases) book in
Text(book.rawValue.capitalized)
.tag(book)
HStack
Picker("Chapter", selection: $selectedChapter)
ForEach(1...100, id: \.self) number in
Text("\(number)")
Picker("Verse", selection: $selectedVerse)
ForEach(1...100, id: \.self) number in
Text("\(number)")
.frame(maxHeight: .infinity)
Spacer()
NavigationLink(destination: VerseDisplay())
Label("Go", systemImage: "arrow.right.circle")
.padding()
struct VersePicker_Previews: PreviewProvider
static var previews: some View
VersePicker(databaseObject: JSONReader().result)
我收到以下错误:
ContentView.swift:13 - “'ObservedObject.Wrapper' 类型的值没有动态成员 'status' 使用来自根类型 'JSONReader' 的密钥路径" ContentView.swift:17 - “无法将 'Binding' 类型的值转换为预期的参数类型 'DatabaseObject'” ContentView.swift:17 - “'ObservedObject.Wrapper' 类型的值没有动态成员 'result' 使用来自根类型 'JSONReader' 的密钥路径" VersePicker.swift:55 - “'JSONReader' 类型的值没有成员 'result'”知道我做错了什么吗?我对 Swift 完全陌生,所以我很茫然。
【问题讨论】:
您试图以错误的方式访问您的元组属性,但我建议您删除该属性并用两个单独的属性替换它而不是修复它 @JoakimDanielson 我似乎也无法让它工作。也许您可以使用示例代码添加答案? 【参考方案1】:首先删除元组并使用两个单独的属性来代替
public class JSONReaderSuperclass
@Published var status: String = ""
@Published var result: DatabaseObject? = nil
这里我建议你将status
改为enum
。
然后我们需要更改 init 中的switch
,因为我们删除了元组
switch result
case .success(let data):
let decoded = self.parse(jsonData: data)
self.status = decoded.status
self.result = decoded.result
case .failure:
self.status = "fail"
self.result = nil
作为未来的更改,我建议您不要在 init 中进行下载和解码,而是将其放在您认为从 .onAppear
或 .task
调用的公共函数中。
在VersePicker
中,您需要将databaseObject
属性设为可选(或删除它,因为您似乎不使用它)
var databaseObject: DatabaseObject?
最后在ContentView
你也需要做一些改变,以适应新的属性,但当你只读取@State、@StateObject 或类似的属性时,你应该不前缀$
符号的属性,只有在将属性传递给将更改它的函数或视图时才这样做。
这是ContentView
的body
var body: some View
switch (databaseObject.status)
case "loading":
Text("Loading...")
case "success":
VersePicker(databaseObject: databaseObject.result)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.navigationTitle("Content Judge")
.navigationBarTitleDisplayMode(.inline)
case "fail":
Text("An unknown error occured. Check your internet connection and try again.")
default:
fatalError() //Avoid this by changing status from string to an enum
【讨论】:
以上是关于如何使用 StateObject 并与 JSON loader 结合?的主要内容,如果未能解决你的问题,请参考以下文章
如何防止 SwiftUI 像使用 @StateObject 一样重新初始化我的包装属性?
SwiftUI:如何在父视图中初始化新的 StateObject?
从子视图设置 StateObject 值会导致 NavigationView 弹出所有视图