Trying to move view when keyboard is appear - some bugs - ios

I'm trying to move my whole view upwards to be able to see what the user is typing in a textfield. However, it works perfectly the first time as I want it to. However, when clicking on return key on the keyboard (to dismiss it), and then clicking on the textfield, I get the keyboard slightly over the textfield and not getting the same results as I do the first time. Why is this?
This is my code:
#IBOutlet weak var commentTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
commentTextField.delegate = self
//Keyboard listeners
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardDidHide, object: nil)
}
#objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y == 0 {
self.view.frame.origin.y -= keyboardSize.height
}
}
}
#objc func keyboardWillHide(notification: NSNotification) {
if self.view.frame.origin.y != 0 {
self.view.frame.origin.y = 0
}
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
commentTextField.resignFirstResponder()
return true
}

Replace
UIKeyboardFrameBeginUserInfoKey
with
UIKeyboardFrameEndUserInfoKey

You are miss-calling the Variable.
UIKeyboardFrameBeginUserInfoKey
The key for an NSValue object containing a CGRect that identifies the
starting frame rectangle of the keyboard in screen coordinates. The
frame rectangle reflects the current orientation of the device.
UIKeyboardFrameEndUserInfoKey
The key for an NSValue object containing a CGRect that identifies the
ending frame rectangle of the keyboard in screen coordinates. The
frame rectangle reflects the current orientation of the device.
Therefore, what you need is to check the UIKeyboardFrameEndUserInfoKey instead of UIKeyboardFrameBeginUserInfoKey.
Do the substitution and you may get it solved.

Related

Keep a view always on top (Don't scroll with keyboard) in IQKeyboardManager

I'm using IQKeyboardManager to keep the text fields to go up after typing with the keyboard.
I don't want to scroll to a specific view even when clicked on the text field. Below is the screenshot of the design. I want the 'header' to remain on top.
From their documentation, there is a way to keep the navigation bar remain on top.
Disable the IQKeyboardManager for your ViewController.
for that,
IQKeyboardManager.sharedManager().disableInViewControllerClass(ViewController.self)
And In that viewController write the following code. It will move your view up as per keyboard height
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y == 0 {
self.view.frame.origin.y -= keyboardSize.height
}
}
}
func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y != 0 {
self.view.frame.origin.y += keyboardSize.height
}
}
}
Now you want your "HEADER" view remain on TOP then,
Do like this :
**
YourViewController.view -> [headerView][contentView]
**
Put textfield in [contentView] And change [contentView].y instead of Self.view in above code.
Disable the IQKeyboardManager for your viewController:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
IQKeyboardManager.sharedManager().enable = false
NotificationCenter.default.addObserver(self, selector: #selector(Login.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(Login.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
Handle keyboard:
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y == 0{
self.table_view.frame.origin.y -= keyboardSize.height
}
}
}
func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y != 0{
self.table_view.frame.origin.y += keyboardSize.height
}
}
}
Remove observer:
override func viewWillDisappear(animated: Bool) {
IQKeyboardManager.sharedManager().enable = true
NSNotificationCenter.defaultCenter().removeObserver(self)
}
Answers from #Wolverine and #Bhavin Ramani were great: the best way to keep your custom header staying at top is to manually handle your keyboard (according to IQKeyboardSwift author's comment). If you use iOS default navigation bar, it seems it's handled for you by the library.
Here I just want to share some updates on this topic, for my future reference, as the answers are a bit old and some Swift syntax has changed. Code below is written in Xcode 13.2, targeting iOS 13+.
Firstly, you want to disable KQKeyboardManager by doing
IQKeyboardManager.shared.enable = false
Please note this lines only disables moving text field up feature, other IQKeyboard features such as resign on touch outside, auto tool bar, and etc., are not disabled by this line, this is usually what you want.
Then, you register keyboard events observer in view controller's viewDidLoad, remove observers in deinit.
override func viewDidLoad() {
super.viewDidLoad()
IQKeyboardManager.shared.enable = false
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillShow),
name: UIResponder.keyboardWillShowNotification,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillHide),
name: UIResponder.keyboardWillHideNotification,
object: nil)
}
deinit {
IQKeyboardManager.shared.enable = true
NotificationCenter.default.removeObserver(self)
}
Next, add view moving up/down methods for keyboard show/hide.
#objc private func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect {
print("keyboardSize.height", keyboardSize.height)
// using the right key here is important, because
// keyboardFrameEndUserInfoKey is an user info key
// to retrieve the keyboard’s frame at the END of its animation.
// here you move up the views you need to move up
// if you use auto layout, update the corresponding constraints
// or you update the views' frame.origin.y
// you may want to do the updates within a 0.25s animation
}
}
#objc private func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? CGRect {
// reset views to their original position on keyboard dismiss
}
}
You may also want to enable/disable auto tool bar, as it could make your keyboard height unstable.
// in viewDidLoad set to false, in deinit set back to true (if you need it)
IQKeyboardManager.shared.enableAutoToolbar = false

