How to fix position of UIView when keyboard moved up? - ios

How do I fix the position of UIView with image and label when the keyboard is moved up?
I made a code to move up my textField when the keyboard shows, but instead this moves everything.
func textFieldDidEndEditing(_ textField: UITextField) {
moveTextField(textField: rasstoyanietextField, moveDistance: -215, up: false)
}
func textFieldDidBeginEditing(_ textField: UITextField) {
moveTextField(textField: rasstoyanietextField, moveDistance: -215, up: true)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
rasstoyanietextField.resignFirstResponder()
return true
}
func moveTextField(textField: UITextField, moveDistance: Int, up: Bool){
let moveDuration = 0.1
let movement: CGFloat = CGFloat(up ? moveDistance : -moveDistance)
UIView.beginAnimations("animateTextField", context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(moveDuration)
self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)
UIView.commitAnimations()
}
How could I fix it?

Because you move whole view, instead of textview only. Change line:
self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)
with:
textField.frame = textField.frame.offsetBy(dx: 0, dy: movement)

Add keybaord observers
NotificationCenter.default.addObserver(
self,
selector: #selector(handleKeyboardDidShow),
name: NSNotification.Name.UIKeyboardDidShow,
object: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(handleKeyboardWillHide),
name:NSNotification.Name.UIKeyboardWillHide,
object: nil)
I think it's better to use scrollviews in viewControllers that have textfeilds/textViews to move up all items when showing but here you may Hook the bottom constraint of the textfeild or it's superview and drag it as IBOutlet and do this
#objc func handleKeyboardDidShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
self.textFeilfBottomCon.constant = -1 * keyboardSize.height
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
})
}
}
#objc func handleKeyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
self.textFeilfBottomCon.constant = 0
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
})
}
}

Related

Handling keyboard with bottom constraint

I'm building a chat app with a regular design and UI of a chat app.
I have a "toolbar" that my app needs (0,0 375x66) and I want it to stay where it is when the keyboard is shown. It has 4 constraints: top: 0, leading: 0, trailing: 0, aspect-ratio: 125:22
I have a UICollectionView (0,66 375x530) that I want to be scrolled like any other chat app when the keyboard is shown. It has 4 constraints: top (to the toolbar): 0, leading: 0, trailing: 0, bottom (to the newMessageView I'll explain shortly): 0
I have a UIView that has a UITextField inside it. let's call it newMessageView (0,596 375x71) and it has 4 constraints: bottom: 0, leading: 0, trailing: 0, aspect-ratio: 375:71
Now, I'm trying to bring the newMessageView app when the keyboard is shown. I'm trying to to it like this:
#objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y == 0{
self.newMessageViewBottomConstraint.constant += keyboardSize.height
}
}
}
#objc func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y != 0{
self.newMessageViewBottomConstraint.constant -= keyboardSize.height
}
}
}
but it completly doesn't work. The view jumps and hides and I really can't understand why.
Am I doing it right? Can anyone help me and guide me with this?
You have to use self.view.layoutIfNeeded() after Update constraint
i write complete solution for you
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
#objc func keyboardWillShow(notification: Notification) {
self.keyboardControl(notification, isShowing: true)
}
#objc func keyboardWillHide(notification: Notification) {
self.keyboardControl(notification, isShowing: false)
}
private func keyboardControl(_ notification: Notification, isShowing: Bool) {
/* Handle the Keyboard property of Default*/
var userInfo = notification.userInfo!
let keyboardRect = (userInfo[UIKeyboardFrameEndUserInfoKey]! as AnyObject).cgRectValue
let curve = (userInfo[UIKeyboardAnimationCurveUserInfoKey]! as AnyObject).uint32Value
let convertedFrame = self.view.convert(keyboardRect!, from: nil)
let heightOffset = self.view.bounds.size.height - convertedFrame.origin.y
let options = UIViewAnimationOptions(rawValue: UInt(curve!) << 16 | UIViewAnimationOptions.beginFromCurrentState.rawValue)
let duration = (userInfo[UIKeyboardAnimationDurationUserInfoKey]! as AnyObject).doubleValue
var pureheightOffset : CGFloat = -heightOffset
if isShowing { /// Wite space of save area in iphonex ios 11
if #available(iOS 11.0, *) {
pureheightOffset = pureheightOffset + view.safeAreaInsets.bottom
}
}
// Here change you Consrant
// self.actionBarPaddingBottomConstranit?.update(offset:pureheightOffset)
UIView.animate(
withDuration: duration!,
delay: 0,
options: options,
animations: {
self.view.layoutIfNeeded()
},
completion: { bool in
})
}

