Home > Blockchain >  How do I modify a realm object without getting these error messages?
How do I modify a realm object without getting these error messages?

Time:02-11

I have a small Xcode project in Swift 5. It is a simple NotePad type app which has categories of notes so for example you could have a category of Home with an associated list of notes relevant to the home. I am having trouble modifying one of the notes of the associated category using Realm. I have tried placing the modification code inside a realm.write closure but I get the error message;

*** Terminating app due to uncaught exception 'RLMException', reason: 'The Realm is already in a write transaction'

When I remove the realm.write I get a different message;

*** Terminating app due to uncaught exception 'RLMException', reason: 'Attempting to modify object outside of a write transaction - call beginWriteTransaction on an RLMRealm instance first.

What's going on and how can I modify a note and update the realm database?

import RealmSwift

class NotePadListViewController: UITableViewController {

    var noteArray: Results<Note>?
    let realm = try! Realm()
    
    var selectedCategory: Category? {
        didSet {
            loadItems()
        }
    }

//    let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    
    @IBOutlet weak var searchBar: UISearchBar!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        searchBar.autocapitalizationType = .none
        
//        print(FileManager.default.urls(for: .documentDirectory, in: .userDomainMask))
        
    }
    
    // MARK: TableView Datasource Methods
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return noteArray?.count ?? 1
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath)
        
        cell.textLabel?.text = noteArray?[indexPath.row].title ?? "No Notes Added."
        
        return cell
    }
    
    // MARK: TableView Delegate Methods
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
        tableView.deselectRow(at: indexPath, animated: true)
        
        let createNoteView = CreateNoteView(frame: CGRect(x: (self.view.frame.width - 240.0)/2.0, y: (self.view.frame.height - 300.0)/2.0, width: 240.0, height: 300.0))
        
        createNoteView.savedNote = noteArray?[indexPath.row]
        view.addSubview(createNoteView)
        
        createNoteView.delegate = self
        
    }
    
    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        
        if let note = noteArray?[indexPath.row] {
            
            if editingStyle == .delete {
                do {
                    try realm.write {
                        realm.delete(note)
                        tableView.deleteRows(at: [indexPath], with: .fade)
                    }
                } catch {
                    print("Error deleting note: \(error)")
                }
            }
        }
    }
        
    // MARK: - Add new note
    
    @IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
            
        let createNoteView = CreateNoteView(frame: CGRect(x: (self.view.frame.width - 240.0)/2.0, y: (self.view.frame.height - 300.0)/2.0, width: 240.0, height: 300.0))
        
        createNoteView.savedNote = nil
        
        view.addSubview(createNoteView)
        
        createNoteView.delegate = self
        
    }
    
    func loadItems() {
        
        noteArray = selectedCategory?.notes.sorted(byKeyPath: "title", ascending: true)

        tableView.reloadData()
    }
}

// MARK: - Create Note View Protocol

extension NotePadListViewController: CreateNoteViewProtocol {

    func send(note: Note) {
        
        if let currentCategory = selectedCategory {
            
            if note.row == -1 {
                
                note.row = noteArray?.count ?? 0
                do {
                    try! realm.write {
                        currentCategory.notes.append(note)
                    }
                }
            } else {
                do {
                    try! realm.write {
                        let row = note.row
                        currentCategory.notes[row] = note
                    }
                }
                
                
            }
        }
        
        tableView.reloadData()
    }
}

// MARK: - SearchBar delegate methods

extension NotePadListViewController: UISearchBarDelegate {

    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        
        noteArray = noteArray?.filter("title CONTAINS[cd] %@ ", searchBar.text!).sorted(byKeyPath: "dateCreated", ascending: true)
        
        tableView.reloadData()
    }

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {

        if searchText == "" {
            loadItems()
            DispatchQueue.main.async {
                searchBar.resignFirstResponder()
            }
        }
    }
}
import UIKit
import RealmSwift

protocol CreateNoteViewProtocol: AnyObject {
    func send(note: Note)
}

class CreateNoteView: UIView, UITextViewDelegate, UITextFieldDelegate {

    var savedNote: Note? {
        didSet {
            if savedNote == nil {
                noteTitle.text = "Note title"
                noteTitle.textColor = UIColor.lightGray
                noteContent.text = "Type something interesting..."
                noteContent.textColor = UIColor.lightGray
            } else {
                noteTitle.text = savedNote!.title
                noteTitle.textColor = UIColor.black
                noteContent.text = savedNote!.content
                noteContent.textColor = UIColor.black
            }
        }
    }
    
