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