Sliding UIView When Keyboard Appears in iOS - ios

I know there are a lot of similar questions on here about sliding a UIView when the keyboard appears on iOS. However, I seem to be having an issue with my implementation that I haven't seen mentioned. When my slide animation occurs, it doesn't start from the top of the screen as I would expect, but rather, starts from a lower origin and moves back up to the top of the screen.
Here's the class extension I've created to achieve this slide-up behavior.
public extension UIViewController {
private dynamic func keyboardWillShow(sender: NSNotification) {
if view.frame.origin.y == 0 {
slideViewVerticallyForKeyboardHeight(sender, directionUp: true)
}
}
private dynamic func keyboardWillHide(sender: NSNotification) {
if view.frame.origin.y < 0 {
slideViewVerticallyForKeyboardHeight(sender, directionUp: false)
}
}
private func slideViewVerticallyForKeyboardHeight(notification: NSNotification, directionUp: Bool) {
UIView.beginAnimations(nil, context: nil)
let keyboardHeight: CGFloat = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue().height ?? 0.0
let animationDuration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey]?.doubleValue ?? 0.0
let animationCurve: Int = notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey]?.integerValue ?? 0
UIView.setAnimationDuration(animationDuration)
UIView.setAnimationCurve(UIViewAnimationCurve(rawValue: animationCurve)!)
UIView.setAnimationBeginsFromCurrentState(true)
var newFrameRect = view.frame
if directionUp {
newFrameRect.origin.y -= keyboardHeight
newFrameRect.size.height += keyboardHeight
} else {
newFrameRect.origin.y += keyboardHeight
newFrameRect.size.height -= keyboardHeight
}
view.frame = newFrameRect
UIView.commitAnimations()
}
public func registerViewSlideOnKeyobard() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillHide:", name: UIKeyboardWillHideNotification, object: nil)
}
public func unregisterViewSlideOnKeyboard() {
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
}
}
I'm not sure what I'm missing. Looking at my view.frame value, I have a negative origin.y as I would expect. Does anyone see what I'm missing?
Edit: User aimak made the very sensible suggestion that I change to the (not deprecated) block-call version of animation. Interestingly, the animation is still broken, but in a different way. Here is the modified code and a screen capture of what happens with the more up-to-date animation call.
private func slideViewVerticallyForKeyboardHeight(notification: NSNotification, directionUp: Bool) {
UIView.beginAnimations(nil, context: nil)
let keyboardHeight: CGFloat = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue().height ?? 0.0
let animationDuration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey]?.doubleValue ?? 0.0
let animationCurve: UIViewAnimationCurve = UIViewAnimationCurve(rawValue: notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey]?.integerValue ?? 0)!
var newFrameRect = view.frame
if directionUp {
newFrameRect.origin.y -= keyboardHeight
newFrameRect.size.height += keyboardHeight
} else {
newFrameRect.origin.y += keyboardHeight
newFrameRect.size.height -= keyboardHeight
}
UIView.animateWithDuration(animationDuration, delay: 0, options: animationCurve.toOptions(), animations: {
self.view.frame = newFrameRect
}, completion: nil)
}

This might not fully help, but those animation methods are discouraged since iOS 4+ :
Use of this method is discouraged in iOS 4.0 and later. You should use the block-based animation methods to specify your animations instead.
Reference : https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/#//apple_ref/occ/clm/UIView/commitAnimations
Have you tried with the block-based API ?
Edit : It was autolayout issue !
Ok, I think I have it : do you use autolayout ? If so, the subviews of UIViewController.view probably have constraints to view.top = 0, and that constraint will never be broken. If you want to achieve the animation you'd want, consider : 1. embed every subview inside a scrollView (then animate the contentOffet.y change) or 2. remove/edit your view.top constraint

The problem, as aimak pointed out, was that I was using AutoLayout and the constraints were overriding my modification of the frame. To solve this, I moved all my content into a scrollview. I now utilizing scrolling the scrollview to offset when the keyboard shows.
Here is the helper class I have to provide the scrolling behavior. Note that I pass in the scroll view now.
public class KeyboardScrollViewHelper {
private var scrollView: UIScrollView?
private var isScrolled = false
private dynamic func keyboardWillShow(sender: NSNotification) {
guard !isScrolled else {
return
}
slideViewVerticallyForKeyboardHeight(sender, directionUp: true)
}
private dynamic func keyboardWillHide(sender: NSNotification) {
guard isScrolled else {
return
}
slideViewVerticallyForKeyboardHeight(sender, directionUp: false)
}
private func slideViewVerticallyForKeyboardHeight(notification: NSNotification, directionUp: Bool) {
guard let scrollView = self.scrollView else {
return
}
let keyboardHeight: CGFloat = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue().height ?? 0.0
let animationDuration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey]?.doubleValue ?? 0.0
let animationCurve: UIViewAnimationCurve = UIViewAnimationCurve(rawValue: notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey]?.integerValue ?? 0)!
var scrollViewOffset: CGFloat = 0
if directionUp {
scrollViewOffset = scrollView.contentOffset.y + keyboardHeight
isScrolled = true
} else {
scrollViewOffset = scrollView.contentOffset.y - keyboardHeight
isScrolled = false
}
scrollViewOffset = max(0, scrollViewOffset)
UIView.animateWithDuration(animationDuration, delay: 0, options: animationCurve.toOptions(), animations: {
scrollView.contentOffset.y = scrollViewOffset
}, completion: nil)
}
public func registerViewSlideOnKeyobard(scrollView: UIScrollView) {
self.scrollView = scrollView
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillHide:", name: UIKeyboardWillHideNotification, object: nil)
}
public func unregisterViewSlideOnKeyboard() {
scrollView?.contentOffset = CGPointZero
scrollView = nil
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
}
}

Related

Autoscroll to the textView cursor which is placed inside the UITableViewCell is not working in iOS11

