Shiyu
(Shiyu Teng)
1
I am going to decode a Magic cards database from a JSON file fetched from this API (here). however, an error appears when I try to plot some data in the canvas with SwiftUI.
I thought I might do something worry when I called the FetchdAndDecodedMtgData model at the first time. But I got confused when I set some print() debugger in my FetchdAndDecodedMtgData model because these debuggers don't print anything in the debug monitor.
I don't know how to solve it. If the FetchdAndDecodedMtgData model is correct, then how to call the decodedData in the ContentView?
Any help is appreciated!
The FetchdAndDecodedMtgData model
struct FetchdAndDecodedMtgData {
let urlString: String
var url: URL?
init(urlString: String) {
self.urlString = urlString
url = URL(string: urlString) ?? nil
}
func fetchdAndDecodeApiData(completion: @escaping (MTGData?, Error?) -> ()) {
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
if let error = error {
completion(nil, error)
print("! Error")
return
}
//check response status code etc if needed
//parse data
guard let data = data else {
completion(nil, NameError.invalidData)
print("! Error")
return
}
do {
let decoder = JSONDecoder()
let decodedData = try decoder.decode(MTGData.self, from: data)
print(decodedData)
DispatchQueue.main.async {
completion(decodedData, nil)
}
} catch {
completion(nil, error)
}
}
task.resume()
}
}
The ContentView:
struct ContentView: View {
static let urlString: String = "https://mtgjson.com/api/v5/YNEO.json"
let fetchdAndDecodedModel: FetchdAndDecodedMtgData = FetchdAndDecodedMtgData(urlString: urlString)
@State var mtgData: MTGData!
var body: some View {
VStack {
Text("API retrive date: \(mtgData.meta.date)")
.padding()
Text("API retrive version: \(mtgData.meta.version)")
.padding()
Divider()
Text("Card Number: \(mtgData.data.baseSetSize)")
}
.onAppear() {
fetchdAndDecodedModel.fetchdAndDecodeApiData { (result, error) in
self.mtgData = result!
}
}
}
}
tera
2
If you put breakpoints on the two lines you'll see that what's happening here is that the line:
Text("API retrive date: \(mtgData.meta.date)")
is executed before the line:
self.mtgData = result!
Easy fix would be this:
@State var mtgData: MTGData = MTGData(... some empty data here)
The view will show that placeholder data first, then when the line "self.mtgData = result!" is executed the view will be updated.
You may want to decouple the model from the view: put it in a separate observable object, then you'll be able using it from more than one view, view's onAppear won't necessarily trigger fetch if that was already done previously, view will focus on view only aspects:
struct MyView: View {
@ObservedObject var model: Model
var body: some View {
VStack {
Text(model.xxxx)
}
.onAppear() {
model.onAppear()
}
.onDisappear {
model.onDisappear()
}
}
}
Shiyu
(Shiyu Teng)
3
Hi Tera,
Thanks for passing by and replying.
If I go with your second solution, by decoupling the model from the view. In this case, I could call the fetchdAndDecodeApiData function in the model like:
@ObservableObject var model: Model
model. fetchdAndDecodeApiData
However, how I can get the mtgData from the completion handler in the fetchdAndDecodeApiData function? The "fetchdAndDecodeApiData" function doesn't have return syntax but saves the fetching result in a closure argument: "completion: (MTGData?, Error?)". So, in the view model, is there a way to fetch the mtgdata from the completion handler?
tera
4
The view model (or "the model" if you don't have the "view model" / "data model" separation) will store the fetched data into a property, and if that property is marked "published" and the model is "observed" by the view - the view will update.
class Model: ObservableObject {
@Published var xxxx: String?
func onAppear() {
if xxxx == nil {
fetch { data, response, error in
let decoded = decode data
xxxx = decoded // assign on main thread
}
}
}
func onDisappear() {}
}
Shiyu
(Shiyu Teng)
5
Hi Tera,
There is another convenient way instead of using a completion handler to fetch and decode JSON is using async/await which is introduced in WWDC21.
Here is the linkWWDC 2021 async/await