tableView vs. scrollView behavior when keyboard appears - ios

If a textfield opens keyboard and textfield is member of a tableView, then it will be able to scroll tableView to be able to see the last item, and keyboard will not hide that item.
How? UITableView is inherited from UIScrollView. I guess opening keyboard increases the content offset? Am I right?
If textfield is part of a scrollView, not a tableView, this effect will not occur, and it keyboard can hide out other controls positioned lower parts of the scrollView.
If I want to see the same effect with scrollView as with tableView, should I set content offset manually?

Yes you have to set your content offset manually .
First you register for the notification
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillShow:", name:UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillHide:", name:UIKeyboardWillHideNotification, object: nil)
And then in your observer methods.
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func keyboardWillShow(notification:NSNotification){
var userInfo = notification.userInfo!
var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).CGRectValue()
keyboardFrame = self.view.convertRect(keyboardFrame, fromView: nil)
var contentInset:UIEdgeInsets = self.scrollView.contentInset
contentInset.bottom = keyboardFrame.size.height
self.scrollView.contentInset = contentInset
}
func keyboardWillHide(notification:NSNotification){
var contentInset:UIEdgeInsets = UIEdgeInsetsZero
self.scrollView.contentInset = contentInset
}

You can import TPKeyboardAvoiding class downloaded from https://github.com/michaeltyson/TPKeyboardAvoiding and set the UITableView class to TPKeyboardAvoidingTableView.

Related

Customize automatic UIScrollView scrolling with UITextField

I would like to customize the scroll-offset when showing the keyboard. As you can see in the GIF, the Textfields are quite close to the keyboard and I would like to have a custom position. The "Name" textfield should have 50px more distance and the "Loan Title" textfield should just scroll to the bottom of my UIScrollView.
To be able to scroll past the keyboard I'm changing the UIScrollView insets. Strangely iOS automatically scrolls to the firstResponder textfield (see GIF).
override func viewDidLoad() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
}
#objc func keyboardWillShow(notification: NSNotification) {
// get the Keyboard size
let userInfo = notification.userInfo!
let keyboardEndFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
// update edge insets for scrollview
self.mainScrollView.scrollIndicatorInsets.bottom = keyboardEndFrame.height - self.view.layoutMargins.bottom
self.mainScrollView.contentInset.bottom = keyboardEndFrame.height - self.view.layoutMargins.bottom
}
I already tried to use the UITextfieldDelegate method: textFieldDidBeginEditing(_ textField: UITextField)
I also tried to use the Apple way described here: https://stackoverflow.com/a/28813720/7421005
None of these ways let me customize the automatic scroll position. In fact it kind of overrides every attempt. Does anyone know a way to workaround this?
You can prevent your view controller from automatically scrolling by setting automaticallyAdjustsScrollviewInsets to false as described here.
Implementing keyboard avoidance is also pretty straight forward. You can see how to do it here.
I don't believe there is any way to keep the automatic positioning and apply your own custom offset. You could experiment with making text field contained in another larger view and making that larger view the first responder, but that would be a hack at best.
I found a solution by myself. The problem was that the automatic scroll (animation) was interfering with my scrollRectToVisible call. Putting this in async fixed the problem.
It now looks similar to this:
override func viewDidLoad() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
}
#objc func keyboardWillShow(notification: NSNotification) {
// get the Keyboard size
let userInfo = notification.userInfo!
let keyboardEndFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
// update edge insets for scrollview
self.mainScrollView.scrollIndicatorInsets.bottom = keyboardEndFrame.height - self.view.layoutMargins.bottom
self.mainScrollView.contentInset.bottom = keyboardEndFrame.height - self.view.layoutMargins.bottom
var frame = CGRect.zero
if nameTextField.isFirstResponder {
frame = CGRect(x: nameTextField.frame.origin.x, y: nameTextField.frame.origin.y + 50, width: nameTextField.frame.size.width, height: nameTextField.frame.size.height)
}
if titleTextField.isFirstResponder {
frame = CGRect(x: titleTextField.frame.origin.x, y: titleTextField.frame.origin.y + titleShortcutsCollectionView.frame.height + 25, width: titleTextField.frame.size.width, height: titleTextField.frame.size.height)
}
DispatchQueue.main.async {
self.mainScrollView.scrollRectToVisible(frame, animated: true)
}
}

UITextView - Scrolling to selected place

