为什么 SwiftUI 中的 ForEach 上的此选择不起作用?

lue*_*dev 5 swift swiftui

有人可以告诉我为什么这个选择不起作用吗?如果我单击其中一行,则ForEach什么也不会发生:/

@FetchRequest(fetchRequest: ContentView.manufacturerByName)
var manufacturers:FetchedResults<Manufacturer>

@State private var selection: Manufacturer?

Section("Manufacturer") {
    List(selection: $selection) {
        ForEach(manufacturers, id:\.self) { manufacturer in
            Text(manufacturer.name ?? "--")
        }
        .onDelete { rows in
            for row in rows {
                let manufacturer = manufacturers[row]
                storageProvider.deleteManufacturer(manufacturer)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

.onDelete方法效果很好。

我想要实现的是,当单击制造商行时,

  • 该行中有一个复选标记
  • 并且变量“选择”应设置为制造商

我知道 AppStore 不允许使用烟草的应用程序。这只是一个学习应用程序。

MyApp.swift

@main
struct MyApp: App {
    let storageProvider = StorageProvider.standard
    
    var body: some Scene {
        WindowGroup {
            ContentView(storageProvider: storageProvider)
                .environment(\.managedObjectContext, storageProvider.persistentContainer.viewContext)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

ContentView.swift

import SwiftUI
import CodeScanner

struct ContentView: View {
    @Environment(\.dismiss) var dismiss
    
    let storageProvider: StorageProvider
    
    // Tobacco
    @State var tobaccoName: String = ""
    @State var tobaccoNr: String? = nil
    @State var ean: String? = nil
    
    // EAN
    @State private var isPresentingScanner = false
    @State private var scannedCode: String?
    
    // Manufacturers
    @FetchRequest(fetchRequest: ContentView.manufacturerByName)
    var manufacturers: FetchedResults<Manufacturer>
    
    @StateObject var searchText = SearchText()
    @State private var selection: Manufacturer?
    
    var body: some View {
        Form {
            Section("Tobacco Details") {
                TextField("Name", text: $tobaccoName)
                TextField("Number (e.g. #017)", text: $tobaccoNr ?? "")
                    .keyboardType(.decimalPad)
                HStack {
                    TextField("ean", text: $ean ?? "")
                        .keyboardType(.numberPad)
                    Button {
                        isPresentingScanner.toggle()
                    } label: {
                        Image(systemName: "camera")
                    }
                }
            }
            Text(selection?.name ?? "no selection") // <-- here
            Button {
                storageProvider.saveManufacturer(named: "Test")
            } label: {
                Text("create manufacturer")
            }
            Section("Manufacturer") {
                List(selection: $selection) {
                    ForEach(manufacturers, id:\.self) { manufacturer in
                        // -- here, use a Label
                        Label(manufacturer.name ?? "--",
                              systemImage: selection == manufacturer ? "checkmark" : "circle")
                    }
                    .onDelete { rows in
                        for row in rows {
                            let manufacturer = manufacturers[row]
                            //  storageProvider.deleteManufacturer(manufacturer)
                        }
                    }
                }
            }
        }
        .navigationTitle("add Tobacco")
        .navigationBarTitleDisplayMode(.inline)
        
        // MARK: - EAN-Scan Sheet
        .sheet(isPresented: $isPresentingScanner) {
            CodeScannerView(codeTypes: [.ean13], showViewfinder: true) { response in
                switch response {
                case .success(let result): do {
                    ean = result.string
                    isPresentingScanner = false
                }
                case .failure(let error):
                    print(error.localizedDescription)
                }
            }
            .presentationDetents([.medium])
        }
        
        // MARK: - Bottom Button
        .safeAreaInset(edge: .bottom) {
            VStack {
                Button {
                    if selection == nil {
                        print("no manufacturer")
                    } else {
                        storageProvider.saveTabacco(
                            named: tobaccoName,
                            number: tobaccoNr,
                            ean: ean,
                            manufacturer: selection
                        )
                        dismiss()
                    }
                } label: {
                    Text("save Tobacco")
                        .font(.callout)
                        .frame(maxWidth: .infinity, minHeight: 44)
                }
                .buttonStyle(.borderedProminent)
                .contentTransition(.identity)
            }
            .frame(maxWidth: .infinity)
            .padding([.horizontal, .top])
            .background(.ultraThinMaterial)
        }
        
        // MARK: - Manufacturer Search-Filter
        .onReceive(searchText.$debounced) { query in
            /// don't filter when searchbar is empty
            guard !query.isEmpty else {
                manufacturers.nsPredicate = nil
                return
            }
            /// set filter when someone searches
            manufacturers.nsPredicate = NSPredicate(format: "%K CONTAINS[cd] %@", argumentArray: [#keyPath(Manufacturer.name), query])
        }
    }
}

func ??<T>(lhs: Binding<Optional<T>>, rhs: T) -> Binding<T> {
    Binding(
        get: { lhs.wrappedValue ?? rhs },
        set: { lhs.wrappedValue = $0 }
    )
}
Run Code Online (Sandbox Code Playgroud)

StorageProvider.swift

public class StorageProvider {
    
    public static var standard = StorageProvider()
    public let persistentContainer: NSPersistentContainer
    
    public init() {
        persistentContainer = NSPersistentContainer(name: "DataModel")
        
        persistentContainer.loadPersistentStores(completionHandler: { description, error in
            if let error = error {
                fatalError("Core Data store failed to load with error: \(error)")
            }
        })
    }
}



// MARK: - Functions to call from the Views
public extension StorageProvider {
    func saveTabacco(named name: String, number: String?, ean: String?, manufacturer: Manufacturer?) {
        let tabacco = Tabacco(context: persistentContainer.viewContext)
        tabacco.name = name
        tabacco.number = number
        tabacco.ean = ean
        tabacco.manufacturer = manufacturer
        tabacco.creationDate = Date.now
        
        do {
            try persistentContainer.viewContext.save()
            print("Tabacco saved succesfully")
        } catch {
            persistentContainer.viewContext.rollback()
            print("Failed to save Tabacco: \(error)")
        }
    }

    func saveManufacturer(named name: String) {
        let manufacturer = Manufacturer(context: persistentContainer.viewContext)
        manufacturer.name = name
        
        do {
            try persistentContainer.viewContext.save()
            print("Manufacturer saved succesfully")
        } catch {
            persistentContainer.viewContext.rollback()
            print("Failed to save Manufacturer: \(error)")
        }
    }

    func getAllTabaccos() -> [Tabacco] {
    let fetchRequest: NSFetchRequest<Tabacco> = Tabacco.fetchRequest()

    do {
      return try persistentContainer.viewContext.fetch(fetchRequest)
    } catch {
      print("Failed to fetch tabacco: \(error)")
      return []
    }
  }



  func deleteTabacco(_ tabacco: Tabacco) {
      persistentContainer.viewContext.delete(tabacco)
      
      do {
          try persistentContainer.viewContext.save()
      } catch {
          persistentContainer.viewContext.rollback()
          print("Failed to save context: \(error)")
      }
  }
    
    func deleteManufacturer(_ manufacturer: Manufacturer) {
        persistentContainer.viewContext.delete(manufacturer)
        
        do {
            try persistentContainer.viewContext.save()
        } catch {
            persistentContainer.viewContext.rollback()
            print("Failed to save context: \(error)")
        }
    }
    
    func updateTabacco(_ tabacco: Tabacco) {
        do {
            try persistentContainer.viewContext.save()
        } catch {
            persistentContainer.viewContext.rollback()
            print("Failed to save context: \(error)")
        }
    }
}

// MARK: - FetchRequest

extension ContentView {
    static var tabaccosByName: NSFetchRequest<Tabacco> {
        /// create Request
        let request: NSFetchRequest<Tabacco> = Tabacco.fetchRequest()
        
        /// sortDescriptor
        request.sortDescriptors = [NSSortDescriptor(keyPath: \Tabacco.name, ascending: true)]
        return request
    }
    
    static var manufacturerByName: NSFetchRequest<Manufacturer> {
        /// create Request
        let request: NSFetchRequest<Manufacturer> = Manufacturer.fetchRequest()
        
        /// sortDescriptor
        request.sortDescriptors = [NSSortDescriptor(keyPath: \Manufacturer.name, ascending: true)]
        return request
    }
}
Run Code Online (Sandbox Code Playgroud)

解决方法

Section("Manufacturer") {
    List() {
        TextField("search for manufacturer", text: $searchText.text)
        
        if manufacturers.isEmpty {
            if searchText.debounced != "" {
                NavigationLink(destination: CreateManuView(storageProvider: storageProvider, manuName: searchText.text)) {
                    Label("add **\(searchText.text)**", systemImage: "plus")
                        .tint(.accentColor)
                }
            } else {
                Text("search for a manufacturer to add")
                    .foregroundColor(.gray)
            }
        }
        
        ForEach(manufacturers) { manufacturer in
            Button(action: {
                selection = manufacturer
            }, label: {
                HStack {
                    Text(manufacturer.name ?? "--")
                        .tint(.primary)
                    Spacer()
                    if manufacturer == selection {
                        Image(systemName: "checkmark")
                    }
                }
            })
        }
        .onDelete { rows in
            for row in rows {
                let manufacturer = manufacturers[row]
                storageProvider.deleteManufacturer(manufacturer)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

wor*_*dog 1

下面是一个示例代码,显示了checkmark选择列表中的一行时的情况,并显示selection变量包含List(selection: $selection).

struct ContentView: View {
    // for testing
    // @State var manufacturers = [Manufacturer(name: "one"), Manufacturer(name: "two"), Manufacturer(name: "three")]

    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Manufacturer.name, ascending: true)],
        animation: .default)
     private var manufacturers: FetchedResults<Manufacturer>
    
    @State private var selection: Manufacturer?
    
    var body: some View{
        Text(selection?.name ?? "no selection") // <-- here
        Section("Manufacturer") {
            List(selection: $selection) {
                ForEach(manufacturers) { manufacturer in
                    // -- here, use a Label
                    Label(manufacturer.name ?? "--",
                          systemImage: selection == manufacturer ? "checkmark" : "circle")
                }
                .onDelete { rows in
                    for row in rows {
                        let manufacturer = manufacturers[row]
                        //  storageProvider.deleteManufacturer(manufacturer)
                    }
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述