Move UITextField up when keyboard appears

I am trying to write a code to ensure UITextField move up when keyboard present. I have written a code based on what I found in stack overflow however it is not working. The original code was written in objective-c but I am not familiar with the it and hence decided to to write code in swift.
can anyone tell what I may be doing wring here?
{
// moving text box up when tyoing
func registerForKeyboardNotifications()
{
//Adding notifies on keyboard appearing
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWasShown(notification:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillBeHidden(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
#objc func keyboardWasShown(notification: NSNotification)
{
//Need to calculate keyboard exact size due to Apple suggestions
self.scrollView.isScrollEnabled = true
let info : NSDictionary = notification.userInfo! as NSDictionary
let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size
let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize!.height, 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
var aRect : CGRect = self.view.frame
aRect.size.height -= keyboardSize!.height
if (yourEmail) != nil
{
if (!aRect.contains(yourEmail!.frame.origin))
{
self.scrollView.scrollRectToVisible(yourEmail!.frame, animated: true)
}
}
}
#objc func keyboardWillBeHidden(notification: NSNotification)
{
//Once keyboard disappears, restore original positions
let info : NSDictionary = notification.userInfo! as NSDictionary as NSDictionary
let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size
let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, -keyboardSize!.height, 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
self.view.endEditing(true)
self.scrollView.isScrollEnabled = false
}
func textFieldDidBeginEditing(_ textField: UITextField)
{
yourEmail = textField
}
func textFieldDidEndEditing(_ textField: UITextField)
{
yourEmail = nil
}
}
Try this code
func textFieldDidBeginEditing(textField: UITextField!)
{
self.scrollView.setContentOffset(CGPoint.init(x: 0, y: scrollBy), animated: true)
// scrollBy - pass the height you want your scrollview to be scrolled when keyboard appears
}
func textFieldDidEndEditing(textField: UITextField!)
{
self.scrollView.setContentOffset(CGPoint.init(x: 0, y: 0), animated: true)
self.view.endEditing(true);
}
below is the code I wrote with the help of suggestions:
{
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField.frame.maxY > self.view.frame.height * 0.6
{
self.scrollView.setContentOffset(CGPoint.init(x: 0, y: textField.frame.maxY - self.view.frame.height * 0.6 + 2.0), animated: true)
}
else{
return
}
}
func textFieldDidEndEditing(_ textField: UITextField)
{
self.scrollView.setContentOffset(CGPoint.init(x: 0, y: 0), animated: true)
self.view.endEditing(true);
}
}

UIScrollView scroll up on Keyboard

Problem Statement : I have Nib file of UIView contains UIScrollview, In scrollview I have several TextFields and a TextView at the Bottom. What I want is to scroll upward when textfield or Textview starts editing.
What I tried :
In custome Method
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
I am calling this method parent view.
Notification Handling:
func keyboardWasShown(notification: NSNotification)
{
var userInfo = notification.userInfo!
var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
keyboardFrame = self.convert(keyboardFrame, from: nil)
var contentInset:UIEdgeInsets = self.mainScroll.contentInset
contentInset.bottom = keyboardFrame.size.height
self.mainScroll.contentInset = contentInset
}
This is working perfectly for UITextFields, But not working UITextView. Any idea where is the mistake.
PS: I have set the Delegates of UITextField and UITextView as well.
Any help will be much appreciated.
Replace keyboardWasShown function with the below function :
func keyboardWasShown(notification: NSNotification)
{
var userInfo = notification.userInfo!
var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
keyboardFrame = self.convert(keyboardFrame, from: nil)
self.mainScroll.contentOffset = CGPoint(x: 0, y: keyboardFrame.size.height - Any number that fits your need.)
}
In keyBoardWillHide :
self.mainScroll.contentOffset = CGPoint(x: 0, y: 0)
Hope it will help.
Happy Coding!
Demo link : https://github.com/harshilkotecha/UIScrollViewWhenKeyboardAppearInSwift3
when you have multiple textview it is so difficult so best solution ->
step 1 : add UITextFieldDelegate
class ScrollViewController: UIViewController,UITextFieldDelegate {
step 2 :create new IBOutlet but don't connect with any text field
// get current text box when user Begin editing
#IBOutlet weak var activeTextField: UITextField?
step 3 : write this two method when user focus on text filed object pass the reference and store in activeTextField
// get current text field
func textFieldDidBeginEditing(_ textField: UITextField)
{
activeTextField=textField;
}
func textFieldDidEndEditing(_ textField: UITextField)
{
activeTextField=nil;
}
step 5 : set Notification in viewdidload setNotificationKeyboard
override func viewWillAppear(_ animated: Bool) {
// call method for keyboard notification
self.setNotificationKeyboard()
}
// Notification when keyboard show
func setNotificationKeyboard () {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(notification:)), name: .UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(notification:)), name: .UIKeyboardWillHide, object: nil)
}
step 6 : two methods for hide and show keyboard
func keyboardWasShown(notification: NSNotification)
{
var info = notification.userInfo!
let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size
let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize!.height+10, 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
var aRect : CGRect = self.view.frame
aRect.size.height -= keyboardSize!.height
if let activeField = self.activeTextField
{
if (!aRect.contains(activeField.frame.origin))
{
self.scrollView.scrollRectToVisible(activeField.frame, animated: true)
}
}
}
// when keyboard hide reduce height of scroll view
func keyboardWillBeHidden(notification: NSNotification){
let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0,0.0, 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
self.view.endEditing(true)
}
Swift 5
NotificationCenter.default.addObserver(self, selector: #selector(keyboardNotification), name: UIResponder.keyboardDidChangeFrameNotification, object: nil)
#objc func keyboardNotification(_ notification: Notification) {
if let userInfo = (notification as NSNotification).userInfo {
let endFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
let duration:TimeInterval = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIView.AnimationOptions().rawValue
let animationCurve:UIView.AnimationOptions = UIView.AnimationOptions(rawValue: animationCurveRaw)
if (endFrame?.origin.y)! >= UIScreen.main.bounds.size.height {
scrollViewBottomConstraint?.constant = 0
} else {
if tabBarController?.tabBar.frame == nil {
return
}
scrollViewBottomConstraint?.constant = endFrame!.size.height - (tabBarController?.tabBar.frame.height)!
let bottomOffset = CGPoint(x: 0, y: 0)
scrollView.setContentOffset(bottomOffset, animated: true)
}
UIView.animate(withDuration: duration, delay: 0, options: animationCurve, animations: { self.view.layoutIfNeeded() }, completion: nil)
}
}
usage:
1.connect the scrollView outlet to your controller and set its name to 'scrollView'
2.connect the scrollView bottom constraint to your controller & set its name to 'scrollViewBottomConstraint'
3.put the notification observer to your ViewDidLoad function.