How to setup the keyboard if l'am using two text fields

I've been trying to set up my keyboard since a few days ago, but I can't manage to do it. I just want the imageView to move up when I am writing in the bottom text field. In fact, it's working, but it also moves up when I try to write at the topTextField. I don't want that, because when that happens, I can't see the textField at the top, and I can't see what I'm writing.
I'll include my screenshots and my code.
In this image, I pressed the topTextField to write something, but as you can see, the topTextField is lost. I mean the view moves up when I press the topTextField and I don't want that. What I want is so that when I press the topTextField the keyboard should appear but the view should be at the same place.
And in the last one I pressed the textFieldBottom, and as you can see, it works. The view moves up so I can see what I'm writing inside the textFieldBottom.
Here is my code:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.subscribeToKeyboardNotifications()
self.subscribeToKeyboardNotificationsDown()
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
self.unsubscribeToKeyBoardNotifications()
self.unsubscribeToKeyBoardNotificationsDown()
}
func subscribeToKeyboardNotifications() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.keyboardWillShow(_:)) , name: UIKeyboardWillShowNotification, object: nil)
}
func unsubscribeToKeyBoardNotifications() {
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
}
func keyboardWillShow(notification: NSNotification) {
view.frame.origin.y -= getKeyboardHeight(notification)
}
func subscribeToKeyboardNotificationsDown() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.keyboardWillHide(_:)), name: UIKeyboardWillHideNotification, object: nil)
}
func unsubscribeToKeyBoardNotificationsDown() {
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWillHide(notification: NSNotification) {
view.frame.origin.y += getKeyboardHeight(notification)
}
func getKeyboardHeight(notification:NSNotification) -> CGFloat {
let userInfo = notification.userInfo
let keyboardSize = userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue
return keyboardSize.CGRectValue().height
}
// do one thing maintain one globle varible to save current textfield which is on editing mode based on that varible u put condition to move imageview
following example code will help you.
var currentTextField: UITextField
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
currentTextField = textField
return true
}
func keyboardWillShow(notification: NSNotification) {
if currentTextField == textFieldBottom {
view.frame.origin.y -= getKeyboardHeight(notification)
}
}
When topTextField becomes first responder, you're changing view.frame.origin, this is moving your topTextField up. Instead of changing view frame, you can set constraints for bottomTextField and you can change constraints for bottomTextField programmatically in keyboardWillShow method. This way your view frame will not change and topTextField will not move up.

Move a view up when the keyboard covers an input field but with leaving some space between them

