0

I'm trying do delete the note from Realm in my NotesApp and facing this error: "Can only delete an object from the Realm it belongs to". This note has been saved before also in Realm and could display it in my TableView by tapping on the date in my FSCalendar. I tried to replace realm.add(item) with realm.create(item), but also got the error: "Cannot convert value of type 'T' to expected argument type 'Object.Type' (aka 'RealmSwiftObject.Type')". I'm new in programming, so any help would be appreciated. Here's the relevant code code:

in my ToDoListItem.swift

class ToDoListItem: Object {
@objc dynamic var noteName: String = ""
@objc dynamic var date: Date =  Date()
@objc dynamic var descriptionText: String = ""
@objc dynamic var noteImage = Data()

init(date: Date, noteName: String) {
    self.date = date
    self.noteName = noteName
}

override init() {
    self.noteName = ""
    self.date = Date()
    self.descriptionText = ""
    self.noteImage = Data()
}

}

in my RealmManager.swift

class RealmManager {

static let shared = RealmManager()
private let realm = try! Realm()

func write<T: Object>(item: T) {
    realm.beginWrite()
    realm.add(item)
    try! realm.commitWrite()
}

func getObjects<T: Object>(type: T.Type) -> [T] {
    return realm.objects(T.self).map({ $0 })
}

func delete<T: Object>(item: T) {

    try! realm.write {
        realm.delete(item)
    }
}

}

in my ViewController where i can edit and delete the notes

@IBAction func didTapDelete() {
    
    let note = ToDoListItem()
    RealmManager.shared.delete(item: note)
    
    self.deletionHandler?()
    navigationController?.popToRootViewController(animated: true)
}

and finally in my TableViewController where the notes are displayed  (honestly i think the problem is hidden here but cannot find it...

@IBOutlet var tableViewPlanner: UITableView!
@IBOutlet var calendarView: FSCalendar!

private var data = [ToDoListItem]()

var datesOfEvents: [String] {
    return self.data.map { DateFormatters.stringFromDatestamp(datestamp: Int($0.date.timeIntervalSince1970)) }
}

var items: [ToDoListItem] = []

func getCount(for Date: String) -> Int {
    var count: [String : Int] = [:]
    for date in datesOfEvents {
        count[date] = (count[date] ?? 0) + 1
    }
    return count[Date] ?? 0
}

func getEventsForDate(date: Date) -> [ToDoListItem] {
    let string = DateFormatters.stringFromDatestamp(datestamp: Int(date.timeIntervalSince1970))
    return self.data.filter {DateFormatters.stringFromDatestamp(datestamp: Int($0.date.timeIntervalSince1970)) == string }.sorted(by: {$0.date < $1.date})
}

override func viewDidLoad() {
    super.viewDidLoad()
    
    calendarView.rounding()
    tableViewPlanner.rounding()
    
    data = RealmManager.shared.getObjects(type: ToDoListItem.self)
    self.items = self.getEventsForDate(date: Date())
    
    calendarView.delegate = self
    calendarView.dataSource = self
    tableViewPlanner.delegate = self
    tableViewPlanner.dataSource = self
    
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(true)
    
    self.calendarView.select(Date())
    self.calendarView.reloadData()
    refresh()
}

//MARK:- TableView Data Source
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return self.items.count //data.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: K.plannerCellIdentifier, for: indexPath) as! NoteTableViewCell
    let note = self.items[indexPath.row]
    cell.configureCell(note: note)
    
    return cell
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath, animated: true)
    
    let note = data[indexPath.row]
    guard
        let vc = storyboard?.instantiateViewController(identifier: K.infoVCIdentifier) as? InfoViewController else { return }
    vc.note = note
    vc.deletionHandler = { [weak self] in
        self?.refresh()
    }
    vc.title = note.noteName
    navigationController?.pushViewController(vc, animated: true)
}

//MARK:- User Interaction
@IBAction func didTapAddButton() {
    guard
        let vc = storyboard?.instantiateViewController(identifier: K.entryVCIdentifier) as? EntryViewController else { return }
    vc.completionHandler = { [weak self] in
        self?.refresh()
    }
    vc.title = K.entryVCTitle
    navigationController?.pushViewController(vc, animated: true)
}

func refresh() {
    
    DispatchQueue.main.async {
        self.data = RealmManager.shared.getObjects(type: ToDoListItem.self)
        self.tableViewPlanner.reloadData()
        self.calendarView.reloadData()
    }
}

}

extension PlannerViewController: FSCalendarDelegateAppearance & FSCalendarDataSource {

func calendar(_ calendar: FSCalendar, appearance: FSCalendarAppearance, eventDefaultColorsFor date: Date) -> [UIColor]? {
    let dateString = DateFormatters.yearAndMonthAndDateFormatter.string(from: date)
    if self.datesOfEvents.contains(dateString) {
        return [UIColor.blue]
    }
    return [UIColor.white]
}

func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
    self.items = self.getEventsForDate(date: date)
    self.tableViewPlanner.reloadData()

}