I have a Notes view controller with a large UITextView in it. When the keyboard is active, I've made sure the contentInset is adjusted so that the user can see what's being typed. This works well.
However, if the textView already has a large amount of text in it and the keyboard isn't active yet, when the user taps on text in the lower portion of the textView, the textView doesn't automatically scroll up to show their cursor. As soon as they start typing, the textView scrolls to the appropriate position, but I'd like the textView to scroll to the position of the cursor as soon as they tap in the textView.
Here's my code:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWasShown), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillBeHidden), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
func keyboardWasShown(_ notification: Notification) {
let mainViewY = self.view.frame.origin.y
let textViewY = self.textView.frame.origin.y
let oneLineHeight = self.textView.font.lineHeight
let delta = (textViewY - mainViewY) - oneLineHeight
let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue
let keyboardHeight = (keyboardSize?.height)!
self.textView.contentInset = UIEdgeInsetsMake(0, 0, keyboardHeight + delta, 0)
self.textView.scrollIndicatorInsets = textView.contentInset
}
func keyboardWillBeHidden(_ notification: Notification) {
self.textView.contentInset = UIEdgeInsets.zero
self.textView.scrollIndicatorInsets = UIEdgeInsets.zero
}
I searched for people asking this question but couldn't find anyone experiencing my particular issue.
How can I ensure that when the user taps the textView to begin typing in a portion of the textView that would be covered by the keyboard, the textView scrolls to show their cursor even before they actually type?
You can achieve this by calling scrollRangeToVisible (docs here) using the text view's selectedRange. That method scrolls the text view to any range of text, and the selectedRange should be at the position of the cursor.
I was having a similar issue, but I tried out your sample code.
In viewDidLoad, I commented out the second observer (self.keyboardWillBeHidden).
When I ran the simulator and selected any part of a large block of text, the textView did automatically scroll to the correct position.

iOS Swift 3 Keep TableView content at the right position when keyboard is shown

I have a Viewcontroller with a ContainerView and a TextField (looks like your typical chat app). Inside the ContainerView is an embedded TableView. I have implemented an observer function to increase the bottom layout constraint of the TextField, making the TextField and the ContainerView move up with the Keyboard.
The problem is the offset / inset of the TableView. It looks like the keyboard hides the TableView.
How can i make the content of the tableView move with the containerView?
Here i have added some Screenshots:
This is the initial Chat View Controller.
Now when the Keyboard shows up, the TableView does not maintain its scroll position. It might look like the containerView is simply behind the Keyboard, but it actually adjusted the size.
Now when you scroll down, you see that the tableView does not maintain its scroll position.
you can use the observer UIKeyboardWillChangeFrame and change the contentOffset of the tableView.
Here's how it's done.
NotificationCenter.default.addObserver(self
, selector: #selector(keyboardWillChangeFrame)
, name: NSNotification.Name.UIKeyboardWillChangeFrame
, object: nil)
and here's the keyboardWillChangeFrame function
func keyboardWillChangeFrame(_ notification: 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.threadTableView.contentOffset = CGPoint(x: 0, y: self.threadTableView.contentOffset.y - delta)
bottomConstraint.constant = view.bounds.height - endFrame.origin.y
self.view.layoutIfNeeded()
}
I also got the idea regarding this with this article.
http://derpturkey.com/maintain-uitableview-scroll-position-with-keyboard-expansion/
Try this:
You can set the tableView contentInset to keyboard's height when the keyboard is shown and set it to 0 when keyboard is hidden.
You can do it using keyboard notifications : UIKeyboardWillShow, UIKeyboardWillHide
override func viewDidLoad()
{
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
func keyboardWasShown(_ notification : Notification)
{
let info = (notification as NSNotification).userInfo
let value = info?[UIKeyboardFrameEndUserInfoKey]
if let rawFrame = (value as AnyObject).cgRectValue
{
let keyboardFrame = self.contentTableView.convert(rawFrame, from: nil)
let keyboardHeight = keyboardFrame.height
var contentInsets : UIEdgeInsets
contentInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardHeight, 0.0)
self.contentTableView.contentInset = contentInsets
self.contentTableView.scrollIndicatorInsets = contentInsets
}
}
func keyboardWillHide(_ notification : Notification)
{
let contentInsets = UIEdgeInsets.zero
self.contentTableView.contentInset = contentInsets
self.contentTableView.scrollIndicatorInsets = contentInsets
}
Could you update the contentInset property on the table view to account for the keyboard height in the observer function you've implemented?
Alternatively could you update the bottom constraint of the ContainerView to account for the keyboard height?

iOS TableView wrong indexPath when selecting cell with keyboard shown

