iOS Swift3 Move a Textfield and a ContainerView with the keyboard - ios

I need help to make my chat ViewController behave like Whatsapp does when you open and close your keyboard. That means: If the keyboard is opened, the view moves up. If it is closed, it returns to the original position.
What I already have: My chat ViewController has a ContainerView and below that ContainerView is a TextField. I have set up two observers which are catching UIKeyboardWillShow and UIKeyboardWillHide. The ContainerView and the TextField both have constraints at the top, left, right and bottom. between them is a vertical spacing.
What I've tried: I took the bottom constraint of the TextField and added the height of the keyboard to it. It moves correctly.
Where I need help: The TableView inside the ContainerView does not move correctly with the TextField. Here are some screenshots that show my problem: http://imgur.com/a/zd7mw
it looks like the keyboard is overlaying the ContainerView. But it seems like the TableView inside the ContainerView is messing it up.
I expect "thank you for reading the example messages" to be shown when I click the TextField, but it gets cut. I thought about scrolling down to the bottom everytime you open the keyboard, but that would not work if you look into the middle of the chat and try to type something, you want to stay exactly where you are looking at.
Thank you for reading through this and thanks for any help!

There are two ways to do what you are trying to accomplish:
programmatically modify the bottom constraint constant to include the height of the keyboard. This will pin the bottom of your tableview to the top.
The method I would use is just adding a content inset to your tableview equal to the height of the keyboard. This approach is more simple because it does not require you modifying view constraint constants.
Start like this:
override func viewDidLoad(){
NotificationCenter.default.addObserver(self, selector: #selector(keyboardHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardShow), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
func keyboardShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
self.tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardSize.height, 0)
}
}
func keyboardHide(notification: NSNotification) {
self.tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
}
Note that if you want to push the content up to the bottom of the textfield, add your textfield height to your keyboard height.

Related

Pin UIView to top of screen when keyboard slides up

I've got the following UIView Layout for a simple chat to the left and I'm shifting the screen content up by the size of the keyboard, when the keyboard shows up, like shown to the right.
#objc func keyboardWillShow(_ notification: Notification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y == 0 { self.view.frame.origin.y -= keyboardSize.height }
}
}
What I would like to achieve is to pin the upper part of my layout (i.e. the user profilepic, username, onine / offline indicator) to the screen, so that it stays in pace and only the UITableView get shifted up by the size of the keyboard.
How can I achieve that?
You are able to move your message content (bottom view) and decrease the height of your tableView the amount equal to keyboard height.
More detail, you constraint your message content and assign to bottomConstraint, your table height constraint is heightConstraint. When keyboard shows, decrease bottomConstraint and heightConstraint by the keyboard height; revert when keyboard hides.
Hope this will be helpful.

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.

how can I move my container up when user displays a screen keyboard in swift?

I have a UIViewController with two containers embedded and one textfield. So far when user taps the textfield the whole screen moves up so the keyboard can fit without covering the lower part of the containers.
This is how it looks in my story board:
My code looks as follows:
override func viewDidLoad() {
super.viewDidLoad()
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "dismissKeyboard")
// view.addGestureRecognizer(tap)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillChangeFrameNotification, object: nil)
}
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
if(isKeyboardShown == false){
realKeyboardSize = CGRect(x: keyboardSize.origin.x, y: keyboardSize.origin.y, width: keyboardSize.width, height: keyboardSize.height)
isKeyboardShown = true
self.view.frame.origin.y -= realKeyboardSize!.height
}else{
isKeyboardShown = false
self.view.frame.origin.y += realKeyboardSize!.height
}
}
}
Is it possible to move the lower container up instead of the whole screen?
I imagine it working like this:
topContainer stays untouched, lowerContainer moves up so that half of it is hidden behind topContainer and the keyboard is visible. When user hides the keyboard everything comes back to normal.
If you are using autolayout, you can achieve this by changing bottom constraint runtime
Here is your hierarchy and related constraints..
For how it works and how to setup things check this link
and Here is the demo, if you don't understand
take outlet of container's bottom constraint and increase it's constant to keyboard size or take outlet of top constraint and decrease it's constant to keyboard size . hope this will help :)

Showing Textfield when Keyboard is up

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!

UIScrollView doesn't move the view properly when the keyboard appears

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

Resources