如何使用 StateObject 并结合 JSON 加载器?
How to use StateObject and Combine with JSON loader?
我目前正在尝试在 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 完全陌生,所以我很茫然。
首先删除元组并改用两个单独的属性
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
调用的 public 函数中在你看来。
在 VersePicker
中,您需要将 databaseObject
属性 设为可选(或删除它,因为您似乎不使用它)
var databaseObject: DatabaseObject?
最后,在 ContentView
中,您还需要进行一些更改,以针对新属性进行调整,而且当您只读取 @State、@StateObject 或类似的 属性 时,您应该 not 在 属性 前加上 $
符号,只有在将 属性 传递给将更改它的函数或视图时才这样做。
这里是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
}
}
我目前正在尝试在 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 完全陌生,所以我很茫然。
首先删除元组并改用两个单独的属性
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
调用的 public 函数中在你看来。
在 VersePicker
中,您需要将 databaseObject
属性 设为可选(或删除它,因为您似乎不使用它)
var databaseObject: DatabaseObject?
最后,在 ContentView
中,您还需要进行一些更改,以针对新属性进行调整,而且当您只读取 @State、@StateObject 或类似的 属性 时,您应该 not 在 属性 前加上 $
符号,只有在将 属性 传递给将更改它的函数或视图时才这样做。
这里是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
}
}