Autoresizing text view in table cells

Continuing from the previous version, I upgraded to Swift 3 and Xcode 8 (beta 4). Most of the glitches are gone, but every now and then one shows up. So I’m not entirely sold on the whole thing yet. I’m still having doubts if it wouldn’t be simpler to just have the user enter text in a separate popup of some kind, and only display read-only views in the actual table. The problem with that is that it is a lot less intuitive and user-friendly. OTOH, it would work… I get a feeling Apple really doesn’t want us to do editing in text views in table cells. I don’t think any of Apple’s own apps do that.

One key thing is to have the table update (tableView.beginUpdates() – tableView.endUpdates()) cycle only be done from the controller, and not as a call from the table cell, else things go sideways quickly. Also, these updates shouldn’t happen on every cell update, else there’s a lot of twitching and jumping. What I’d really need to do is to set a flag in the controller, then call the table updates once the UI goes into idle. But I can’t find anything in UIKit that tells me the UI is in idle. Windows has that, but it doesn’t seem like iOS does, so I have to fake it using a timer. The table cells call “refresh()” when they change anything. The timer is recreated every time it gets called and only triggers a real update once it’s been left alone for a quarter of a second.

var runningTimer: Timer? = nil

    

func refresh() {

  if let timer = runningTimer {

    stopTimer(timer)

  }

  runningTimer = startTimer()

}

    

private func startTimer() -> Timer {

  returnTimer.scheduledTimer(timeInterval: 0.25, target: self,

      selector: #selector(MyTableViewController.DoTheRefresh),

      userInfo: nil, repeats: false)

}

   

private func stopTimer(_ timer: Timer) {

  if let timer = runningTimer {

    timer.invalidate()

    runningTimer = nil

  }

}

    

func DoTheRefresh(_ timer: Timer) {

  stopTimer(timer)

  tableView.beginUpdates()

  tableView.endUpdates()

}

 
When typing in a field, you can have the cursor disappear behind the onscreen keyboard if you type long enough. I added a scrolling routine to take care of that:

// find out where the curson is and make sure it’s within

// the visible area of the tableView

// else typing a long text in a growing textView tends

// to let the cursor position slip behind the keyboard

    

private func makeCursorPositionVisible() {

  if let textRange = textView.selectedTextRange {

    let textPosition = textRange.end

    let caretRect = textView.caretRect(for: textPosition)

    if let caretRectInTableView = tableView?.convert(caretRect, from: textView) {

      let adjustedRect = CGRect(x: caretRectInTableView.minX,

                  y: caretRectInTableView.minY,

                  width: caretRectInTableView.width,

                  height: caretRectInTableView.height + tableView!.contentInset.top)

      // the scroll actually looks better to me without animation,

      // but YMMV

      tableView!.scrollRectToVisible(adjustedRect, animated: false)

    }

  }

}

 
The whole project is now on Github for your viewing pleasure.