I have an autoscroll functionality of the tableView based on the UITextView cursor location inside the cell when editing.
It worked in the previous iOS versions. Starting from iOS11 it is broken.
I have set the tableView contentInset based on the keyboard height. For autoscrolling am using following code in textViewDidChange
if let confirmedTextViewCursorPosition = textView.selectedTextRange?.end {
let caretPosition = textView.caretRect(for: confirmedTextViewCursorPosition)
var textViewActualPosition = tableView.convert(caretPosition, from: textView.superview?.superview)
textViewActualPosition.origin.y += 22.0
tableView.scrollRectToVisible(textViewActualPosition, animated: false)
}
Askh1t is correct, here is my implementation:
var info = notification.userInfo!
var keyboardSize:CGRect = (info[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
if keyboardSize.size.height <= 0 { // to fix bug on iOS 11
keyboardSize = (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
}
as well as the full modular implementation that should work for you:
//MARK: - Properties
var activeTextView: UITextView?
//MARK: - Scroll View Notifications
// add in viewDidLoad
func registerForKeyboardNotifications(){
//Adding notifies on keyboard appearing
NotificationCenter.default.addObserver(forName: Notification.Name.UIKeyboardWillShow, object: nil, queue: nil, using: keyboardWasShown)
NotificationCenter.default.addObserver(forName: Notification.Name.UIKeyboardWillHide, object: nil, queue: nil, using: keyboardWillBeHidden)
}
//add in viewWillDisappear
func deregisterFromKeyboardNotifications(){
//Removing notifies on keyboard appearing
NotificationCenter.default.removeObserver(self, name: Notification.Name.UIKeyboardDidShow, object: nil)
NotificationCenter.default.removeObserver(self, name: Notification.Name.UIKeyboardWillHide, object: nil)
}
func keyboardWasShown(notification: Notification) -> Void {
//Need to calculate keyboard exact size due to Apple suggestions
self.tableView.isScrollEnabled = true
var info = notification.userInfo!
var keyboardSize:CGRect = (info[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
if keyboardSize.size.height <= 0 { // to fix bug on iOS 11
keyboardSize = (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
}
self.tableView.contentInset.bottom = keyboardSize.height //add this much
self.tableView.scrollIndicatorInsets.bottom = keyboardSize.height //scroll too it.
var aRect : CGRect = self.view.frame
aRect.size.height -= keyboardSize.height
if let activeField = self.activeTextView {
if (!aRect.contains(activeField.frame.origin)){
self.tableView.scrollRectToVisible(activeField.frame, animated: true)
}
}
}
func keyboardWillBeHidden(notification: Notification){
self.tableView.contentInset.bottom = 0
self.tableView.isScrollEnabled = true
self.tableView.alwaysBounceVertical = true
}
func textViewDidBeginEditing(_ textView: UITextView){
activeTextView = textView
}
func textViewDidEndEditing(_ textView: UITextView){
tableView.isScrollEnabled = true
activeTextView = nil
}

Detect keyboard height while UIScrollView is scrolled down and the keypad is being interactively dragged

I have a UIScrollView and a UITextView, just like in any messaging / chat app, whenUIScrollView is scrolled down, the keypad interactively being dragged too.
I need to detect keyboard height while UIScrollView is scrolled, I tried UIKeyboardWillChangeFrame observer, but this event is called after scroll tap is released.
Without knowing keyboard height, I am unable to update the UITextView bottom constraint, and I get a gap between the keypad and bottom view #screenshot.
Also attaching screenshot from Viber, that does align the bottom bar when keyboard being dragged from scroll bar, also can be seen in WhatsApp too.
As of iOS 10, Apple doesn't provide a NSNotification observer to detect the frame change while the keypad is dragged interactively by UIScrollView, UIKeyboardWillChangeFrame and UIKeyboardDidChangeFrame are observed only once releasing tap.
Anyways, after looking around DAKeyboardControl library, I had the idea to attach UIScrollView.UIPanGestureRecognizer in the UIViewController, so any gesture events that are produced will be handled in UIViewController as well. After screwing around several hours, I got it to work, here is all the code that is necessary for this:
class ViewController: UIViewController, UIGestureRecognizerDelegate {
fileprivate let collectionView = UICollectionView(frame: .zero)
private let bottomView = UIView()
fileprivate var bottomInset: NSLayoutConstraint!
// This holds height of keypad
private var maxKeypadHeight: CGFloat = 0 {
didSet {
self.updateCollectionViewInsets(maxKeypadHeight + self.bottomView.frame.height)
self.bottomInset.constant = -maxKeypadHeight
}
}
private var isListeningKeypadChange = false
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(keypadWillChange(_:)), name: .UIKeyboardWillChangeFrame, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keypadWillShow(_:)), name: .UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keypadWillHide(_:)), name: .UIKeyboardWillHide, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keypadDidHide), name: .UIKeyboardDidHide, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
func keypadWillShow(_ notification: Notification) {
guard !self.isListeningKeypadChange, let userInfo = notification.userInfo as? [String : Any],
let animationDuration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? TimeInterval,
let animationCurve = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? UInt,
let value = userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue
else {
return
}
self.maxKeypadHeight = value.cgRectValue.height
let options = UIViewAnimationOptions.beginFromCurrentState.union(UIViewAnimationOptions(rawValue: animationCurve))
UIView.animate(withDuration: animationDuration, delay: 0, options: options, animations: { [weak self] in
self?.view.layoutIfNeeded()
}, completion: { finished in
guard finished else { return }
// Some delay of about 500MS, before ready to listen other keypad events
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
self?.beginListeningKeypadChange()
}
})
}
func handlePanGestureRecognizer(_ pan: UIPanGestureRecognizer) {
guard self.isListeningKeypadChange, let windowHeight = self.view.window?.frame.height else { return }
let barHeight = self.bottomView.frame.height
let keypadHeight = abs(self.bottomInset.constant)
let usedHeight = keypadHeight + barHeight
let dragY = windowHeight - pan.location(in: self.view.window).y
let newValue = min(dragY < usedHeight ? max(dragY, 0) : dragY, self.maxKeypadHeight)
print("Old: \(keypadHeight) New: \(newValue) Drag: \(dragY) Used: \(usedHeight)")
guard keypadHeight != newValue else { return }
self.updateCollectionViewInsets(newValue + barHeight)
self.bottomInset.constant = -newValue
}
func keypadWillChange(_ notification: Notification) {
if self.isListeningKeypadChange, let value = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue {
self.maxKeypadHeight = value.cgRectValue.height
}
}
func keypadWillHide(_ notification: Notification) {
guard let userInfo = notification.userInfo as? [String : Any] else { return }
self.maxKeypadHeight = 0
var options = UIViewAnimationOptions.beginFromCurrentState
if let animationCurve = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? UInt {
options = options.union(UIViewAnimationOptions(rawValue: animationCurve))
}
let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? TimeInterval
UIView.animate(withDuration: duration ?? 0, delay: 0, options: options, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
func keypadDidHide() {
self.collectionView.panGestureRecognizer.removeTarget(self, action: nil)
self.isListeningKeypadChange = false
if (self.maxKeypadHeight != 0 || self.bottomInset.constant != 0) {
self.maxKeypadHeight = 0
}
}
private func beginListeningKeypadChange() {
self.isListeningKeypadChange = true
self.collectionView.panGestureRecognizer.addTarget(self, action: #selector(self.handlePanGestureRecognizer(_:)))
}
fileprivate func updateCollectionViewInsets(_ value: CGFloat) {
let insets = UIEdgeInsets(top: 0, left: 0, bottom: value + 8, right: 0)
self.collectionView.contentInset = insets
self.collectionView.scrollIndicatorInsets = insets
}
}
You can simply add this pod:
pod 'IQKeyboardManagerSwift'
Then in your AppDelegate.swift add:
import IQKeyboardManagerSwift
And a in the didFinishLaunchingWithOptions function
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
IQKeyboardManager.sharedManager().enable = true // ADD THIS !!!
return true
}
That this simple.
It seems like you have a wrong constant of bottom constraint.
Try to reset bottom constraint everytime and set new height value
func keyboardDidChangeFrame(notification: Notification) {
if let userInfo = notification.userInfo {
if 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 {
self.inputBarBottomSpacing.constant = 0
} else {
//the most important logic branch, reset current bottom constant constraint value
if self.inputBarBottomSpacing.constant != 0 {
self.inputBarBottomSpacing.constant = 0
}
self.inputBarBottomSpacing.constant = -endFrame.size.height
}
UIView.animate(withDuration: duration, delay: TimeInterval(0), options: animationCurve, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
}
}
swift 3.0
You can try this way, i have implemented in my project. Hope it will help you.
#IBOutlet weak var constant_ViewBottom: NSLayoutConstraint! // 0
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
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)
}
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)
}
func keyboardWillShow(_ notification: NSNotification){
if let keyboardRectValue = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size {
let keyboardHeight = keyboardRectValue.height
print("keyboardHeight:=\(keyboardHeight)")
constant_ViewBottom.constant = keyboardHeight
self.view.layoutIfNeeded()
}
}
func keyboardWillHide(_ notification: NSNotification){
constant_ViewBottom.constant = 0.0
self.view.layoutIfNeeded()
}

possible to have a button on the bottom of the screen move up with the ios keyboard [duplicate]

I'm using Swift for programing with iOS and I'm using this code to move the UITextField, but it does not work. I call the function keyboardWillShow correctly, but the textfield doesn't move. I'm using autolayout.
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);
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self);
}
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
//let contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardSize.height, right: 0)
var frame = self.ChatField.frame
frame.origin.y = frame.origin.y - keyboardSize.height + 167
self.chatField.frame = frame
println("asdasd")
}
}
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)
}
}
If you're using Auto Layout, I assume you've set the Bottom Space to Superview constraint. If that's the case, you simply have to update the constraint's value. Here's how you do it with a little bit of animation.
func keyboardWasShown(notification: NSNotification) {
let info = notification.userInfo!
let keyboardFrame: CGRect = (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
UIView.animateWithDuration(0.1, animations: { () -> Void in
self.bottomConstraint.constant = keyboardFrame.size.height + 20
})
}
The hardcoded 20 is added only to pop the textfield above the keyboard just a bit. Otherwise the keyboard's top margin and textfield's bottom margin would be touching.
When the keyboard is dismissed, reset the constraint's value to its original one.
A simple solution is to move view up with constant of keyboard height.
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);
}
#objc func keyboardWillShow(sender: NSNotification) {
self.view.frame.origin.y = -150 // Move view 150 points upward
}
#objc func keyboardWillHide(sender: NSNotification) {
self.view.frame.origin.y = 0 // Move view to original position
}
Swift 5:
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(sender:)), name: UIResponder.keyboardWillShowNotification, object: nil);
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(sender:)), name: UIResponder.keyboardWillHideNotification, object: nil);
For moving your view while editing textfield try this , I have applied this ,
Option 1 :- ** **Update in Swift 5.0 and iPhone X , XR , XS and XS Max
Move using NotificationCenter
Register this Notification in func viewWillAppear(_ animated: Bool)
Deregister this Notification in func viewWillDisappear(_ animated: Bool)
Note:- If you will not deregister than it will call from child class and will reason of crashing or else.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver( self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil )
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
}
#objc func keyboardWillShow( notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
var newHeight: CGFloat
let duration:TimeInterval = (notification.userInfo![UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = notification.userInfo![UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIView.AnimationOptions.curveEaseInOut.rawValue
let animationCurve:UIView.AnimationOptions = UIView.AnimationOptions(rawValue: animationCurveRaw)
if #available(iOS 11.0, *) {
newHeight = keyboardFrame.cgRectValue.height - self.view.safeAreaInsets.bottom
} else {
newHeight = keyboardFrame.cgRectValue.height
}
let keyboardHeight = newHeight + 10 // **10 is bottom margin of View** and **this newHeight will be keyboard height**
UIView.animate(withDuration: duration,
delay: TimeInterval(0),
options: animationCurve,
animations: {
self.view.textViewBottomConstraint.constant = keyboardHeight **//Here you can manage your view constraints for animated show**
self.view.layoutIfNeeded() },
completion: nil)
}
}
Option 2 :-
Its work fine
func textFieldDidBeginEditing(textField: UITextField) {
self.animateViewMoving(up: true, moveValue: 100)
}
func textFieldDidEndEditing(textField: UITextField) {
self.animateViewMoving(up: false, moveValue: 100)
}
func animateViewMoving (up:Bool, moveValue :CGFloat){
var movementDuration:NSTimeInterval = 0.3
var movement:CGFloat = ( up ? -moveValue : moveValue)
UIView.beginAnimations( "animateView", context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(movementDuration )
self.view.frame = CGRectOffset(self.view.frame, 0, movement)
UIView.commitAnimations()
}
I got this answer from this source UITextField move up when keyboard appears in Swift
IN the Swift 4 ---
func textFieldDidBeginEditing(_ textField: UITextField) {
animateViewMoving(up: true, moveValue: 100)
}
func textFieldDidEndEditing(_ textField: UITextField) {
animateViewMoving(up: false, moveValue: 100)
}
func animateViewMoving (up:Bool, moveValue :CGFloat){
let movementDuration:TimeInterval = 0.3
let movement:CGFloat = ( up ? -moveValue : moveValue)
UIView.beginAnimations( "animateView", context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(movementDuration )
self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)
UIView.commitAnimations()
}
I love clean Swift code. So here's the tightest code I could come up with to move a text view up/down with the keyboard. It's currently working in an iOS8/9 Swift 2 production app.
UPDATE (March 2016):
I just tightened up my previous code as much as possible. Also, there are a bunch of popular answers here that hardcode the keyboard height and animation parameters. There's no need for that, not to mention that the numbers in these answers don't always line up with the actual values I'm seeing on my 6s+ iOS9 (keyboard height of 226, duration of 0.25, and animation curve of 7). In any case, it's almost no extra code to get those values straight from the system. See below.
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "animateWithKeyboard:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "animateWithKeyboard:", name: UIKeyboardWillHideNotification, object: nil)
}
func animateWithKeyboard(notification: NSNotification) {
// Based on both Apple's docs and personal experience,
// I assume userInfo and its documented keys are available.
// If you'd like, you can remove the forced unwrapping and add your own default values.
let userInfo = notification.userInfo!
let keyboardHeight = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue().height
let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as! Double
let curve = userInfo[UIKeyboardAnimationCurveUserInfoKey] as! UInt
let moveUp = (notification.name == UIKeyboardWillShowNotification)
// baseContraint is your Auto Layout constraint that pins the
// text view to the bottom of the superview.
baseConstraint.constant = moveUp ? -keyboardHeight : 0
let options = UIViewAnimationOptions(rawValue: curve << 16)
UIView.animateWithDuration(duration, delay: 0, options: options,
animations: {
self.view.layoutIfNeeded()
},
completion: nil
)
}
NOTE: This code covers the most comment/general case. However, more code may be needed to handle different orientations and/or custom keyboards Here's an in-depth article on working with the iOS keyboard. If you need to handle every scenario, this may help.
Edit: I recommend an easier and cleaner solution. Just change the class of bottom spacing constraint to KeyboardLayoutConstraint. It will automatically expand to the keyboard height.
This is an improved version of #JosephLord 's answer.
As tested on iOS 8.3 iPad Simulator, Portrait. Xcode6.3 beta4, I found his answer doesn't work when keyboard is hiding because UIKeyboardFrameEndUserInfoKey is "NSRect: {{0, 1024}, {768, 264}}";. The height is never 0.
This goes back to use the traditional UIKeyboardWillShowNotification and UIKeyboardWillHideNotification to better tell when keyboard is hiding rather than relying on the end frame's height. UIKeyboardWillShowNotification is also sent when keyboard frame is changed so it should cover all use cases.
// You have to set this up in storyboard first!.
// It's a vertical spacing constraint between view and bottom of superview.
#IBOutlet weak var bottomSpacingConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardNotification:"), name:UIKeyboardWillShowNotification, object: nil);
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardNotification:"), name:UIKeyboardWillHideNotification, object: nil);
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func keyboardNotification(notification: NSNotification) {
let isShowing = notification.name == UIKeyboardWillShowNotification
if let userInfo = notification.userInfo {
let endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue()
let endFrameHeight = endFrame?.size.height ?? 0.0
let duration:NSTimeInterval = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.unsignedLongValue ?? UIViewAnimationOptions.CurveEaseInOut.rawValue
let animationCurve:UIViewAnimationOptions = UIViewAnimationOptions(rawValue: animationCurveRaw)
self.bottomSpacingConstraint?.constant = isShowing ? endFrameHeight : 0.0
UIView.animateWithDuration(duration,
delay: NSTimeInterval(0),
options: animationCurve,
animations: { self.view.layoutIfNeeded() },
completion: nil)
}
}
i am working with swift 4 and i am solved this issue without use any extra bottom constraint look my code is here.its really working on my case
1) Add Notification Observer in did load
override func viewDidLoad() {
super.viewDidLoad()
setupManager()
// Do any additional setup after loading the view.
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)
}
2) Remove Notification Observer like
deinit {
NotificationCenter.default.removeObserver(self)
}
3) Add keyboard show/ hide methods like
#objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
UIView.animate(withDuration: 0.1, animations: { () -> Void in
self.view.frame.origin.y -= keyboardSize.height
self.view.layoutIfNeeded()
})
}
}
#objc func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
UIView.animate(withDuration: 0.1, animations: { () -> Void in
self.view.frame.origin.y += keyboardSize.height
self.view.layoutIfNeeded()
})
}
}
4) Add textfeild delegate and add touchesBegan methods .usefull for hide the keyboard when touch outside the textfeild on screen
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
view.endEditing(true)
}
You can use this library and just one line of code in appDidFinishedLaunching and u are done..
func application(application: UIApplication,didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
IQKeyboardManager.sharedManager().enable = true
return true
}
IQKeyboardManager - adjust view whenever keyboard appear
link - https://github.com/hackiftekhar/IQKeyboardManager
This is an improved version of #JosephLord and #Hlung's answer.
It can apply whether you have tabbar or not.
And it would perfectly restore view that is moved by keyboard to original position.
// You have to set this up in storyboard first!.
// It's a vertical spacing constraint between view and bottom of superview.
#IBOutlet weak var bottomSpacingConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// Receive(Get) Notification
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardNotification:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardNotification:", name: UIKeyboardWillHideNotification, object: nil)
self.originalConstraint = self.keyboardHeightLayoutConstraint?.constant //for original coordinate.
}
func keyboardNotification(notification: NSNotification) {
let isShowing = notification.name == UIKeyboardWillShowNotification
var tabbarHeight: CGFloat = 0
if self.tabBarController? != nil {
tabbarHeight = self.tabBarController!.tabBar.frame.height
}
if let userInfo = notification.userInfo {
let endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue()
let duration:NSTimeInterval = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.unsignedLongValue ?? UIViewAnimationOptions.CurveEaseInOut.rawValue
let animationCurve:UIViewAnimationOptions = UIViewAnimationOptions(rawValue: animationCurveRaw)
self.keyboardHeightLayoutConstraint?.constant = isShowing ? (endFrame!.size.height - tabbarHeight) : self.originalConstraint!
UIView.animateWithDuration(duration,
delay: NSTimeInterval(0),
options: animationCurve,
animations: { self.view.layoutIfNeeded() },
completion: nil)
}
}
I created a Swift 3 protocol to handle the keyboard appearance / disappearance
import UIKit
protocol KeyboardHandler: class {
var bottomConstraint: NSLayoutConstraint! { get set }
func keyboardWillShow(_ notification: Notification)
func keyboardWillHide(_ notification: Notification)
func startObservingKeyboardChanges()
func stopObservingKeyboardChanges()
}
extension KeyboardHandler where Self: UIViewController {
func startObservingKeyboardChanges() {
// NotificationCenter observers
NotificationCenter.default.addObserver(forName: NSNotification.Name.UIKeyboardWillShow, object: nil, queue: nil) { [weak self] notification in
self?.keyboardWillShow(notification)
}
// Deal with rotations
NotificationCenter.default.addObserver(forName: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil, queue: nil) { [weak self] notification in
self?.keyboardWillShow(notification)
}
// Deal with keyboard change (emoji, numerical, etc.)
NotificationCenter.default.addObserver(forName: NSNotification.Name.UITextInputCurrentInputModeDidChange, object: nil, queue: nil) { [weak self] notification in
self?.keyboardWillShow(notification)
}
NotificationCenter.default.addObserver(forName: NSNotification.Name.UIKeyboardWillHide, object: nil, queue: nil) { [weak self] notification in
self?.keyboardWillHide(notification)
}
}
func keyboardWillShow(_ notification: Notification) {
let verticalPadding: CGFloat = 20 // Padding between the bottom of the view and the top of the keyboard
guard let value = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardHeight = value.cgRectValue.height
// Here you could have more complex rules, like checking if the textField currently selected is actually covered by the keyboard, but that's out of this scope.
self.bottomConstraint.constant = keyboardHeight + verticalPadding
UIView.animate(withDuration: 0.1, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
func keyboardWillHide(_ notification: Notification) {
self.bottomConstraint.constant = 0
UIView.animate(withDuration: 0.1, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
func stopObservingKeyboardChanges() {
NotificationCenter.default.removeObserver(self)
}
}
Then, to implement it in a UIViewController, do the following:
let the viewController conform to this protocol :
class FormMailVC: UIViewControlle, KeyboardHandler {
start observing keyboard changes in viewWillAppear:
// MARK: - View controller life cycle
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
startObservingKeyboardChanges()
}
stop observing keyboard changes in viewWillDisappear:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
stopObservingKeyboardChanges()
}
create an IBOutlet for the bottom constraint from the storyboard:
// NSLayoutConstraints
#IBOutlet weak var bottomConstraint: NSLayoutConstraint!
(I recommend having all of your UI embedded inside a "contentView", and linking to this property the bottom constraint from this contentView to the bottom layout guide)
change the constraint priority of the top constraint to 250 (low)
This is to let the whole content view slide upwards when the keyboard appears.
The priority must be lower than any other constraint priority in the subviews, including content hugging priorities / content compression resistance priorities.
Make sure that your Autolayout has enough constraints to determine how the contentView should slide up.
You may have to add a "greater than equal" constraint for this:
And here you go!
Complete code for managing keyboard.
override func viewWillAppear(_ animated: Bool) {
NotificationCenter.default.addObserver(self, selector: #selector(StoryMediaVC.keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(StoryMediaVC.keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
}
#objc func keyboardWillShow(notification: NSNotification) {
guard let userInfo = notification.userInfo else {return}
guard let keyboardSize = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else {return}
let keyboardFrame = keyboardSize.cgRectValue
if self.view.bounds.origin.y == 0{
self.view.bounds.origin.y += keyboardFrame.height
}
}
#objc func keyboardWillHide(notification: NSNotification) {
if self.view.bounds.origin.y != 0 {
self.view.bounds.origin.y = 0
}
}
Easiest way that doesn't require any code:
Download KeyboardLayoutConstraint.swift and add (drag & drop) the file into your project, if you're not using the Spring animation framework already.
In your storyboard, create a bottom constraint for the object/view/textfield, select the constraint (double-click it) and in the Identity Inspector, change its class from NSLayoutConstraint to KeyboardLayoutConstraint.
Done!
The object will auto-move up with the keyboard, in sync.
Such simple UIViewController extension can be used
//MARK: - Observers
extension UIViewController {
func addObserverForNotification(notificationName: String, actionBlock: (NSNotification) -> Void) {
NSNotificationCenter.defaultCenter().addObserverForName(notificationName, object: nil, queue: NSOperationQueue.mainQueue(), usingBlock: actionBlock)
}
func removeObserver(observer: AnyObject, notificationName: String) {
NSNotificationCenter.defaultCenter().removeObserver(observer, name: notificationName, object: nil)
}
}
//MARK: - Keyboard observers
extension UIViewController {
typealias KeyboardHeightClosure = (CGFloat) -> ()
func addKeyboardChangeFrameObserver(willShow willShowClosure: KeyboardHeightClosure?,
willHide willHideClosure: KeyboardHeightClosure?) {
NSNotificationCenter.defaultCenter().addObserverForName(UIKeyboardWillChangeFrameNotification,
object: nil, queue: NSOperationQueue.mainQueue(), usingBlock: { [weak self](notification) in
if let userInfo = notification.userInfo,
let frame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue(),
let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? Double,
let c = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? UInt,
let kFrame = self?.view.convertRect(frame, fromView: nil),
let kBounds = self?.view.bounds {
let animationType = UIViewAnimationOptions(rawValue: c)
let kHeight = kFrame.size.height
UIView.animateWithDuration(duration, delay: 0, options: animationType, animations: {
if CGRectIntersectsRect(kBounds, kFrame) { // keyboard will be shown
willShowClosure?(kHeight)
} else { // keyboard will be hidden
willHideClosure?(kHeight)
}
}, completion: nil)
} else {
print("Invalid conditions for UIKeyboardWillChangeFrameNotification")
}
})
}
func removeKeyboardObserver() {
removeObserver(self, notificationName: UIKeyboardWillChangeFrameNotification)
}
}
Example of usage
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
removeKeyboardObserver()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
addKeyboardChangeFrameObserver(willShow: { [weak self](height) in
//Update constraints here
self?.view.setNeedsUpdateConstraints()
}, willHide: { [weak self](height) in
//Reset constraints here
self?.view.setNeedsUpdateConstraints()
})
}
Swift 4 solution
//MARK: - Observers
extension UIViewController {
func addObserverForNotification(_ notificationName: Notification.Name, actionBlock: #escaping (Notification) -> Void) {
NotificationCenter.default.addObserver(forName: notificationName, object: nil, queue: OperationQueue.main, using: actionBlock)
}
func removeObserver(_ observer: AnyObject, notificationName: Notification.Name) {
NotificationCenter.default.removeObserver(observer, name: notificationName, object: nil)
}
}
//MARK: - Keyboard handling
extension UIViewController {
typealias KeyboardHeightClosure = (CGFloat) -> ()
func addKeyboardChangeFrameObserver(willShow willShowClosure: KeyboardHeightClosure?,
willHide willHideClosure: KeyboardHeightClosure?) {
NotificationCenter.default.addObserver(forName: NSNotification.Name.UIKeyboardWillChangeFrame,
object: nil, queue: OperationQueue.main, using: { [weak self](notification) in
if let userInfo = notification.userInfo,
let frame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue,
let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? Double,
let c = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? UInt,
let kFrame = self?.view.convert(frame, from: nil),
let kBounds = self?.view.bounds {
let animationType = UIViewAnimationOptions(rawValue: c)
let kHeight = kFrame.size.height
UIView.animate(withDuration: duration, delay: 0, options: animationType, animations: {
if kBounds.intersects(kFrame) { // keyboard will be shown
willShowClosure?(kHeight)
} else { // keyboard will be hidden
willHideClosure?(kHeight)
}
}, completion: nil)
} else {
print("Invalid conditions for UIKeyboardWillChangeFrameNotification")
}
})
}
func removeKeyboardObserver() {
removeObserver(self, notificationName: NSNotification.Name.UIKeyboardWillChangeFrame)
}
}
Swift 4.2
//MARK: - Keyboard handling
extension UIViewController {
func addObserverForNotification(_ notificationName: Notification.Name, actionBlock: #escaping (Notification) -> Void) {
NotificationCenter.default.addObserver(forName: notificationName, object: nil, queue: OperationQueue.main, using: actionBlock)
}
func removeObserver(_ observer: AnyObject, notificationName: Notification.Name) {
NotificationCenter.default.removeObserver(observer, name: notificationName, object: nil)
}
typealias KeyboardHeightClosure = (CGFloat) -> ()
func removeKeyboardObserver() {
removeObserver(self, notificationName: UIResponder.keyboardWillChangeFrameNotification)
}
func addKeyboardChangeFrameObserver(willShow willShowClosure: KeyboardHeightClosure?,
willHide willHideClosure: KeyboardHeightClosure?) {
NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillChangeFrameNotification,
object: nil, queue: OperationQueue.main, using: { [weak self](notification) in
if let userInfo = notification.userInfo,
let frame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue,
let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double,
let c = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? UInt,
let kFrame = self?.view.convert(frame, from: nil),
let kBounds = self?.view.bounds {
let animationType = UIView.AnimationOptions(rawValue: c)
let kHeight = kFrame.size.height
UIView.animate(withDuration: duration, delay: 0, options: animationType, animations: {
if kBounds.intersects(kFrame) { // keyboard will be shown
willShowClosure?(kHeight)
} else { // keyboard will be hidden
willHideClosure?(kHeight)
}
}, completion: nil)
} else {
print("Invalid conditions for UIKeyboardWillChangeFrameNotification")
}
})
}
}
A Swift 5 solution for frédéric-adda :
protocol KeyboardHandler: class {
var bottomConstraint: NSLayoutConstraint! { get set }
func keyboardWillShow(_ notification: Notification)
func keyboardWillHide(_ notification: Notification)
func startObservingKeyboardChanges()
func stopObservingKeyboardChanges()
}
extension KeyboardHandler where Self: UIViewController {
func startObservingKeyboardChanges() {
// NotificationCenter observers
NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: nil) { [weak self] notification in
self?.keyboardWillShow(notification)
}
// Deal with rotations
NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillChangeFrameNotification, object: nil, queue: nil) { [weak self] notification in
self?.keyboardWillShow(notification)
}
// Deal with keyboard change (emoji, numerical, etc.)
NotificationCenter.default.addObserver(forName: UITextInputMode.currentInputModeDidChangeNotification, object: nil, queue: nil) { [weak self] notification in
self?.keyboardWillShow(notification)
}
NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: nil) { [weak self] notification in
self?.keyboardWillHide(notification)
}
}
func keyboardWillShow(_ notification: Notification) {
let verticalPadding: CGFloat = 20 // Padding between the bottom of the view and the top of the keyboard
guard let value = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardHeight = value.cgRectValue.height
// Here you could have more complex rules, like checking if the textField currently selected is actually covered by the keyboard, but that's out of this scope.
self.bottomConstraint.constant = keyboardHeight + verticalPadding
UIView.animate(withDuration: 0.1, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
func keyboardWillHide(_ notification: Notification) {
self.bottomConstraint.constant = 0
UIView.animate(withDuration: 0.1, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
func stopObservingKeyboardChanges() {
NotificationCenter.default.removeObserver(self)
}
}
In any UIViewController:
Conform to KeyboardHandler protocol
extension AnyViewController: KeyboardHandler {}
Add bottom constraint for the last-bottom element on the screen.
#IBOutlet var bottomConstraint: NSLayoutConstraint!
Add Observer subscribe/unsubscribe:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
startObservingKeyboardChanges()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
stopObservingKeyboardChanges()
}
Enjoy!
struct MoveKeyboard {
static let KEYBOARD_ANIMATION_DURATION : CGFloat = 0.3
static let MINIMUM_SCROLL_FRACTION : CGFloat = 0.2;
static let MAXIMUM_SCROLL_FRACTION : CGFloat = 0.8;
static let PORTRAIT_KEYBOARD_HEIGHT : CGFloat = 216;
static let LANDSCAPE_KEYBOARD_HEIGHT : CGFloat = 162;
}
func textFieldDidBeginEditing(textField: UITextField) {
let textFieldRect : CGRect = self.view.window!.convertRect(textField.bounds, fromView: textField)
let viewRect : CGRect = self.view.window!.convertRect(self.view.bounds, fromView: self.view)
let midline : CGFloat = textFieldRect.origin.y + 0.5 * textFieldRect.size.height
let numerator : CGFloat = midline - viewRect.origin.y - MoveKeyboard.MINIMUM_SCROLL_FRACTION * viewRect.size.height
let denominator : CGFloat = (MoveKeyboard.MAXIMUM_SCROLL_FRACTION - MoveKeyboard.MINIMUM_SCROLL_FRACTION) * viewRect.size.height
var heightFraction : CGFloat = numerator / denominator
if heightFraction < 0.0 {
heightFraction = 0.0
} else if heightFraction > 1.0 {
heightFraction = 1.0
}
let orientation : UIInterfaceOrientation = UIApplication.sharedApplication().statusBarOrientation
if (orientation == UIInterfaceOrientation.Portrait || orientation == UIInterfaceOrientation.PortraitUpsideDown) {
animateDistance = floor(MoveKeyboard.PORTRAIT_KEYBOARD_HEIGHT * heightFraction)
} else {
animateDistance = floor(MoveKeyboard.LANDSCAPE_KEYBOARD_HEIGHT * heightFraction)
}
var viewFrame : CGRect = self.view.frame
viewFrame.origin.y -= animateDistance
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(NSTimeInterval(MoveKeyboard.KEYBOARD_ANIMATION_DURATION))
self.view.frame = viewFrame
UIView.commitAnimations()
}
func textFieldDidEndEditing(textField: UITextField) {
var viewFrame : CGRect = self.view.frame
viewFrame.origin.y += animateDistance
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(NSTimeInterval(MoveKeyboard.KEYBOARD_ANIMATION_DURATION))
self.view.frame = viewFrame
UIView.commitAnimations()
}
And Lastly since we are using delegates methods
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
refactored from using objective-c http://www.cocoawithlove.com/2008/10/sliding-uitextfields-around-to-avoid.html
Another solution that doesn't depend on autolayout, constraints or any outlets. What you do need is your field(s) in a scrollview.
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "makeSpaceForKeyboard:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "makeSpaceForKeyboard:", name: UIKeyboardWillHideNotification, object: nil)
}
func makeSpaceForKeyboard(notification: NSNotification) {
let info = notification.userInfo!
let keyboardHeight:CGFloat = (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue().size.height
let duration:Double = info[UIKeyboardAnimationDurationUserInfoKey] as! Double
if notification.name == UIKeyboardWillShowNotification {
UIView.animateWithDuration(duration, animations: { () -> Void in
var frame = self.view.frame
frame.size.height = frame.size.height - keyboardHeight
self.view.frame = frame
})
} else {
UIView.animateWithDuration(duration, animations: { () -> Void in
var frame = self.view.frame
frame.size.height = frame.size.height + keyboardHeight
self.view.frame = frame
})
}
}
Here is my version for a solution for Swift 2.2:
First register for Keyboard Show/Hide Notifications
NSNotificationCenter.defaultCenter().addObserver(self,
selector: #selector(MessageThreadVC.keyboardWillShow(_:)),
name: UIKeyboardWillShowNotification,
object: nil)
NSNotificationCenter.defaultCenter().addObserver(self,
selector: #selector(MessageThreadVC.keyboardWillHide(_:)),
name: UIKeyboardWillHideNotification,
object: nil)
Then in methods coresponding for those notifications move the main view up or down
func keyboardWillShow(sender: NSNotification) {
if let keyboardSize = (sender.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue() {
self.view.frame.origin.y = -keyboardSize.height
}
}
func keyboardWillHide(sender: NSNotification) {
self.view.frame.origin.y = 0
}
The trick is in the "keyboardWillShow" part which get calls every time "QuickType Suggestion Bar" is expanded or collapsed. Then we always set the y coordinate of the main view which equals the negative value of total keyboard height (with or without the "QuickType bar" portion).
At the end do not forget to remove observers
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
Very Simple and no need to code more.
Just add pod 'IQKeyboardManagerSwift' in your podfile, and in your AppDelegate page add code below.
import IQKeyboardManagerSwift
and in method didFinishLaunchingWithOptions() type
IQKeyboardManager.shared.enable = true
that is it.
check this video link for better understanding https://youtu.be/eOM94K1ZWN8
Hope this will help you.
The following is a simple solution, whereby the text field has a constraint tying it to the bottom layout guide. It simply adds the keyboard height to the constraint's constant.
// This constraint ties the text field to the bottom layout guide
#IBOutlet var textFieldToBottomLayoutGuideConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillShow:", name:UIKeyboardWillShowNotification, object: nil);
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillHide:", name:UIKeyboardWillHideNotification, object: nil);
}
func keyboardWillShow(sender: NSNotification) {
if let keyboardSize = (sender.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
self.textFieldToBottomLayoutGuideConstraint?.constant += keyboardSize.height
}
}
func keyboardWillHide(sender: NSNotification) {
if let keyboardSize = (sender.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
self.textFieldToBottomLayoutGuideConstraint?.constant -= keyboardSize.height
}
}
Well, I think i might be too late but i found another simple version of Saqib's answer. I'm using Autolayout with constraints. I have a small view inside of another main view with username and password fields. Instead of changing the y coordinate of the view i'm saving the original constraint value in a variable and changing the constraint's constant to some value and again after the keyboard dismisses, i'm setting up the constraint to original one. This way it avoids the problem Saqib's answer has, (The view keeps on moving up and does not stop). Below is my code...
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);
self.originalConstraint = self.centerYConstraint.constant
}
func keyboardWillShow(sender: NSNotification) {
self.centerYConstraint.constant += 30
}
func keyboardWillHide(sender: NSNotification) {
self.centerYConstraint.constant = self.originalConstraint
}
Swift 4.x answer, merging answers from #Joseph Lord and #Isuru. bottomConstraint represents the bottom constraint of the view you're interested in moving.
override func viewDidLoad() {
// Call super
super.viewDidLoad()
// Subscribe to keyboard notifications
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardNotification(notification:)),
name: UIResponder.keyboardWillChangeFrameNotification,
object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
#objc func keyboardNotification(notification: NSNotification) {
if let userInfo = notification.userInfo {
// Get keyboard frame
let keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
// Set new bottom constraint constant
let bottomConstraintConstant = keyboardFrame.origin.y >= UIScreen.main.bounds.size.height ? 0.0 : keyboardFrame.size.height
// Set animation properties
let duration = (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(rawValue: animationCurveRaw)
// Animate the view you care about
UIView.animate(withDuration: duration, delay: 0, options: animationCurve, animations: {
self.bottomConstraint.constant = bottomConstraintConstant
self.view.layoutIfNeeded()
}, completion: nil)
}
}
I have done by following manner:
This is useful when textfield superview is view
class AdminLoginViewController: UIViewController,
UITextFieldDelegate{
#IBOutlet weak var txtUserName: UITextField!
#IBOutlet weak var txtUserPassword: UITextField!
#IBOutlet weak var btnAdminLogin: UIButton!
private var activeField : UIView?
var param:String!
var adminUser : Admin? = nil
var kbHeight: CGFloat!
override func viewDidLoad()
{
self.addKeyBoardObserver()
self.addGestureForHideKeyBoard()
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func addGestureForHideKeyBoard()
{
let tapGesture = UITapGestureRecognizer(target: self, action: Selector("hideKeyboard"))
tapGesture.cancelsTouchesInView = false
view.addGestureRecognizer(tapGesture)
}
func hideKeyboard() {
self.view.endEditing(true)
}
func addKeyBoardObserver(){
NSNotificationCenter.defaultCenter().addObserver(self, selector: "willChangeKeyboardFrame:",
name:UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "willChangeKeyboardFrame:",
name:UIKeyboardWillHideNotification, object: nil)
}
func removeObserver(){
NSNotificationCenter.defaultCenter().removeObserver(self)
}
//MARK:- textfiled Delegate
func textFieldShouldBeginEditing(textField: UITextField) -> Bool
{
activeField = textField
return true
}
func textFieldShouldEndEditing(textField: UITextField) -> Bool
{
if activeField == textField
{
activeField = nil
}
return true
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
if txtUserName == textField
{
txtUserPassword.becomeFirstResponder()
}
else if (textField == txtUserPassword)
{
self.btnAdminLoginAction(nil)
}
return true;
}
func willChangeKeyboardFrame(aNotification : NSNotification)
{
if self.activeField != nil && self.activeField!.isFirstResponder()
{
if let keyboardSize = (aNotification.userInfo![UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue()
{
let dy = (self.activeField?.superview?.convertRect((self.activeField?.frame)!, toView: view).origin.y)!
let height = (self.view.frame.size.height - keyboardSize.size.height)
if dy > height
{
var frame = self.view.frame
frame.origin.y = -((dy - height) + (self.activeField?.frame.size.height)! + 20)
self.view.frame = frame
}
}
}
else
{
var frame = self.view.frame
frame.origin.y = 0
self.view.frame = frame
}
} }
func registerForKeyboardNotifications(){
//Keyboard
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(keyboardWasShown), name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(keyboardWillBeHidden), name: UIKeyboardDidHideNotification, object: nil)
}
func deregisterFromKeyboardNotifications(){
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWasShown(notification: NSNotification){
let userInfo: NSDictionary = notification.userInfo!
let keyboardInfoFrame = userInfo.objectForKey(UIKeyboardFrameEndUserInfoKey)?.CGRectValue()
let windowFrame:CGRect = (UIApplication.sharedApplication().keyWindow!.convertRect(self.view.frame, fromView:self.view))
let keyboardFrame = CGRectIntersection(windowFrame, keyboardInfoFrame!)
let coveredFrame = UIApplication.sharedApplication().keyWindow!.convertRect(keyboardFrame, toView:self.view)
let contentInsets = UIEdgeInsetsMake(0, 0, (coveredFrame.size.height), 0.0)
self.scrollViewInAddCase .contentInset = contentInsets;
self.scrollViewInAddCase.scrollIndicatorInsets = contentInsets;
self.scrollViewInAddCase.contentSize = CGSizeMake((self.scrollViewInAddCase.contentSize.width), (self.scrollViewInAddCase.contentSize.height))
}
/**
this method will fire when keyboard was hidden
- parameter notification: contains keyboard details
*/
func keyboardWillBeHidden (notification: NSNotification) {
self.scrollViewInAddCase.contentInset = UIEdgeInsetsZero
self.scrollViewInAddCase.scrollIndicatorInsets = UIEdgeInsetsZero
}
For anyone not using storyboards to set the layout constraint. This is a pure programmatic way to get it working on Swift 5:
Define an empty constraint in your viewController. In this case, I am building a chat app that has a UIView containing text view at the very bottom of the screen.
var discussionsMessageBoxBottomAnchor: NSLayoutConstraint = NSLayoutConstraint()
In viewDidLoad add the constraints for the bottom-most view. In this case discussionsMessageBox. Also, add the listener for keyboard events.
For correctly initializing the constraint, you need to first add the subview and then define the constraint.
NotificationCenter.default.addObserver(self,
selector: #selector(self.keyboardNotification(notification:)),
name: NSNotification.Name.UIKeyboardWillChangeFrame,
object: nil)
view.addSubview(discussionsMessageBox)
if #available(iOS 11.0, *) {
discussionsMessageBoxBottomAnchor = discussionsMessageBox.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0)
} else {
// Fallback on earlier versions
discussionsMessageBoxBottomAnchor = discussionsMessageBox.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0)
}
NSLayoutConstraint.activate([ discussionsMessageBoxBottomAnchor ])
Define deinit.
deinit {
NotificationCenter.default.removeObserver(self)
}
Next add the code that #JosephLord defined, with one correction to fix offset math.
extension DiscussionsViewController {
#objc func keyboardNotification(notification: NSNotification) {
guard let userInfo = notification.userInfo else { return }
let endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
let endFrameY = endFrame?.origin.y ?? 0
let duration:TimeInterval = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = userInfo[UIKeyboardAnimationCurveUserInfoKey] 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.discussionsMessageBoxBottomAnchor.constant = 0.0
} else {
//Changed line
self.discussionsMessageBoxBottomAnchor.constant = -1 * (endFrame?.size.height ?? 0.0)
}
UIView.animate(
withDuration: duration,
delay: TimeInterval(0),
options: animationCurve,
animations: { self.view.layoutIfNeeded() },
completion: nil)
}
}
If you have more than one text field on the view, then I suggest that you look at this method. When switching between fields, you will not have a problem with the fact that the view runs away, it will simply adapt to the desired text field. It works in swift 5
override func viewDidLoad() {
super.viewDidLoad()
registerForKeyboardNotification()
}
All methods in extensions
extension StartViewController: UITextFieldDelegate {
func registerForKeyboardNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(sender:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(sender:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
#objc func keyboardWillShow(sender: NSNotification) {
guard let userInfo = sender.userInfo,
let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue,
let currentTextField = UIResponder.currentFirst() as? UITextField else { return }
let keyboardTopY = keyboardFrame.cgRectValue.origin.y
let convertedTextFieldFrame = view.convert(currentTextField.frame, from: currentTextField.superview)
let textFieldBottomY = convertedTextFieldFrame.origin.y + convertedTextFieldFrame.size.height
if textFieldBottomY > keyboardTopY {
let textBoxY = convertedTextFieldFrame.origin.y
let newFrameY = (textBoxY - keyboardTopY / 2) * -1
view.frame.origin.y = newFrameY
}
}
#objc func keyboardWillHide(sender: NSNotification) {
self.view.frame.origin.y = 0
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
switch textField {
case emailTextField :
passwordTextField.becomeFirstResponder()
default:
emailTextField.becomeFirstResponder()
}
return true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches , with:event)
view.endEditing(true)
}
}
At the end we set up the method with UIResponder
extension UIResponder {
private struct Static {
static weak var responder: UIResponder?
}
static func currentFirst() -> UIResponder? {
Static.responder = nil
UIApplication.shared.sendAction(#selector(UIResponder._trap), to: nil, from: nil, for: nil)
return Static.responder
}
#objc private func _trap() {
Static.responder = self
}
}
I have done in following manner :
class SignInController: UIViewController , UITextFieldDelegate {
#IBOutlet weak var scrollView: UIScrollView!
// outlet declartion
#IBOutlet weak var signInTextView: UITextField!
var kbHeight: CGFloat!
/**
*
* #method viewDidLoad
*
*/
override func viewDidLoad() {
super.viewDidLoad()
self.signInTextView.delegate = self
}// end viewDidLoad
/**
*
* #method viewWillAppear
*
*/
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UIKeyboardWillHideNotification, object: nil)
}// end viewWillAppear
/**
*
* #method viewDidAppear
*
*/
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
}// end viewDidAppear
/**
*
* #method viewWillDisappear
*
*/
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
/**
*
* #method textFieldShouldReturn
* retun the keyboard value
*
*/
// MARK -
func textFieldShouldReturn(textField: UITextField) -> Bool {
signInTextView.resignFirstResponder()
return true;
}// end textFieldShouldReturn
// MARK - keyboardWillShow
func keyboardWillShow(notification: NSNotification) {
if let userInfo = notification.userInfo {
if let keyboardSize = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
kbHeight = keyboardSize.height
self.animateTextField(true)
}
}
}// end keyboardWillShow
// MARK - keyboardWillHide
func keyboardWillHide(notification: NSNotification) {
self.animateTextField(false)
}// end keyboardWillHide
// MARK - animateTextField
func animateTextField(up: Bool) {
var movement = (up ? -kbHeight : kbHeight)
UIView.animateWithDuration(0.3, animations: {
self.view.frame = CGRectOffset(self.view.frame, 0, movement)
})
}// end animateTextField
/**
*
* #method didReceiveMemoryWarning
*
*/
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}// end didReceiveMemoryWarning
}// end SignInController
If you are like me who has tried all the above solutions and still your problem is not solved, I have a got a great solution for you that works like a charm. First I want clarify few things about some of solutions mentioned above.
In my case IQkeyboardmanager was working only when there is no auto layout applied on the elements, if it is applied then IQkeyboard manager will not work the way we think.
Same thing with upward movement of self.view.
i have wriiten a objective c header with a swift support for pushing UITexfield upward when user clicks on it, solving the problem of keyboard covering the UITextfield : https://github.com/coolvasanth/smart_keyboard.
One who has An intermediate or higher level in iOS app development can easily understand the repository and implement it. All the best
Here is a generic solution for all TextField
Steps -
1) Create a common ViewController that is extended by other ViewControllers
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.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y == 0 {
self.view.frame.origin.y -= getMoveableDistance(keyboarHeight: keyboardSize.height)
}
}
}
#objc func keyboardWillHide(notification: NSNotification) {
if self.view.frame.origin.y != 0 {
self.view.frame.origin.y = 0
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
//get the distance to move up the main view for the focus textfiled
func getMoveableDistance(keyboarHeight : CGFloat) -> CGFloat{
var y:CGFloat = 0.0
if let activeTF = getSelectedTextField(){
var tfMaxY = activeTF.frame.maxY
var containerView = activeTF.superview!
while containerView.frame.maxY != self.view.frame.maxY{
let contViewFrm = containerView.convert(activeTF.frame, to: containerView.superview)
tfMaxY = tfMaxY + contViewFrm.minY
containerView = containerView.superview!
}
let keyboardMinY = self.view.frame.height - keyboarHeight
if tfMaxY > keyboardMinY{
y = (tfMaxY - keyboardMinY) + 10.0
}
}
return y
}
2) Create a extension of UIViewController and the currently active TextField
//get active text field
extension UIViewController {
func getSelectedTextField() -> UITextField? {
let totalTextFields = getTextFieldsInView(view: self.view)
for textField in totalTextFields{
if textField.isFirstResponder{
return textField
}
}
return nil
}
func getTextFieldsInView(view: UIView) -> [UITextField] {
var totalTextFields = [UITextField]()
for subview in view.subviews as [UIView] {
if let textField = subview as? UITextField {
totalTextFields += [textField]
} else {
totalTextFields += getTextFieldsInView(view: subview)
}
}
return totalTextFields
}
}
I modified #Simpa solution a little bit.........
override func viewDidLoad()
{
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("makeSpaceForKeyboard:"), name:UIKeyboardWillShowNotification, object: nil);
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("makeSpaceForKeyboard:"), name:UIKeyboardWillHideNotification, object: nil);
}
deinit{
NSNotificationCenter.defaultCenter().removeObserver(self)
}
var keyboardIsVisible = false
override func makeSpaceForKeyboard(notification: NSNotification) {
let info = notification.userInfo!
let keyboardHeight:CGFloat = (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue().size.height
let duration:Double = info[UIKeyboardAnimationDurationUserInfoKey] as! Double
if notification.name == UIKeyboardWillShowNotification && keyboardIsVisible == false{
keyboardIsVisible = true
UIView.animateWithDuration(duration, animations: { () -> Void in
var frame = self.view.frame
frame.size.height = frame.size.height - keyboardHeight
self.view.frame = frame
})
} else if keyboardIsVisible == true && notification.name == UIKeyboardWillShowNotification{
}else {
keyboardIsVisible = false
UIView.animateWithDuration(duration, animations: { () -> Void in
var frame = self.view.frame
frame.size.height = frame.size.height + keyboardHeight
self.view.frame = frame
})
}
}
None of them worked for and I ended up using content insets to move my view up when the keyboard appears.
Note: I was using a UITableView
Referenced solution # keyboard-content-offset which was entirely written in objective C, the below solution is clean Swift.
Add the notification observer # viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(yourClass.keyboardWillBeShown), name:UIKeyboardWillShowNotification, object: nil);
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(yourClass.keyboardWillBeHidden), name:UIKeyboardWillHideNotification, object: nil);
To get the keyboard size, we first get the userInfo dictionary from the notification object, which stores any additional objects that our receiver might use.
From that dictionary we can get the CGRect object describing the keyboard’s frame by using the key UIKeyboardFrameBeginUserInfoKey.
Apply the content inset for the table view # keyboardWillBeShown method,
func keyboardWillBeShown(sender: NSNotification)
{
// Move the table view
if let keyboardSize = (sender.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue()
{
let contentInsets = UIEdgeInsetsMake(0.0, 0.0, (keyboardSize.height), 0.0);
yourTableView.contentInset = contentInsets;
yourTableView.scrollIndicatorInsets = contentInsets;
}
}
Restore the view # keyboardWillBeHidden method
func keyboardWillBeHidden(sender: NSNotification)
{
// Moving back the table view back to the default position
yourTableView.contentInset = UIEdgeInsetsZero;
yourTableView.scrollIndicatorInsets = UIEdgeInsetsZero;
}
If you want to keep the device orientation also into consideration, use conditional statements to tailor the code to your needs.
// Portrait
UIEdgeInsetsMake(0.0, 0.0, (keyboardSize.height), 0.0);
// Landscape
UIEdgeInsetsMake(0.0, 0.0, (keyboardSize.width), 0.0);

Move a view up only when the keyboard covers an input field

I am trying to build an input screen for the iPhone. The screen has a number of input fields. Most of them on the top of the screen, but two fields are at the bottom.
When the user tries to edit the text on the bottom of the screen, the keyboard will pop up and it will cover the screen.
I found a simple solution to move the screen up when this happens, but the result is that the screen always moves up and the fields on top of the screen move out of reach when the user tries to edit those.
Is there a way to have the screen only move when the bottom fields are edited?
I have used this code I found 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(sender: NSNotification) {
self.view.frame.origin.y -= 150
}
func keyboardWillHide(sender: NSNotification) {
self.view.frame.origin.y += 150
}
Your problem is well explained in this document by Apple. Example code on this page (at Listing 4-1) does exactly what you need, it will scroll your view only when the current editing should be under the keyboard. You only need to put your needed controls in a scrollViiew.
The only problem is that this is Objective-C and I think you need it in Swift..so..here it is:
Declare a variable
var activeField: UITextField?
then add these methods
func registerForKeyboardNotifications()
{
//Adding notifies on keyboard appearing
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWasShown:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillBeHidden:", name: UIKeyboardWillHideNotification, object: nil)
}
func deregisterFromKeyboardNotifications()
{
//Removing notifies on keyboard appearing
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWasShown(notification: NSNotification)
{
//Need to calculate keyboard exact size due to Apple suggestions
self.scrollView.scrollEnabled = true
var info : NSDictionary = notification.userInfo!
var keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue().size
var 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 let activeFieldPresent = activeField
{
if (!CGRectContainsPoint(aRect, activeField!.frame.origin))
{
self.scrollView.scrollRectToVisible(activeField!.frame, animated: true)
}
}
}
func keyboardWillBeHidden(notification: NSNotification)
{
//Once keyboard disappears, restore original positions
var info : NSDictionary = notification.userInfo!
var keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue().size
var 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.scrollEnabled = false
}
func textFieldDidBeginEditing(textField: UITextField!)
{
activeField = textField
}
func textFieldDidEndEditing(textField: UITextField!)
{
activeField = nil
}
Be sure to declare your ViewController as UITextFieldDelegate and set correct delegates in your initialization methods:
ex:
self.you_text_field.delegate = self
And remember to call registerForKeyboardNotifications on viewInit and deregisterFromKeyboardNotifications on exit.
Edit/Update: Swift 4.2 Syntax
func registerForKeyboardNotifications(){
//Adding notifies on keyboard appearing
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(notification:)), name: NSNotification.Name.UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(notification:)), name: NSNotification.Name.UIResponder.keyboardWillHideNotification, object: nil)
}
func deregisterFromKeyboardNotifications(){
//Removing notifies on keyboard appearing
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIResponder.keyboardWillHideNotification, object: nil)
}
#objc func keyboardWasShown(notification: NSNotification){
//Need to calculate keyboard exact size due to Apple suggestions
self.scrollView.isScrollEnabled = true
var info = notification.userInfo!
let keyboardSize = (info[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size
let contentInsets : UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardSize!.height, right: 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.activeField {
if (!aRect.contains(activeField.frame.origin)){
self.scrollView.scrollRectToVisible(activeField.frame, animated: true)
}
}
}
#objc func keyboardWillBeHidden(notification: NSNotification){
//Once keyboard disappears, restore original positions
var info = notification.userInfo!
let keyboardSize = (info[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size
let contentInsets : UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: -keyboardSize!.height, right: 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
self.view.endEditing(true)
self.scrollView.isScrollEnabled = false
}
func textFieldDidBeginEditing(_ textField: UITextField){
activeField = textField
}
func textFieldDidEndEditing(_ textField: UITextField){
activeField = nil
}
Here is My 2 cents:
Have you tried: https://github.com/hackiftekhar/IQKeyboardManager
Extremely easy to install Swift or Objective-C.
Here how it works:
IQKeyboardManager (Swift):- IQKeyboardManagerSwift is available through CocoaPods, to install it simply add the following line to your Podfile: (#236)
pod 'IQKeyboardManagerSwift'
In AppDelegate.swift, just import IQKeyboardManagerSwift framework and enable IQKeyboardManager.
import IQKeyboardManagerSwift
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
IQKeyboardManager.sharedManager().enable = true
// For Swift 4, use this instead
// IQKeyboardManager.shared.enable = true
return true
}
}
And that is all. Easy!
The one I found to work perfectly for me was this:
func textFieldDidBeginEditing(textField: UITextField) {
if textField == email || textField == password {
animateViewMoving(true, moveValue: 100)
}
}
func textFieldDidEndEditing(textField: UITextField) {
if textField == email || textField == password {
animateViewMoving(false, moveValue: 100)
}
}
func animateViewMoving (up:Bool, moveValue :CGFloat){
let movementDuration:NSTimeInterval = 0.3
let movement:CGFloat = ( up ? -moveValue : moveValue)
UIView.beginAnimations("animateView", context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(movementDuration)
self.view.frame = CGRectOffset(self.view.frame, 0, movement)
UIView.commitAnimations()
}
You can also change the height values. Remove the "if statement" if you want to use it for all text fields.
You can even use this for all controls that require user input like TextView.
Is there a way to have the screen only move when the bottom fields are edited?
I had a similar problem and found a pretty straightforward solution without using a scrollView, and instead using if statements within the keyboardWillShow/Hide methods.
func keyboardWillShow(notification: NSNotification) {
if bottomText.editing{
self.view.window?.frame.origin.y = -1 * getKeyboardHeight(notification)
}
}
func keyboardWillHide(notification: NSNotification) {
if self.view.window?.frame.origin.y != 0 {
self.view.window?.frame.origin.y += getKeyboardHeight(notification)
}
}
This was a good solution for me because I only had two text fields.
Shifts whole view up: only when certain text fields (bottomText) are edited
Shifts whole view down: only when the view is not at the original location
Just use this extension to move any UIView when keyboard is presented.
extension UIView {
func bindToKeyboard(){
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillChange(_:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
#objc func keyboardWillChange(_ notification: NSNotification){
let duration = notification.userInfo![UIKeyboardAnimationDurationUserInfoKey] as! Double
let curve = notification.userInfo![UIKeyboardAnimationCurveUserInfoKey] as! UInt
let beginningFrame = (notification.userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
let endFrame = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let deltaY = endFrame.origin.y - beginningFrame.origin.y
UIView.animateKeyframes(withDuration: duration, delay: 0.0, options: UIViewKeyframeAnimationOptions(rawValue: curve), animations: {
self.frame.origin.y += deltaY
}, completion: nil)
}
}
Then in your viewdidload bind your view to the keyboard
UiView.bindToKeyboard()
Why not implement this in a UITableViewController instead? The keyboard won't hide any text fields when its shown.
Swift 4 (**updated) with extension**
add the buttons in one container
connect bottom constraint of the container with IBOutlet containerBtmConstrain
inViewDidLoad
self.containerDependOnKeyboardBottomConstrain = containerBtmConstrain
self.watchForKeyboard()
add the following extension
import UIKit
private var xoAssociationKeyForBottomConstrainInVC: UInt8 = 0
extension UIViewController {
var containerDependOnKeyboardBottomConstrain :NSLayoutConstraint! {
get {
return objc_getAssociatedObject(self, &xoAssociationKeyForBottomConstrainInVC) as? NSLayoutConstraint
}
set(newValue) {
objc_setAssociatedObject(self, &xoAssociationKeyForBottomConstrainInVC, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
func watchForKeyboard() {
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWasShown(notification:)), name:UIResponder.keyboardWillShowNotification, object: nil);
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide(notification:)), name:UIResponder.keyboardWillHideNotification, object: nil);
}
#objc func keyboardWasShown(notification: NSNotification) {
let info = notification.userInfo!
guard let keyboardFrame: CGRect = (info[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else {
return
}
UIView.animate(withDuration: 0.3, animations: { () -> Void in
self.containerDependOnKeyboardBottomConstrain.constant = -keyboardFrame.height
self.view.layoutIfNeeded()
})
}
#objc func keyboardWillHide(notification: NSNotification) {
UIView.animate(withDuration: 0.3, animations: { () -> Void in
self.containerDependOnKeyboardBottomConstrain.constant = 0
self.view.layoutIfNeeded()
})
}
}
I use SwiftLint, which had a few issues with the formatting of the accepted answer. Specifically:
no space before the colon,
no force casting,
prefer UIEdgeInset(top: etc... instead of UIEdgeInsetMake.
so here are those updates for Swift 3
func registerForKeyboardNotifications() {
//Adding notifies on keyboard appearing
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
func deregisterFromKeyboardNotifications() {
//Removing notifies on keyboard appearing
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
func keyboardWasShown(notification: NSNotification) {
//Need to calculate keyboard exact size due to Apple suggestions
scrollView?.isScrollEnabled = true
var info = notification.userInfo!
if let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size {
let contentInsets: UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardSize.height, right: 0.0)
scrollView?.contentInset = contentInsets
scrollView?.scrollIndicatorInsets = contentInsets
var aRect: CGRect = self.view.frame
aRect.size.height -= keyboardSize.height
if let activeField = self.activeField {
if !aRect.contains(activeField.frame.origin) {
self.scrollView.scrollRectToVisible(activeField.frame, animated: true)
}
}
}
}
func keyboardWillBeHidden(notification: NSNotification) {
//Once keyboard disappears, restore original positions
var info = notification.userInfo!
if let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size {
let contentInsets: UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: -keyboardSize.height, right: 0.0)
scrollView?.contentInset = contentInsets
scrollView?.scrollIndicatorInsets = contentInsets
}
view.endEditing(true)
scrollView?.isScrollEnabled = false
}
func textFieldDidBeginEditing(_ textField: UITextField) {
activeField = textField
}
func textFieldDidEndEditing(_ textField: UITextField) {
activeField = nil
}
Swift: You can do this by checking which textField is being presented.
#objc func keyboardWillShow(notification: NSNotification) {
if self.textField.isFirstResponder == true {
self.view.frame.origin.y -= 150
}
}
#objc func keyboardWillHide(notification: NSNotification){
if self.textField.isFirstResponder == true {
self.view.frame.origin.y += 150
}
}
I think this clause is wrong:
if (!CGRectContainsPoint(aRect, activeField!.frame.origin))
While the activeField's origin may well be above the keyboard, the maxY might not...
I would create a 'max' point for the activeField and check if that is in the keyboard Rect.
Since there were no answer on how to do this in Combine, here is the approach i used.
We create a publisher to listen to both Notifications, show and hide.
For show we get the frame of the keyboard from the notifications userInfo and check if the current active responder is contained in it. If it's covered return keyboard frame height. If it is not covered return 0, we don't want to move the frame. For the hide notification we simply return 0.
private var keyboardHeightPublisher: AnyPublisher<CGFloat, Never> {
Publishers.Merge(
NotificationCenter.default
.publisher(for: UIResponder.keyboardWillShowNotification)
.compactMap { $0.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect }
.map { $0.intersects(self.view.firstResponder!.frame) ? $0.height : 0 }
.map { $0 * -1 },
NotificationCenter.default
.publisher(for: UIResponder.keyboardWillHideNotification)
.map { _ in CGFloat(0) }
).eraseToAnyPublisher()
}
In the viewDidLoad we simply subscribe to the publisher changing the views frame accordingly.
override func viewDidLoad() {
super.viewDidLoad()
keyboardHeightPublisher.sink{ [weak self] height in
self?.view.frame.origin.y = height
}.store(in: &cancelables)
}
EDIT
Be careful! If the firstResponder is in a subview, you have to calculate the frame corresponding to the whole screen to check if they actually intersect.
Example:
let myViewGlobalFrame = myView.convert(myView.frame, to: parentView)
Here's my version after reading the documentation provided by Apple and the previous posts. One thing I noticed is that the textView was not handled when covered by the keyboard. Unfortunately, Apple's documentation won't work because, for whatever reason, the keyboard is called AFTER the textViewDidBeginEditing is called. I handled this by calling a central method that checks if the keyboard is displayed AND if a textView or textField is being edited. This way, the process is only fired when BOTH conditions exists.
Another point with textViews is that their height may be such that the keyboard clips the bottom of the textView and would not adjust if the Top-Left point of the was in view. So, the code I wrote actually takes the screen-referenced Bottom-Left point of any textView or textField and sees if it falls in the screen-referenced coordinates of the presented keyboard implying that the keyboard covers some portion of it.
let aRect : CGRect = scrollView.convertRect(activeFieldRect!, toView: nil)
if (CGRectContainsPoint(keyboardRect!, CGPointMake(aRect.origin.x, aRect.maxY))) {
// scroll textView/textField into view
}
If you're using a navigation controller, the subclass also sets the scroll view automatic adjustment for insets to false.
self.automaticallyAdjustsScrollViewInsets = false
It walks through each textView and textField to set delegates for handling
for view in self.view.subviews {
if view is UITextView {
let tv = view as! UITextView
tv.delegate = self
} else if view is UITextField {
let tf = view as! UITextField
tf.delegate = self
}
}
Simply set your base class to the subclass created here for results.
import UIKit
class ScrollingFormViewController: UIViewController, UITextViewDelegate, UITextFieldDelegate {
var activeFieldRect: CGRect?
var keyboardRect: CGRect?
var scrollView: UIScrollView!
override func viewDidLoad() {
self.automaticallyAdjustsScrollViewInsets = false
super.viewDidLoad()
// Do any additional setup after loading the view.
self.registerForKeyboardNotifications()
for view in self.view.subviews {
if view is UITextView {
let tv = view as! UITextView
tv.delegate = self
} else if view is UITextField {
let tf = view as! UITextField
tf.delegate = self
}
}
scrollView = UIScrollView(frame: self.view.frame)
scrollView.scrollEnabled = false
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
scrollView.addSubview(self.view)
self.view = scrollView
}
override func viewDidLayoutSubviews() {
scrollView.sizeToFit()
scrollView.contentSize = scrollView.frame.size
super.viewDidLayoutSubviews()
}
deinit {
self.deregisterFromKeyboardNotifications()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func registerForKeyboardNotifications()
{
//Adding notifies on keyboard appearing
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ScrollingFormViewController.keyboardWasShown), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ScrollingFormViewController.keyboardWillBeHidden), name: UIKeyboardWillHideNotification, object: nil)
}
func deregisterFromKeyboardNotifications()
{
//Removing notifies on keyboard appearing
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWasShown(notification: NSNotification)
{
let info : NSDictionary = notification.userInfo!
keyboardRect = (info[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue()
adjustForKeyboard()
}
func keyboardWillBeHidden(notification: NSNotification)
{
keyboardRect = nil
adjustForKeyboard()
}
func adjustForKeyboard() {
if keyboardRect != nil && activeFieldRect != nil {
let aRect : CGRect = scrollView.convertRect(activeFieldRect!, toView: nil)
if (CGRectContainsPoint(keyboardRect!, CGPointMake(aRect.origin.x, aRect.maxY)))
{
scrollView.scrollEnabled = true
let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardRect!.size.height, 0.0)
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
scrollView.scrollRectToVisible(activeFieldRect!, animated: true)
}
} else {
let contentInsets : UIEdgeInsets = UIEdgeInsetsZero
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
scrollView.scrollEnabled = false
}
}
func textViewDidBeginEditing(textView: UITextView) {
activeFieldRect = textView.frame
adjustForKeyboard()
}
func textViewDidEndEditing(textView: UITextView) {
activeFieldRect = nil
adjustForKeyboard()
}
func textFieldDidBeginEditing(textField: UITextField)
{
activeFieldRect = textField.frame
adjustForKeyboard()
}
func textFieldDidEndEditing(textField: UITextField)
{
activeFieldRect = nil
adjustForKeyboard()
}
}
Awesome answers are already given but this is a different way to deal with this situation (using Swift 3x):
First of all call the following method in viewWillAppear()
func registerForKeyboardNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWasShown), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillBeHidden), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
Now take one IBOutlet of UIView's top constraints of your UIViewcontroller like this: (here the UIView is the subview of UIScrollView that means you should have a UIScrollView for all your subViews)
#IBOutlet weak var loginViewTopConstraint: NSLayoutConstraint!
And an another variable like following and add a delegate i.e. UITextFieldDelegate:
var activeTextField = UITextField() //This is to keep the reference of UITextField currently active
After that here is the magical part just paste this below snippet:
func keyboardWasShown(_ notification: Notification) {
let keyboardInfo = notification.userInfo as NSDictionary?
//print(keyboardInfo!)
let keyboardFrameEnd: NSValue? = (keyboardInfo?.value(forKey: UIKeyboardFrameEndUserInfoKey) as? NSValue)
let keyboardFrameEndRect: CGRect? = keyboardFrameEnd?.cgRectValue
if activeTextField.frame.origin.y + activeTextField.frame.size.height + 10 > (keyboardFrameEndRect?.origin.y)! {
UIView.animate(withDuration: 0.3, delay: 0, options: .transitionFlipFromTop, animations: {() -> Void in
//code with animation
//Print some stuff to know what is actually happening
//print(self.activeTextField.frame.origin.y)
//print(self.activeTextField.frame.size.height)
//print(self.activeTextField.frame.size.height)
self.loginViewTopConstraint.constant = -(self.activeTextField.frame.origin.y + self.activeTextField.frame.size.height - (keyboardFrameEndRect?.origin.y)!) - 30.0
self.view.layoutIfNeeded()
}, completion: {(_ finished: Bool) -> Void in
//code for completion
})
}
}
func keyboardWillBeHidden(_ notification: Notification) {
UIView.animate(withDuration: 0.3, animations: {() -> Void in
self.loginViewTopConstraint.constant = self.view.frame.origin.y
self.view.layoutIfNeeded()
})
}
//MARK: textfield delegates
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
activeTextField = textField
return true
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
switch textField {
case YOUR_TEXTFIELD_ONE:
YOUR_TEXTFIELD_TWO.becomeFirstResponder()
break
case YOUR_TEXTFIELD_TWO:
YOUR_TEXTFIELD_THREE.becomeFirstResponder()
break
default:
textField.resignFirstResponder()
break
}
return true
}
Now the last snippet:
//Remove Keyboard Observers
override func viewWillDisappear(_ animated: Bool) {
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardDidShow, object: nil)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
Don't forget to assign delegates to all your UITextFields in UIStoryboard
Good luck!
Swift 3 syntax:
func textFieldDidBeginEditing(_ textField: UITextField) {
// add if for some desired textfields
animateViewMoving(up: true, moveValue: 100)
}
func textFieldDidEndEditing(_ textField: UITextField) {
// add if for some desired textfields
animateViewMoving(up: false, moveValue: 100)
}
func animateViewMoving (up:Bool, moveValue :CGFloat){
textFieldDidEndEditing(_ textField: UITextField) {
let movementDuration:TimeInterval = 0.5
let movement:CGFloat = ( up ? -moveValue : moveValue)
UIView.beginAnimations("animateView", context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(movementDuration)
self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)
UIView.commitAnimations()
}
this is a nice method to get what you want
you can add "if" conditions for certain textfields
but this type works for all... Hope it can be useful for everyone
First of all declare a variable to identify your active UITextField.
Step 1:-
Like as var activeTextField: UITextField?
Step 2:-
After this add these two lines in 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)
Step 3:-
Now define these two methods in your controller class.
func keyboardWillShow(_ notification: NSNotification) {
self.scrollView.isScrollEnabled = true
var info = notification.userInfo!
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 let activeField = self.activeField {
if (!aRect.contains(activeField.frame.origin)){
self.scrollView.scrollRectToVisible(activeField.frame, animated: true)
}
}
}
func keyboardWillHide(_ 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)
self.scrollView.isScrollEnabled = true
}
func textFieldDidBeginEditing(_ textField: UITextField){
activeField = textField
}
func textFieldDidEndEditing(_ textField: UITextField){
activeField = nil
}
for swift 4.2.
This will apply to any form. No need of scrollview.
do not forget to set delegate.
Make a var of uitextfield
var clickedTextField = UITextField()
In your viewdid load
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);
Know the clicked text field. probably you are having textfields on the entire screen.
func textFieldDidBeginEditing(_ textField: UITextField) {
clickedTextField = textField
}
Check if the keyboard is covering textfield or not.
#objc func keyboardWillShow(sender: NSNotification,_ textField : UITextField) {
if let keyboardSize = (sender.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
if clickedTextField.frame.origin.y > keyboardSize.origin.y {
self.view.frame.origin.y = keyboardSize.origin.y - clickedTextField.center.y - 20
}
}
}
#objc func keyboardWillHide(sender: NSNotification) {
self.view.frame.origin.y = 0
}
Return to close keyboard
func textFieldShouldReturn(_ textField: UITextField) -> Bool { //delegate method
textField.resignFirstResponder()
return true
}
UPDATE :
NSNotification.Name.UIKeyboardWillShow & NSNotification.Name.UIKeyboardWillHide are renamed to UIResponder.keyboardWillShowNotification & UIResponder.keyboardWillHideNotification respectively.
Swift 3
#IBOutlet var scrollView: UIScrollView!
#IBOutlet var edtEmail: UITextField!
#IBOutlet var bottomTextfieldConstrain: NSLayoutConstraint! // <- this guy is the constrain that connect the bottom of textField to lower object or bottom of page!
#IBAction func edtEmailEditingDidBegin(_ sender: Any) {
self.bottomTextfieldConstrain.constant = 200
let point = CGPoint(x: 0, y: 200)
scrollView.contentOffset = point
}
#IBAction func edtEmailEditingDidEnd(_ sender: Any) {
self.bottomTextfieldConstrain.constant = 50
}
The accepted anwser is nearly perfect. But I need to use UIKeyboardFrameEndUserInfoKey instead of UIKeyboardFrameBeginUserInfoKey, because the latter return keyborad height 0. And change the hittest point to the bottom not origin.
var aRect : CGRect = self.view.frame
aRect.size.height -= keyboardSize!.height
if let activeField = self.activeField {
var point = activeField.frame.origin
point.y += activeField.frame.size.height
if (!aRect.contains(point)){
self.scrollView.scrollRectToVisible(activeField.frame, animated: true)
}
}
Swift 4 Updated my solution
with constraint animation on keyboard show/hide,
enjoy.
import Foundation
import UIKit
class PhoneController: UIViewController, UITextFieldDelegate{
var phoneLayoutYConstraint: NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyBoardNotification(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyBoardNotification(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
phoneField.delegate = self
view.addSubview(phoneField)
NSLayoutConstraint.activate([phoneField.heightAnchor.constraint(equalToConstant: 50),
phoneField.centerXAnchor.constraint(equalTo: view.centerXAnchor),
phoneField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
phoneField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)])
phoneLayoutYConstraint = NSLayoutConstraint(item: phoneField, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0)
phoneLayoutYConstraint?.isActive = true
}
let phoneField: UITextField = {
let text = UITextField()
text.translatesAutoresizingMaskIntoConstraints = false
text.keyboardType = .numberPad
text.font = UIFont.systemFont(ofSize: 30)
text.layer.cornerRadius = 5.0
text.layer.masksToBounds = true
text.layer.borderColor = UIColor.darkGray.cgColor
text.layer.borderWidth = 2.0
return text
}()
override func viewDidDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
func textFieldDidBeginEditing(_ textField: UITextField) {
}
func textFieldDidEndEditing(_ textField: UITextField) {
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
#objc func handleKeyBoardNotification(_ notification: NSNotification) {
if let info = notification.userInfo {
let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size
let isKeyBoardShowing = notification.name == NSNotification.Name.UIKeyboardWillShow
var aRect : CGRect = self.phoneField.frame
aRect.size.height -= keyboardSize!.height
phoneLayoutYConstraint?.constant = isKeyBoardShowing ? -keyboardSize!.height : 0
UIView.animate(withDuration: 0, delay: 0, options: .curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: { (boo) in
})
}
}
}
Swift 4
You Can Easily Move Up And Down UITextField With Keyboard With Animation
import UIKit
class ViewController: UIViewController {
#IBOutlet var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChange), name: .UIKeyboardWillChangeFrame, object: nil)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
textField.resignFirstResponder()
}
#objc func keyboardWillChange(notification: NSNotification) {
let duration = notification.userInfo![UIKeyboardAnimationDurationUserInfoKey] as! Double
let curve = notification.userInfo![UIKeyboardAnimationCurveUserInfoKey] as! UInt
let curFrame = (notification.userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
let targetFrame = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let deltaY = targetFrame.origin.y - curFrame.origin.y
UIView.animateKeyframes(withDuration: duration, delay: 0.0, options: UIViewKeyframeAnimationOptions(rawValue: curve), animations: {
self.textField.frame.origin.y+=deltaY
},completion: nil)
}
Swift 4.2
My solution will (vertically) center the view on a UITextField if its position is under the keyboard.
Step 1: Create new swift file and copy-paste UIViewWithKeyboard class.
Step 2: In Interface Builder set it as a Custom Class for your top most UIView.
import UIKit
class UIViewWithKeyboard: UIView {
#IBInspectable var offsetMultiplier: CGFloat = 0.75
private var keyboardHeight = 0 as CGFloat
private weak var activeTextField: UITextField?
override func awakeFromNib() {
super.awakeFromNib()
NotificationCenter.default.addObserver(self, selector: #selector(UIViewWithKeyboard.textDidBeginEditing),
name: UITextField.textDidBeginEditingNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(UIViewWithKeyboard.keyboardWillShow),
name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(UIViewWithKeyboard.keyboardWillHide),
name: UIResponder.keyboardWillHideNotification, object: nil)
}
#objc func textDidBeginEditing(_ notification: NSNotification) {
self.activeTextField = notification.object as? UITextField
}
#objc func keyboardWillShow(_ notification: Notification) {
if let frameValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
keyboardHeight = frameValue.cgRectValue.size.height
if let textField = self.activeTextField {
let offset = textField.frame.maxY < frame.maxY - keyboardHeight ? 0
: textField.frame.maxY - (frame.maxY - keyboardHeight) * offsetMultiplier
self.setView(offset: offset)
}
}
}
#objc func keyboardWillHide(_ notification: NSNotification) {
self.setView(offset: 0)
}
func setView(offset: CGFloat) {
UIView.animate(withDuration: 0.25) {
self.bounds.origin.y = offset
}
}
}
Rewritten for swift 4.2
In ViewDidLoad..
NotificationCenter.default.addObserver(self, selector: #selector(trailViewController.keyboardWasShown), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(trailViewController.keyboardWillBeHidden), name: UIResponder.keyboardWillHideNotification, object: nil)
Remaining Funtions
func registerForKeyboardNotifications(){
//Adding notifies on keyboard appearing
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
func deregisterFromKeyboardNotifications(){
//Removing notifies on keyboard appearing
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
}
#objc func keyboardWasShown(notification: NSNotification){
//Need to calculate keyboard exact size due to Apple suggestions
self.scrollView.isScrollEnabled = true
var info = notification.userInfo!
let keyboardSize = (info[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size
let contentInsets : UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardSize!.height, right: 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.activeField {
if (!aRect.contains(activeField.frame.origin)){
self.scrollView.scrollRectToVisible(activeField.frame, animated: true)
}
}
}
#objc func keyboardWillBeHidden(notification: NSNotification){
//Once keyboard disappears, restore original positions
var info = notification.userInfo!
let keyboardSize = (info[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size
let contentInsets : UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: -keyboardSize!.height, right: 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
self.view.endEditing(true)
self.scrollView.isScrollEnabled = false
}
func textFieldDidBeginEditing(_ textField: UITextField){
activeField = textField
}
func textFieldDidEndEditing(_ textField: UITextField){
activeField = nil
}
"I forgot to mention that I am new to Swift :( What would be the correct syntax to check this? (how do I get the field name in this function?) "
Ok . First confirm to the UITextFieldDelegate protocol
class YourClass:UITextFieldDelegate
Then implement the function
func textFieldDidBeginEditing(textField: UITextField!) {
if textField == txtOne
{
println("TextOne")
}
if textField == txtTwo
{
println("TextTwo")
}
}
You should note that the proper approach is to use a scrollview and place the view that should be shifted up/down inside the scroll view and handle keyboard events accordingly
This code moves up the text field you are editing so that you can view it in Swift 3 for this answer you also have to make your view a UITextFieldDelegate:
var moveValue: CGFloat!
var moved: Bool = false
var activeTextField = UITextField()
func textFieldDidBeginEditing(_ textField: UITextField) {
self.activeTextField = textField
}
func textFieldDidEndEditing(_ textField: UITextField) {
if moved == true{
self.animateViewMoving(up: false, moveValue: moveValue )
moved = false
}
}
func animateViewMoving (up:Bool, moveValue :CGFloat){
let movementDuration:TimeInterval = 0.3
let movement:CGFloat = ( up ? -moveValue : moveValue)
UIView.beginAnimations("animateView", context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(movementDuration)
self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)
UIView.commitAnimations()
}
And then in viewDidLoad:
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: .UIKeyboardWillShow, object: nil)
Which calls (outside viewDidLoad):
func keyboardWillShow(notification: Notification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
let keyboardHeight = keyboardSize.height
if (view.frame.size.height-self.activeTextField.frame.origin.y) - self.activeTextField.frame.size.height < keyboardHeight{
moveValue = keyboardHeight - ((view.frame.size.height-self.activeTextField.frame.origin.y) - self.activeTextField.frame.size.height)
self.animateViewMoving(up: true, moveValue: moveValue )
moved = true
}
}
}
For Swift 4.2
This code will allow you to control the Y axis moment of the frame for a specific device screen size.
PS: This code will not intelligently move the frame based on the location of TextField.
Create an extension for UIDevice
extension UIDevice {
enum ScreenType: String {
case iPhone4_4S = "iPhone 4 or iPhone 4s"
case iPhones_5_5s_5c_SE = "iPhone 5, iPhone 5s, iPhone 5c or iPhone SE"
case iPhones_6_6s_7_8 = "iPhone 6, iPhone 6s, iPhone 7 or iPhone 8"
case iPhones_6Plus_6sPlus_7Plus_8Plus = "iPhone 6 Plus, iPhone 6s Plus, iPhone 7 Plus or iPhone 8 Plus"
case iPhoneX_Xs = "iPhone X, iPhone Xs"
case iPhoneXR = "iPhone XR"
case iPhoneXSMax = "iPhone Xs Max"
case unknown
}
var screenType: ScreenType {
switch UIScreen.main.nativeBounds.height {
case 960:
return .iPhone4_4S
case 1136:
return .iPhones_5_5s_5c_SE
case 1334:
return .iPhones_6_6s_7_8
case 1920, 2208:
return .iPhones_6Plus_6sPlus_7Plus_8Plus
case 1792:
return .iPhoneXR
case 2436:
return .iPhoneX_Xs
case 2688:
return .iPhoneXSMax
default:
return .unknown
}
}
}
Add NotificationObserver on 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)
Selector
#objc func keyboardWillShow(notification: NSNotification) {
if ((notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue) != nil {
if self.view.frame.origin.y == 0 {
switch (UIDevice.current.screenType.rawValue) {
case (UIDevice.ScreenType.iPhones_5_5s_5c_SE.rawValue):
self.view.frame.origin.y -= 210
case (UIDevice.ScreenType.iPhones_6_6s_7_8.rawValue):
self.view.frame.origin.y -= 110
case (UIDevice.ScreenType.iPhones_6Plus_6sPlus_7Plus_8Plus.rawValue):
self.view.frame.origin.y -= 80
case (UIDevice.ScreenType.iPhoneX_Xs.rawValue):
self.view.frame.origin.y -= 70
case (UIDevice.ScreenType.iPhoneXR.rawValue):
self.view.frame.origin.y -= 70
case (UIDevice.ScreenType.iPhoneXSMax.rawValue):
self.view.frame.origin.y -= 70
default:
self.view.frame.origin.y -= 150
}
}
}
}
#objc func keyboardWillHide(notification: NSNotification) {
if ((notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue) != nil {
if self.view.frame.origin.y != 0 {
switch (UIDevice.current.screenType.rawValue) {
case (UIDevice.ScreenType.iPhones_5_5s_5c_SE.rawValue):
self.view.frame.origin.y += 210
case (UIDevice.ScreenType.iPhones_6_6s_7_8.rawValue):
self.view.frame.origin.y += 110
case (UIDevice.ScreenType.iPhones_6Plus_6sPlus_7Plus_8Plus.rawValue):
self.view.frame.origin.y += 80
case (UIDevice.ScreenType.iPhoneX_Xs.rawValue):
self.view.frame.origin.y += 70
case (UIDevice.ScreenType.iPhoneXR.rawValue):
self.view.frame.origin.y += 70
case (UIDevice.ScreenType.iPhoneXSMax.rawValue):
self.view.frame.origin.y += 70
default:
self.view.frame.origin.y += 150
}
}
}
}

What is the animation speed of the keyboard appearing in iOS8?

The following is an animation for a textField and toolBar which move upward when the keyboard appears.
baseConstraint.constant = 211
self.view.setNeedsUpdateConstraints()
UIView.animateWithDuration(0.30, animations: {
self.view.layoutIfNeeded()
})
It is close but not quite identical. How would you modify the above animation?
Edit:
Here is the final code using the answer below!
func keyboardWillShow(aNotification: NSNotification) {
let duration = aNotification.userInfo.objectForKey(UIKeyboardAnimationDurationUserInfoKey) as Double
let curve = aNotification.userInfo.objectForKey(UIKeyboardAnimationCurveUserInfoKey) as UInt
self.view.setNeedsLayout()
baseConstraint.constant = 211
self.view.setNeedsUpdateConstraints()
UIView.animateWithDuration(duration, delay: 0, options: UIViewAnimationOptions.fromMask(curve), animations: {
self.view.layoutIfNeeded()
}, completion: {
(value: Bool) in println()
})
}
You can get the animation duration and the animation curve from the userInfo dictionary on the keyboardWillShow: notifications.
First register for the notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
Then get the values from the notifications userInfo keys.
- (void)keyboardWillShow:(NSNotification*)notification {
NSNumber *duration = [notification.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey];
NSNumber *curve = [notification.userInfo objectForKey: UIKeyboardAnimationCurveUserInfoKey];
// Do stuff with these values.
}
There are a lot more of these keys, and you can also get them from the UIKeyboardWillDismiss notification.
This functionality is available all the way back to iOS 3.0 :D
Heres the docs:
https://developer.apple.com/documentation/uikit/uiresponder/1621576-keyboardwillshownotification
Swift Version
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
#objc func keyboardWillShow(_ notification: Notification) {
let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey]
let curve = notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey]
}
Updated for latest swift version
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
private func keyboardWillShow(_ notification: Notification) {
let animationDuration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double
let animationCurve = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber
guard let duration = animationDuration, let curve = animationCurve else {
// values weren't available
return
}
let curveAnimationOption = UIView.AnimationOptions(rawValue: curve.uintValue)
UIView.animate(withDuration: duration, delay: 0.0, options: curveAnimationOption, animations: {
// do animations
print("ANIMATING---")
}, completion: { completed in
// completion block
print("COMPLETING---")
})
}
The answer with the variable duration is right and work iOS 3 to 8, but with the new version of Swift, the answer's code is not working anymore.
Maybe it is a mistake on my side, but I have to write:
let duration = aNotification.userInfo![UIKeyboardAnimationDurationUserInfoKey] as Double
let curve = aNotification.userInfo![UIKeyboardAnimationCurveUserInfoKey] as UInt
self.view.setNeedsLayout()
//baseConstraint.constant = 211
self.view.setNeedsUpdateConstraints()
UIView.animateWithDuration(duration, delay: 0.0, options: UIViewAnimationOptions(curve), animations: { _ in
//self.view.layoutIfNeeded()
}, completion: { aaa in
//(value: Bool) in println()
})
Looks like objectForKey is not working anymore and conversion is more strict.
swift3
let duration = noti.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber
let curve = noti.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber
self.view.setNeedsLayout()
UIView.animate(withDuration: TimeInterval(duration), delay: 0, options: [UIViewAnimationOptions(rawValue: UInt(curve))], animations: {
self.view.layoutIfNeeded()
}, completion: nil)
Swift 4 update, iOS 11+
Register for the notification first in a view's lifecycle method :
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: .UIKeyboardWillShow, object: nil)
}
Then in keyBoardWillShow method :
#objc func keyBoardWillShow(notification: NSNotification) {
guard let duration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? Double else {return}
print(duration) // you got animation's duration safely unwraped as a double
}
Finally, don't forget to remove observer in deinit method :
deinit {
NotificationCenter.default.removeObserver(self)
}
Firstly, the selected answer is the right way to go.
More can be provided here is what the animation really is. If you print all CAAnimations in the UIViewAnimation block, you'll find that it's a CASpringAnimation when setting the animation curve to the one provided in keyboard notification. The duration is 0.5, and other parameters are:
let ani = CASpringAnimation(keyPath: someKey)
ani.damping = 500
ani.stiffness = 1000
ani.mass = 3
ani.duration = 0.5
The code above can reproduce the animation precisely.
Once animation curve is set to the keyboard one, UIView animation will ignore the duration in the parameter. (If you really want to change the duration, adjust the mass value.)
Solution for Swift 5
let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as! Double
let curve = notification.userInfo![UIResponder.keyboardAnimationCurveUserInfoKey] as! UInt
UIView.animate(
withDuration: duration,
delay: 0.0,
options: UIView.AnimationOptions(rawValue: curve),
animations: {
self.view.layoutIfNeeded()
}
)
Right solution for Swift 5
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChange), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
and then
#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 targetFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
self.bottomConstraint.constant = UIScreen.main.bounds.height - targetFrame.origin.y
UIView.animate(withDuration: duration) {
self.view.layoutIfNeeded()
}
}
// first of all declare delegate in your class UITextFieldDelegate
//Place at the top of the view controller
// ****************** Keyboard Animation ***************
var animateDistance = CGFloat()
struct MoveKeyboard {
static let KEYBOARD_ANIMATION_DURATION : CGFloat = 0.3
static let MINIMUM_SCROLL_FRACTION : CGFloat = 0.2;
static let MAXIMUM_SCROLL_FRACTION : CGFloat = 0.8;
static let PORTRAIT_KEYBOARD_HEIGHT : CGFloat = 216;
static let LANDSCAPE_KEYBOARD_HEIGHT : CGFloat = 162;
}
//
//Copy and pest textfields delegate method in you class
func textFieldDidBeginEditing(textField: UITextField) {
let textFieldRect : CGRect = self.view.window!.convertRect(textField.bounds, fromView: textField)
let viewRect : CGRect = self.view.window!.convertRect(self.view.bounds, fromView: self.view)
let midline : CGFloat = textFieldRect.origin.y + 0.5 * textFieldRect.size.height
let numerator : CGFloat = midline - viewRect.origin.y - MoveKeyboard.MINIMUM_SCROLL_FRACTION * viewRect.size.height
let denominator : CGFloat = (MoveKeyboard.MAXIMUM_SCROLL_FRACTION - MoveKeyboard.MINIMUM_SCROLL_FRACTION) * viewRect.size.height
var heightFraction : CGFloat = numerator / denominator
if heightFraction > 1.0 {
heightFraction = 1.0
}
let orientation : UIInterfaceOrientation = UIApplication.sharedApplication().statusBarOrientation
if (orientation == UIInterfaceOrientation.Portrait || orientation == UIInterfaceOrientation.PortraitUpsideDown) {
animateDistance = floor(MoveKeyboard.PORTRAIT_KEYBOARD_HEIGHT * heightFraction)
} else {
animateDistance = floor(MoveKeyboard.LANDSCAPE_KEYBOARD_HEIGHT * heightFraction)
}
var viewFrame : CGRect = self.view.frame
viewFrame.origin.y -= animateDistance
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(NSTimeInterval(MoveKeyboard.KEYBOARD_ANIMATION_DURATION))
self.view.frame = viewFrame
UIView.commitAnimations()
}
func textFieldDidEndEditing(textField: UITextField) {
var viewFrame : CGRect = self.view.frame
viewFrame.origin.y += animateDistance
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(NSTimeInterval(MoveKeyboard.KEYBOARD_ANIMATION_DURATION))
self.view.frame = viewFrame
UIView.commitAnimations()
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
I'd like to point out something that tripped me up while solving this issue. I needed the size of the keyboard due to the new "quickype" view and its ability to show/hide (iOS8 only). This is how I ended up solving it:
- (void)keyboardWillChangeFrame:(NSNotification *)notification {
NSValue *value = notification.userInfo[UIKeyboardFrameEndUserInfoKey];
self.keyboardFrame = [value CGRectValue];
NSTimeInterval duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
[UIView animateWithDuration:duration animations:^{
//ANIMATE VALUES HERE
}];
}
//--------------------------------------------------------------
// MARK: -
// MARK: - UITextFieldDelegate
//--------------------------------------------------------------
//To trigger event when user types in fields
//right click in IB and choose EditingChanged >> textField_EditingChanged
//NOTE IF KEYBOARD NOT SHOWING IN SIMULATOR and no view appearing ITS TURNED OFF BY DEFAULT SO YOU CAN TYPE WITH YOUR MAC KEYBOARD - HIT CMD+K or Simulator > Menu > Toggle Software Keyboard...
#IBAction func textField_EditingChanged(textField: UITextField) {
//if more than one search
if(textField == self.textFieldAddSearch){
appDelegate.log.error("self.textFieldAddSearch: '\(self.textFieldAddSearch.text)'")
if textField.text == ""{
}else{
callJSONWebservices(textField.text)
}
}else{
appDelegate.log.error("textFieldDidBeginEditing: unhandled textfield")
}
}
//TWO WAYS TO HIDE THE VIEW
//textFieldShouldReturn
//buttonCancel_Action
//USER HIT RETURN BUTTON ON keyboard >> resignFirstResponder >> triggers keyboardWillHide
func textFieldShouldReturn(textField: UITextField)-> Bool{
//triggers keyboardWillHide: which also fades out view
self.textFieldAddSearch.resignFirstResponder()
return false
}
//--------------------------------------------------------------
// MARK: -
// MARK: - KEYBORAD
//--------------------------------------------------------------
private func subscribeToKeyboardNotifications() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillHide:", name: UIKeyboardWillHideNotification, object: nil)
}
private func unsubscribeFromKeyboardNotifications() {
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWillShow(notification: NSNotification) {
if let userInfo = notification.userInfo {
if let heightKeyboard = userInfo[UIKeyboardFrameEndUserInfoKey]?.CGRectValue().height {
if let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey]?.doubleValue {
self.viewAddNewSearchResults.alpha = 0.0
self.viewAddNewSearchResults.hidden = false
if let curve = userInfo[UIKeyboardAnimationDurationUserInfoKey]?.integerValue {
appDelegate.log.info("keyboardWillShow: duration:\(duration)")
UIView.animateWithDuration(duration, delay:0.0, options: .CurveEaseInOut,
animations: {
//self.view.frame = CGRectMake(0, 0, Geo.width(), Geo.height() - height)
self.viewAddNewSearchResults_BottomConstraint.constant = heightKeyboard;
self.viewAddNewSearchResults.alpha = 1.0
},
completion: nil)
}
}
}
}
}
func keyboardWillHide(notification: NSNotification) {
if let userInfo = notification.userInfo {
if let heightKeyboard = userInfo[UIKeyboardFrameEndUserInfoKey]?.CGRectValue().height {
if let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey]?.doubleValue {
if let curve = userInfo[UIKeyboardAnimationDurationUserInfoKey]?.integerValue {
appDelegate.log.info("keyboardWillHide: duration:\(duration)")
UIView.animateWithDuration(duration, delay:0.0, options: .CurveEaseInOut,
animations: {
self.viewAddNewSearchResults_BottomConstraint.constant = 0;
self.viewAddNewSearchResults.alpha = 0.0
},
completion: nil)
}
}
}
}
}
//Add button shows search result panel below search text fields
//just set focus in the textField
//then the keyboardWillShow will fade in the view and resize it to fit above the keyboard
//and match fade in duration to animation of keyboard moving up
#IBAction func buttonAdd_Action(sender: AnyObject) {
//triggers keyboardWillHide: which also fades out view
self.textFieldAddSearch.resignFirstResponder()
}
//TWO WAYS TO HIDE THE VIEW
//textFieldShouldReturn
//buttonCancel_Action
//Cancel on the search results - just resignFirstResponder >> triggers keyboardWillHide: which also fades out view
#IBAction func buttonCancel_Action(sender: AnyObject) {
//triggers keyboardWillHide: which also fades out view
self.textFieldAddSearch.resignFirstResponder()
}
Swift 4
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: .UIKeyboardWillShow, object: nil)
func keyboardWillShow(notification: NSNotification) {
let duration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey]
print("duration",duration)
}

Resources