TextView and RealmSwift


#1

I have a project that segues from a tableViewController to a UITextView in a new controller. I am having a problem appending data to the realm file and then displaying it in the textView. Any help would be great.

Thanks

Cole

let realm = try! Realm()
var cloudNote: Results?
var selectedCategory : Notes? {
didSet{
loadModel()
}
}
@IBOutlet weak var quickNotes: UITextView!

override func viewDidLoad() {
    super.viewDidLoad()
    
    loadModel()
    self.quickNotes.delegate = self
    quickNotes.font = UIFont.preferredFont(forTextStyle: .headline)
  
    NotificationCenter.default.addObserver(self, selector: #selector(QuickNoteViewController.updateTextView(notification:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(QuickNoteViewController.updateTextView(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}

// textView Methods    
@objc func updateTextView(notification : Notification) {
    
            let userInfo = notification.userInfo
    
            let keyboardEndFrameScreenCoordinates = (userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
            let keyboardEndFrame = self.view.convert(keyboardEndFrameScreenCoordinates, to: view.window)
    
            if notification.name == UIApplication.keyboardWillHideNotification {
                quickNotes.contentInset = UIEdgeInsets.zero
            } else {
                quickNotes.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardEndFrame.height, right: 0)
    
                quickNotes.scrollIndicatorInsets = quickNotes.contentInset
            }
    
                quickNotes.scrollRangeToVisible(quickNotes.selectedRange)
    
        }


override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesBegan(touches, with: event)
    self.view.endEditing(true)
}

func textViewDidBeginEditing(_ textView: UITextView) {
    
    quickNotes.isEditable = true
    quickNotes.isUserInteractionEnabled = true
    quickNotes.isScrollEnabled = true
  

}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    return true
}


func textViewDidEndEditing(_ textView: UITextView) {
    appendData()
    quickNotes.resignFirstResponder()
    
}
@IBAction func doneEditing(_ sender: UIBarButtonItem) {
    appendData()
    quickNotes.resignFirstResponder()
}

//MARK: - Data manipulation Begins
func loadModel() {
    cloudNote = selectedCategory?.notes.sorted(byKeyPath: "cNotes", ascending: true)
}

func appendData() {
    if let appendedData = self.selectedCategory {
        do {
            try self.realm.write {
                let newNote = ListedNotes()
                newNote.cNotes = quickNotes.text!
                 newNote.dateCreated = Date()
                appendedData.notes.append(newNote)
            }
        } catch {
            print("There was an error appending notes \(error)")
        }
    }

}

#2

Can you clarify what ‘a problem’ means? It helps us to help you if the issue is clearly defined and a minimal amount of code to duplicate the issue is provided.


#3

So the problem is that when I call quickNotes.text = selectedCategory.cNotes it Is Grabbing data from the parent Model Notes() where as I need it to grab the (List) appended Data from the Model ListNotes().

Typically in a tableView you would just call tableView.reloadData(), but with a textView you need to assign the textView to equal the data in the model so it will display the data in the textView


#4

A few of things - and I probably am just overlooking it but this line

quickNotes.text = selectedCategory.cNotes

isn’t in the code in your question but it seems like that’s assigning a List of cNotes objects to a single text object. So maybe it should be

quickNotes.text = selectedCategory.cNotes.first

for the first object or maybe selectedCategory.cNotes[5] for the object at the 5th index? It’s not clear what Notes are (a List?) and the code that populates the var selectedCategory wasn’t included.

I think your objective is to get data from a certain selectedCategory.cNotes object into a textView, and per your comment the problem is that when you do that it’s pulling data from a Notes parent model instead of a ListNotes object.

it Is Grabbing data from the parent Model Notes() where as I need it to grab the (List) appended Data from the Model ListNotes().

but the selectedCategory var doesn’t contain ListNotes() objects, it contains Notes() objects. Perhaps that’s the issue?

var selectedCategory : Notes?


#5

Continuing the discussion from TextView and RealmSwift:

Thanks you have been helpful. Sorry I haven’t been clear. I have been working on this code and it wasn’t complete.

Here is my latest situation, trying to figure it out why quickNotes is not displaying cNotes model data.
In a separate view controller I am creating a list name for a note, when the list is pressed it segues to my ViewController with a textView where the note is displayed. The following code is from the textViewController. I have a model called ListedNotes that is the child to the Notes model. I am using the relationship method provided by realm. The Notes model is called under the call didSet with the variable selectedCategory. This calls my loadModel method. You’re right this does call from the Notes Model when I call quickNotes.text = selectedCategory.cNotes. Yet from what I understand my loadCategory function should be grabbing the ListNotes model which should in turn allow me to access the properties from ListNotes when I load them in to my viewDidLoad, but every time I try this, Xcode tells me that this property doesn’t exist in the model Notes. For some reason it is still try to grab data from Notes and not ListNotes.

let realm = try! Realm()
var cloudNote: Results?

