Cannot convert value of type 'Binding<[ContactEntity]>.Element' (aka 'Binding<ContactEntity>') to expected argument type 'ContactEntity' - swift

Using Xcode 13.4.1 on macOS 12.5. I revised the working code to conform to MVVM. This was successful for the first Entity (all properties Optional) for all CRUD operations.
Using this code as a base, I tackled the second Entity (one Bool property NOT Optional), but it throws the compiler error inside the ForEach loop, against 'contact'. This code was error-free before the MVVM conversion. I've been at this for 4 days and am reaching out, but clearly my limited knowledge is inadequate.
ContactListView code below, supported by the ContactViewModel, which in turn relies on the CoreDataManager code.
import SwiftUI
import CoreData
//class FirstNameSort: ObservableObject {
// #Published var firstNameSort: Bool = false
//}
struct ContactsListView: View {
// MARK: - PROPERTIES
#Environment(\.managedObjectContext) var viewContext
#ObservedObject var contactVM = ContactViewModel()
#State private var totalContacts: Int = 0
#State private var search: String = ""
#State private var searchByChampions = false
#State var searchByFirstNames = false
#State private var totalChampions = 0
// MARK: - BODY
var body: some View {
NavigationView {
VStack {
// HStack {
// Toggle("Display Champions only", isOn: $searchByChampions)
// .toggleStyle(.switch)
// .foregroundColor(.blue)
// .padding()
// Toggle("Sort by First Names", isOn: $contactVM.sortFirstName)
// .toggleStyle(.switch)
// .foregroundColor(.blue)
// .padding()
//}
List {
HStack {
Text(searchByChampions ? "Total Champions" : "Total Contacts")
.foregroundColor(.gray)
Spacer()
Text("\(searchByChampions ? totalChampions : totalContacts)")
.bold()
}.foregroundColor(.green)
.padding()
ForEach($contactVM.listofContacts) { contact in
NavigationLink(destination:
ModifyContactView(contact: ***contact***)
.id(UUID()), label: {
ContactRowView(contact: ***contact***)
.id(UUID())
})
}
.onDelete(perform: contactVM.deleteContact)
}.navigationTitle("Contacts")
.toolbar {
#if os(iOS)
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
#endif
ToolbarItem(placement: .navigationBarTrailing) {
NavigationLink(destination: AddContactView(), label: {
Image(systemName: "plus.circle")
})
}
}
.onAppear {
countContacts()
countChampions()
}
.searchable(text: $search, prompt: Text("Contact Last Name?"))
// .onChange(of: search) { value in
// if !value.isEmpty {
// listofContacts.nsPredicate = NSPredicate(format: "contactLastName CONTAINS[dc] %#", value)
// } else {
// listofContacts.nsPredicate = nil
// }
// }
}
}.navigationViewStyle(.stack)
}
func countContacts() {
totalContacts = $contactVM.listofContacts.count
}
// func countChampions() {
// totalChampions = $contactVM.listOfChampions.count
// }
}
import CoreData
import SwiftUI
class ContactViewModel: ObservableObject {
#Environment(\.dismiss) var dismiss
#ObservedObject var dataVM = CoreDataManager()
#ObservedObject var qualifierVM = QualifierViewModel()
#Published var inputFirstName: String = ""
#Published var inputLastName: String = ""
#Published var inputCellNumber: String = ""
#Published var inputEmail: String = ""
#Published var inputChampion: Bool = false
#Published var inputComments: String = ""
#Published var inputCreated: Date = Date()
#Published var inputUpdated: Date = Date()
#Published var listOfFirstNames = []
#Published var listofContacts: [ContactEntity] = []
func fetchContacts() {
let request = NSFetchRequest<ContactEntity>(entityName: "ContactEntity")
do {
dataVM.listofContacts = try dataVM.container.viewContext.fetch(request)
} catch let error {
print("Error fetching. \(error)")
}
}
func addContact(
contactFirstName: String,
contactLastName: String,
contactCellNumber: String,
contactEmail: String,
contactChampion: Bool,
contactComments: String,
contactCreated: Date,
contactUpdated: Date) {
let newContact = ContactEntity(context: dataVM.container.viewContext)
newContact.contactFirstName = contactFirstName
newContact.contactLastName = contactLastName
newContact.contactCellNumber = contactCellNumber
newContact.contactEmail = contactEmail
newContact.contactChampion = contactChampion
newContact.contactComments = contactComments
newContact.contactUpdated = Date()
newContact.contactCreated = Date()
let uniqueClient = Set(dataVM.selectedClient)
for client in uniqueClient {
newContact.addToClients(client)
print("Client: \(client.clientName ?? "No client")")
}
saveContact()
dismiss()
}
func deleteContact(indexSet: IndexSet) {
guard let index = indexSet.first else { return }
let entity = dataVM.listofContacts[index]
dataVM.container.viewContext.delete(entity)
saveContact()
}
func saveContact() {
do {
try dataVM.container.viewContext.save()
fetchContacts()
} catch let error {
print("Error saving. \(error)")
}
}
func sortLastName() -> [ Array<Any>] {
let listOfLastNames = dataVM.listofContacts.sorted {
$0.contactLastName ?? "" < $1.contactLastName ?? ""
}
return [listOfLastNames]
}
func sortFirstName() -> [ Array<Any>] {
let listOfFirstNames = dataVM.listofContacts.sorted {
$0.contactFirstName ?? "" < $1.contactFirstName ?? ""
}
return [listOfFirstNames]
}
}
import Foundation
import CoreData
class CoreDataManager: ObservableObject {
let container: NSPersistentContainer
#Published var listOfQualifiers: [QQEntity] = []
#Published var listofContacts: [ContactEntity] = []
#Published var listOfClients: [ClientEntity] = []
#Published var listOfOpportunities: [OpportunityEntity] = []
//#Published var selectedClient: [ClientEntity] = []
init() {
container = NSPersistentContainer(name: "B2BContainer")
container.loadPersistentStores { (description, error) in
if let error = error {
fatalError("Error loading Core Data. \(error)")
} else {
print("Successfully loaded Core Data...")
}
}
}
}

