Home > Software engineering >  SwiftUI: Toggle for individual items of ForEach
SwiftUI: Toggle for individual items of ForEach

Time:08-15

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.

Individual toggles for each row

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:

Alternative: Individual toggles for each row

Best, Sebastian

  • Related