I have bottomView (the one with black border in the images) its bottom anchor has a constraint to bottom view safeAreaLayoutGuide, but when keyboard will show the bottomView became toolbar to the keyboard.
Here every thing was working as expected in iOS 14 (like image 1 and 3) but when running the project in iOS 13 I found that safeAreaInsets changes its value after the keyboard dismissed (red space increases its height like image 2)
I've tried some of the solutions that mentioned here but it didn't help
can anyone suggest a solution for this?
#objc func adjustForKeyboard(notification: Notification) {
guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardScreenEndFrame = keyboardValue.cgRectValue
let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
if notification.name == UIResponder.keyboardWillHideNotification {
bottomViewConstraint?.constant = 0
NotesText.contentInset = .zero
} else {
bottomViewConstraint?.constant = -(keyboardViewEndFrame.height - self.view.safeAreaInsets.bottom)
NotesText.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: -(keyboardViewEndFrame.height + bottomViewConstraint!.constant), right: 0)
//FIXME:- force unwrap
}
NotesText.scrollIndicatorInsets = NotesText.contentInset
let selectedRange = NotesText.selectedRange
NotesText.scrollRangeToVisible(selectedRange)
}
and called this function in viewDidAppear also tried to called it at viewSafeAreaInsetsDidChange but the behavior didn't change
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
addKeyboardNotification()
// Show keyboard by default
NotesText.becomeFirstResponder()
}
Keyboard notification function:
fileprivate func addKeyboardNotification() {
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
}
Related
In the app I am building I have a Textfield with a button at the bottom of my screen, and I want to animate them up when the keyboard appears.
For that I use this:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
#objc func keyboardWillShow(notification: Notification) {
let keyboardSize = (notification.userInfo? [UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
let keyboardHeight = keyboardSize?.height
if #available(iOS 12.0, *) {
self.submitConstraint.constant = (keyboardHeight! - view.safeAreaInsets.bottom) + 10
}
else {
self.submitConstraint.constant = view.safeAreaInsets.bottom + 10
}
UIView.animate(withDuration: 0.5){
self.view.layoutIfNeeded()
}
}
#objc func keyboardWillHide(notification: Notification){
self.submitConstraint.constant = 34 // or change according to your logic
UIView.animate(withDuration: 0.5){
self.view.layoutIfNeeded()
}
}
but, then a part of the button gets hidden under the suggestions toolbar.
I have googled this, and I found this answer: How to get the height of the keyboard including suggestions bar in swift. I tried the answers, and it didn't work for me, a part of the button still is hidden under the suggestions bar.
Could anybody help me?
Thanks!
Although I've searched, I'm confused about how best to approach this.
I have a tableView where the bottom cell is an input to the list, in the same way apple reminders work. When there are too many items on the list, the keyboard covers the list and I can't see what I'm typing.
My thought it I need to change the physical size of the table view and ensure it is scrolled to the bottom when the keyboard shows.
Some have said keyboard observers but the majority of code I've found for this is out of date and errors when put into Xcode.
NSNotificationCenter Swift 3.0 on keyboard show and hide
This is the best I can find but I'm also hearing about contraints, using a UITableViewController instead of embedding a UITableView and so on ...
This is the code I have so far:
NotificationCenter.default.addObserver(self, selector: #selector(EntryViewController.keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(EntryViewController.keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
#objc func keyboardWillShow(notification: Notification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
print("notification: Keyboard will show")
if self.view.frame.origin.y == 0{
self.view.frame.origin.y -= keyboardSize.height
}
}
}
#objc func keyboardWillHide(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
}
}
}
This moves the whole view up I think, which means safeareas (such as navigation bar and so on) have the TableView underneath them. Do I make the navigationview non-transparent in this approach?
One solution (which I sometimes use) is simply change the content offset of the tableView when the keyboard appears/disappears. I believe this would work in your instance as opposed to varying the tableView's constraints as you mentioned your UIViewController is a UITableViewController. Please see the below code for my suggestion, hope this helps!
Handle Notifications:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(EntryViewController.keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(EntryViewController.keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
Actions:
#objc func keyboardWillShow(notification: Notification) {
if let keyboardHeight = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height {
print("Notification: Keyboard will show")
tableView.setBottomInset(to: keyboardHeight)
}
}
#objc func keyboardWillHide(notification: Notification) {
print("Notification: Keyboard will hide")
tableView.setBottomInset(to: 0.0)
}
Extensions:
extension UITableView {
func setBottomInset(to value: CGFloat) {
let edgeInset = UIEdgeInsets(top: 0, left: 0, bottom: value, right: 0)
self.contentInset = edgeInset
self.scrollIndicatorInsets = edgeInset
}
}
Hi I am trying to make a view's bottom align with the top of UIKeyboard.
Update 1: I have created a github project if you would like to give it a try: https://github.com/JCzz/KeyboardProject
Note: I need the aView to be dynamic.
Update 2: Just pushed - to include using frames
I might have been looking at this for too long, I can not wrap my brain around it :-)
Do you know how?
How do I know if the UIKeyboard is on the way down or up?
If UIKeyboard is up, then how to align it with the view(attachKeyboardToFrame - see code).
I have found the following UIView extension:
import UIKit
extension UIView {
func bindToKeyboard(){
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChange), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
func unbindFromKeyboard(){
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
#objc
func keyboardWillChange(notification: NSNotification) {
guard let userInfo = notification.userInfo else { return }
let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as! Double
let curve = userInfo[UIKeyboardAnimationCurveUserInfoKey] as! UInt
let curFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
let targetFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
// To get the total height of view
let topView = UIApplication.shared.windows.last
//
let attachKeyboardToFrame = Singleton.sharedInstance.attachKeyboardToFrame
let global_attachKeyboardToFrame = self.superview?.convert(attachKeyboardToFrame!, to: topView)
if (targetFrame.height + attachKeyboardToFrame!.height) > (topView?.frame.height)! {
self.frame.origin.y = -targetFrame.origin.y
}else{
}
}
}
You can achieve it using following Autolayout solution.
First you need UILayoutGuide that will be used simulate Keyboard aware bottom anchor, and a NSLayoutConstraint that will control this layout guide:
fileprivate let keyboardAwareBottomLayoutGuide: UILayoutGuide = UILayoutGuide()
fileprivate var keyboardTopAnchorConstraint: NSLayoutConstraint!
In the viewDidLoad add the keyboardAwareBottomLayoutGuide to the view and setup the appropriate contraints:
self.view.addLayoutGuide(self.keyboardAwareBottomLayoutGuide)
// this will control keyboardAwareBottomLayoutGuide.topAnchor to be so far from bottom of the bottom as is the height of the presented keyboard
self.keyboardTopAnchorConstraint = self.view.layoutMarginsGuide.bottomAnchor.constraint(equalTo: keyboardAwareBottomLayoutGuide.topAnchor, constant: 0)
self.keyboardTopAnchorConstraint.isActive = true
self.keyboardAwareBottomLayoutGuide.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor).isActive = true
Then use following lines to start listening to keyboard showing and hiding:
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShowNotification(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHideNotification(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
And finally, use following methods to control the keyboardAwareBottomLayoutGuide to mimic the keyboard:
#objc fileprivate func keyboardWillShowNotification(notification: NSNotification) {
updateKeyboardAwareBottomLayoutGuide(with: notification, hiding: false)
}
#objc fileprivate func keyboardWillHideNotification(notification: NSNotification) {
updateKeyboardAwareBottomLayoutGuide(with: notification, hiding: true)
}
fileprivate func updateKeyboardAwareBottomLayoutGuide(with notification: NSNotification, hiding: Bool) {
let userInfo = notification.userInfo
let animationDuration = (userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue
let keyboardEndFrame = (userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
let rawAnimationCurve = (userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber)?.uint32Value
guard let animDuration = animationDuration,
let keybrdEndFrame = keyboardEndFrame,
let rawAnimCurve = rawAnimationCurve else {
return
}
let convertedKeyboardEndFrame = view.convert(keybrdEndFrame, from: view.window)
let rawAnimCurveAdjusted = UInt(rawAnimCurve << 16)
let animationCurve = UIViewAnimationOptions(rawValue: rawAnimCurveAdjusted)
// this will move the topAnchor of the keyboardAwareBottomLayoutGuide to height of the keyboard
self.keyboardTopAnchorConstraint.constant = hiding ? 0 : convertedKeyboardEndFrame.size.height
self.view.setNeedsLayout()
UIView.animate(withDuration: animDuration, delay: 0.0, options: [.beginFromCurrentState, animationCurve], animations: {
self.view.layoutIfNeeded()
}, completion: { success in
//
})
}
Now with all this set up, you can use Autolayout to constraint your views to keyboardAwareBottomLayoutGuide.topAnchor instead of self.view.layoutMarginsGuide.bottomAnchor (or self.view.bottomAnchor, whichever you use). keyboardAwareBottomLayoutGuide will automatically adjust to the keyboard showed or hidden.
Example:
uiTextField.bottomAnchor.constraint(equalTo: keyboardAwareBottomLayoutGuide.topAnchor).isActive = true
EDIT: Directly setting frames
While I strongly recommend using Autolayout, in cases when you cannot go with this, directly setting frames can be also a solution. You can use the same principle. In this approach you don't need layout guide, so you don't need any additional instance properties. Just use viewDidLoad to register for listening notifications:
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShowNotification(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHideNotification(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
Then implement methods that will react to these notifications:
#objc fileprivate func keyboardWillShowNotification(notification: NSNotification) {
adjustToKeyboard(with: notification, hiding: false)
}
#objc fileprivate func keyboardWillHideNotification(notification: NSNotification) {
adjustToKeyboard(with: notification, hiding: true)
}
fileprivate func adjustToKeyboard(with notification: NSNotification, hiding: Bool) {
let userInfo = notification.userInfo
let animationDuration = (userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue
let keyboardEndFrame = (userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
let rawAnimationCurve = (userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber)?.uint32Value
guard let animDuration = animationDuration,
let keybrdEndFrame = keyboardEndFrame,
let rawAnimCurve = rawAnimationCurve else {
return
}
let convertedKeyboardEndFrame = view.convert(keybrdEndFrame, from: view.window)
let rawAnimCurveAdjusted = UInt(rawAnimCurve << 16)
let animationCurve = UIViewAnimationOptions(rawValue: rawAnimCurveAdjusted)
// we will go either up or down depending on whether the keyboard is being hidden or shown
let diffInHeight = hiding ? convertedKeyboardEndFrame.size.height : -convertedKeyboardEndFrame.size.height
UIView.animate(withDuration: animDuration, delay: 0.0, options: [.beginFromCurrentState, animationCurve], animations: {
// this will move the frame of the aView according to the diffInHeight calculated above
// of course here you need to set all the frames that would be affected by the keyboard (this is why I prefer using autolayout)
self.aView?.frame = (self.aView?.frame.offsetBy(dx: 0, dy: diff))!
// of course, you can do anything more complex than just moving the aView up..
})
}
In both cases, don't forget to unregister observing the notifications once the viewController is deinitialized to prevent retain cycles:
deinit {
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
I am adding a keyboard observer to adjust the view height when the keyboard shows. But when the text field is at the top of the view and the keyboard shows, the text field goes up even more. Can someone please guide me on how to fix this properly because after performing lots of searches, they all suggest the same thing. I am using iOS 9 on an iPhone 5.
Here is my code. In viewDidLoad():
//SET OBSERVER WHEN KEYBOARD IS SHOWN
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name:UIKeyboardWillShowNotification, object: self.view.window)
//SET OBSERVER WHEN KEYBOARD IS HIDDEN
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name:UIKeyboardWillHideNotification, object: self.view.window)
override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: self.view.window)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: self.view.window)
}
//HANDLING KEYBOARD APPEARING AND DISAPPEARING
func keyboardWillHide(sender: NSNotification) {
let userInfo: [NSObject : AnyObject] = sender.userInfo!
let keyboardSize: CGSize = userInfo[UIKeyboardFrameBeginUserInfoKey]!.CGRectValue.size
self.view.frame.origin.y += keyboardSize.height
}
func keyboardWillShow(sender: NSNotification) {
let userInfo: [NSObject : AnyObject] = sender.userInfo!
let keyboardSize: CGSize = userInfo[UIKeyboardFrameBeginUserInfoKey]!.CGRectValue.size
let offset: CGSize = userInfo[UIKeyboardFrameEndUserInfoKey]!.CGRectValue.size
print(("\(keyboardSize.height)---\(offset.height)")) **//this is always showing the values 216.0 for both**
if keyboardSize.height == offset.height {
UIView.animateWithDuration(0.1, animations: { () -> Void in
self.view.frame.origin.y -= keyboardSize.height
})
} else {
UIView.animateWithDuration(0.1, animations: { () -> Void in
self.view.frame.origin.y += keyboardSize.height - offset.height
})
}
}
You're taking a complicated approach to what isn't a very difficult problem. Please remember that phones come in a variety of sizes, so trying to move a view around might work great for one device (e.g. iPhone 5) but be a poor experience on others (e.g. iPhone 4, iPhone 6s).
The solution you're looking for is to place your controls inside a UIScrollView. You then don't move anything when the keyboard shows or hides – you just adjust the content inset of your scroll view, and let iOS do the rest.
The keyboard being shown and hidden is complicated by the fact that there are also hardware keyboards that can be connected and unplugged at will; in my (extremely extensive!) testing, I've managed to pull together code that seems to behave properly in all conditions, no matter what happens with the keyboard. I've tried to trim it down smaller and have always found an edge case where it stopped working, so I've settled with this fixed approach and it works great. If you're able to use a UIScrollView, this exact approach will work for you too.
Step 1: Register for the right notifications. Specifically, put this into viewDidLoad():
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: "adjustForKeyboard:", name: UIKeyboardWillHideNotification, object: nil)
notificationCenter.addObserver(self, selector: "adjustForKeyboard:", name: UIKeyboardWillChangeFrameNotification, object: nil)
Step 2: Add this method to your view controller:
func adjustForKeyboard(notification: NSNotification) {
let userInfo = notification.userInfo!
let keyboardScreenEndFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
let keyboardViewEndFrame = view.convertRect(keyboardScreenEndFrame, fromView: view.window)
if notification.name == UIKeyboardWillHideNotification {
yourScrollView.contentInset = UIEdgeInsetsZero
} else {
yourScrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height, right: 0)
}
yourScrollView.scrollIndicatorInsets = yourScrollView.contentInset
}
Step 3: Anyone using a UITextView should also add these two lines to the end of that method so the user doesn't lose their place:
let selectedRange = yourScrollView.selectedRange
yourScrollView.scrollRangeToVisible(selectedRange)
…although presumably in that case it would be yourTextView or similar.
I have set the notifications to keep the text in textView above the keyboard. My problem is, when I select the table view cell to display my text in the next VC, that the text appears few lines below the top and only after I start editing (keyboard shows up) and than swipe to dismiss the keyboard, text repositions itself at the top, where it should be.How can I force the text to be at the top as soon as VC displays its content, before the keyboard appears? Thanks.
override func viewDidLoad() {
super.viewDidLoad()
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: "adjustForKeyboard:", name: UIKeyboardWillHideNotification, object: nil)
notificationCenter.addObserver(self, selector: "adjustForKeyboard:", name: UIKeyboardWillChangeFrameNotification, object: nil)
storyTextView.text = passedText
title = passedTitle
navigationController?.navigationBar.tintColor = UIColor.redColor()
}
func adjustForKeyboard(notification: NSNotification) {
let userInfo = notification.userInfo!
let keyboardScreenEndFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
let keyboardViewEndFrame = view.convertRect(keyboardScreenEndFrame, fromView: view.window)
if notification.name == UIKeyboardWillHideNotification {
storyTextView.contentInset = UIEdgeInsetsZero
}else {
storyTextView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height, right: 0)
}
storyTextView.scrollIndicatorInsets = storyTextView.contentInset
let selectedRange = storyTextView.selectedRange
storyTextView.scrollRangeToVisible(selectedRange)
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
storyTextView.contentInset = UIEdgeInsetsZero
}