Moving Keyboard When Editing Multiple Textfields with Constraints Swift

Moving keyboard when editing multiple textfields with constraints swift

You must use scrollview for your uiview controller and then you can use notification for adjusting your uiview constraint.

Your UIViewController hierarchy should be like this

ContainerView --> ScrollView --> Content View --> Now Your View.

I'm suggesting you to use pod 'TPKeyboardAvoiding' for future animations in scrollview since for every controller you don't want to put notifications to shifting your UIView.

Here is link demo for your UIView shifting https://github.com/Vasu05/ScrollViewExample

Move textfield when keyboard appears swift

There are a couple of improvements to be made on the existing answers.

Firstly the UIKeyboardWillChangeFrameNotification is probably the best notification as it handles changes that aren't just show/hide but changes due to keyboard changes (language, using 3rd party keyboards etc.) and rotations too (but note comment below indicating the keyboard will hide should also be handled to support hardware keyboard connection).

Secondly the animation parameters can be pulled from the notification to ensure that animations are properly together.

There are probably options to clean up this code a bit more especially if you are comfortable with force unwrapping the dictionary code.

 class MyViewController: UIViewController {

// This constraint ties an element at zero points from the bottom layout guide
@IBOutlet var keyboardHeightLayoutConstraint: NSLayoutConstraint?

override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self,
selector: #selector(self.keyboardNotification(notification:)),
name: UIResponder.keyboardWillChangeFrameNotification,
object: nil)
}

deinit {
NotificationCenter.default.removeObserver(self)
}

@objc func keyboardNotification(notification: NSNotification) {
guard let userInfo = notification.userInfo else { return }

let endFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
let endFrameY = endFrame?.origin.y ?? 0
let duration:TimeInterval = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIView.AnimationOptions.curveEaseInOut.rawValue
let animationCurve:UIView.AnimationOptions = UIView.AnimationOptions(rawValue: animationCurveRaw)

if endFrameY >= UIScreen.main.bounds.size.height {
self.keyboardHeightLayoutConstraint?.constant = 0.0
} else {
self.keyboardHeightLayoutConstraint?.constant = endFrame?.size.height ?? 0.0
}

UIView.animate(
withDuration: duration,
delay: TimeInterval(0),
options: animationCurve,
animations: { self.view.layoutIfNeeded() },
completion: nil)
}
}

iOS Swift - Multiple UITextFields moving UIView

Since you are using Auto Layout with constraints on your view it automatically gets reset back to the original position. So instead of changing the view position if you change the value of the constraint this should work.

SWIFT: UIView which moves up with keyboard, moves very strangely after initial move, doesn't come back down

You are doing too many things wrong for me to list, so I have just fixed your project and made a pull request. Merge the pull request into your repo and you will see that it now works fine.

Just for the record, here are some of the main things you were doing wrong:

  • You added a bottom constraint, in code, to the blue view. But you already had a bottom constraint on the blue view. Thus you now have two of them, and any change in one of them will cause a conflict. The Xcode console was telling you very clearly that this was happening, but you ignored what it told you.

  • You were changing the constraint constant but also changing the blue view center. That probably caused no harm but it was pointless. You cannot govern a view's position by its center if you are governing it with constraints; they are opposites.

  • In your show and hide methods you examined keyboardFrameBeginUserInfoKey. That's wrong. You want to examine keyboardFrameEndUserInfoKey. The question is not where the keyboard is now but where it will be when it finishes moving.

  • The animation is wrong. There is no need for a UIView animation; you are already in an animation block. Just call layoutIfNeeded and the animation will happen together with the movement of the keyboard.

  • Your entire way of speaking of and accessing constraints is wrong. You use an incorrect expression super.view (you probably meant self.view). But even more important, you attempt to access the desired constraint by saying self.constraints[2]. That sort of thing is fragile in the extreme. The correct approach is to keep a reference to the actual constraint (an instance property). In this situation, since the constraint already exists (in the storyboard), that reference can be an outlet.

So, with all that said, here's my rewrite of your code; this is the complete code needed:

class ViewController: UIViewController {

@IBOutlet weak var sampleTextField: UITextField!
@IBOutlet weak var bottomConstraint: NSLayoutConstraint!
var originalConstant: CGFloat = 0

override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIWindow.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIWindow.keyboardWillHideNotification, object: nil)
self.originalConstant = bottomConstraint.constant
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
sampleTextField.endEditing(true)
}
}

extension ViewController {
@objc func keyboardWillShow(notification: NSNotification) {
print("keyboardWillShow")
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
self.bottomConstraint.constant += keyboardSize.height + 5
self.view.layoutIfNeeded()
}
}

@objc func keyboardWillHide(notification: NSNotification){
print("keyboardWillHide")
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
self.bottomConstraint.constant = self.originalConstant
self.view.layoutIfNeeded()
}
}
}

With all of that said, the code is still wrong, because you are not taking account of the very real possibility that you will get a keyboardWillShow notification when the keyboard is already showing. However, I leave that for your later investigation.

