Want ability to manually scroll to pull textfield above keyboard - ios

The problem is apparently when a user sees that the keyboard is blocking textfields that they want to type in, instead of hitting return, or closing the keyboard and tapping on the next field, they try to pan to the next field. Since my content matches the size of the iPad, the scrollview doesn't automatically scroll when the user tries to pan. Honestly, I don't want it to scroll unless the keyboard is on-screen anyway.
However, enabling scrolling on the scrollview doesn't solve the problem; it still won't respond to panning even in that case. Neither does making the viewcontroller the delegate of the scrollview and overriding the function scrollViewDidScroll. How do I get the scrollview to enable panning, particularly only when the keyboard is enabled?
Since a solution has been posted that doesn't quite work, I think I will post my keyboardWillBeShown and keyboardWillBeHidden code:
func keyboardWillBeShown(_ sender: Notification)
{
self.myScrollView.isScrollEnabled = true
let info: NSDictionary = (sender as NSNotification).userInfo! as NSDictionary
let value: NSValue = info.value(forKey: UIKeyboardFrameBeginUserInfoKey) as! NSValue
let keyboardSize: CGSize = value.cgRectValue.size
let contentInsets: UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize.height, 0.0)
self.myScrollView.contentInset = contentInsets
self.myScrollView.scrollIndicatorInsets = contentInsets
self.myScrollView.scrollRectToVisible(self.myScrollView.frame, animated: true)
var aRect: CGRect = self.view.frame
aRect.size.height -= keyboardSize.height
let activeTextFieldRect: CGRect? = activeField?.frame
let activeTextFieldOrigin: CGPoint? = activeTextFieldRect?.origin
if activeTextFieldOrigin != nil
{
if (!aRect.contains(activeTextFieldOrigin!))
{
let scrollpoint : CGPoint = CGPoint(x: 0.0, y: activeField!.frame.origin.y - keyboardSize.height)
self.myScrollView.setContentOffset(scrollpoint, animated: true)//.scrollRectToVisible((activeField?.frame)!, animated: true)
}
}
}
func keyboardWillBeHidden(_ sender: Notification)
{
myScrollView.isScrollEnabled = false
let contentInsets: UIEdgeInsets = UIEdgeInsets.zero
myScrollView.contentInset = contentInsets
myScrollView.scrollIndicatorInsets = contentInsets
}

