Advertisement

Finding the Location of the Cursor in UITextView – Swift 3.0

My post from 2015 (Finding the Location of the Cursor in UITextView, UITextField), works great in objective C. However, here’s the UITextView methodology for Swift 3.0.

Put these NSNotifications into the viewDidLoad method.
// keyboard notifications for textView processing
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: .UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: .UIKeyboardWillHide, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardMayAdjustView), name: .UITextViewTextDidChange, object: nil)

Here are the three methods that these notifications require.
// determines if the view needs to move above the keyboard
func keyboardWillShow(notification: Notification) {
var pointed = CGPoint.zero
isKeyboardUp = true
let info = notification.userInfo! as NSDictionary as! [String: AnyObject]
let kbRect = (info[UIKeyboardFrameEndUserInfoKey]! as! NSValue)
self.keyboardHeight = kbRect.cgRectValue.size.height
self.keyboardOriginY = (self.view.frame.size.height - self.keyboardHeight)
for viewa in self.view.subviews {
for viewb in viewa.subviews {
if viewb.isKind(of: UITextView .classForCoder()) {
if viewb.isFirstResponder { // got the textView
pointed = viewb.convert(viewb.frame.origin, to:self.view)
break
}
}
}
}
var viewNewY : CGFloat = 0.0
if pointed.y > self.keyboardOriginY { // move the view
viewNewY = self.keyboardOriginY - pointed.y - 50.0
}

UIView.animate(withDuration: TimeInterval(info[UIKeyboardAnimationDurationUserInfoKey] as! CGFloat)) {
var framer = self.view.frame
framer.origin.y = viewNewY
self.view.frame = framer
}
}

// resets the view origin to 0,0 if necessary
func keyboardWillHide(notification: Notification) {
isKeyboardUp = false
let info = notification.userInfo! as NSDictionary as! [String: AnyObject]
UIView.animate(withDuration: TimeInterval(info[UIKeyboardAnimationDurationUserInfoKey] as! CGFloat)) {
var framer = self.view.frame
framer.origin.y = 0.0
self.view.frame = framer
}
}

// as things change inside the textView
func keyboardMayAdjustView(notification: Notification) {
var cursor = CGPoint.zero
var pointed = CGPoint.zero

for viewa in self.view.subviews {
for viewb in viewa.subviews {
if viewb.isKind(of: UITextView .classForCoder()) {
if viewb.isFirstResponder { // got the textView
let tv = viewb as! UITextView
pointed = viewb.convert(viewb.frame.origin, to:self.view)
cursor = tv.caretRect(for: (tv.selectedTextRange?.start)!).origin
break
}
}
}
if (cursor.y > 0.0) {
break
}
}
var viewNewY : CGFloat = 0.0
if self.isKeyboardUp {
if (pointed.y + cursor.y > self.keyboardOriginY) { // move the view
viewNewY = self.keyboardOriginY - (pointed.y + cursor.y) - 50.0

// don't move the view up too high

if (-self.view.frame.origin.y < self.keyboardHeight) { UIView.animate(withDuration: 0.2) { var framer = self.view.frame framer.origin.y = viewNewY self.view.frame = framer } } } } }
I've also generalized the implementation and sharpened it's use to only the UITextView case.
And, as a final improvement to this code, remove the notification listening on deinit as follows.

deinit {
NotificationCenter.default.removeObserver(self)
}