Moving a textfield with auto layout constraints when the keyboard appears

Instead of adjusting theframe values, I think you should be creating @IBOutlet references to constraints in Interface Builder and then changing the constant value of those constraints when you want to animate them, followed by a call to layoutIfNeeded. As I understand it, manually changing the values of a view's frame and auto layout don't mix.

Also, I wouldn't mess around with setTranslatesAutoresizingMaskIntoConstraints unless you are adding your constraints programmatically, in which case you're most likely just setting it to false.

Move view with keyboard using Swift

Here is a solution, without handling the switch from one textField to another:

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?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue() {
self.view.frame.origin.y -= keyboardSize.height
}
}

func keyboardWillHide(notification: NSNotification) {
self.view.frame.origin.y = 0
}

To solve this, replace the two functions keyboardWillShow/Hide with these:

func keyboardWillShow(notification: NSNotification) {        
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue() {
if view.frame.origin.y == 0 {
self.view.frame.origin.y -= keyboardSize.height
}
}
}

func keyboardWillHide(notification: NSNotification) {
if view.frame.origin.y != 0 {
self.view.frame.origin.y = 0
}
}

Swift 3.0:

override func viewDidLoad() {
super.viewDidLoad()
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)
}

@objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y == 0 {
self.view.frame.origin.y -= keyboardSize.height
}
}
}

@objc func keyboardWillHide(notification: NSNotification) {
if self.view.frame.origin.y != 0 {
self.view.frame.origin.y = 0
}
}

Swift 4.0:

override func viewDidLoad() {
super.viewDidLoad()
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)
}

@objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y == 0 {
self.view.frame.origin.y -= keyboardSize.height
}
}
}

@objc func keyboardWillHide(notification: NSNotification) {
if self.view.frame.origin.y != 0 {
self.view.frame.origin.y = 0
}
}

Swift 4.2:

override func viewDidLoad() {
super.viewDidLoad()
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: 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
}
}
}

@objc func keyboardWillHide(notification: NSNotification) {
if self.view.frame.origin.y != 0 {
self.view.frame.origin.y = 0
}
}

How to move UITextFeild with keyboard in order to avoid blocking [SWIFT 5+ ]

You can use the following code:

First, add these two lines of code in your controller viewDidLoad() method:

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)

Second, add these two methods inside your controller:

@objc func keyboardWillShow(notification: NSNotification) {

if yourTextfield.isEditing == true
{
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
self.view.frame.origin.y -= keyboardSize.height
}
}
}

@objc func keyboardWillHide(notification: NSNotification) {
if self.view.frame.origin.y != 0 {
self.view.frame.origin.y = 64
}
}

Enjoy :).

How to make textFields stay in place when keyboard moves up? Swift

So I think you are close and I am not sure if it is a code issue or an autolayout issue. My guess is you are getting complaints about your scrollview not know the content size so I will cover both.

EDIT:
Also you button will have to be below the view container that I first add and will need to be handled separately from the scrollview. Do not put it in the scrollview.

The methods work below except add 50 or whatever to the layout bottom for the view that will hold the scrollview. Also the method below will help make edits

Autolayout:
First for forms that will only take up on page I like to start by adding a view to the storyboard and pin in top layout guide, (whatever space you need for button), left and right. Then I add my ScrollView(pin the scrollview to that view) to the view just added.Then next I add my content view to the scrollview. Now I pin this to the scrollview. You will see that autolayout is still not happy. So why the first view and how to fix this. I drag from the contentView to the view holding the scrollview and choose equal heights and equal widths. Now you will not have auto layout screaming at you. Note:This works for content you want to fill the first view size but allow it to scroll to avoid the keyboard. See images

Complaining

Adding Equal Heights

After adding this equal heights I can continue with the storyboard. I set the textfields up. The bottom textfield you may or may not want to pin it to the bottom but if you do make it >= yourNumber.

EDIT:
Now add your NEXT Button to the storyboard below the view that is holding everything. The button has to be pinned to the bottom of the main view with a 0 to stick to the keyboard. It will now look like this.

ButtonAdded

Obviously this conflicts slightly with the initial images but all you have to do is increase the space to the bottom layout guide just make sure your button is added to the main view and not the view holding the scrollview. Now connect your button to the controller in an iboutlet. we will need it.

Next make sure you have the right keyboard in your simulator. **Not using hardware keyboard
HardwareKeyboard

Finally the code. some of it you would need to substitute your textfield variables as I looped through the subviews to set the delegate. I also added padding to the scroll up. You should move your deRegister to deint(). See my code and finally you might want to scroll the scroll view on keyboard will appear instead of did appear but I did not alter this.

import UIKit

class ViewController: UIViewController,UITextFieldDelegate {

//added in storyboard. removed the code
@IBOutlet weak var nextButton: UIButton!

@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var contentView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
//called whenever keyboard is shown/hidden

registerForKeyboardNotifications()

//when identifies single or multiple taps, call DismissKeyboard
var tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "DismissKeyboard")
contentView.addGestureRecognizer(tap)