I have tableView size set by AutoLayout (bottom to Bottom Layout Guide, top to another view and so on but first UISearchBar to Top Layout Guide):
Controller with tableView:
I need to change table offset when keyboard is shown so I have these two methods:
// MARK: - Keyboard
func keyboardWasShown (notification: NSNotification) {
let info: NSDictionary = notification.userInfo!
let value: NSValue = info.valueForKey(UIKeyboardFrameBeginUserInfoKey) as! NSValue
let keyboardSize: CGSize = value.CGRectValue().size
self.tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardSize.height, 0)
self.tableView.scrollIndicatorInsets = self.tableView.contentInset
}
func keyboardWillBeHidden (notification: NSNotification) {
self.tableView.contentInset = UIEdgeInsetsZero
self.tableView.scrollIndicatorInsets = UIEdgeInsetsZero
}
And it's working but I have problem when keyboard is shown. The last item can't be selected and instead of that I get previous item. I tapped where is last item and it should navigate to detail page with last item but instead I see detail page with previous item. It isn't shift for all items but just for the last one and when I filtered to just one item it's working okay. When keyboard is hidden (and items are still filtered) then It's okay too (it selects the right thing). So I guess the problem must be here:
self.tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardSize.height, 0)
self.tableView.scrollIndicatorInsets = self.tableView.contentInset
So where could be problem? Thanks for help
I got my solution. I was using UIKeyboardWillHideNotification and method keyboardWillBeHidden was called before didSelectRowAtIndexPath so contentInset of tableView was set back to UIEdgeInsetsZero and then there was wrong indexPath. So now I use keyboardDidHide instead of keyboardWillBeHidden:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWasShown:", name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillBeHidden:", name: UIKeyboardWillHideNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardDidHide:", name: UIKeyboardDidHideNotification, object: nil)
...
func keyboardDidHide (notification: NSNotification) {
self.tableView.contentInset = UIEdgeInsetsZero
self.tableView.scrollIndicatorInsets = UIEdgeInsetsZero
}
So, assuming keyboardHeight is storing your keyboard height (pay attention because the keyboard frame may vary across devices), try this:
CGRect *frame = [tableView frame];
frame.size.height -= keyboardHeight;
[tableView setFrame:frame]
Do the same thing (but replace -= with +=) when keyboard hides.

contentInset of the UIScrollView isn't working

I’m trying to move the content up when the keyboard appears. This is the content which is a simple login form.
Please note that these are not UITextFields. Its just a small UITableView in the middle of a UIViewController. And I have a UIScrollView filling the view controller embedding that table view.
I’ve registered for the keyboard notifications, UIKeyboardDidShowNotification and UIKeyboardWillHideNotification.
And in the method that fires when the keyboard appears, I've implemented a variation of this answer which does pretty much the same thing, setting the bottom value of the UIEdgeInsets which is used to set the contentInset of the scroll view.
func keyboardWasShown(notification: NSNotification) {
var info: NSDictionary = notification.userInfo as NSDictionary
var keyboardSize: CGSize = info.objectForKey(UIKeyboardFrameBeginUserInfoKey).CGRectValue().size
let contentInsets: UIEdgeInsets = UIEdgeInsetsMake(0, 0, keyboardSize.height, 0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
}
The problem is it doesn't do anything. I successfully get the height of the keyboard but nothing changes. Can anybody tell me why and what I should do to fix this please?
Thank you.
After much struggle, I was able to accomplish what I wanted. What I did was, in the viewDidLoad I registered for the keyboard notifications. And when the keyboard appears, I got its frame in to a variable and the login form's (which is a table view) frame into another variable.
By using CGRectIntersection I got the frame of the intersected portion when the keyboard overlaps the login form and go its height. Then I simply set it in scroll view's setContentOffset method to automatically move the scroll view up. To move it back down to the original position I just set scroll view's contentOffset property again to CGPointZero.
func registerForKeyboardNotifications() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWasShown:", name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillBeHidden:", name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWasShown(notification: NSNotification) {
var info: NSDictionary = notification.userInfo as NSDictionary
var keyboardFrame: CGRect = info.objectForKey(UIKeyboardFrameEndUserInfoKey).CGRectValue()
var loginFormFrame: CGRect = self.view.convertRect(self.tableView.frame, fromView: nil)
var coveredFrame: CGRect = CGRectIntersection(loginFormFrame, keyboardFrame)
self.scrollView.setContentOffset(CGPointMake(0, coveredFrame.height + 20), animated: true)
}
func keyboardWillBeHidden(notification: NSNotification) {
self.scrollView.contentOffset = CGPointZero
}
UIEdgeInsets UIEdgeInsetsMake (
CGFloat top,
CGFloat left,
CGFloat bottom,
CGFloat right
);
You are setting the bottom. Try setting the top insets. Maybe a negative value will help.
ContentInset means The distance that the content view is inset from the enclosing scroll view. What you need to do is to change the origin.y value.
See How to make a UITextField move up when keyboard is present?

Resources