enum SetRecordKeys: String { case type = "Set" case name case code case releaseDate case numberOfCards } struct Set: CKRecordValueProtocol, Identifiable { var id: CKRecord.ID? var name: String var code: String var releaseDate: Date var numberOfCards: Int static let example = Set(id: CKRecord.ID(), name: "Serviteur du pharaon", code: "PSV", releaseDate: Date(), numberOfCards: 105) } extension Set { init?(record: CKRecord) { guard let name = record[SetRecordKeys.name.rawValue] as? String, let code = record[SetRecordKeys.code.rawValue] as? String, let releaseDate = record[SetRecordKeys.releaseDate.rawValue] as? Date, let numberOfCards = record[SetRecordKeys.numberOfCards.rawValue] as? Int else { return nil } self.init(id: record.recordID, name: name, code: code, releaseDate: releaseDate, numberOfCards: numberOfCards) } } extension Set { var set: CKRecord { let set = CKRecord(recordType: SetRecordKeys.type.rawValue) set[SetRecordKeys.name.rawValue] = name set[SetRecordKeys.code.rawValue] = code set[SetRecordKeys.releaseDate.rawValue] = releaseDate set[SetRecordKeys.numberOfCards.rawValue] = numberOfCards return set } } struct SetsView: View { @StateObject private var setsViewModel = SetsViewModel() @State private var searchText = "" @State private var showingConfirmationDialog = false @State private var showingAddSetView = false var filteredSets: [Set] { if searchText.isEmpty { return setsViewModel.sets } else { return setsViewModel.sets.filter { set in set.code.lowercased().contains(searchText.lowercased()) || set.name.lowercased().contains(searchText.lowercased()) } } } var body: some View { NavigationView { List { ForEach(filteredSets) { set in VStack { HStack { Text(set.name) .font(.headline) .alignmentGuide(.leading) {_ in 0 } Spacer() } HStack { Text(set.code) Text("-") Text("\(set.releaseDate.formatted(date: .numeric, time: .omitted))") Text("-") Text(set.numberOfCards == 1 ? "1 card" : "\(set.numberOfCards) cards") } } } .onDelete { indexSet in indexSet.forEach { index in let set = setsViewModel.sets[index] if let recordId = set.id { setsViewModel.delete(recordId) } } } } .toolbar { Button { showingAddSetView = true } label: { Image(systemName: "plus.circle.fill") } .sheet(isPresented: $showingAddSetView) { AddSetView(addSetViewModel: AddSetView.AddSetViewModel(set: Set.example)) } .task { do { let _: () = try await setsViewModel.fetchSets() // Fetching Sets records } catch { print(error.localizedDescription) } } } .searchable(text: $searchText, prompt: "Search a set") .navigationTitle("Sets") } } } struct AddSetView: View { @Environment(\.dismiss) var dismiss @ObservedObject var addSetViewModel: AddSetViewModel var body: some View { NavigationView { VStack(spacing: 20) { HStack { Text("Name:") .font(.headline) TextField("Magician's Force", text: $addSetViewModel.name) .textFieldStyle(RoundedBorderTextFieldStyle()) } HStack { Text("Code:") .font(.headline) TextField("MFC", text: $addSetViewModel.code) .textFieldStyle(RoundedBorderTextFieldStyle()) .textInputAutocapitalization(.characters) .autocorrectionDisabled() } DatePicker( "Release Date:", selection: $addSetViewModel.releaseDate, displayedComponents: [.date] ) .font(.headline) HStack { Text("Number of Cards:") TextField("", value: $addSetViewModel.numberOfCards, formatter: NumberFormatter()) .keyboardType(.numberPad) .textFieldStyle(.roundedBorder) } .font(.headline) Button(action: { Task { do { let _: () = try await addSetViewModel.add() // Adding this new set to records } catch { print(error.localizedDescription) } } dismiss() }, label: { Text("Add") .font(.headline) .foregroundColor(.white) .padding() .frame(maxWidth: .infinity) .background(isEmptyFields(name: addSetViewModel.name, code: addSetViewModel.code) ? Color.gray : Color.blue) .cornerRadius(10) }) } .padding() .navigationTitle("New set") } } }