//disable scroll bouncing
scrollView.bounces = false

//replace with your textfields
for subs in self.contentView.subviews{
if subs is UITextField{
print("setting")
(subs as! UITextField).delegate = self
}
}
}

//Call this function when the tap is recognized.
func DismissKeyboard(){
contentView.endEditing(true)
}

// Stop Editing on Return Key Tap.
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}

//edited for next button
weak var activeField: UITextField?
func keyboardDidShow(_ notification: Notification) {

//when a textfield is edited lift the button above the keyboard
if let activeField = self.activeField,let keyboardSize =
(notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as?
NSValue)?.cgRectValue {

//20 in insets and offset is just padding
let contentInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom:
keyboardSize.height + 20 + nextButton.bounds.height, right: 0.0)
self.scrollView.contentInset = contentInsets

var aRect = self.view.frame
aRect.size.height -= keyboardSize.height

let bottomPoint = CGPoint(x: activeField.frame.origin.x, y:activeField.frame.origin.y)

if aRect.contains(bottomPoint){
let scrollPoint = CGPoint(x: 0.0, y: bottomPoint.y - keyboardSize.height - 20 - nextButton.bounds.height)
scrollView.setContentOffset(scrollPoint, animated: true)
}

}

}
func keyboardWillHide(_ notification: Notification) {

let contentInsets = UIEdgeInsets.zero
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
}

//Keep track of which textfield is being edited to make sure the field is visible when keyboard pops up
func textFieldDidBeginEditing(_ textField: UITextField) {
self.activeField = textField
}

func textFieldDidEndEditing(_ textField: UITextField, reason: UITextFieldDidEndEditingReason) {
self.activeField = nil
}

//register for keyboard notifications
func registerForKeyboardNotifications() {

NotificationCenter.default.addObserver(self, selector:
#selector(keyboardDidShow),
name: NSNotification.Name.UIKeyboardDidShow, object: nil)

NotificationCenter.default.addObserver(self, selector:
#selector(keyboardWillHide), name:
NSNotification.Name.UIKeyboardWillHide, object: nil)
}

//remove keyBoard observers
func deregisterFromKeyboardNotifications() {
NotificationCenter.default.removeObserver(self, name:
NSNotification.Name.UIKeyboardDidShow, object: nil)

NotificationCenter.default.removeObserver(self, name:
NSNotification.Name.UIKeyboardWillHide, object: nil)
}

deinit {
//deregister keyboard notifications
deregisterFromKeyboardNotifications()
}
} //end of class

Now one more step. We have to handle the move up of the button. Instead of just killing this controller and putting more handling in it you can subclass the bottomconstraint to handle it. (Just make sure not to add a top constraint to the bottom.) Here is a constraint to drop in your project.

import UIKit

class AvoidingConstraint: NSLayoutConstraint {

private var offset : CGFloat = 0
private var keyboardVisibleHeight : CGFloat = 0

override public func awakeFromNib() {
super.awakeFromNib()

offset = constant

NotificationCenter.default.addObserver(self, selector: #selector(AvoidingConstraint.keyboardWillShowNotification(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(AvoidingConstraint.keyboardWillHideNotification(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}

deinit {
NotificationCenter.default.removeObserver(self)
}

// MARK: Notification

func keyboardWillShowNotification(_ notification: Notification) {
if let userInfo = notification.userInfo {
if let frameValue = userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue {
let frame = frameValue.cgRectValue
keyboardVisibleHeight = frame.size.height
}

self.updateConstant()
switch (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber, userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber) {
case let (.some(duration), .some(curve)):

let options = UIViewAnimationOptions(rawValue: curve.uintValue)

UIView.animate(
withDuration: TimeInterval(duration.doubleValue),
delay: 0,
options: options,
animations: {
UIApplication.shared.keyWindow?.layoutIfNeeded()
return
}, completion: { finished in
})
default:

break
}

}

}

func keyboardWillHideNotification(_ notification: NSNotification) {
keyboardVisibleHeight = 0
self.updateConstant()

if let userInfo = notification.userInfo {

switch (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber, userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber) {
case let (.some(duration), .some(curve)):

let options = UIViewAnimationOptions(rawValue: curve.uintValue)

UIView.animate(
withDuration: TimeInterval(duration.doubleValue),
delay: 0,
options: options,
animations: {
UIApplication.shared.keyWindow?.layoutIfNeeded()
return
}, completion: { finished in
})
default:
break
}
}
}

func updateConstant() {
self.constant = offset + keyboardVisibleHeight
}

}

Add this to a file in your project. Then all you have to do is on the bottom constraint for your button in story board change it to this subclass. Just make sure there is no top constraint. The view holding the scrollview needs to have a bottom constraint to the main view and not the button with enough space for the button. Run the project and enjoy. See link for test project if this explanation is not enough. https://www.dropbox.com/s/ir5x324mvhhne64/ScrollView.zip?dl=0

Button



Related Topics



Leave a reply



Submit