    @IBOutlet weak var noteTitle: UITextField!
    @IBOutlet weak var noteContent: UITextView!
    
    weak var delegate: CreateNoteViewProtocol!
    
    let realm = try! Realm()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
        noteTitle.delegate = self
        noteContent.delegate = self
    }
    
    func commonInit() {
        
        let viewFromXib = Bundle.main.loadNibNamed("CreateNoteView", owner: self, options: nil)![0] as! UIView
        viewFromXib.frame = self.bounds
        addSubview(viewFromXib)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    @IBAction func cancelButtonTapped(_ UIButton: Any) {
        self.removeFromSuperview()
    }
    
    @IBAction func saveButtonTapped(_ UIButton: Any) {

        if let existingNote = savedNote {
            
            existingNote.title = noteTitle.text ?? "No Title"
            existingNote.content = noteContent.text
            delegate.send(note: existingNote)
            
        } else {
            
            let note = Note()
            note.title = noteTitle.text ?? "No Title"
            note.content = noteContent.text
            note.row = -1
            note.dateCreated = Date()
            delegate.send(note: note)
            
        }
        
        self.removeFromSuperview()
    }
    
    func textViewDidBeginEditing(_ textView: UITextView) {
        if textView.textColor == UIColor.lightGray {
            textView.text = nil
            textView.textColor = UIColor.black
        }
    }
    
    func textFieldDidBeginEditing(_ textField: UITextField) {
        if textField.textColor == UIColor.lightGray {
            textField.text = nil
            textField.textColor = UIColor.black
        }
    }
}
import Foundation
import RealmSwift

class Note: Object {
    @objc dynamic var title: String = ""
    @objc dynamic var content: String = ""
    @objc dynamic var row: Int = 0
    @objc dynamic var dateCreated: Date = Date()
    var parentCategory = LinkingObjects(fromType: Category.self, property: "notes")
}
import Foundation
import RealmSwift

class Category: Object {
    @objc dynamic var name: String = ""
    let notes = List<Note>()
}```

CodePudding user response:

Instead of declaring a realm instance multiple times, you can use it by making it sharedInstance.

Try to make RealmManager class.

import UIKit
import RealmSwift

class RealmManager {

    private var realm: Realm

    static let sharedInstance = RealmManager()

    private init() {
        realm = try! Realm()
    }

    func add(object: Object, shouldUpdate: Bool = true) {
        try! realm.write {
            if shouldUpdate {
                realm.add(object, update: .all)
            } else {
                realm.add(object)
            }
        }
    }
    
    func update(action: () -> Void) {
        try! realm.write {
            action()
        }
    }

    func deleteAll() {
        try! realm.write {
            realm.deleteAll()
        }
    }

    func delete(object: Object) {
        try! realm.write {
            realm.delete(object)
        }
    }

}

Now to use this in your code: Take an example of your NotePadListViewController.

Where you have written this block:

extension NotePadListViewController: CreateNoteViewProtocol {

    func send(note: Note) {        
        if let currentCategory = selectedCategory {
            if note.row == -1 {                
                note.row = noteArray?.count ?? 0
                do {
                    try! realm.write {
                        currentCategory.notes.append(note)
                    }
                }
            } else {
                do {
                    try! realm.write {
                        let row = note.row
                        currentCategory.notes[row] = note
                    }
                }               
            }
        }
        
        tableView.reloadData()
    }
}

Replace this code with the following block:

extension NotePadListViewController: CreateNoteViewProtocol {

    func send(note: Note) {        
        if let currentCategory = selectedCategory {
            if note.row == -1 {                
                note.row = noteArray?.count ?? 0
                RealmManager.sharedInstance.add(object: note)
            } else {
                RealmManager.sharedInstance.update {
                    let row = note.row
                    currentCategory.notes[row] = note
                }               
            }
        }
        tableView.reloadData()
    }
}

After this whenever you want to perform any action in the entire project with the realm database make use of RealmManager.sharedInstance.

CodePudding user response:

It works now when I have the following code in CreateNoteView;

    @IBAction func saveButtonTapped(_ UIButton: Any) {

        if let existingNote = savedNote {
            
            try! realm.write {
                existingNote.title = noteTitle.text ?? "No Title"
                existingNote.content = noteContent.text
            }
            delegate.send(note: existingNote)
            
        } else {
            
            let note = Note()
            note.title = noteTitle.text ?? "No Title"
            note.content = noteContent.text
            note.row = -1
            note.dateCreated = Date()
            delegate.send(note: note)
            
        }
        
        self.removeFromSuperview()
    }
  • Related