ScrollView and keyboard in Swift

I started creating a simple iOS app that does some operations.
But I'm having some problems when the keyboard appears, hiding one of my textfields.
I think it's a common problem and I did some research but I couldn't find anything that solved my problem.
I want to use a ScrollView rather than animate the textfield to make it visible.
In ViewDidLoad, register the notifications:
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name:UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name:UIResponder.keyboardWillHideNotification, object: nil)
Add below observer methods which does the automatic scrolling when keyboard appears.
#objc func keyboardWillShow(notification:NSNotification) {
guard let userInfo = notification.userInfo else { return }
var keyboardFrame:CGRect = (userInfo[UIResponder.keyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
keyboardFrame = self.view.convert(keyboardFrame, from: nil)
var contentInset:UIEdgeInsets = self.scrollView.contentInset
contentInset.bottom = keyboardFrame.size.height + 20
scrollView.contentInset = contentInset
}
#objc func keyboardWillHide(notification:NSNotification) {
let contentInset:UIEdgeInsets = UIEdgeInsets.zero
scrollView.contentInset = contentInset
}
The top answer for swift 3:
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)
And then:
func keyboardWillShow(notification:NSNotification){
//give room at the bottom of the scroll view, so it doesn't cover up anything the user needs to tap
var userInfo = notification.userInfo!
var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
keyboardFrame = self.view.convert(keyboardFrame, from: nil)
var contentInset:UIEdgeInsets = self.theScrollView.contentInset
contentInset.bottom = keyboardFrame.size.height
theScrollView.contentInset = contentInset
}
func keyboardWillHide(notification:NSNotification){
let contentInset:UIEdgeInsets = UIEdgeInsets.zero
theScrollView.contentInset = contentInset
}
Here is a complete solution, utilizing guard and concise code. Plus correct code in keyboardWillHide to only reset the bottom to 0.
#IBOutlet private weak var scrollView: UIScrollView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
registerNotifications()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
scrollView.contentInset.bottom = 0
}
private func registerNotifications() {
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){
guard let keyboardFrame = notification.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
scrollView.contentInset.bottom = view.convert(keyboardFrame.cgRectValue, from: nil).size.height
}
#objc private func keyboardWillHide(notification: NSNotification){
scrollView.contentInset.bottom = 0
}
for Swift 4.0
In ViewDidLoad
// setup keyboard event
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
Add below observer methods which does the automatic scrolling when keyboard appears.
#objc func keyboardWillShow(notification:NSNotification){
var userInfo = notification.userInfo!
var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
keyboardFrame = self.view.convert(keyboardFrame, from: nil)
var contentInset:UIEdgeInsets = self.ui_scrollView.contentInset
contentInset.bottom = keyboardFrame.size.height
ui_scrollView.contentInset = contentInset
}
#objc func keyboardWillHide(notification:NSNotification){
let contentInset:UIEdgeInsets = UIEdgeInsets.zero
ui_scrollView.contentInset = contentInset
}
contentInset doesn't work for me, because I want the scrollview move all the way up above the keyboard. So I use contentOffset:
func keyboardWillShow(notification:NSNotification) {
guard let keyboardFrameValue = notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue else {
return
}
let keyboardFrame = view.convert(keyboardFrameValue.cgRectValue, from: nil)
scrollView.contentOffset = CGPoint(x:0, y:keyboardFrame.size.height)
}
func keyboardWillHide(notification:NSNotification) {
scrollView.contentOffset = .zero
}
Swift 5 Only adjust ScrollView when TextField is hidden by keyboard (for multiple TextFields)
Add / Remove Observers:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
Keep track of these values so you can return to your original position:
var scrollOffset : CGFloat = 0
var distance : CGFloat = 0
Adjust ScrollView contentOffset depending on TextField Location:
#objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
var safeArea = self.view.frame
safeArea.size.height += scrollView.contentOffset.y
safeArea.size.height -= keyboardSize.height + (UIScreen.main.bounds.height*0.04) // Adjust buffer to your liking
// determine which UIView was selected and if it is covered by keyboard
let activeField: UIView? = [textFieldA, textViewB, textFieldC].first { $0.isFirstResponder }
if let activeField = activeField {
if safeArea.contains(CGPoint(x: 0, y: activeField.frame.maxY)) {
print("No need to Scroll")
return
} else {
distance = activeField.frame.maxY - safeArea.size.height
scrollOffset = scrollView.contentOffset.y
self.scrollView.setContentOffset(CGPoint(x: 0, y: scrollOffset + distance), animated: true)
}
}
// prevent scrolling while typing
scrollView.isScrollEnabled = false
}
}
#objc func keyboardWillHide(notification: NSNotification) {
if distance == 0 {
return
}
// return to origin scrollOffset
self.scrollView.setContentOffset(CGPoint(x: 0, y: scrollOffset), animated: true)
scrollOffset = 0
distance = 0
scrollView.isScrollEnabled = true
}
Make sure to use [UIResponder.keyboardFrameEndUserInfoKey] to get the proper keyboard height the first time.
From the answer by Sudheer Palchuri, converted for Swift 4.
In ViewDidLoad, register the notifications:
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)
And then:
// MARK: - Keyboard Delegates
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
#objc func keyboardWillShow(notification:NSNotification){
var userInfo = notification.userInfo!
var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
keyboardFrame = self.view.convert(keyboardFrame, from: nil)
var contentInset:UIEdgeInsets = self.scrollView.contentInset
contentInset.bottom = keyboardFrame.size.height
self.scrollView.contentInset = contentInset
}
#objc func keyboardWillHide(notification:NSNotification){
let contentInset:UIEdgeInsets = UIEdgeInsets.zero
self.scrollView.contentInset = contentInset
}
Reading the links you sent to me, I found a way to make it work, thanks!:
func textFieldDidBeginEditing(textField: UITextField) {
if (textField == //your_field) {
scrollView.setContentOffset(CGPointMake(0, field_extra.center.y-280), animated: true)
callAnimation()
viewDidLayoutSubviews()
}
}
func textFieldDidEndEditing(textField: UITextField) {
if (textField == //your_field){
scrollView .setContentOffset(CGPointMake(0, 0), animated: true)
viewDidLayoutSubviews()
}
}
This is a refinement on Zachary Probst solution posted above.
I ran into a few issues with his solution and fixed it and enhanced it a bit.
This version does not need to pass in a list of UITextView controls. It finds the first responder in the current view. It also handles UITextView controls at any level in the View hierarchy.
I think his safeArea calculation wasn't quite right scrollView.contentOffset.y needed sign changed. It didn't show up if it was not scrolled. This fixed incremental scrolling. It might have been from other changes I made.
This works if the user jumps around to other UITextViews while the keyboard is up.
This is a Base Class I use for a bunch of ViewControllers. The inherited ViewController just needs to set the UIScrollViewer which activates this code behavior.
class ThemeAwareViewController: UIViewController
{
var scrollViewForKeyBoard: UIScrollView? = nil
var saveOffsetForKeyBoard: CGPoint?
func findViewThatIsFirstResponder(view: UIView) -> UIView?
{
if view.isFirstResponder {
return view
}
for subView in view.subviews {
if let hit = findViewThatIsFirstResponder(view: subView) {
return hit
}
}
return nil
}
#objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
if let scrollView = scrollViewForKeyBoard {
var safeArea = self.view.frame
safeArea.size.height -= scrollView.contentOffset.y
safeArea.size.height -= keyboardSize.height
safeArea.size.height -= view.safeAreaInsets.bottom
let activeField: UIView? = findViewThatIsFirstResponder(view: view)
if let activeField = activeField {
// This line had me stumped for a while (I was passing in .frame)
let activeFrameInView = view.convert(activeField.bounds, from: activeField)
let distance = activeFrameInView.maxY - safeArea.size.height
if saveOffsetForKeyBoard == nil {
saveOffsetForKeyBoard = scrollView.contentOffset
}
scrollView.setContentOffset(CGPoint(x: 0, y: distance), animated: true)
}
}
}
}
#objc func keyboardWillHide(notification: NSNotification) {
guard let restoreOffset = saveOffsetForKeyBoard else {
return
}
if let scrollView = scrollViewForKeyBoard {
scrollView.setContentOffset(restoreOffset, animated: true)
self.saveOffsetForKeyBoard = nil
}
}
}
You can animate your scrollview to center on your UITextField on keyboard appearance (ie. making your textfield the first responder) via a scroll offset. Here are a couple of good resources to get you started (there are a bunch on this site):
How programmatically move a UIScrollView to focus in a control above keyboard?
How to make a UIScrollView auto scroll when a UITextField becomes a first responder
Additionally, if you simply use a UITableView with your content in cells, when the textfield becomes first responder, the UITableViewController will automatically scroll to the textfield cell for you (though I'm not sure this is what you want to do).
An answer for Swift 3, based on the one proposed by Daniel Jones, but safer (thanks to the guard), more concise and with consistent scroll indicator insets:
#objc private func keyboardWillBeShown(notification: NSNotification) {
guard let keyboardFrameValue = notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue else { return }
let keyboardFrame = view.convert(keyboardFrameValue.cgRectValue, from: nil)
scrollView.contentInset.bottom = keyboardFrame.size.height
scrollView.scrollIndicatorInsets = scrollView.contentInset
}
#objc private func keyboardWillBeHidden() {
scrollView.contentInset = .zero
scrollView.scrollIndicatorInsets = scrollView.contentInset
}
In Swift4, just add the following extension.
extension UIViewController {
func setupViewResizerOnKeyboardShown() {
NotificationCenter.default.addObserver(self,
selector: #selector(self.keyboardWillShowForResizing),
name:
Notification.Name.UIKeyboardWillShow,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(self.keyboardWillHideForResizing),
name: Notification.Name.UIKeyboardWillHide,
object: nil)
}
#objc func keyboardWillShowForResizing(notification: Notification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue,
let window = self.view.window?.frame {
// We're not just minusing the kb height from the view height because
// the view could already have been resized for the keyboard before
self.view.frame = CGRect(x: self.view.frame.origin.x,
y: self.view.frame.origin.y,
width: self.view.frame.width,
height: window.origin.y + window.height - keyboardSize.height)
} else {
debugPrint("We're showing the keyboard and either the keyboard size or window is nil: panic widely.")
}
}
#objc func keyboardWillHideForResizing(notification: Notification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
let viewHeight = self.view.frame.height
self.view.frame = CGRect(x: self.view.frame.origin.x,
y: self.view.frame.origin.y,
width: self.view.frame.width,
height: viewHeight + keyboardSize.height)
} else {
debugPrint("We're about to hide the keyboard and the keyboard size is nil. Now is the rapture.")
}
}
}
There is a simple solution here
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
func keyboardWillShow(_ notification:Notification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardSize.height, 0)
}
}
func keyboardWillHide(_ notification:Notification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
}
}
In case anyone is looking for Objective-C code for this solution:
- (void)keyboardWasShown:(NSNotification *)notification {
NSDictionary* info = [notification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
UIEdgeInsets contentInsets = baseScrollView.contentInset;
contentInsets.bottom = kbSize.height;
baseScrollView.contentInset = contentInsets;
}
- (void)keyboardWillBeHidden:(NSNotification *)notification {
UIEdgeInsets contentInsets = UIEdgeInsetsZero;
baseScrollView.contentInset = contentInsets;
[baseScrollView endEditing:YES];
}
In my situation it was
ScrollView
-->
TableView
-->
TableViewCell
So I had to get y position in relative to keyboard frame and check if keyboard y position and my active field y position was intersecting or not
#objc func keyboardWillShow(_ notification: Foundation.Notification) {
var userInfo = notification.userInfo!
var keyboardFrame:CGRect = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
keyboardFrame = self.view.convert(keyboardFrame, from: nil)
var contentInset:UIEdgeInsets = self.scrollView!.contentInset
contentInset.bottom = keyboardFrame.size.height
let loc = self.activeTextField?.convert(activeTextField!.bounds, to: self.view)
if keyboardFrame.origin.y < loc!.origin.y {
self.scrollView?.contentOffset = CGPoint.init(x: (self.scrollView?.contentOffset.x)!, y: loc!.origin.y)
}
if self.scrollView?.contentInset.bottom == 0 {
self.scrollView?.contentInset = contentInset
}
}