Try this,
Declare variables to be used
var originalViewCGRect: CGRect?
var originalOffset: CGPoint!
in viewDidLoad add keyboard observers
NotificationCenter.default.addObserver(self
, selector: #selector(keyboardWillAppear(_:))
, name: NSNotification.Name.UIKeyboardWillShow
, object: nil)
NotificationCenter.default.addObserver(self
, selector: #selector(keyboardWillDisappear(_:))
, name: NSNotification.Name.UIKeyboardWillHide
, object: nil)
And finally, add these functions
func keyboardWillAppear(_ notification: Foundation.Notification){
self.scrollView.scrollRectToVisible(self.scrollView.frame, animated: true)
let keyboardSize:CGSize = ((notification as NSNotification).userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue.size
let insets = UIEdgeInsetsMake(0, 0, keyboardSize.height, 0)
self.scrollView.contentInset = insets
}
func keyboardWillDisappear(_ notification: Foundation.Notification){
let beginFrame = ((notification as NSNotification).userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
let endFrame = ((notification as NSNotification).userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let delta = (endFrame.origin.y - beginFrame.origin.y)
self.view.frame = self.originalViewCGRect!
self.scrollView.setContentOffset(CGPoint(x: 0, y: max(self.scrollView.contentOffset.y - delta, 0)), animated: true)
}
In summary, this will adjust the content inset of the scrollview when the keyboard is shown and/or hidden.
Hope this helps.
EDIT
Fixed adjustments

I figured out how to get what I want. Set myScrollView.contentSize to be as big as the screen plus the keyboard when the keyboard is shown, and reduce the size back to what it was when keyboard is hidden. Then make the view controller a UIScrollViewDelegate, set myScrollView.delegate = self, and implement scrollViewDidScroll so that if a textfield is editing, and that textfield isn't blank, then change which text field is the first responder. Piece of cake once you realize the trick of setting .contentSize!

Related

How to prevent the last table cell from overlapping with the keyboard

I have a chat app that displays the messages in a table view. When I invoke the keyboard, I want:
The table view to scroll to the bottom
I want there to be no overlap between any messages and the keyboard.
#objc private func keyboardWillShow(notification: NSNotification) {
if let userInfo = notification.userInfo {
let keyBoardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
let keyboardViewEndFrame = view.convert(keyBoardFrame!, from: view.window)
let keyboardHeight = keyboardViewEndFrame.height
tableView.scrollToBottom() { indexPath in
let rectofCell = self.tableView.rectForRow(at: indexPath)
let lastCellFrame = self.tableView.convert(rectofCell, from: self.view.window)
if lastCellFrame.origin.y + lastCellFrame.size.height > keyboardViewEndFrame.origin.y {
let overlap = lastCellFrame.origin.y + lastCellFrame.size.height - keyboardViewEndFrame.origin.y
self.tableView.frame.origin.y = -overlap
}
}
}
}
extension UITableView {
func scrollToBottom(animated: Bool = true, completion: ((IndexPath) -> Void)? = nil) {
let sections = self.numberOfSections
let rows = self.numberOfRows(inSection: sections - 1)
if (rows > 0){
let indexPath = IndexPath(row: rows - 1, section: sections - 1)
self.scrollToRow(at: indexPath, at: .bottom, animated: true)
completion?(indexPath)
}
}
}
The value for rectOfCell shows (0.0, 5305.333518981934, 375.0, 67.33333587646484) and the converted value (0.0, 9920.000185648601, 375.0, 67.33333587646484) and the table view disappears out of the screen.
I only want to move the table view upward if the last message overlaps with the eventual position of the keyboard. For example, when the keyboard is invoked (either when the table view is already at the bottom or not at the bottom), the table view shouldn't move upward if the appearance of the keyboard doesn't cover the messages (i.e., there is only one message).
You don't need to manage tableView.frame for such a thing. You just need to make sure that when keyboard appears, tableView adds necessary contentInset value from bottom so that user can still see all the content inside tableView with the keyboard still on screen.
All you need is this -
// when keyboard appears
let insets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)
tableView.contentInset = insets
tableView.scrollIndicatorInsets = insets
// when keyboard disappears
let insets: UIEdgeInsets = .zero
tableView.contentInset = insets
tableView.scrollIndicatorInsets = insets

wrong order of execution of settings in keyboardWillShow

My ViewController view contains scrollView with bounds of it's superview and there is a tableView at the bottom of the scrollView as it's subview. TableView height constraint is set to it's content size height and above it there is textfield. So when the user touches textField keyboard appears. And in order to the textField could be visible I want to scroll the scrollView up, but usually there is not enough space so I want to increase the tableView height. And the problem is that it behaves like first the scrollView is scrolled and later the tableViewConstraint is set so it never scrolls enough when there is no space. Could I 'dispatch'
tableViewConstraint.constant = keyboardHeight in some way that it will be executed first on UI.
override func viewDidLoad() {
tableViewConstraint.constant = TableView.contentSize.height
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name:NSNotification.Name.UIKeyboardWillShow, object: nil)
...
}
#objc func keyboardWillShow(notification:NSNotification){
if let keyboardFrame: NSValue = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
tableViewConstraint.constant = keyboardHeight
let boundsOfOffsetView = scrollView.bounds.offsetBy(dx: CGFloat(0), dy: keyboardHeight)
scrollView.scrollRectToVisible(boundsOfOffsetView, animated: true)
}
}
You can do your layout changes in an animate(withDuration:animations:completion:) block and call layoutIfNeeded() to force the changes to occur at the time of your choosing. I would try something like this to start:
// Within your if statement…
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
UIView.animate(withDuration: 1.0/3.0) {
tableViewConstraint.constant = keyboardHeight
scrollView.layoutIfNeeded()
}
let boundsOfOffsetView = scrollView.bounds.offsetBy(dx: CGFloat(0), dy: keyboardHeight)
scrollView.scrollRectToVisible(boundsOfOffsetView, animated: true)

