It is a question of timing.
When onAppear returns, and DataView is called, the array is not yet populated, even though its count is already OK.
You have to wait for a short time in onAppear (0.01 s should be OK).
Here is the solution.
I had to rename Data to avoid the error above (Data is a Foundation type). I renamed to Data2
struct Data2: Decodable, Hashable {
let index: Int
let str: String
}
struct DataView: View {
@State var data: Data2
var body: some View {
Text("DataView \(data.str)")
}
}
struct ContentView: View {
@State var showView = false // To wait for array to be populated before we display the view
@State var data: [Data2]
@State var index: Int = 0
var body: some View {
VStack {
if showView {
DataView(data: data[index])
// Text("Hello \(data.count)") // To test if needed ; without async, data.count is 2. Seems OK, but…
// Text("Hello \(data.count) index \(index) \(data[0].str)") // but without async, it will crash, as data is not populated yet, even if count is already 2
}
}
.onAppear {
let filePath = Bundle.main.path(forResource: "data", ofType: "json")
let url = URL(fileURLWithPath: filePath!)
data = getData(url: url)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { // <<-- THE KEY POINT 0.1 sec delay
self.showView = true
}
}
}
func getData(url: URL) -> [Data2] {
do {
let data = try Data(contentsOf: url)
let jsonDecoded = try JSONDecoder().decode([Data2].self, from: data)
return jsonDecoded
} catch let error as NSError {
print("Fail: \(error.localizedDescription)")
} catch {
print("Fail: \(error)")
}
return []
}
}
Credit: https://stackoverflow.com/questions/62355428/how-do-i-show-a-new-view-after-some-delay-amount-of-time-swiftui
A simpler option is to use an init:
struct ContentView: View {
@State var data: [Data2]
@State var index: Int = 0
init() {
let filePath = Bundle.main.path(forResource: "data", ofType: "json")
let url = URL(fileURLWithPath: filePath!)
// data = getData(url: url) // We cannot call getData in init
do {
let dataRead = try Data(contentsOf: url)
let jsonDecoded = try JSONDecoder().decode([Data2].self, from: dataRead)
data = jsonDecoded
return
} catch let error as NSError {
print("Fail: \(error.localizedDescription)")
} catch {
print("Fail: \(error)")
}
data = []
return
}
var body: some View {
VStack {
DataView(data: data[index])
}
}
}