How to make UITextField move up when keyboard is present?

How do I prevent a UITextField from being hidden by the keyboard?
I assume this is happening on a UIViewController. If so, you can setup the following two functions to be called when the keyboard will show/hide, and respond appropriately in their blocks.
Setting up the UIViewController:
class ViewController: UIViewController, UITextFieldDelegate... {
var frameView: UIView!
First, in viewDidLoad():
override func viewDidLoad() {
self.frameView = UIView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height))
// Keyboard stuff.
let center: NotificationCenter = NotificationCenter.default
center.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
center.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
Then implement the following two functions to respond to your NotificationCenter functions defined in viewDidLoad() above. I give you an example of moving the entire view, but you can also animate just the UITextFields.
#objc func keyboardWillShow(notification: NSNotification) {
let info:NSDictionary = notification.userInfo! as NSDictionary
let keyboardSize = (info[UIResponder.keyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
let keyboardHeight: CGFloat = keyboardSize.height
let _: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
UIView.animate(withDuration: 0.25, delay: 0.25, options: .curveEaseInOut, animations: {
self.frameView.frame = CGRect(x: 0, y: (self.frameView.frame.origin.y - keyboardHeight), width: self.view.bounds.width, height: self.view.bounds.height)
}, completion: nil)
}
#objc func keyboardWillHide(notification: NSNotification) {
let info: NSDictionary = notification.userInfo! as NSDictionary
let keyboardSize = (info[UIResponder.keyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
let keyboardHeight: CGFloat = keyboardSize.height
let _: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
UIView.animate(withDuration: 0.25, delay: 0.25, options: .curveEaseInOut, animations: {
self.frameView.frame = CGRect(x: 0, y: (self.frameView.frame.origin.y + keyboardHeight), width: self.view.bounds.width, height: self.view.bounds.height)
}, completion: nil)
}
Don't forget to remove the notifications when leaving your view
override func viewWillDisappear(_ animated: Bool) {
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
}
Here is the simple solution in Swift. I translated some Objective-C code that worked for me in the past.
func textFieldDidBeginEditing(textField: UITextField) { // became first responder
//move textfields up
let myScreenRect: CGRect = UIScreen.mainScreen().bounds
let keyboardHeight : CGFloat = 216
UIView.beginAnimations( "animateView", context: nil)
var movementDuration:NSTimeInterval = 0.35
var needToMove: CGFloat = 0
var frame : CGRect = self.view.frame
if (textField.frame.origin.y + textField.frame.size.height + /*self.navigationController.navigationBar.frame.size.height + */UIApplication.sharedApplication().statusBarFrame.size.height > (myScreenRect.size.height - keyboardHeight)) {
needToMove = (textField.frame.origin.y + textField.frame.size.height + /*self.navigationController.navigationBar.frame.size.height +*/ UIApplication.sharedApplication().statusBarFrame.size.height) - (myScreenRect.size.height - keyboardHeight);
}
frame.origin.y = -needToMove
self.view.frame = frame
UIView.commitAnimations()
}
func textFieldDidEndEditing(textField: UITextField) {
//move textfields back down
UIView.beginAnimations( "animateView", context: nil)
var movementDuration:NSTimeInterval = 0.35
var frame : CGRect = self.view.frame
frame.origin.y = 0
self.view.frame = frame
UIView.commitAnimations()
}
Swift 4 code, It is very simple instead of using many things like NSNotificationCenter, then calculating the height of everything and making conditions makes this more complicated,
The Simple way to do this is coded below, it will work to move up the view.
func textFieldDidBeginEditing(_ textField: UITextField) {
moveTextField(textfield: textField, moveDistance: -250, up: true)
}
func textFieldDidEndEditing(_ textField: UITextField) {
moveTextField(textfield: textField, moveDistance: -250, up: false)
}
func moveTextField(textfield: UITextField, moveDistance: Int, up: Bool) {
let moveDuration = 0.3
let movement: CGFloat = CGFloat(up ? moveDistance: -moveDistance)
UIView.beginAnimations("animateTextField", context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(moveDuration)
self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)
UIView.commitAnimations()
}
you can change that -250 value according to the placement of your textfields.
In case you are using a UIScrollView or any of its subclasses, e.g., UITableView, you can also manipulate the contentInset property. That way you do not have to mess with frame, bounds, NSLayoutConstraint or NSLayoutAnchor.
func keyboardWillShow(notification: Notification) {
let info = notification.userInfo!
let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
let keyboardHeight: CGFloat = keyboardSize.height
let duration = info[UIKeyboardAnimationDurationUserInfoKey] as! TimeInterval
UIView.animate(withDuration: duration, delay: 0, options: UIViewAnimationOptions.curveEaseInOut, animations: {
self.tableView?.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)
}, completion: nil)
}
func keyboardWillHide(notification: Notification) {
let info = notification.userInfo!
let duration = info[UIKeyboardAnimationDurationUserInfoKey] as! TimeInterval
UIView.animate(withDuration: duration, delay: 0, options: UIViewAnimationOptions.curveEaseInOut, animations: {
self.tableView?.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}, completion: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
Swift 3.0
var activeField: UITextField?
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(ProfileViewController.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(ProfileViewController.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
func textFieldDidBeginEditing(_ textField: UITextField){
activeField = textField
}
func textFieldDidEndEditing(_ textField: UITextField){
activeField = nil
}
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if (self.activeField?.frame.origin.y)! >= keyboardSize.height {
self.view.frame.origin.y = keyboardSize.height - (self.activeField?.frame.origin.y)!
} else {
self.view.frame.origin.y = 0
}
}
}
func keyboardWillHide(notification: NSNotification) {
self.view.frame.origin.y = 0
}
In Swift 3 use this code
override func viewDidLoad() {
super.viewDidLoad()
let center: NotificationCenter = NotificationCenter.default
center.addObserver(self, selector: #selector(RFLogInViewController.keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
center.addObserver(self, selector: #selector(RFLogInViewController.keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
func keyboardWillShow(notification: NSNotification) {
let info:NSDictionary = notification.userInfo! as NSDictionary
let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
let keyboardHeight: CGFloat = keyboardSize.height
let _: CGFloat = info[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
UIView.animate(withDuration: 0.25, delay: 0.25, options: .curveEaseInOut, animations: {
self.view.frame = CGRect(x: 0, y: (self.view.frame.origin.y - keyboardHeight), width: self.view.bounds.width, height: self.view.bounds.height)
}, completion: nil)
}
func keyboardWillHide(notification: NSNotification) {
let info: NSDictionary = notification.userInfo! as NSDictionary
let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
let keyboardHeight: CGFloat = keyboardSize.height
let _: CGFloat = info[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
UIView.animate(withDuration: 0.25, delay: 0.25, options: .curveEaseInOut, animations: {
self.view.frame = CGRect(x: 0, y: (self.view.frame.origin.y + keyboardHeight), width: self.view.bounds.width, height: self.view.bounds.height)
}, completion: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
Just add 'IQKeyboardManager' library into your project, and Done. You have not to do anything else. For reference please check this url.
https://github.com/hackiftekhar/IQKeyboardManager
Swift 4
I have seen numerous of answer and plenty of them did not work for me where I have a UIViewController With numerous of text fields.
According to the Apple documentation I have translate the example to Swift 4
Your content needs to be embedded within an scrollview.
Add the notification listeners for when the keyboard will appear or dissappear.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow), name: .UIKeyboardDidShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: .UIKeyboardWillHide, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
Implement UITextField Delegates
func textFieldDidBeginEditing(_ textField: UITextField) {
currentTextField = textField
}
func textFieldDidEndEditing(_ textField: UITextField, reason: UITextFieldDidEndEditingReason) {
currentTextField = nil
}
Selectors
#objc func keyboardDidShow(notification: NSNotification) {
print("\(logClassName): keyboardWDidShow")
let keyboardFrame = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let keyboardSize:CGSize = keyboardFrame!.size
let contentInsets:UIEdgeInsets = UIEdgeInsetsMake(0, 0, keyboardSize.height, 0)
trackScrollView.contentInset = contentInsets
trackScrollView.scrollIndicatorInsets = contentInsets
var aRect:CGRect = self.view.frame
aRect.size.height -= keyboardSize.height
if !(aRect.contains(currentTextField!.frame.origin)){
trackScrollView.scrollRectToVisible(currentTextField!.frame, animated: true)
}
}
#objc func keyboardWillHide(notification: NSNotification){
print("\(logClassName): keyboardWillHide")
let contentInsents:UIEdgeInsets = UIEdgeInsets.zero
trackScrollView.contentInset = contentInsents
trackScrollView.scrollIndicatorInsets = contentInsents
}
first of all you should have scrollview
step 1:find the height of key board
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo ? [UIKeyboardFrameBeginUserInfoKey] as ? NSValue) ? .cgRectValue {
let keyboardHeight = keyboardSize.height
a = keyboardHeight
}
}
step 2: bind the delegete methods of textfield
func textFieldDidBeginEditing(_ textField: UITextField) {
utility.setUserDefaultBool(value: false, key: "FrameMoveFlag") //flag
let b = view1.frame.height - textField.frame.origin.y
if (b < 350) {
view1.frame.origin.y = view1.frame.origin.y + (view1.frame.height - textField.frame.origin.y) - (a + 50)
utility.setUserDefaultBool(value: true, key: "FrameMoveFlag") //flag
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
if (utility.getUserDefaultBOOLForKey(key: "FrameMoveFlag") == true) {
view1.frame.origin.y = 0
}
}
Swift 3 code for Geiger answer
func textFieldDidBeginEditing(_ textField: UITextField) { // became first responder
//move textfields up
let myScreenRect: CGRect = UIScreen.main.bounds
let keyboardHeight : CGFloat = 216
UIView.beginAnimations( "animateView", context: nil)
var movementDuration:TimeInterval = 0.35
var needToMove: CGFloat = 0
var frame : CGRect = self.view.frame
if (textField.frame.origin.y + textField.frame.size.height + UIApplication.shared.statusBarFrame.size.height > (myScreenRect.size.height - keyboardHeight - 30)) {
needToMove = (textField.frame.origin.y + textField.frame.size.height + UIApplication.shared.statusBarFrame.size.height) - (myScreenRect.size.height - keyboardHeight - 30);
}
frame.origin.y = -needToMove
self.view.frame = frame
UIView.commitAnimations()
}
func textFieldDidEndEditing(_ textField: UITextField) {
//move textfields back down
UIView.beginAnimations( "animateView", context: nil)
var movementDuration:TimeInterval = 0.35
var frame : CGRect = self.view.frame
frame.origin.y = 0
self.view.frame = frame
UIView.commitAnimations()
}
If you don't want to manually work with appearing and disappearing of the keyboard, just use the UITableViewController and it will handle all text fields in the table view.
Check out my gist, I use a scrollView for my case, but it works for any kind of view, you only have to remove the scrollView part and replace it with your view.
The gist is very well commented so you will also understand how this case is handled.
https://gist.github.com/Sjahriyar/916e93153a29dc602b45f29d39182352
I created KeyboardController to handle the keyboard issue. All that needs to be done is call setUpKeyBoardListeners() and set the lastElement as whatever the last element in your view is.
Gist: https://gist.github.com/espitia/ef830cf677fa1bc33ffdf16ac12d0204

Resources