Keyboard management blocking UITextView

So I followed apple guidelines on moving text above the keyboard, this works fine when the textfields are on scrollview, however the scrollview contains a collection view and that collection view loads a nib with textfields populated within it, they are all assigned as the delegate and when they are pressed the didEdit/endEdit functions of the delegate do fire however the keyboard management code doesn't work as expected... here is the keyboard management code
http://creativecoefficient.net/swift/keyboard-management/
heres a link to the code am using..
func keyboardWillBeShown(sender: NSNotification) {
print("KEYBOARD SHOWN")
let info: NSDictionary = sender.userInfo!
let value: NSValue = info.valueForKey(UIKeyboardFrameBeginUserInfoKey) as! NSValue
let keyboardSize: CGSize = value.CGRectValue().size
let contentInsets: UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0,keyboardSize.height, 0.0)
print(keyboardSize.height)
ScrollView.contentInset = contentInsets
ScrollView.scrollIndicatorInsets = contentInsets
// If active text field is hidden by keyboard, scroll it so it's visible
// Your app might not need or want this behavior.
var aRect: CGRect = self.view.frame
aRect.size.height -= keyboardSize.height
let activeTextFieldOrigin: CGPoint? = activeTextFieldRect?.origin
if (!CGRectContainsPoint(aRect, activeTextFieldOrigin!)) {
let dHeight = displayView.frame.height
ScrollView.scrollRectToVisible(activeTextFieldRect!, animated:true)
}
The problem with this code is that the activeTextfield works well with the textfields of the view, i get these points printed when i click on the textfield
activetextfield Frame
(0.0, 20.5, 150.0, 20.5)
but when i click on the collection view nib textfields i get these points
0.0, 0.0, 259.5, 30.0
I believe this is the reason the keyboard is blocking the textfields, the activetextfieldRect is giving the wrong coordinates to
ScrollView.scrollRectToVisible(activeTextFieldRect!, animated:true)
can someone give me some direction on how to go about fixing this?
Yes, you're right. scrollRectToVisible receive wrong coordinates, and this is why it is not work.
To achieve your goal consider using scrollToItemAtIndexPath(_:atScrollPosition:animated:). All what you need is IndexPath of cell to move. And I guess you can put that info in tag property of your textField (for instance).
Something like this:
// inside cellForItem..
cell.textField.tag = indexPath.item
// inside keyboardWillShown
let indexPath = NSIndexPath(forItem: activeTextField.tag inSection: 0) // assume that you have one section
collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: .Top, animated: true) // or try .Center position
func keyboardWillBeShown(sender: NSNotification) {
print("KEYBOARD SHOWN")
let info: NSDictionary = sender.userInfo!
let value: NSValue = info.valueForKey(UIKeyboardFrameBeginUserInfoKey) as! NSValue
let keyboardSize: CGSize = value.CGRectValue().size
let contentInsets: UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize.height, 0.0)
ScrollView.contentInset = contentInsets
ScrollView.scrollIndicatorInsets = contentInsets
// If active text field is hidden by keyboard, scroll it so it's visible
// Your app might not need or want this behavior.
var aRect: CGRect = self.ScrollView.frame
aRect.size.height -= keyboardSize.height
let activeTextFieldRect: CGRect? = activeTextField?.frame
let activeTextFieldOrigin: CGPoint? = TextfieldPoint
if (!CGRectContainsPoint(aRect, activeTextFieldOrigin!)) {
ScrollView.scrollRectToVisible(testBox, animated:true)
}
}
// Called when the UIKeyboardWillHideNotification is sent
func keyboardWillBeHidden(sender: NSNotification) {
print("KEYBOARD HIDDEN")
let contentInsets: UIEdgeInsets = UIEdgeInsetsZero
ScrollView.contentInset = contentInsets
ScrollView.scrollIndicatorInsets = contentInsets
}
func textFieldDidBeginEditing(textField: UITextField) {
let point: CGPoint = textField.convertPoint(CGPointZero, toView: self.ScrollView);
TextfieldPoint = point
testBox = CGRectMake(point.x, point.y, textField.frame.width, 100 );
//let indexPath:NSIndexPath? = collview1.indexPathForItemAtPoint(point)
print("TEXTFIELD EDIT")
activeTextField = textField
print(activeTextField)
ScrollView.scrollEnabled = true
}
func textFieldDidEndEditing(textField: UITextField) {
print("TEXTFIELD STOP EDIT")
activeTextField = nil
ScrollView.scrollEnabled = false
}
it works just really messy i guess, i got the point where textfield was, made CGRect out of it, if the keyboard overlaps the text, the view adjusts, tested on iPhone 4s to 6s plus, works fine

