i'm using a UITextView and set it's contentInset & scrollIndicatorInsets when keyboard is show/hide like this:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardShow:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardHide:", name: UIKeyboardWillHideNotification, object: nil)
}
override func shouldAutorotate() -> Bool {
return !self.keyboardShowing
}
func keyboardShow(n:NSNotification) {
self.keyboardShowing = true
let d = n.userInfo!
var r = (d[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
r = self.tv.convertRect(r, fromView:nil)
self.tv.contentInset.bottom = r.size.height
self.tv.scrollIndicatorInsets.bottom = r.size.height
}
func keyboardHide(n:NSNotification) {
self.keyboardShowing = false
self.tv.contentInset = UIEdgeInsetsZero
self.tv.scrollIndicatorInsets = UIEdgeInsetsZero
}
It works fine, but when I rotated my device, the scrollIndicatorInsets & contentInset didn't change with my new height. So what can I do? Thanks!
NOTE: The view has a Navigation bar on the top
Considering the UIKeyboardDidChangeFrameNotification notification. It's sent when the keyboard’s frame has just changed. Actually, you should use the UIKeyboardDidChangeFrameNotification instead of using UIKeyboardWillShowNotification.
Related
I have a simple ViewController with a UITextView inside of a UIStackView. I've also stored an NSLayoutConstraint which is the bottom anchor of the stackView.
When the textView becomes first responder or resigns from first responder, two Notifications get triggered. The keyboardDidShowNotification works great. The issue is with keyboardWillHideNotification.
I’ve noticed that when I change the keyboard's frame by clicking the emojis button and then dismiss the keyboard, something breaks with the constraints.
As you can see in the gif below, as long as I don't go to the emojis section, the keyboard dismisses properly and the constraints are updated also properly. When I do go to the emojis section, then the bottomStackViewBottomConstraint and schoolTextViewTopConstraint go below that they should.
Here's what happens;
And here is the code;
fileprivate var schoolTextViewTopConstraint: NSLayoutConstraint!
fileprivate var bottomStackViewBottomConstraint: NSLayoutConstraint!
fileprivate func setupNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidShow), name: UIResponder.keyboardDidShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
#objc fileprivate func keyboardDidShow(notification: Notification) {
if let infoKey = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey], let rawFrame = (infoKey as AnyObject).cgRectValue {
let keyboardFrame = self.view.convert(rawFrame, from: nil)
bottomStackViewBottomConstraint.constant = -(keyboardFrame.height) + self.view.safeAreaInsets.bottom
schoolTextViewTopConstraint.constant = -keyboardFrame.height + 180
}
UIView.animate(withDuration: 0, animations: {
self.sendCommentButton.isHidden = false
self.toggleMuteButton.isHidden = true
self.shareButton.isHidden = true
self.leaveRoomButton.isHidden = true
self.view.layoutIfNeeded()
}, completion: nil)
}
#objc fileprivate func keyboardWillHide(notification: Notification) {
schoolTextViewTopConstraint.constant = 0
bottomStackViewBottomConstraint.constant = 0
sendCommentButton.isHidden = true
UIView.animate(withDuration: 0.5, animations: {
self.toggleMuteButton.isHidden = self.currentUserRole == VoiceRoomRole.listener
self.shareButton.isHidden = false
self.leaveRoomButton.isHidden = false
self.view.layoutIfNeeded()
}, completion: nil)
}
I set Keyboard listener with NotificationCenter and want to change view.frame.origin.y when focus field. But when I start typing my view.frame.origin.y move to initial position.
Register keyboard function
private func registerKeyboardAppearenceObserver() {
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 private func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
if (self.view.frame.origin.y == 0) {
self.view.frame.origin.y -= keyboardSize.height / 3
}
}
}
#objc private func keyboardWillHide(notification: NSNotification) {
if self.view.frame.origin.y != 0 {
self.view.frame.origin.y = 0
}
}
Initial app screen
Screen when any field focuses
Screen when start typing in any field
I don't think its the best idea to change the parent view's frame. Perhaps put your content in a container view that initially has the same frame as the parent view and update that container's frame
I have done keyboard appearing below textfield using
on View did Load adding a observer()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(Gold_Loan_First_ViewController.keyboardDidShow(_:)), name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(Gold_Loan_First_ViewController.keyboardWillBeHidden(_:)), name: UIKeyboardWillHideNotification, object: nil)
And then updating the frame
weak var activeField: UITextField?
func textFieldDidEndEditing(textField: UITextField) {
self.activeField = nil
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if textField==txtOTP {
txtOTP.errorMessage=""
}
return true
}
func textFieldDidBeginEditing(textField: UITextField) {
self.activeField = textField
}
func keyboardDidShow(notification: NSNotification)
{
if let activeField = self.activeField,
let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
let contentInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardSize.height, right: 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
var aRect = self.view.frame
aRect.size.height -= keyboardSize.size.height
if (!CGRectContainsPoint(aRect, activeField.frame.origin)) {
self.scrollView.scrollRectToVisible(activeField.frame, animated: true)
}
}
}
func keyboardWillBeHidden(notification: NSNotification)
{
let contentInsets = UIEdgeInsetsZero
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
}
But how do I do it for a textView. I tried the same code with didBeginEditing of textView with no positive effect
One of the easy and no code of line solution is to use the following pods in your app.
IQKeyboardManger
Later you need to just import that in App Delegate and add this two lines of code in didfinishLaunching method:
IQKeyboardManager.sharedManager().enable = true
Your problem will be solved for whole app.
For Swift 5:
IQKeyboardManager.shared.enable = true
I have gone through such a situation. For this first I added extension in my UiViewController :
extension CCViewController : UITextViewDelegate {
func textViewDidBeginEditing(_ textView: UITextView) {
// write code as per your requirements
}
func textViewDidEndEditing(_ textView: UITextView) {
// write code as per your requirements
}
func textViewDidChange(_ textView: UITextView) {
self.p_adjustMessageFieldFrameForTextView(textView)
}
}
// Defining the p_adjustMessageFieldFrameForTextView method
fileprivate func p_adjustMessageFieldFrameForTextView(_ textView : UITextView) {
var finalheight : CGFloat = textView.contentSize.height + 10
var kMaxMessageFieldHeight : CGFloat = 50
if (finalheight > kMaxMessageFieldHeight) {
finalheight = kMaxMessageFieldHeight;
} else if (finalheight < kCommentTextViewHeight){
finalheight = kCommentTextViewHeight;
}
scrollView!.view.frame = CGRect(x: 0, y: kNavBarHeight + statuBarHeight, width: SCREEN_WIDTH,height: SCREEN_HEIGHT - finalheight - keyboardRect!.size.height - kNavBarHeight - statuBarHeight)
// It is there for understanding that we have to calculate the exact frame of scroll view
commentTextView!.frame = CGRect(x: 0, y: bottomOfView(scrollView!.view), width: SCREEN_WIDTH, height: finalheight)
self.p_setContentOffsetWhenKeyboardIsVisible()
}
// Defining the p_setContentOffsetWhenKeyboardIsVisible method
fileprivate func p_setContentOffsetWhenKeyboardIsVisible() {
// here you can set the offset of your scroll view
}
Also there is a method named bottomView() :
func bottomOfView(_ view : UIView) -> CGFloat {
return view.frame.origin.y + view.frame.size.height
}
You can simply use TPKAScrollViewController.h & TPKAScrollViewController.m files by using Bridging Header.
While dragging these objective-C file to your swift project, it will automatically ask for Create Bridging. Create Bridging Header and Import #import "TPKAScrollViewController.h" to YourApp-Bridging-Header.h file.
After that, simply select your scrollView in XIB and change its class to TPKeyboardAvoidingScrollView as showing below.
Salman Ghumsani is Right.
Please change UIKeyboardFrameBeginUserInfoKey to UIKeyboardFrameEndUserInfoKey.
Apple Debeloper Document: Keyboard Notification User Info Keys
UIKeyboardFrameEndUserInfoKey
The key for an NSValue object containing a CGRect that identifies the ending frame rectangle of the keyboard in screen coordinates. The frame rectangle reflects the current orientation of the device.
UIKeyboardFrameBeginUserInfoKey
The key for an
NSValue
object containing a
CGRect
that identifies the starting frame rectangle of the keyboard in screen coordinates. The frame rectangle reflects the current orientation of the device.
Conclusion
So, if u use UIKeyboardFrameBeginUserInfoKey u might get the keyboard height to 0. Causing the system thought UITextView was not covered.
Find the correct height of keyboard and assign it to the bottom constraint of the textView or reduce the y position of textView.
Like:
Step-1:
make a property keyboardHeight in you viewController.
var keyboardHeight: CGFloat = 0.0
Step-2: Make #IBOutlet of bottom constraint of the textView.
#IBOutlet weak var textViewBottomConstraint: NSLayoutConstraint!
Step-3:
fileprivate func addKeyboardNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
fileprivate func removeKeyboardNotification() {
IQKeyboardManager.shared().isEnabled = true
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
Copy & paste these functions to your view controllers, and call self.addKeyboardNotification() in viewDidLoad() and of you viewController
Step-4:
deinit {
self.removeKeyboardNotification()
}
also add this code to your viewController.
Step-5:
func keyboardWillShow(_ notification: Notification) {
if let keboardFrame = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue, self.keyboardHeight <= 0.0 {
self.keyboardHeight = keboardFrame.height + 45.0 //(Add 45 if your keyboard have toolBar if not then remove it)
}
UIView.animate(withDuration: 0.3, animations: {
self.textViewBottomConstraint.constant = self.keyboardHeight
}, completion: { (success) in
})
}
func keyboardWillHide(_ notification: Notification) {
UIView.animate(withDuration: 0.3, animations: {
self.textViewBottomConstraint.constant = 0.0
}, completion: { (success) in
})
}
That's it to manage the textView with keyboard.
If you don't want to use textViewBottomConstraint the you can work with the y position of the textView.
I learned from this existing question to get the func "keyboardwillshow" move up the entire view by the same height of the keyboard, in order to avoid the textfields/textviews at the bottom being covered by the keyboard.
However, if I tap two or more textfields in a row, the "keyboardwillshow" func will run by the same amount of tap, moving further up the view and eventually off the screen, leaving only black emptiness.
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)
}
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
self.view.frame.origin.y -= keyboardSize.height
}
}
func keyboardWillHide(notification: NSNotification) {
self.view.frame.origin.y = 0
}
Someone said in the answers that we can implement a boolean to detect whether the keyboard is already appeared or not, so the func will only work once.
Can somebody show me how to do this boolean? Thanks!
First add an instance variable to store the boolean and initialise to false (var keyboardIsShown = false. Next within the if statement in keyboardWillShow, set the boolean you created earlier to true, as the keyboard has now been shown. Next step is to add another if statement to within keyboardWillShow before you move the view, to check if the keyboard is already showing. Finally make sure to set the boolean back to false when the keyboard is hidden again.
// instance variable here
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)
}
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
// if statement here
self.view.frame.origin.y -= keyboardSize.height
// set boolean to true
}
}
func keyboardWillHide(notification: NSNotification) {
self.view.frame.origin.y = 0
// set boolean to false
}
Ok. I figured out that a better solution is to use UITextFieldTextDidBeginEditingNotification and UITextViewTextDidBeginEditingNotification, or UITextViewTextDidBeginEditingNotification and UITextViewTextDidEndEditingNotification.
Example:
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UITextFieldTextDidBeginEditingNotification, object: yourTextField)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UITextFieldTextDidEndEditingNotification, object: yourTextView)
This way, the keyboard behaviour applies to only the specified textfield or textview identified as object:. If you want this to work on all textfields or textviews within your View, then object is nil.
The gray box at the bottom is a text view. When I tap the text view, the keyboard will pop up from bottom. However, the text view has been covered by the pop up keyboard.
What functions should I add in order to move up the whole view when the keyboard pops up?
To detect when a keyboard shows up you could listen to the NSNotificationCenter
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: “keyboardWillHide:", name: UIKeyboardWillHideNotification, object: nil)
That will call keyboardWillShow and keyboardWillHide. Here you can do what you want with your UITextfield
func keyboardWillShow(notification: NSNotification) {
if let userInfo = notification.userInfo {
if let keyboardSize = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
//use keyboardSize.height to determine the height of the keyboard and set the height of your textfield accordingly
}
}
}
func keyboardWillHide(notification: NSNotification) {
//pull everything down again
}
As Milo says, to do this yourself you register for for keyboard show/hide notifications.
You then need to write code that figures out how much of the screen the keyboard is hiding, and how high on the screen the field in question is located, so you know how much to shift your views.
Once you've done that what you do depends on whether you're using AutoLayout or autoresizing masks (a.k.a. "Struts and springs" style layout.)
I wrote a developer blog post about a project that includes working code for shifting the keyboard. See this link:
http://wareto.com/animating-shapes-using-cashapelayer-and-cabasicanimation
In that post, look for the link titled "Sliding your views to make room for the keyboard" at the bottom.
Swift 4 complete solution.
I use this in all of my projects that need it.
on viewWillAppear register to listen for the keyboard showing/hiding and use the functions below to move the view up and back down.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
subscribeToKeyboardNotifications()
}
// stop listening for changes when view is dissappearing
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
unsubscribeFromKeyboardNotifications()
}
// listen for keyboard show/show events
func subscribeToKeyboardNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(_:)), name: .UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide(_:)), name: .UIKeyboardWillHide, object: nil)
}
func unsubscribeFromKeyboardNotifications() {
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillShow, object: nil)
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillHide, object: nil)
}
#objc func keyboardWillHide(_ notification: Notification) {
view.frame.origin.y = 0
}
In my case I have a text field at the bottom which gets hidden by the keyboard so if this is in use then I move the view up
#objc func keyboardWillShow(_ notification: Notification) {
if bottomTextField.isFirstResponder {
view.frame.origin.y = -getKeyboardHeight(notification: notification)
}
}
func getKeyboardHeight(notification: Notification) -> CGFloat {
let userInfo = notification.userInfo
let keyboardSize = userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue
return keyboardSize.cgRectValue.height
}
Swift 4
This code is not so perfect but worth a try!
First embed the whole view in a scrollView.
Add this little cute function to your class:
#objc func keyboardNotification(_ notification: Notification) {
if let userInfo = (notification as NSNotification).userInfo {
let endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
let duration:TimeInterval = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIViewAnimationOptions().rawValue
let animationCurve:UIViewAnimationOptions = UIViewAnimationOptions(rawValue: animationCurveRaw)
if (endFrame?.origin.y)! >= UIScreen.main.bounds.size.height {
scrollViewBottomConstraint?.constant = 0
} else {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
let keyboardHeight:Int = Int(keyboardSize.height)
scrollViewBottomConstraint?.constant = CGFloat(keyboardHeight)
scrollView.setContentOffset(CGPoint(x: 0, y: (scrollViewBottomConstraint?.constant)! / 2), animated: true)
}
}
UIView.animate(withDuration: duration, delay: TimeInterval(0), options: animationCurve, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
}
.
Add this one to your ViewDidLoad:
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardNotification(_:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
Don't forget to connect bottom constraint of your scrollView to your class (with name: "scrollViewBottomConstraint").