Related

Cannot convert value of type 'Notifications.Type' to expected argument type 'Notifications'

I trying to integreat firebase with my program to make true or false but I keep get the error
Cannot convert value of type 'Notifications.Type' to expected argument type 'Notifications'
Here is my code
import SwiftUI
struct ProfileHost: View {
#Environment(\.editMode) var editMode
#EnvironmentObject var modelData: ModelData
#State private var draftProfile = Profile.default
#State private var notifications = Notifications(id: "", prefersNotifications: true)
var body: some View {
VStack(alignment: .leading, spacing: 20) {
HStack {
if editMode?.wrappedValue == .active {
Button("Cancel", role: .cancel) {
draftProfile = modelData.profile
notifications = modelData.notifications
editMode?.animation().wrappedValue = .inactive
}
}
Spacer()
EditButton()
}
if editMode?.wrappedValue == .inactive {
ProfileSummary(profile: modelData.profile, notifications: modelData.notifications)
} else {
ProfileEditor(profile: $draftProfile, notifications: $notifications)
.onAppear {
draftProfile = modelData.profile
}
.onDisappear {
modelData.profile = draftProfile
}
}
}
.padding()
}
}
struct ProfileHost_Previews: PreviewProvider {
static var previews: some View {
ProfileHost()
.environmentObject(ModelData())
}
}
Modeldata is the file I made the notifications var
This the code of modeldata
import Foundation
import Combine
final class ModelData: ObservableObject {
#Published var landmarks: [Landmark] = load("landmarkData.json")
var hikes: [Hike] = load("hikeData.json")
#Published var profile = Profile.default
#Published var notifications = Notifications.self
var features: [Landmark] {
landmarks.filter { $0.isFeatured }
}
var categories: [String: [Landmark]] {
Dictionary(
grouping: landmarks,
by: { $0.category.rawValue }
)
}
}
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)")
}
}
at
if editMode?.wrappedValue == .inactive {
ProfileSummary(profile: modelData.profile, notifications: modelData.notifications)
I get an error that says.
Cannot convert value of type 'Notifications.Type' to expected argument
type 'Notifications'
I tried to look at other wedsites like github and reddit

Save objects in Realm only when Save button is pressed

I'm trying to create a really simple app in SwifUI + Realm but I cannot understand how can I make it work. Basically I have a list of objects. When I click on a row it will open a sheet to edit the underlying object, if i press "+" it will open same sheet to create a new object.
In the sheet object will be save only if "Save" button is pressed.
Here is my model
class PayableEntityType: Object {
#Persisted(primaryKey: true) var _id: ObjectId
#Persisted var name: String
#Persisted var iconName: String
#Persisted var mainColor: String
#Persisted var secondaryColor: String
#Persisted var _partitionValue: String = AppInfo.partitionValue
convenience init(name: String, mainColor: String, secondaryColor: String, iconName: String) {
self.init()
self.name = name
self.mainColor = mainColor
self.secondaryColor = secondaryColor
self.iconName = iconName
}
}
This is my list view
struct PayableEntityTypesListScreen: View {
#Environment(\.presentationMode) private var presentationMode
#ObservedResults(PayableEntityType.self) private var payableEntityTypes
#State var selectedPayableEntityType: PayableEntityType
#State var forEdit: PayableEntityType?
#State var openForSelect: Bool = false
#State private var showEdit: Bool = false
#State private var isNew: Bool = false
var body: some View {
NavigationView {
List {
ForEach(self.payableEntityTypes.sorted(byKeyPath: "name")) { payableEntityType in
Text(payableEntityType.name).onTapGesture {
self.rowTap(payableEntityType)
}
}
}.padding(.top, 1)
}.navigationTitle("Entity type")
.toolbar {
if !openForSelect {
self.newPayableEntityTypeButton()
}
}
}
private func rowTap(_ payableEntityType: PayableEntityType) {
self.selectedPayableEntityType = payableEntityType
if self.openForSelect {
self.presentationMode.wrappedValue.dismiss()
} else {
self.isNew = false
self.showEdit = true
}
}
#ToolbarContentBuilder
private func newPayableEntityTypeButton() -> some ToolbarContent {
ToolbarItem(placement: .navigationBarTrailing) {
AddButton(action: {
self.isNew = true
self.showEdit = true
}).sheet(isPresented: self.$showEdit) {
EditPayableEntityTypeScreen(payableEntityType: self.isNew ? PayableEntityType() : self.selectedPayableEntityType)
}
}
}
}
and this is the edit\create view
struct EditPayableEntityTypeScreen: View {
#Environment(\.realm) var realm
#Environment(\.presentationMode) private var presentationMode
#ObservedRealmObject var payableEntityType: PayableEntityType
#State private var errorMessage: String = ""
#State private var showingAlert: Bool = false
#State private var name: String = ""
#State private var iconName: String = ""
#State private var selectedMainColor: Color = .green
#State private var selectedSecondaryColor: Color = .black
private var isUpdating: Bool {
self.payableEntityType.realm != nil
}
init(payableEntityType: PayableEntityType) {
self.payableEntityType = payableEntityType
if self.isUpdating {
self._name = .init(initialValue: self.payableEntityType.name)
self._iconName = .init(initialValue: self.payableEntityType.iconName)
self._selectedMainColor = State<Color>.init(initialValue: Color(hex: self.payableEntityType.mainColor))
self._selectedSecondaryColor = State<Color>.init(initialValue: Color(hex: self.payableEntityType.secondaryColor))
}
}
var body: some View {
NavigationView {
Form {
Section(header: Text("Info")) {
TextField("name", text: self.$name)
.disableAutocorrection(true)
}
Button(action: {
self.save()
}, label: {
Text("SAVE")
}).alert(isPresented: $showingAlert) {
Alert(title: Text("Warning"), message: Text(self.errorMessage), dismissButton: .default(Text("OK")))
}
}
}
}
private func save() {
if self.name.isEmpty {
self.errorMessage = "Missing name 😅"
self.showingAlert = true
return
}
self.errorMessage = ""
do {
if self.isUpdating {
try realm.write {
self.$payableEntityType.name.wrappedValue = self.name
self.$payableEntityType.iconName.wrappedValue = self.iconName
self.$payableEntityType.mainColor.wrappedValue = self.selectedMainColor.toHex()
self.$payableEntityType.secondaryColor.wrappedValue = self.selectedSecondaryColor.toHex()
}
} else {
self.payableEntityType.name = self.name
self.payableEntityType.iconName = self.iconName
self.payableEntityType.mainColor = self.selectedMainColor.toHex()
self.payableEntityType.secondaryColor = self.selectedSecondaryColor.toHex()
try realm.write {
realm.add(self.payableEntityType)
}
}
self.presentationMode.wrappedValue.dismiss()
} catch {
self.errorMessage = error.localizedDescription
self.showingAlert = true
}
}
}
This code works, but I think is not the right way to use Realm property wrappers.
Note: the PayableEntityTypesListScreen has a openForSelect variable because I want to reuse that view to only select one of list elements and the close it.

Swiftui #binding ForEach loop with timer function not working

Hy folks, I work on a litte project for a time tracker and use Core Data for storing the values. Every timer Note has a seconds value stored that runs from inside of each timer view. I want to populate now these values to the parent view, but it's not working even when binding the note to the view. I know i have to populate the changes somehow... Can somebody help?
ContentView:
import SwiftUI
extension Int: Identifiable {
public var id: Int { self }
}
struct ContentView: View {
let coreDM: CoreDataManager
#State private var noteTitle: String = ""
#State private var notes: [Note] = [Note]() // That's the Core Data Model
private func populateNotes() {
notes = coreDM.getAllNotes()
}
var body: some View {
VStack {
if notes.count > 0 {
ForEach(0..<$notes.count,id: \.self) { i in
Text("\(notes[i].seconds)")
}
}
TextField("Enter title", text: $noteTitle)
.textFieldStyle(RoundedBorderTextFieldStyle())
Button("Save") {
coreDM.saveNote(title: noteTitle, seconds: 0)
populateNotes()
}
List {
if notes.count > 0 {
ForEach(0..<$notes.count,id: \.self) { i in
NoteListView(note: $notes[i], coreDM: coreDM)
Button("Delete"){
coreDM.deleteNote(note: notes[i])
populateNotes()
}
}
}
}.listStyle(PlainListStyle())
Spacer()
}.padding()
.onAppear(perform: {
populateNotes()
})
}
}
NoteListView:
import SwiftUI
import Combine
struct NoteListView: View {
#Binding var note: Note
let coreDM: CoreDataManager
#State private var noteSeconds: Double = 0.0
#State private var noteIsRunning: Bool = false
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View {
VStack{
Text(note.title ?? "")
Text("\(noteSeconds)")
.onReceive(timer) { time in
if noteIsRunning {
noteSeconds += 1
note.seconds = noteSeconds
}
}
if noteIsRunning {
Image(systemName: "pause.circle")
.resizable()
.frame(width:20, height: 20)
.onTapGesture {
withAnimation{
noteIsRunning.toggle()
note.seconds = noteSeconds
coreDM.updateNote()
}
}
}else{
Image(systemName: "record.circle")
.resizable()
.foregroundColor(.orange)
.frame(width:20, height: 20)
.onTapGesture {
withAnimation{
noteIsRunning.toggle()
}
}
}
}
.onAppear(){
noteSeconds = note.seconds
}
}
}
CoreDataManager:
import Foundation
import CoreData
class CoreDataManager {
let persistentContainer: NSPersistentContainer
init() {
persistentContainer = NSPersistentContainer(name: "TimeTrackerDataModel2")
persistentContainer.loadPersistentStores { (description, error) in
if let error = error {
fatalError("Core Data Store failed \(error.localizedDescription)")
}
}
}
func updateNote() {
do {
try persistentContainer.viewContext.save()
} catch {
persistentContainer.viewContext.rollback()
}
}
func deleteNote(note: Note) {
persistentContainer.viewContext.delete(note)
do {
try persistentContainer.viewContext.save()
} catch {
persistentContainer.viewContext.rollback()
print("Failed to save context \(error)")
}
}
func getAllNotes() -> [Note] {
let fetchRequest: NSFetchRequest<Note> = Note.fetchRequest()
do {
return try persistentContainer.viewContext.fetch(fetchRequest)
} catch {
return []
}
}
func saveNote(title: String, seconds: Double) {
let note = Note(context: persistentContainer.viewContext)
note.title = title
note.seconds = seconds
do {
try persistentContainer.viewContext.save()
} catch {
print("Failed to save note: \(error)")
}
}
}

SwiftUI ToDoList with checkboxes?

I want to write a ToDoList in swiftUI with core data. Everything works so far but I want to have a checkbox next to each item it Signify whether it is completed or not.
I have added a property isChecked:boolean in core data but I don't know how to properly read it from the database. How to use a Toggle() in my case?
struct ContentView: View {
#Environment(\.managedObjectContext) var context
#FetchRequest(fetchRequest: ToDoListItem.getAllToDoListItems())
var items: FetchedResults<ToDoListItem>
#State var text: String = ""
var body: some View {
NavigationView {
List {
Section (header: Text("NewItem")){
HStack {
TextField("Enter new Item.",text: $text)
Button(action: {
if !text.isEmpty{
let newItem = ToDoListItem(context: context)
newItem.name = text
newItem.createdAt = Date()
// current date as created
newItem.isChecked = false
do {
try context.save()
} catch {
print(error)
}
// to clear the textField from the previous entry
text = ""
}
}, label: {
Text("Save")
})
}// end of vstack
}
Section {
ForEach(items){ toDoListItem in
VStack(alignment: .leading){
// to have a checkbox
Button {
toDoListItem.isChecked.toggle()
} label: {
Label(toDoListItem.name!, systemImage: toDoListItem.isChecked ? "checkbox.square" : "square")
}
if let name = toDoListItem.name {
// Toggle(isOn: toDoListItem.isChecked)
Text(name)
.font(.headline)
}
//Text(toDoListItem.name!)
//.font(.headline)
if let createdAt = toDoListItem.createdAt {
//Text("\(toDoListItem.createdAt!)")
Text("\(createdAt)")
}
}
}.onDelete(perform: { indexSet in
guard let index = indexSet.first else {
return
}
let itemToDelete = items[index]
context.delete(itemToDelete)
do {
try context.save()
}
catch {
print(error)
}
})
}
}
.navigationTitle("To Do List")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
ToDoListItem.swift
class ToDoListItem: NSManagedObject,Identifiable {
#NSManaged var name:String?
#NSManaged var createdAt:Date?
#NSManaged var isChecked:Bool
// mapped to the entry properties in database
}
extension ToDoListItem {
static func getAllToDoListItems() -> NSFetchRequest<ToDoListItem>{
let request:NSFetchRequest<ToDoListItem> = ToDoListItem.fetchRequest() as!
NSFetchRequest<ToDoListItem>
// cast as todolist item
let sort = NSSortDescriptor(key: "createdAt", ascending: true)
// above order of sorting
request.sortDescriptors = [sort]
return request
}
}
Should isChecked be an optional as well?

Crash when deleting data with relation in RealmSwift

I am creating an application using RealmSwift.
The following implementation crashed when deleting related data.
After removing only "UnderlayerItem", it succeeded.
Crash when deleting UnderlayerItem and deleting Item.
The error is:
Thread 1: Exception: "The RLMArray has been invalidated or the object
containing it has been deleted."
How do I delete without crashing?
struct ListView: View {
#ObservedObject private var fetcher = Fetcher()
#State private var title = ""
var body: some View {
NavigationView {
VStack {
TextField("add", text: $title) {
let item = Item()
item.title = self.title
let realm = try! Realm()
try! realm.write {
realm.add(item)
}
self.title = ""
}
ForEach(self.fetcher.items) { (item: Item) in
NavigationLink(destination: DetailView(item: item, id: item.id)) {
Text(item.title)
}
}
}
}
}
}
struct DetailView: View {
var item: Item
var id: String
#State private var title = ""
#ObservedObject private var fetcher = Fetcher()
#Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
TextField("add", text: $title) {
let realm = try! Realm()
if let item = realm.objects(Item.self).filter("id == '\(self.id)'").first {
try! realm.write() {
let underlayerItem = UnderlayerItem()
underlayerItem.title = self.title
item.underlayerItems.append(underlayerItem)
}
}
self.title = ""
}
ForEach(self.item.underlayerItems) { (underlayerItems: UnderlayerItem) in
Text(underlayerItems.title)
}
Button(action: {
self.presentationMode.wrappedValue.dismiss()
self.fetcher.delete(id: self.id)
}) {
Text("Delete")
}
}
}
}
class Fetcher: ObservableObject {
var realm = try! Realm()
var objectWillChange: ObservableObjectPublisher = .init()
private(set) var items: Results<Item>
private var notificationTokens: [NotificationToken] = []
init() {
items = realm.objects(Item.self)
notificationTokens.append(items.observe { _ in
self.objectWillChange.send()
})
}
func delete(id: String) {
guard let item = realm.objects(Item.self).filter("id == '\(id)'").first else { return }
try! realm.write() {
for underlayerItem in item.underlayerItems {
realm.delete(realm.objects(UnderlayerItem.self).filter("id == '\(underlayerItem.id)'").first!)
}
}
try! realm.write() {
realm.delete(item)
}
}
}
class Item: Object, Identifiable {
#objc dynamic var id = NSUUID().uuidString
#objc dynamic var title = ""
#objc dynamic var createdAt = NSDate()
let underlayerItems: List<UnderlayerItem> = List<UnderlayerItem>()
override static func primaryKey() -> String? {
return "id"
}
}
class UnderlayerItem: Object, Identifiable {
#objc dynamic var id = NSUUID().uuidString
#objc dynamic var title = ""
#objc dynamic var createdAt = NSDate()
override static func primaryKey() -> String? {
return "id"
}
}
You don't need to iterate over the objects in the list to delete them. Just do this
try! realm.write() {
realm.delete(item.underlayerItems)
}
I believe it's crashing because you're attempting to access an item that was deleted
self.item.underlayerItems