Using ForEach
, I want to create individual Toggles for each row. Right now, the @State
binding toggles all of the items at the same time, and I can't figure out how to separate them.
In the code below, I put a hard-coded array, but it really comes from an ever-changing .json file. Therefore, I need the ForEach
and the binding to be dynamic.
ALTERNATIVE:
The approach from last night
//
// GreekWordTest.swift
// GreekWordTest
//
// Created by Sebastian on 14.08.22.
//
import SwiftUI
struct ContentView: View {
@StateObject var greekWordsViewModel = GreekWordsViewModel()
var body: some View {
VStack() {
GreekWordView(greekWordsViewModel: greekWordsViewModel)
}
// For this test I am fetching the data once in the beginning when ContentView apears the first time, later I also added a button to fetch it again, it'll overwrite the existing data. You can also add a logic just to update it, that is up to you and your needs.
.onAppear(){
greekWordsViewModel.fetchData()
}
}
}
struct GreekWordView: View {
@ObservedObject var greekWordsViewModel: GreekWordsViewModel
var body: some View {
VStack(){
ForEach(greekWordsViewModel.greekWordArray.indices, id: \.self){ id in
Toggle(greekWordsViewModel.greekWordArray[id].name, isOn: $greekWordsViewModel.greekWordArray[id].isOn)
.padding()
}
// Here is the extra button to (re-)fetch the data from the json.
Button(action: {
greekWordsViewModel.fetchData()
}) {
Text("Fetch Data")
}
.padding()
}
}
}
struct GreekWord: Identifiable, Hashable {
var id: String = UUID().uuidString
var name: String
var isOn: Bool
}
class GreekWordsViewModel: ObservableObject {
@Published var greekWordArray: [GreekWord] = []
func fetchData(){
// As mentioned above, in his example I empty the array on each new loading event. You can also implement a logic to just update the data.
greekWordArray = []
let greekWords: [String] = load("greekWordsData.json")
for greekWord in greekWords {
greekWordArray.append(GreekWord(name: greekWord, isOn: false))
}
}
}
For decoding the json, I used the following:
//
// ModelData.swift
// SwiftTest
//
// Created by Sebastian Fox on 14.08.22.
//
import Foundation
// This function is used to decode a file with a json. I guess you already created something that is decoding a json according to your need, of course you can still use it.
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
And finally for testing, I used a very simple greekWordsData.json file that just contains:
["Alpha", "Beta", "Delta", "Gamma", "Epsilon", "Zeta"]
Here a screenshot:
Best, Sebastian