Description
I have a few UITextField which I want to scroll up, when one of them is covered by keyboard during edition. There are a tons of answers here on SO with many flavors: moving a view (by changing its frame), modifying a constraint, using UIScrollView and UITableView, or using UIScrollView and modifying contentInset.
I decided to use the last one. This one is also described by Apple, and has a Swift version on SO as well as being described on this blog including a sample project on the GitHub.
Partial code
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillBeHidden:", name: UIKeyboardWillHideNotification, object: nil)
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func textFieldDidEndEditing(textField: UITextField) {
self.activeField = nil
}
func textFieldDidBeginEditing(textField: UITextField) {
self.activeField = textField
}
func keyboardWillShow(notification: NSNotification) {
if let activeField = self.activeField, keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
let contentInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardSize.height, right: 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
var aRect = self.view.frame
aRect.size.height -= keyboardSize.size.height
if (!CGRectContainsPoint(aRect, activeField.frame.origin)) {
self.scrollView.scrollRectToVisible(activeField.frame, animated: true)
}
}
}
func keyboardWillBeHidden(notification: NSNotification) {
let contentInsets = UIEdgeInsetsZero
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
}
Issue
I want just one simple modification - a little more space between the keyboard and an edited field. Because out of the box it looks like this:
I modified CGRect in scrollRectToVisible call, but it changed nothing. What's more, even commenting out the line with scrollRectToVisible had no effect at all - everything worked exactly as before (including scrolling up the content). Checked on iOS 9.2
Replicating, if needed, is trivial - just download the working code using the GitHub link above and comment out the scrollRectToVisible line.
Tested workarounds
Workarounds I tried, but didn't like the final effect:
Increasing contentInset - user could scroll up more then the contentSize
Replacing UIKeyboardDidShowNotification with UIKeyboardWillShowNotification, add another UIKeyboardDidShowNotification observer with just scrollRectToVisible inside - it works, but then there are two scroll up animations, which doesn't look good.
Questions
Why changing contentInset (without scrollRectToVisible call) scrolls content in the scrollView? I've not see in any docs information about such behavior
And more important - what to do, to scroll it up a little more, to have some space between an edited text field and the keyboard?
What did I miss? Is there some easy way to fix it? Or maybe it's better to play with scrollView.contentSize, instead of contentInset?
I haven't found why scrollRectToVisible doesn't work in the scenario described above, however I found other solution which works. In the mentioned Apple article Managing the Keyboard at the very bottom there is a hint
There are other ways you can scroll the edited area in a scroll view
above an obscuring keyboard. Instead of altering the bottom content
inset of the scroll view, you can extend the height of the content
view by the height of the keyboard and then scroll the edited text
object into view.
Solution below is based exactly on extending the height of the content view (scrollView.contentSize). It takes into account orientation change and scrolling back when keyboard is being hidden. Works as required - has some space between the active field and the keyboard, see the image below
Working code
I've placed the full working code on the GitHub: ScrollViewOnKeyboardShow
var animateContenetView = true
var originalContentOffset: CGPoint?
var isKeyboardVisible = false
let offset : CGFloat = 18
override func viewDidLoad() {
super.viewDidLoad()
for case let textField as UITextField in contentView.subviews {
textField.delegate = self
}
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: #selector(ViewController.keyboardWillBeShown(_:)), name: UIKeyboardWillShowNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(ViewController.keyboardWillBeHidden(_:)), name: UIKeyboardWillHideNotification, object: nil)
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func textFieldDidEndEditing(textField: UITextField) {
self.activeField = nil
}
func textFieldDidBeginEditing(textField: UITextField) {
self.activeField = textField
}
func keyboardWillBeShown(notification: NSNotification) {
originalContentOffset = scrollView.contentOffset
if let activeField = self.activeField, keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
var visibleRect = self.scrollView.bounds
visibleRect.size.height -= keyboardSize.size.height
//that's to avoid enlarging contentSize multiple times in case of many UITextFields,
//when user changes an edited text field
if isKeyboardVisible == false {
scrollView.contentSize.height += keyboardSize.height
}
//scroll only if the keyboard would cover a bottom edge of an
//active field (including the given offset)
let activeFieldBottomY = activeField.frame.origin.y + activeField.frame.size.height + offset
let activeFieldBottomPoint = CGPoint(x: activeField.frame.origin.x, y: activeFieldBottomY)
if (!CGRectContainsPoint(visibleRect, activeFieldBottomPoint)) {
var scrollToPointY = activeFieldBottomY - (self.scrollView.bounds.height - keyboardSize.size.height)
scrollToPointY = min(scrollToPointY, scrollView.contentSize.height - scrollView.frame.size.height)
scrollView.setContentOffset(CGPoint(x: 0, y: scrollToPointY), animated: animateContenetView)
}
}
isKeyboardVisible = true
}
func keyboardWillBeHidden(notification: NSNotification) {
scrollView.contentSize.height = contentView.frame.size.height
if var contentOffset = originalContentOffset {
contentOffset.y = min(contentOffset.y, scrollView.contentSize.height - scrollView.frame.size.height)
scrollView.setContentOffset(contentOffset, animated: animateContenetView)
}
isKeyboardVisible = false
}
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animateAlongsideTransition(nil) { (_) -> Void in
self.scrollView.contentSize.height = self.contentView.frame.height
}
}
deinit {
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
notificationCenter.removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
}

Recieve two different height of keyboard after rotation