func calendar(_ calendar: FSCalendar, numberOfEventsFor date: Date) -> Int {
    let dateString = DateFormatters.yearAndMonthAndDateFormatter.string(from: date)
    let count = self.getCount(for: dateString)
    self.tableViewPlanner.reloadData()
    return count
}

}

  • You are attempting to delete an object that does not exist in Realm. Look in your `tapToDelete` function. You first create a new item in memory `let note = ToDoListItem()` and then try to delete it from Realm `RealmManager.shared.delete(item: note)`. You have to write it to Realm in order to delete it from Realm. – Jay Mar 30 '21 at 17:49
  • Also, this code `realm.objects(T.self).map({ $0 })` will become an issue as you are casting your live updating realm objects to an array which then disconnects them from Realm, so they are no longer live updating. Also, Realm Results are lazily loaded to even thousands of objects have little memory impact. When thats cast to an array ALL of the items are loaded in memory which could overwhelm the device and cause it to crash. See [this answer](https://stackoverflow.com/questions/66603093/modeling-sub-collections-in-mongodb-realm-sync/66643920#66643920) – Jay Mar 30 '21 at 17:52
  • @Jay Thank you so much! So, how exactly do I need to change my tapToDelete() method? My "let note" is useless and how do I reach the real object in Realm? From my TableViewController I'm sending the note(by clicking on it) to my InfoViewController where I can edit or delete this note (here: https://github.com/NikolaiBorisov/DailyPlanner/blob/main/DailyPlanner/Controller/InfoViewController.swift). And how do I need to change this code "realm.objects(T.self).map({ $0 })" to avoid the issues? Sorry for the questions, I'm just trying to understand what I need to do step by step. Thanks=) – Nikolai Borisov Mar 30 '21 at 22:20
  • At a high level, your tableView is backed by a dataSource - typically an array or realm collection (results or list). Within that array or list you will have objects that contain whatever data is displayed in your tableView. If you tap to delete row 1 in your table, you need to get the row index, 1 in this case, get that object from your dataSource and then delete it from Realm and remove it from your dataSource. There are caveats to this; if your dataSource is a Results object, it will automatically update. If you observing the objects, then update the list via the observe event. – Jay Mar 31 '21 at 14:59
  • @Jay Thanks a lot! – Nikolai Borisov Mar 31 '21 at 16:08

1 Answers1

0

The real problem is the didTapDelete function -- Why are you creating a new note just to delete it (I hope it was only to test out realm delete syntax). You should delete the note object that you passed to the view controller to edit / delete. (vc.note in did select row -> self.note in the other VC - where didTapDelete is) So your did tap delete will look like --

RealmManager.shared.delete(item: note)
//show deleted alert & go back

A little explanation on the error - Just instantiating a Realm object (ToDoListItem()) does not add it to Realm (the Database system). To delete / edit a realm object, it has to be either fetched from a realm (RealmManager.shared.getObjects(type: ToDoListItem.self)) or added to the realm.

I'd advise going through a Realm tutorial before jumping in the code (there are plenty of them)

  • Thank you, my friend! I've changed my didTapDelete function: didTapDelete() { RealmManager.shared.delete(item: note!) } But after that I'm getting the new error: "Thread 1: "Object has been deleted or invalidated." ". In TableViewController I'm working with "private var data = [ToDoListItem]()" and in ViewController, where i'm trying to delete the note, I'm working with "public var note: ToDoListItem?" maybe it's the problem? Also here's the full project: https://github.com/NikolaiBorisov/DailyPlanner, maybe it helps to clarify my question. Thank you in advance!=) – Nikolai Borisov Mar 30 '21 at 07:46
  • I had a look at the project -- can't figure out why you would get that error. Some things I would recommend though- 1. replace data & items with a single object - items of type Results, you'll have to do the same for get objects method in RealmManager. 2. instead of refresh method & deletion, completion blocks, use realm observer block on the above items (Results). That should sort out the problem. My best guess is its because of copying of Items in data & items (though it still shouldn't cause the crash). In any case, the above points will improve the code at least. – Harman Orsay Mar 30 '21 at 08:26
  • Thanks a lot, my friend! I'll try! – Nikolai Borisov Mar 30 '21 at 08:39
  • @NikolaiBorisov There is no reason to write an object in order to delete it. You're trying to delete an object from Realm that doesn't exist in Realm. This `let note = ToDoListItem()` creates a new object. If you want to delete an existing object in `tapToDelete`, that's not how to do it. Oh, and see my comment as you've got other issues to address as well. – Jay Mar 30 '21 at 17:56