簡體   English   中英

SwiftUI:切換 ForEach 的各個項目

[英]SwiftUI: Toggle for individual items of ForEach

使用ForEach ,我想為每一行創建單獨的切換。 現在, @State綁定同時切換所有項目,我不知道如何將它們分開。

在下面的代碼中,我放了一個硬編碼的數組,但它確實來自一個不斷變化的.json文件。 因此,我需要ForEach和綁定是動態的。

這篇關於隱藏List的文章和這篇關於List行問題的文章很有幫助,但我無法使綁定適用於我的項目。 我在第 2 天試圖弄清楚這一點,我在網上找到的內容都沒有解決這個具體問題。

下面是我的代碼的一個小例子,它重現了我的挑戰。 數組中的動態數據來自.json 文件。

import SwiftUI

struct GreekWords: Codable, Hashable {
    var greekWordArray = ["Alpha", "Beta", "Gamma", "Delta", "Epsilon", "Zeta"]
    // The array data comes from a dynamic .json file
}

struct ContentView: View {
    var greekWords: GreekWords
    
    @State private var wordToggle = false
    
    var body: some View {
        VStack(spacing: 0) {
            ForEach(greekWords.greekWordArray, id: \.self) { word in
                Toggle(word, isOn: $wordToggle)
            }
        }
        .padding(.horizontal)
    }
}

我希望這是一個簡單的解決方案,所以我提前感謝您的幫助。 另外,如果您能指出我更好地學習 SwiftUI 的任何方向,我將不勝感激。 我已經在 HackingWithSwift 上嘗試了所有 Apple 教程和書籍以及 SwiftUI 的 100 天。

干杯!

在您的示例代碼中,所有切換都引用同一個變量。 因此,當然所有切換都將始終顯示相同的 state。

在您提供的鏈接中的示例實現中,它不僅僅是一個字符串數組,它是一個對象數組,還包含一個 bool 變量來通過切換控制該特定項目。

更新(2):

也許以下方法更符合您的預期。 抱歉,昨晚沒想到。 但請記住,切換 state 的 var 僅在該視圖中可用,您可以在該視圖中顯示狀態,但不能真正使用它。 如果您想(重新)使用該信息,我寧願選擇昨晚的替代方案(見下文)。

//
//  GreekWordTest.swift
//  GreekWordTest
//
//  Created by Sebastian on 15.08.22.
//

import SwiftUI

struct GreekWords: Codable, Hashable {
    var greekWordArray = ["Alpha", "Beta", "Gamma", "Delta", "Epsilon", "Zeta"]
    // The array data comes from a dynamic .json file
}

struct ContentView: View {
    var greekWords: GreekWords
    
    var body: some View {
        VStack(spacing: 0) {
            ForEach(greekWords.greekWordArray, id: \.self) { word in
                GreekWordToggleView(greekWord: word)
                    .padding()
            }
        }
        .padding(.horizontal)
    }
}

struct GreekWordToggleView: View {
    
    var greekWord: String
    @State private var wordToggle = false
    
    var body: some View {
        VStack(spacing: 0) {
            Toggle(greekWord, isOn: $wordToggle)
        }
        .padding(.horizontal)
    }
}

這里是截圖:

每行的單獨切換

選擇:

昨晚的接近

//
//  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))
        }
    }
}

為了解碼 json,我使用了以下內容:

//
//  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)")
    }
}

最后為了測試,我使用了一個非常簡單的 greekWordsData.json 文件,它只包含:

["Alpha", "Beta", "Delta", "Gamma", "Epsilon", "Zeta"]

這是一個屏幕截圖:

備選方案:每行單獨切換

最好的,塞巴斯蒂安

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM