如何使用StateObject并与JSON加载程序结合



我目前正试图在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">

知道我做错了什么吗?我对斯威夫特完全陌生,所以我不知所措。

首先删除元组并使用两个单独的属性代替

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或类似属性时,您不应该在该属性前面加上$号,只有当您将该属性传递给将要更改它的函数或视图时,您才能这样做。

这是ContentViewbody

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
}
}

最新更新