I have a view which is resize a constraint when the keyboard appear. So I have notifications when the keyboard appears and disappears.
The above behaviour occurs when the keyboard is already shown and I rotate the screen. Then the next actions occurs:
UIKeyboardWillHideNotification called
UIKeyboardWillShowNotification called (with the old height of the keyboard)
UIKeyboardWillShowNotification called (with the new height of the keyboard)
So, the updateView function receives first one height and later a different height. This results in a weird movements of the view adjusting twice the value.
override func viewDidLoad() {
super.viewDidLoad()
// Creates notification when keyboard appears and disappears
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, name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWillShow(notification: NSNotification) {
self.adjustingHeight(true, notification: notification)
}
func keyboardWillHide(notification: NSNotification) {
self.adjustingHeight(false, notification: notification)
}
private func adjustingHeight(show: Bool, notification: NSNotification) {
// Gets notification information in an dictionary
var userInfo = notification.userInfo!
// From information dictionary gets keyboard’s size
let keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).CGRectValue()
// Gets the time required for keyboard pop up animation
let animationDurarion = userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSTimeInterval
// Animation moving constraint at same speed of moving keyboard & change bottom constraint accordingly.
if show {
self.bottomConstraint.constant = (CGRectGetHeight(keyboardFrame) + self.bottomConstraintConstantDefault / 2)
} else {
self.bottomConstraint.constant = self.bottomConstraintConstantDefault
}
UIView.animateWithDuration(animationDurarion) {
self.view.layoutIfNeeded()
}
self.hideLogoIfSmall()
}
Finally I found the solution. When I get keyboardFrame, I was using UIKeyboardFrameBeginUserInfoKey which returns the frame of the keyboard before the animation begins. The correct way of do that is with UIKeyboardFrameEndUserInfoKeywhich returns the frame of the keyboard after the animation has completed.
let keyboardFrame: CGRect = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()

How to move view if only a certain textView is selected

Hi I am using this code to move my view when a textView is selected, this is to make sure my texView is visible for when the keyboard pops up.
override func viewDidLoad() {
super.viewDidLoad() NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name:UIKeyboardWillShowNotification, object: self.view.window)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name:UIKeyboardWillHideNotification, object: self.view.window)
}
func keyboardWillHide(sender: NSNotification) {
let userInfo: [NSObject : AnyObject] = sender.userInfo!
let keyboardSize: CGSize = userInfo[UIKeyboardFrameBeginUserInfoKey]!.CGRectValue.size
self.view.frame.origin.y += keyboardSize.height
}
func keyboardWillShow(sender: NSNotification) {
let userInfo: [NSObject : AnyObject] = sender.userInfo!
let keyboardSize: CGSize = userInfo[UIKeyboardFrameBeginUserInfoKey]!.CGRectValue.size
let offset: CGSize = userInfo[UIKeyboardFrameEndUserInfoKey]!.CGRectValue.size
if keyboardSize.height == offset.height {
if self.view.frame.origin.y == 0 {
UIView.animateWithDuration(0.1, animations: { () -> Void in
self.view.frame.origin.y -= keyboardSize.height
})
}
} else {
UIView.animateWithDuration(0.1, animations: { () -> Void in
self.view.frame.origin.y += keyboardSize.height - offset.height
})
}
print(self.view.frame.origin.y)
}
override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: self.view.window)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: self.view.window)
}
How can i only move the view if the bottom textView is selected? Because currently if you select the uppermost textView it moves half of it off screen.
I appreciate any help, thanks in advance.
In the notification callbacks check for bottomTextView.isFirstResponder() and move the view only if its true. Otherwise don't move the view.
Make your class a UITextViewDelegate like so:
class ViewController: UIViewController, UITextViewDelegate {
Then in viewDidLoad set only the bottom textView to be controlled by the delegate:
bottomTextView.delegate = self
Then you can use these functions, changing the values to suit your needs:
func textViewDidBeginEditing(textView: UITextView) {
self.view.frame.origin.y -= 150
}
func textViewDidEndEditing(textView: UITextView) {
self.view.frame.origin.y += 150
}
Rather than only moving the keyboard for a single text field, I suggest a more flexible approach.
What I do is to keep track of which text field is being selected and do some calculations to move the view controller's content view up just enough to expose the text field that is becoming active.
To do that you need to set up your view controller to be the text fields' delegates.
I have a development blog post that explains this in detail:
Shifting views to make room for the keyboard
The code in that post is in Objective-C, but the concepts are identical. I'm not sure I have the same code in Swift that I can share (The only code I've found is in a project I did for a client.)
That blog post references a project called RandomBlobs on Github.
I would suggest using a delegate. For example, you can set you bottom textView's delegate to self (add UITextViewDelegate first of course) and implement the
textViewDidBeginEditing(_:) method. When that's triggered, you will know that the user started editing this particular view.

Resources