skip to Main Content

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>()
}```

2

Answers


  1. 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.

    Login or Signup to reply.
  2. 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()
        }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search