@IBOutlet weak var quickNotes: UITextView!

    var selectedCategory : Notes? {
    didSet{
        loadModel()
    }
}

override func viewDidLoad() {
    super.viewDidLoad()
    loadModel()
    quickNotes.delegate = self
    quickNotes.font = UIFont.preferredFont(forTextStyle: .headline)
    quickNotes.text = selectedCategory?.cNotes

}
func loadModel() {
cloudNote = selectedNote?.notePad.sorted(byKeyPath: “cNotes”, ascending: true)
}

func appendData() {

    if let appendedData = self.selectedCategory {
        do {
            try self.realm.write {
                let newNote = ListedNotes()
                newNote.cNotes = self.quickNotes.text!
                 newNote.dateCreated = Date()
                appendedData.notePad.append(newNote)
            }
        } catch {
            print("There was an error appending notes \(error)")
        }
    }
}

#6

It’s going to be tricky trying to help as there’s not enough information in the question to really understand the question. For example

The Notes model is called under the call didSet with the variable selectedCategory.

so effectively it’s this

var selectedCategory : Notes? {
    didSet {
        cloudNote = selectedNote?.notePad.sorted(byKeyPath: “cNotes”, ascending: true)
    }
}

and has no affect on the selectedCategory var. What is cloudNote? This appears to be a sorted list so is cloudNote supposed to be a sorted list?

Yet from what I understand my loadCategory function

that function wasn’t included so what does is it supposed to do?

Do your models look like this?

class ListedNotes: Object {
    var some_var = ""
}

class Notes: Object {
    var notesList = List<ListedNotes>()
}

if not, what do they look like. If they do what part isn’t working?


#7

The question is why is realm writing data into my UITextVIew, but not displaying it on viewDidLoad. Here is the full code after some adjustments.

Thanks

This is the first View Controller it has a tableView that when you select a cell it segues to its own list.

import UIKit
import RealmSwift
import SwipeCellKit

class NotesListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextViewDelegate {

@IBOutlet var quickNoteTableView: UITableView!

let realm = try! Realm()
var notesListed: Results<Notes>?

override func viewDidLoad() {
    super.viewDidLoad()
    
    loadCategory()
    quickNoteTableView.rowHeight = 70.0
    quickNoteTableView.delegate = self
    quickNoteTableView.dataSource = self
}


func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return notesListed?.count ?? 0
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
    let cell = tableView.dequeueReusableCell(withIdentifier: "categoryCell", for: indexPath) as! SwipeTableViewCell
    cell.delegate = self
    cell.textLabel?.text = notesListed?[indexPath.row].title ?? "There were no notes added"
    
    return cell
    
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    performSegue(withIdentifier: "segueToNoteList", sender: self)
    tableView.deselectRow(at: indexPath, animated: true)
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    
    if segue.identifier == "segueToNoteList" {
        let destinationVC = segue.destination as! QuickNoteViewController
        
        if let indexPath = quickNoteTableView.indexPathForSelectedRow {
            destinationVC.selectedNote = notesListed?[indexPath.row]
        }
}
}

//    Save notes data
func saveNotes(notes: Notes){
    do {
        try realm.write {
            realm.add(notes)
        }
    } catch {
        print("there was an error saving content \(error)")
    }
    self.quickNoteTableView.reloadData()
}

//MARK: - Data Manipulation Methods
func loadCategory() {
    notesListed = realm.objects(Notes.self)
    quickNoteTableView.reloadData()
}

func updateModel(at indexPath: IndexPath) {
    
    if let categoryForDeletion = self.notesListed?[indexPath.row] {
        do {
            try self.realm.write {
                self.realm.delete(categoryForDeletion)
            }
        } catch {
            print("Error deleting category \(error)")
        }
        
    }
    
}
@IBAction func homeButtonPressed(_ sender: UIBarButtonItem) {
    performSegue(withIdentifier: "segueToHome", sender: self)
}

@IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
    
    var textField = UITextField()
    
    let alert = UIAlertController(title: "Name your Note", message: "Add A title for your note", preferredStyle: .alert)
    
    let action = UIAlertAction(title: "Add Note", style: .default) { (action) in
 
        let categoryName = Notes()
        categoryName.title = textField.text!
        self.saveNotes(notes: categoryName)
    
}
    alert.addTextField { (alertTextField) in
        alertTextField.placeholder = "Create New Note"
        textField = alertTextField
    }
    alert.addAction(action)
    
     alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: nil))
    
    present(alert, animated: true, completion: nil)
    
    
}

}

extension NotesListViewController: SwipeTableViewCellDelegate {

func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
    
    guard orientation == .right else { return nil }
    
    let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
        
        //Handle action by calling data model
        self.updateModel(at: indexPath)
        
    }
    
    deleteAction.image = UIImage(named: "delete-icon")
    
    return [deleteAction]
    
}

func tableView(_ tableView: UITableView, editActionsOptionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeOptions {
    var options = SwipeOptions()
    options.expansionStyle = .destructive
    options.transitionStyle = .border
    return options
}

}

