Search code examples
swiftuitextviewdelegateuitextinput

Delegate method, textViewDidChangeSelection not triggered everytime


I have a UITextView embedded in a UITableView Cell, CustomCell. If I tap the return key when my curser is at the end of the text, I want to create a new cell below that cell, but if I tap return when I am in the middle of the textView, I want to get rest of the words after the curser and create a cell with that text in the textView. To do that I need to know curser position in textView which I am getting with the help of the UITextViewDelegate method textViewDidChangeSelection(_:).

It works only when I tap on different location on the same cell but if I move to a different cell and tap again on a previously tapped cell(in the textview)at the same position, the delegate method doesn't get triggered.

Let me show the case with an exapmle-

I type some texts for testing. Here, after the 5th line, I decide to tap on the 2nd line just before the word "juice" like -

enter image description here

The delegate method, textViewDidChangeSelection(_:) gets triggered.

Now, I click on the 3rd line after the whole text "Skim Milk" like-

enter image description here

and again the delegate method, textViewDidChangeSelection(_:) gets called.

But now when I move back to the 2nd line at the same position, meaning before the word “juice”, the delegate method doesn’t get triggered. Again if I tap on the 3rd line at the same position meaning after the word “milk”, the delegate method doesn't get called.

I think, the reason is in those cells, I tapped on the sane position, and as the selection didn’t get changed, the delegate method didn’t get called. Following is the code for the delegate method which is in the CustomCell(UITableViewCell) class-

 func textViewDidChangeSelection(_ textView: UITextView){
        if let range = textView.selectedTextRange, let indexPath = currentIndexPath{
            let curserStartPosition = textView.offset(from: textView.beginningOfDocument, to: range.start)
            setActiveCell?(indexPath, curserStartPosition)
        }
    }

The following is the code for the Controller where new cells are created when tapped the return key-

class MyViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
     var cellCount  = 1
     var initialCellText: String?
     var activeCell: CustomCell?
     var cursorPosition: Int?

    @IBOutlet weak var myTableView: UITableView!{
        didSet{
            myTableView.delegate = self
            myTableView.dataSource = self
        }
    }
    override func viewDidLoad() {
        super.viewDidLoad()  
    }
    
    func goToNextCell(_ indexPath:IndexPath){

        let nextCelIndexPath = IndexPath(row: indexPath.row + 1, section: 0)      
        if let text = activeCell?.myTextView.text, let cursorPos = cursorPosition, cursorPos < text.count-1 {
            let fromIndex = text.index(text.startIndex, offsetBy: cursorPos)
            let toIndex = text.index(text.endIndex, offsetBy: -1)
        
        if fromIndex < toIndex {
            let truncatedText = text[fromIndex...toIndex]
            self.initialCellText = String(truncatedText)
        }
    }
    cellCount += 1
    myTableView.beginUpdates()
    myTableView.insertRows(at: [nextCelIndexPath], with: .none)
    myTableView.endUpdates()

    initialCellText = nil
    cursorPosition = nil
    
    if let cell = self.myTableView.cellForRow(at: nextCelIndexPath) as? CustomCell{
        self.activeCell = cell
        self.activeCell?.myTextView.becomeFirstResponder()
    }
}
    func setActiveCell(on indexPath: IndexPath, at curserPos: Int){
        if let tappedOnCell = myTableView.cellForRow(at: indexPath) as? CustomCell{
            self.activeCell = tappedOnCell
            self.cursorPosition = curserPos
        }
    }   
}
// MARK: - UITableViewDelegate and UITableViewDataSource

extension MyViewController{
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return cellCount
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if let cell = tableView.dequeueReusableCell(withIdentifier: "customCell") as? CustomCell{
            cell.myTextField.becomeFirstResponder()
            cell.returnKeyTapped = goToNextCell(_:)
            cell.setActiveCell = setActiveCell(on:at:)
            return cell
        }
        return UITableViewCell()
    }
}

To create the following cell, I need to know where the cursor position is on a particular cell everytime. So, I need the delegate method to be triggered everytime.

Can anyone have any suggestions as to how can I solve my problem.


Solution

  • If I understand you right, you basically want to know the curser position after you have typed into a UITextView.
    Now, if you type into a UITextView, that text view becomes the first responder, and begins an editing session. This is announced by a UITextView.textDidBeginEditingNotification for which you can add an observer, see here.
    In the function called by the notification, you could compute the cursor position as you do in textViewDidChangeSelection