Scroll UIScrollView on selection UITextField, can't get the right sizes

What I've got is a UIScrollView with a containerview inside it. This has a UITableViewcontroller embedded that contains static cells. I'd like to scroll the UIScrollView when I'm selecting a UITextField inside the static cells. I tried it two ways, 1. calculating the position, 2. calculating the cell index. Both ways I keep getting the same values when I'm selection an any UIField. Can someone help me to get this fixed?
I used this question:
How do I scroll the UIScrollView when the keyboard appears?
func keyboardWasShown(notification : NSNotification){
var info = notification.userInfo
// var kbSize = info(UIKeyboa
if let userInfo = notification.userInfo {
if let kbSize = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
let contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: kbSize.height, right: 0)
scrollView.contentInset = contentInsets;
scrollView.scrollIndicatorInsets = contentInsets;
//first approach
println("---- approach 1 ------")
var activeField = self.container?.groupField.superview?.superview
println(activeField!.frame.origin)
//prints (110.0, 5.0)
//second approach
println("---- approach 2 ------")
var cell = self.container?.groupField.superview?.superview as! UITableViewCell
var table = self.container?.view as! UITableView
var index = table.indexPathForCell(cell)
println("index: \(index?.row)")
//prints always 4
var aRect = self.view.frame
aRect.size.height -= kbSize.height
// println("aRect: \(aRect)")
if (!CGRectContainsPoint(aRect, activeField!.frame.origin) ) {
println("ActiveField: \(activeField!.frame.origin.y)")
var scrollPoint = CGPointMake(0.0, activeField!.frame.origin.y-kbSize.height)
scrollView.setContentOffset(scrollPoint, animated:true)
}
}
}
}
It was a stupid mistake, I selected a default field as activefield
I replaced:
var activeField = self.container?.groupField.superview?.superview
to:
var activeField = self.container?.activeField.superview?.superview

UITableView with Footerview ( emdedded text view) issues

I am building this simple example where there is a UITableView and a footerview associated with the uitableview. To avoid overlapping the keyboard I added the keyboard show an hide notifications and it works fine... Initially when the app load the uitextview in the footerview is placed correctly at the bottom of the UITableView.. But once type in some text and dismiss the keyboard the footerview vanishes and goes far below the viewable region. Any idea why this would happen?
On application load
On editing
After the keyboard is dismissed
At this point the uitextfield vanishes and is hidden below the tab bar. Here is the code for keyboard show and dismiss events.
func keyBoardWillShow(notification: NSNotification) {
println("Keyboard will show notification")
var info:NSDictionary = notification.userInfo!
var keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as NSValue).CGRectValue()
var keyboardHeight:CGFloat = keyboardSize.height
var animationDuration:CGFloat = info[UIKeyboardAnimationDurationUserInfoKey] as CGFloat
var contentInsets: UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, (keyboardSize.height), 0.0);
self.tableView.contentInset = contentInsets;
self.tableView.scrollIndicatorInsets = contentInsets;
}
func keyBoardWillHide(notification: NSNotification) {
self.tableView.contentInset = UIEdgeInsetsZero
self.tableView.scrollIndicatorInsets = UIEdgeInsetsZero
}

Resources