// :MARK: - Search Bar Methods
extension NotesListViewController: UISearchBarDelegate {

func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
    searchBar.resignFirstResponder()
    notesListed = notesListed?.filter("title CONTAINS[cd] %@", searchBar.text!).sorted(byKeyPath: "dateCreated", ascending: true)
    quickNoteTableView.reloadData()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
    searchBar.resignFirstResponder()
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
    if searchBar.text?.count == 0 {
        loadCategory()
        
        DispatchQueue.main.async {
            searchBar.resignFirstResponder()
        }
    }
}

}

This is The second viewController, this is where notes are written into the textView

import UIKit
import RealmSwift
import SwipeCellKit

class QuickNoteViewController: UIViewController, UITextViewDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {

let realm = try! Realm()
var cloudNote: Results<ListedNotes>?

@IBOutlet weak var quickNotes: UITextView!

    var selectedNote : Notes? {
    didSet{
        loadModel()
    }
}

override func viewDidLoad() {
    super.viewDidLoad()
    loadModel()
    quickNotes.delegate = self
    quickNotes.font = UIFont.preferredFont(forTextStyle: .headline)
    quickNotes.text = selectedNote?.qNotes


    NotificationCenter.default.addObserver(self, selector: #selector(QuickNoteViewController.updateTextView(notification:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(QuickNoteViewController.updateTextView(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}

// textView Methods
@objc func updateTextView(notification : Notification) {

            let userInfo = notification.userInfo

            let keyboardEndFrameScreenCoordinates = (userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
            let keyboardEndFrame = self.view.convert(keyboardEndFrameScreenCoordinates, to: view.window)

            if notification.name == UIApplication.keyboardWillHideNotification {
                quickNotes.contentInset = UIEdgeInsets.zero
            } else {
                quickNotes.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardEndFrame.height, right: 0)

                quickNotes.scrollIndicatorInsets = quickNotes.contentInset
            }

                quickNotes.scrollRangeToVisible(quickNotes.selectedRange)

        }


override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesBegan(touches, with: event)
    self.view.endEditing(true)
}

func textViewDidBeginEditing(_ textView: UITextView) {

    quickNotes.isEditable = true
    quickNotes.isUserInteractionEnabled = true
    quickNotes.isScrollEnabled = true

}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    return true
}


func textViewDidEndEditing(_ textView: UITextView) {
    appendData()
    quickNotes.resignFirstResponder()
}
@IBAction func doneEditing(_ sender: UIBarButtonItem) {
    appendData()
    quickNotes.resignFirstResponder()
}

//MARK: - Data manipulation Begins
func loadModel() {
    cloudNote = selectedNote?.notePad.sorted(byKeyPath: "cNotes", ascending: true)
}

func appendData() {

    if let appendedData = self.selectedNote {
        do {
            try self.realm.write {
                let newNote = ListedNotes()
                newNote.cNotes = self.quickNotes.text!
                 newNote.dateCreated = Date()
                appendedData.notePad.append(newNote)
            }
        } catch {
            print("There was an error appending notes \(error)")
        }
    }

}

@IBAction func segueToHome(_ sender: UIBarButtonItem) {
    performSegue(withIdentifier: "segueToHome", sender: self)
}

}

These are my models

import Foundation
import RealmSwift

class Notes: Object {

@objc dynamic var title = ""
@objc dynamic var qNotes = ""
@objc dynamic var imageData: NSData?
@objc dynamic var done: Bool = false
@objc dynamic var dateCreated: Date?
let notePad = List<ListedNotes>()

}

import Foundation
import RealmSwift

class ListedNotes: Object {

@objc dynamic var title = ""
@objc dynamic var cNotes = ""
@objc dynamic var imageData: NSData?
@objc dynamic var dateCreated: Date?
var parentCategory = LinkingObjects(fromType: Notes.self, property: "notePad")

}

I have done this exact same project as a todo list project and in the second viewController it records a list rather than notes, no problem.


#8

I am having some difficulty following the code as some of the function calls don’t represent what the function does (func loadCategory actually loads Notes objects) and the variable names are confusing; parentCategory isn’t a category but links back to a ListedNotes list.

Where is the selectedNote property of the QuickNoteViewController?

destinationVC.selectedNote = notesListed?[indexPath.row]

To answer the question:

The question is why is realm writing data into my UITextVIew, but not displaying it on viewDidLoad

And viewDidLoad is

override func viewDidLoad() {
    super.viewDidLoad()
    
    loadCategory()
    quickNoteTableView.rowHeight = 70.0
    quickNoteTableView.delegate = self
    quickNoteTableView.dataSource = self
}

and then loadCategory is

func loadCategory() {
    notesListed = realm.objects(Notes.self)
    quickNoteTableView.reloadData()
}

If you take a look at loadCategory, you’ll see your calling quickNoteTableView.reloadData() before the tableView has a delegate or dataSource set.