I have a view controller with a UIScrollView pinned to all 4 sides. Then a UIView inside with all its 4 sides pinned to the scroll view and as well as equal width and equal height constraints added.
Inside this view, there are two container views. These two container views embed two separate UITableViewControllers. I'm getting no auto layout errors or warnings.
This is how it looks when it's run.
In the bottom table view, one cell(middle one of the first section) has a UITextField and the bottom cell has a UITextView. So obviously when the keyboard appears, these fields get obscured.
So what I wanted to do was to move the entire view that contains both container views when the keyboard appears. That's why I embedded it inside a scrollview. I use this code to monitor keyboard showing/hiding and set the scrollview's content inset accordingly.
class ViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UIKeyboardWillHideNotification, object: nil)
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func keyboardWillShow(notification: NSNotification) {
adjustInsetForKeyboard(true, notification: notification)
}
func keyboardWillHide(notification: NSNotification) {
adjustInsetForKeyboard(false, notification: notification)
}
func adjustInsetForKeyboard(show: Bool, notification: NSNotification) {
let userInfo = notification.userInfo ?? [:]
let keybaordFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).CGRectValue()
let adjustmentHeight = (CGRectGetHeight(keybaordFrame)) * (show ? 1 : -1)
scrollView.contentInset.bottom += adjustmentHeight
}
}
But there are a couple of issues.
When the keyboard appears and although I change the scrollview's content inset, the entire view doesn't move. It does this weird thing. The bottom tableview goes under the top table view. It's easier to show so here is a video.
Tableviews overlapping issue
When I refocus on a textfield for more than 1 time, the scrollview goes off the screen!
Tableview going off the screen
Anyone got an idea why this is happening?
Dropbox link to demo project
A UITableViewController already automatically handles the adjustment of the content inset when the keyboard is shown. There is no documented way to disable this behaviour. You can override viewWillAppear(animated: Bool) in your StaticTableViewController and not call it's super method:
override func viewWillAppear(animated: Bool) {
}
It's probably where the UITableViewController registers for the keyboard events, as this disables the content inset adjustment. However, I can't tell you if there will be other adverse effects of not calling viewWillAppear of UITableViewController and this behaviour might change with future versions of iOS. So a safer way is to just not use UITableViewController and add a standard UITableView to a UIViewController and load your cells in there.
Also note that with your design the user could scroll all the way up and hide your lower content view behind the keyboard. Then the user can't scroll down as any scrolling only scrolls and bounces the upper tableview. So rethink your design or hide the keyboard as soon as the user scrolls
There are couple ways:
To observe UIKeyboadWillShowNotification and UIKeyboardWillHideNotification, get keyboard size data from it and adjust your scrollView contentInset bottom value properly.
func viewDidAppear() {
super.viewDidAppear()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "increaseContentInset:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "decreaseContentInset:", name: UIKeyboardWillHideNotification, object: nil)
}
func viewDidDisappear(){
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func increaseContentInset(notification: NSNotification) {
let endRect = notification.userInfo![UIKeyboardFrameEndUserInfoKey]
scrollView.contentInset = UIEdgeInsetsMake(0, 0, CGRectGetHeight(endRect), 0)
}
func decreaseContentInset(notification: NSNotification) {
scrollView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
}
Use the library for it. I strongly recommend you to use TPKeyboardAvoiding
Related
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)
}
}
I understand that the issue I'm posting has been discussed a lot but as far as I have searched in SO, I couldn't find a solution specific to my issue. I'm posting this to get some inputs.
Problem:
View hierarchy: View -> ScrollView -> View -> Textfield
I have implemented two screens with multiple textfields laid on top of a scrollview to avoid keyboard obscuring the textfield when its at the bottom of the page. I have achieved this using the sample code provided by Apple with minor modifications.
Subscribe to UIKeyboardWillShowNotification and UIKeyboardWillHideNotification
When keyboard is shown, get the height of the keyboard from userInfo dictionary and set appropriate content inset to move the textfield above the Keyboard
When keyboard is hidden, set the content inset to UIEdgeInsetZero to bring back to normal size
The above implementation works perfectly fine when there are more textfields that could occupy the entire screen (assume there are 10 textfields and they extend beyond the frame of the the scroll view). Whenever I tap on a textfield, it moves above the keyboard as expected and I can also scroll the page till the bottom most textfield is visible above the keyboard frame (when the keyboard is still up).
My problem arises when I have only two textfield in the scrollview that are centered vertically in the screen. When I tap on a textfield, the scrollview get an inset equivalent to the keyboard height and it moves above the keyboard as expected but when I scroll the screen, I could see a huge blank space (due to the additional inset) below the textfields.
How can I avoid that blank space when there are only one or two textfields in the page? Should I have to write a different logic to handle that scenario? Any help would be appreciated.
Below is the code:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillHide:", name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWillShow(notification: NSNotification) {
if let userInfo = notification.userInfo {
let keyboardFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue()
if let kbFrame = keyboardFrame {
let inset = CGRectGetHeight(kbFrame)
scrollView.contentInset = UIEdgeInsetsMake(0, 0, inset, 0)
scrollView.scrollIndicatorInsets = UIEdgeInsetsMake(0, 0, inset, 0)
}
}
}
func keyboardWillHide(notfication: NSNotification) {
scrollView.contentInset = UIEdgeInsetsZero
scrollView.scrollIndicatorInsets = UIEdgeInsetsZero
}
TPKeyboardAvoidingScrollView is good, but i recently switched to IQKeyboardManager -> https://github.com/hackiftekhar/IQKeyboardManager
Its upto you to choose but i prefer IQKeyboardManager cause its easy
There are a number of solutions out there, but my preferred solution is to use TPKeyboardAvoiding. To use it here you would just make your UIScrollView an instance or subclass of TPKeyboardAvoidingScrollView in SB or code and it should care of the rest, no other code required!
I've made it so a textfield is always on top of the keyboard (like in the messages app, whatsapp...) but my problem is that this poses the table above it up too because everything is in a scrollview. How can i make it so the table resizes so it fits in the space between the keyboard and top of the screen.
enter image description here
You can resize your UITableView when you change the frame.
For example:
tableView.frame = CGRectMake(0,0,100,200);
To setup this, you should check when the keyboard is present. With:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name:UIKeyboardWillShowNotification, object: nil);
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name:UIKeyboardWillHideNotification, object: nil);
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self);
}
func keyboardWillShow(notification: NSNotification) {
yourTableView.frame = newFrame // use CGRectMake()
}
Why you want to "resize" your TableView? if its on an scrollView, it dont need to be resized. Just use scrollRectToVisible on your scrollView to show your textBox.
Read here more about usage of UIScrollView:
http://www.appcoda.com/uiscrollview-introduction/
Or by Apple:
https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/UIScrollView_pg/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008179-CH1-SW1
I have a search bar text field and a table view (for google auto complete) that I would like to translate up when the keyboard comes into view. I am successfully doing this, however, I am getting warnings/errors about my constraints. I am using auto layout via storyboard on this view and tried to disable/enable the constraints prior to/after showing/hiding the keyboard, but I am still getting these errors. Am I not disabling auto layout correctly? I followed what was given in this SO response.
override func viewDidLoad() {
...
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name:UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name:UIKeyboardWillHideNotification, object: nil)
...
}
func keyboardWillShow(sender: NSNotification) {
self.pixieLabel.hidden = true
self.searchBar.setTranslatesAutoresizingMaskIntoConstraints(true)
self.startingTableView.setTranslatesAutoresizingMaskIntoConstraints(true)
self.searchBar.frame.origin.y -= 150
self.startingTableView.frame.origin.y -= 150
}
func keyboardWillHide(sender: NSNotification) {
self.pixieLabel.hidden = false
self.searchBar.setTranslatesAutoresizingMaskIntoConstraints(false)
self.startingTableView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.searchBar.frame.origin.y += 150
self.startingTableView.frame.origin.y += 150
}
Solution Code
func keyboardWillShow(sender: NSNotification) {
self.pixieLabel.hidden = true
self.seachBarTopConstraint.constant -= 150
self.searchBar.layoutIfNeeded()
}
func keyboardWillHide(sender: NSNotification) {
self.pixieLabel.hidden = false
self.seachBarTopConstraint.constant += 150
self.searchBar.layoutIfNeeded()
}
Instead of adjusting theframe values, I think you should be creating #IBOutlet references to constraints in Interface Builder and then changing the constant value of those constraints when you want to animate them, followed by a call to layoutIfNeeded. As I understand it, manually changing the values of a view's frame and auto layout don't mix.
Also, I wouldn't mess around with setTranslatesAutoresizingMaskIntoConstraints unless you are adding your constraints programmatically, in which case you're most likely just setting it to false.
I have a UITableViewController with around 20 static cells, some of these cells have UITextFields within them and some are just for selecting with a checkmark. The table is about 1.5 views worth so scrolling is required to get at the lower text fields.
When I click within a textfield at the bottom of the table the keyboard pops up as it should but this then appears over the cell/textfield.
I was under the impression (From Apple docs and elsewhere) that the UITableViewController class handles scrolling of the view automatically when a keyboard appears in any orientation and shifts the tableview up so that the cell is visible, this isn't happening though.
IOS 5.1, iPad Portrait.
Make sure that if you are overriding viewWillAppear that you call
[super viewWillAppear:animated];
If you don't, the Scroll View will not scroll up properly.
Swift
super.viewWillAppear(animated)
I found non of these answers to be correct. After a while, I notice that if you push a controller it won't work ... but if you present it modally.. the table will automatically scroll to the used textfield.
Hope that saves time and stress to anyone.
If the auto scroll of UITableViewController doesn't work with the
UITextFields in cells or scroll weirdly, do these steps. Swift 5 iOS
13.2 tested 100%
First implement viewWillAppear but don't call super.viewWillAppear (this will stop auto scroll)
override func viewWillAppear(_ animated: Bool) {
}
Then let's do the scroll manually.
override func viewWillAppear(_ animated: Bool) {
//Notification center observers
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)),
name: UIResponder.keyboardDidShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)),
name: UIResponder.keyboardWillHideNotification, object: nil)
}
//keybord show action
#objc func keyboardWillShow(notification: Notification) {
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: notification.getKeyBoardHeight, right: 0)
}
//keyboard hide action
#objc func keyboardWillHide(notification: Notification) {
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
extension Notification {
var getKeyBoardHeight: CGFloat {
let userInfo: NSDictionary = self.userInfo! as NSDictionary
let keyboardFrame: NSValue = userInfo.value(forKey: UIResponder.keyboardFrameEndUserInfoKey) as! NSValue
let keyboardRectangle = keyboardFrame.cgRectValue
return keyboardRectangle.height
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
I ran into this issue myself. I just converted my view controller from a UIViewController to a UITableViewController in addition to adding the [super viewWillAppear:animated]; call, you will need to remove these lines:
[self.tableView setDataSource:self];
[self.tableView setDelegate:self];
As they are no longer needed and setDelegate interferes with the keyboard scrolling behavior.