I have a collection view I made in storyboard and a container view with text field constrained to the bottom of the view programmatically. However, when I show the keyboard the collection view stays hidden underneath and I'm not sure how to. I looked and most answers say to use self.view.frame.origin.y in the keyboard notification listener but that's hasn't worked for me.
my keyboard notification code
#objc func keyboardWillShow(notification: NSNotification) {
let keyboardFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
let duration = ((notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue)!
containerViewbottomAnchor?.constant = -keyboardFrame!.height
UIView.animate(withDuration: duration) {
self.view.layoutIfNeeded()
}
}
first image is the normal chat and second is the max view you ca see when the keyboard is shown. the container view contains the textfield and button which I added programmatically. they are within an input accessory view to update their positions when the keyboard comes up.
Have you tried this solution? https://stackoverflow.com/a/57438748/13042987
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
#objc func handleKeyboardWillShow(notification: Notification) {
collectionView.scrollToItem(at: IndexPath(row: messagesList.count - 1, section: chatSection), at: .top, animated: false)
}
I managed to fix it by changing to a UICollectionview controller class
Related
I have an input accessory view called chatBoxView, which contains a custom growingTextView and couple of buttons to send the message. And this inputAccessoryView is setup in my main view controller.
override var inputAccessoryView: UIView? {
get {
return chatBoxView
}
}
override var canBecomeFirstResponder: Bool {
return true
}
So every time I go to this viewController, I see the chatBoxView at the bottom of my screen, which is wanted. However, the keyboard isn't visible yet (only the chatBoxView), but why does my KeyBoardWillShow notification keep getting called?
Also, when the keyboard is up, then disappears when I tap the outside, KeyboardWillDisappear function gets called, but KeyBoardWillShow function gets called right after.
Here are the function for the Keyboard notifications.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
#objc func keyboardWillShow(notification: Notification) {
if let keyboardHeight = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height {
print("Notification: Keyboard will show")
DispatchQueue.main.async {
self.tableView.setBottomInset(to: keyboardHeight - self.chatBoxView.frame.height)
self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .bottom, animated: true)
}
}
}
#objc func keyboardWillHide(notification: Notification) {
print("Notification: Keyboard will hide")
tableView.setBottomInset(to: 0.0)
}
So basically, I get the log "Notification: Keyboard will show", when the keyboard isn't showing and only the accessory view. Any ideas?
The problem is that you are throwing away important information in the UIResponder.keyboardFrameEndUserInfoKey. It is not enough just to look at the keyboard's height. You need to look at where the keyboard will be — look at its entire frame information. If the top of the keyboard is not going to be above the bottom of the screen, clearly you should not be doing anything. Similarly, if the keyboard frame is not going to be changing from where it was, you should not be doing anything.
In simulator, make sure you show software keyboard.
Click here or ⌘ + K
I create multiple UITextFields in my storyboard. Some text field are edited using keyboard while others using picker view. In my view controller, for the fields using pickerView, I created separate picker views for each of the text fields. For eg.
aTextField.inputView = aPickerView
Now for the text fields which are at the bottom of the screen, when I tap on one of them, picker view corresponding to that text field opens at the bottom and it hides the textfield. I want to shift the currently selected text field up when the picker view opens.
In case of keyboard, shifting of views can be done by responding to UIResponder.keyboardWillShowNotification. How to do this in case of picker view?
One solution that I can think of is observing keyboard height. When you press the picker view that has to show up the bottom textField, use keyboard height and some extra points to move it over the keyboard. Here are some helper methods to achieve that:
override func viewDidLoad() {
super.viewDidLoad()
// Add keyboard observers
addObservers()
}
private func addObservers() {
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
// keyboard will be active
#objc private func keyboardWillShow(notification: NSNotification) {
// Get keyboard Size
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
// update constraints / footerview height with keyboard height "keyboardSize.height"
}
}
// keyboard will dismiss
#objc private func keyboardWillHide(notification: NSNotification) {
if (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue != nil {
// update constraints / footerview height when keyboard is hidden,
}
}
I'm working on a simple chat as a part of my App.
I have a list of Chats (i.e. friends) in a Table View Controller and Chat View Controller displaying messages for each chat. Both of them are embedded in a NavigationController, which is embedded in a TabBarController.
I want to place UITextView message text input field in a ChatViewController below my UITableVeiw that shows messages of that chat. I would also like the UITextView to look like it's embedded in a tab bar.
I've run through dozens of manuals tutorials and guides and that's where I got so far.
App simulator screenshots here: nice at the top, buggy at the bottom
Main.storyboard screenshot here
I use UITextView instead of UITextField because I want it to be able to change its size depending on content size
I use UIViewController instead of UITableViewController to be able to add another UIView along with UITableView
I don't use actual UITabBar to embed my UITextView because it behaves really weird when UITextView needs to change its height. And as far as I understood Apple doesn't support embedding UI element to UITabBar
What I do is I hide TabBar in ChatViewController viewWillAppear:
override func viewWillAppear(_ animated: Bool) {
tabBarController?.tabBar.isHidden = true
}
And I show it again in parent ChatsTableViewController:
override func viewWillAppear(_ animated: Bool) {
tabBarController?.tabBar.isHidden = false
}
Once TabBar is hidden, my custom stackView embedding textView and sendButton takes its place and operates quite normally as I want it to.
To handle keyboard behaviour properly I have an outlet for my inputStackView bottom constraint (selected on screenshot), which I update on keyboardWillShow/Hide Notifications. Code for that looks like this:
#IBOutlet weak var inputBottomConstraint: NSLayoutConstraint!
var inputBottomConstraintInitialValue: CGFloat!
//MARK: - Keyboard handling
private func enableKeyboardHideOnTap(){
NotificationCenter.default.addObserver(self, selector: #selector(ChatTableViewController.keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(ChatTableViewController.keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ChatTableViewController.hideKeyboard))
self.view.addGestureRecognizer(tap)
}
#objc func hideKeyboard() {
textView.resignFirstResponder()
}
#objc func keyboardWillShow(notification: NSNotification) {
let info = notification.userInfo!
let keyboardFrame: CGRect = (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let duration = info[UIKeyboardAnimationDurationUserInfoKey] as! Double
UIView.animate(withDuration: duration) { [weak self] () -> Void in
self?.inputBottomConstraint.constant = keyboardFrame.size.height + 8
self?.view.layoutIfNeeded()
}
}
#objc func keyboardWillHide(notification: NSNotification) {
let duration = notification.userInfo![UIKeyboardAnimationDurationUserInfoKey] as! Double
UIView.animate(withDuration: duration) { [weak self] () -> Void in
guard let `self` = self else { return }
self.inputBottomConstraint.constant = self.inputBottomConstraintInitialValue
self.view.layoutIfNeeded()
}
}
Everything seems to work fine at this point:
TabBar is hidden in a ChatViewController and is shown in ChatsTableViewController
textView and sendButton embedded in a stackView slide along will keyboard up and down, as shown in simulator screenshots above.
The BUG appears when I swipe between ChatViewController and ChatsTableViewController instead of using NavigationControllers
When I start swiping back to ChatsTableViewController, it perform its viewWillAppear method, so tabBarController's tabBar become visible. And if I don't finish that swipe to ChatsTableViewController and go back ChatViewController, ChatViewController's viewWillAppear sets it back to invisible, but the input stackView doesn't get back to it's initial position. It just hangs there above the invisible tabBar.
I've tried moving
tabBarController?.tabBar.isHidden = false
from viewWillAppear to viewDidAppear in ChatsTableViewController.
It fixes the "floating input" issue but it adds a lag of about a second, when ChatsTableViewController is showing without tabBar. Looks not too good either.
Any ideas how that could be fixed properly?
I've been struggling with that issue for about a week))
Because your ChatsTableViewController is embedded in in UINavigationController and your ChatViewController is pushed you can override this hidesBottomBarWhenPushed in ChatViewController which will determine to show your view controller or not:
public override var hidesBottomBarWhenPushed: Bool {
get { return true }
set { super.hidesBottomBarWhenPushed = newValue }
}
This is a better way to hide the tab bar to my mind, because the frame of the view controllers will be adjusted.
Why do you change constraint on keyboard show/Hide. Try to Google inputAccesoryView, many chats use it. It’s natural behaviour is to go with the keyboard. Try FaceBook Messenger for example and see there how swiping hides the keyboard and Textfield together, you can’t accomplish this kind od behaviour with regular view.
Issue
I have a number of cases on iOS 11 (that do not occur on iOS 10 and below) where .UIKeyboardWillChangeFrame notification is not being fired — specifically when transitioning between view controllers where both view controllers have a UITextfield which is set as the fristResponder.
Since I have UI that needs to animate above the keyboard in response to the keyboard showing, receiving this notification is essential.
On iOS 10 and below, I get the notification on both view controllers (on showing VC A and also when pushing VC B). However, on iOS 11, the notification does not fire when pushing VC B. It's as if the keyboard remained in place.
Does anyone know what the cause of this is?
Details
Both view controllers inherit from a base view controller with the following implementation:
class BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame), name: .UIKeyboardWillChangeFrame, object: nil)
}
// Update layout when the keyboard is shown or hidden
#objc func keyboardWillChangeFrame(notification : Notification) {
// Check if got info
if (notification.userInfo == nil) {
return;
}
// Get resize properties
let dict = notification.userInfo!
let rect = self.view.convert((((dict[UIKeyboardFrameEndUserInfoKey as NSObject] as Any) as AnyObject).cgRectValue)!, from: nil)
let size = self.view.bounds.size.height - rect.origin.y
let duration = ((dict[UIKeyboardAnimationDurationUserInfoKey] as Any) as AnyObject).doubleValue
let curve = UIViewAnimationCurve.init(rawValue: (((dict[UIKeyboardAnimationCurveUserInfoKey] as Any) as AnyObject).intValue)!)
self.keyboardOffset = max(0, size)
// Set animation options
var options : UIViewAnimationOptions
switch (curve!) {
case .easeInOut:
options = UIViewAnimationOptions()
case .easeIn:
options = UIViewAnimationOptions.curveEaseIn
case .easeOut:
options = UIViewAnimationOptions.curveEaseOut
case .linear:
options = UIViewAnimationOptions.curveLinear
}
// Animate the change
UIView.animate(withDuration: duration!, delay: 0, options: options, animations: { () -> Void in
// Relayout
self.relayout()
}, completion: nil)
}
}
Example of a subclass:
class ViewControllerA: BaseViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
passwordField.becomeFirstResponder()
}
func relayout() {
// ... do animations to show stuff above keyboard.
}
}
Note that .keyboardWillShow and .keybaordWillHide are also not fired on the transition to VC B.
You need to add self.view.endEditing(force:Bool) in you first view controller's viewWillDisappear method.
Generally, you can always call endEditing method when you changing the screen.
I've worked around this by saving the keyboard height in a class variable in VC A and then passing that along to VC B.
It's not ideal, as I would prefer my VC's to be independent for those details.
I have this code in a UIViewController that changes the height of the view when the keyboard opens.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// Subscribe to keyboard events.
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
}
func keyboardWillShow(notification: NSNotification) {
let keyboardHeight = notification.userInfo![UIKeyboardFrameEndUserInfoKey]!.CGRectValue.height
// UIView.animateWithDuration(0.5) {
self.view.frame.size.height -= keyboardHeight
self.view.layoutIfNeeded()
// }
}
I notice that the view animates even without UIView.animateWithDuration. Why is this so?
The notification is send out as part of an animation block. If you extract the information from the notification related to the animation, you can have other views animate alongside with it. In your case, all the changes to any view you make between keyboardWillShow and keyboardDidShow are going to be animated as part of that keyboard-animation.