Search code examples
iosarraysuitableviewuikituitextview

Cursor Disappearing When moving to new line in UITextView embedded in UITableViewCell


I have a UITextView embedded in a UITableViewCell.

storyboard

Calling reloadTableView() upon an update to the UITextView achieves the UITableViewCell automatically resizing when the UITextView gets too big or too small.

The problem is when moving to a brand new line by creating a word too big for the previous line or pressing return, the cursor will not immediately appear. Instead the cursor will disappear and the the character or word that should be on the new line cannot be seen until an additional character is added, at which point it appears as it should. You can see the problem in the gif link on giphy: gif image of problem

It is worth noting that this only happens at the very bottom of the UITextView. If there are an existing two paragraphs appearing normally and I decide to enter in a paragraph in between the existing two, the disappearing cursor problem does not appear.

I have included the code below and I am stumped as to what is causing this problem.

import Foundation

import UIKit

import CoreData


class textViewTableViewCell: UITableViewCell, UITextViewDelegate {



//https://stackoverflow.com/questions/15711645/how-to-get-uitableview-from-uitableviewcell



var tableView: UITableView? {

    var view = self.superview

    while (view != nil && view!.isKind(of: UITableView.self) == false) {

        view = view!.superview

    }

    return view as? UITableView

}





public var cellText: String = ""

var peopleArray: [NSManagedObject] = []



@IBOutlet weak var textView: UITextView!



override func layoutSubviews() {



    textView.delegate = self

    //textView.text = cellText



}



func textViewDidChange(_ textView: UITextView) {



    save()







}



func save() {

    let offset = textView.contentOffset

    guard let appDelegate =

        UIApplication.shared.delegate as? AppDelegate else {

            return

    }



    // 1

    let managedContext =

        appDelegate.persistentContainer.viewContext



    // 2

    let entity =

        NSEntityDescription.entity(forEntityName: "Person",

                                   in: managedContext)!



    let person = NSManagedObject(entity: entity,

                                 insertInto: managedContext)



    //2.5

    let predicate = NSPredicate(format: "name == %@", cellText)



    let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")

    fetchRequest.predicate = predicate



    do {

        let fetchedEntities = try managedContext.fetch(fetchRequest) as! [Person]

        fetchedEntities.first?.name = textView.text

        cellText = textView.text



    } catch {

        // Do something in response to error condition

    }



    do {



        try managedContext.save()

        tableView?.beginUpdates()

        tableView?.endUpdates()

    } catch {

        // Do something in response to error condition

    }





}

}



class JournalEntryDetailViewController:  UITableViewController, UITextViewDelegate   {

public var hasViewAppeared: Bool = false

public var text: String = ""

var people: [NSManagedObject] = []



//@IBOutlet weak var tableView: UITableView!

override func viewDidLoad() {





    tableView.estimatedRowHeight = 500

    tableView.rowHeight = UITableViewAutomaticDimension



    super.viewDidLoad()







}



override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {



    return UITableViewAutomaticDimension





}



override func viewWillAppear(_ animated: Bool) {



   //when the journal entry view is about to appear, we do not want the iOS 10 added large titles

    //https://chariotsolutions.com/blog/post/large-titles-ios-11/

    self.navigationController?.navigationBar.prefersLargeTitles = false







}



override func viewDidAppear(_ animated: Bool) {



    hasViewAppeared = true

    tableView.reloadData()

    //tableView.beginUpdates()

    //tableView.endUpdates()



}



override func prepare(for segue: UIStoryboardSegue, sender: Any?)

{

    if segue.destination is ViewController

    {

        let vc = segue.destination as? ViewController



        vc?.removeBlanks()

        vc?.tableView.reloadData()

    }

}



override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

    return 1

}



override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell: textViewTableViewCell = tableView.dequeueReusableCell(withIdentifier: "textCell",

                                                                         for: indexPath) as! textViewTableViewCell

    cell.cellText = text

    cell.textView.text = text

    cell.peopleArray = people        

    return cell

}

}


Solution

  • Fixed the bug but not the most satisfying solution. For some reason

    tableView?.beginUpdates()
    tableView?.endUpdates()
    

    needs to be run twice.

    tableView?.beginUpdates()
    tableView?.endUpdates()
    tableView?.beginUpdates()
    tableView?.endUpdates()
    

    Don't know why? The first cycle of updates only updates the UITableViewCell height, and then the second cycle updates the UITextView. Strange... If someone can explain why I'll accept their answer.