okay so this question is similar to this one , but i followed answer of that question but i didn't worked , so the thing is that i have a Textfield in my view and i want to move it up when keyboard appears this is my code :
Notification observer for keyboard's state
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(LaunchScreenViewController.keyboardWillShow(_:)), name: UIKeyboardWillShowNotification, object: nil)
function for getting the keyboard's height
func keyboardWillShow(notification:NSNotification) {
let userInfo:NSDictionary = notification.userInfo!
duration = (notification.userInfo![UIKeyboardAnimationDurationUserInfoKey] as! Double)
let keyboardFrame:NSValue = userInfo.valueForKey(UIKeyboardFrameEndUserInfoKey) as! NSValue
let keyboardRectangle = keyboardFrame.CGRectValue()
keyboardHeight = keyboardRectangle.height
}
my animation
func textFieldDidBeginEditing(textField: UITextField) {
self.nextButtonConstraint.constant = keyboardHeight
UIView.animateWithDuration(duration) {
self.nextButton.layoutIfNeeded()
self.emailTextField.layoutIfNeeded()
}
}
as you can see that my animation is on textFieldDidBeginEditing because according to similar question's answer putting it there will solve the problem , but still on first run (when keyboard appears for the first time) my animation is not smooth
I think the solution may be quite simple.
Try to put all you Code inside of this:
DispatchQueue.main.async{
// Your Code here
}
This moves the animation to the main Thread, which should speed it up and avoid the unsmooth animation.
Related
I'm moving a UIView up when the keyboard is called, works fine in the simulator but when i run the code on an actual device for whatever reason the UIKeyboardWillShow notification is called twice. I am not using any custom keyboards.
In the viewDidLoad method i call this method.
func registerKeyBoardNotifications(){
NotificationCenter.default.addObserver(self, selector: #selector(keyBoardWillAppear(notification:)), name: .UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(notification:)), name: .UIKeyboardWillHide, object: nil)
}
Then remove these observers in viewWillDisappear.
#objc func keyBoardWillAppear(notification: NSNotification){
if let userInfo = notification.userInfo,
let endFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue,
let beginFrame = userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue,
beginFrame.isEqual(to: endFrame) == false{
let keyboardSize = endFrame.cgRectValue
self.view.frame.origin.y -= keyboardSize.height - keyboardConstant
signUpButton.isEnabled = false
}
}
the keyboardWillAppear handler is called twice on a physical device but once in the simulator, spent the last 2 days trying to figure this out.
Xcode 9.4.1 swift 4.1
This can happen and there is no documentation that guarantees just one notification.
So you should just deal with it in your code and not substract the keyboard height every time. A smarter solution would be to calculate your view frame depending on keyboard's end frame.
The right approach, as you've been told, is to ask yourself the right question. For example, is it the case that previously the keyboard was not covering my view, but now it is? That is the occasion that should be regarded as "entering".
I have a utility function that works out the geometry based on the notification's userInfo dictionary and the bounds of the view we're concerned with. If the keyboard wasn't within the view's bounds and now it will be, it is entering; if it was within the view's bounds and now it won't be, it is exiting. We return that information, along with the keyboard's frame in the view's bounds coordinates:
enum KeyboardState {
case unknown
case entering
case exiting
}
func keyboardState(for d:[AnyHashable:Any], in v:UIView?)
-> (KeyboardState, CGRect?) {
var rold = d[UIResponder.keyboardFrameBeginUserInfoKey] as! CGRect
var rnew = d[UIResponder.keyboardFrameEndUserInfoKey] as! CGRect
var ks : KeyboardState = .unknown
var newRect : CGRect? = nil
if let v = v {
let co = UIScreen.main.coordinateSpace
rold = co.convert(rold, to:v)
rnew = co.convert(rnew, to:v)
newRect = rnew
if !rold.intersects(v.bounds) && rnew.intersects(v.bounds) {
ks = .entering
}
if rold.intersects(v.bounds) && !rnew.intersects(v.bounds) {
ks = .exiting
}
}
return (ks, newRect)
}
When the keyboard shows, we check whether it is entering, and respond accordingly:
#objc func keyboardShow(_ n:Notification) {
let d = n.userInfo!
let (state, rnew) = keyboardState(for:d, in:myView)
if state == .entering {
// ...
}
}
i seem to have figured out why the UIkeyboardWillShow Notification fires twice on the physical device and not the simulator. It has to do with the keyboard suggestions above the keyboard when you set certain Text Input traits. Any text input trait that causes a suggestion above the keyboard will cause the notification to fire twice instead of once, i'm guessing once for the keyboard itself and again for the suggestion bar above the keyboard. (in my case because there were no suggestions on the simulator it fired once in the simulator, but fired twice on the device because there were suggestions on a real device). This is a weird case thing that doesn't seem to be documented.
One way to deal with this (the route i chose) is to disable all suggestions by setting all Text Input traits to default and the content type of the text field to Username.
The other solution is to account for the second UIkeyboardWillShowNotifcation notification in your code before adjusting the frame of the UIView you're trying move.
I have an textview at bottom of screen and search bar at top of screen. Following is my code to solve the problem of keyboard when textview is pressed
extension UIView {
func bindToKeyboard(){
NotificationCenter.default.addObserver(self, selector: #selector(UIView.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 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.frame.origin.y += deltaY
},completion: {(true) in
self.layoutIfNeeded()
})
}
}
But when I press search bar then the screen moves up and search bar disappears. If I do view.bindToKeyboard() then the edittext is proper after displaying the keyboard.
One solution which I tried was binding the outlet of textview to keyboard but the textview disappears as soon as I start typing.
I think the problem is that you are trying to know when the keyboard is going to appear. Searchbar has a textfield. So when it's tapped it opens the keyboard like your textview and the keyboardWillChange is called.
keyboardWillChange(_ notification: NSNotification)
So the keyboard appears and hides your searchbar. You can detect searchbar tap and cancel the notification there.
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool
{
//Dismiss your keyboard notification here
return true
}
You should use a library to handle this situation. I'm suggesting IQKeyboardManager. IQKeyBoardManager Github.
I edit my earlier respond... have you try to put your searchBar into navigationBar?
something like this (just put on viewDidLoad):
navigationItem.titleView = searchBar
I am dealing with the problem of moving the view when the keyboard covers an element that just gained the first responder. I started by looking at this question and it gave me a great head start.
After adding the observers to UIKeyboardWillShow and UIKeyboardWillHide I ended with the following code:
func keyboardWillShow(notification: Notification) {
guard let userInfo = notification.userInfo,
let kbRect = userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue,
let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? Double else {
return
}
let kbSize = kbRect.cgRectValue.size
UIView.animate(withDuration: duration) {
self.view.transform = CGAffineTransform(translationX: 0, y: -kbSize.height)
}
}
func keyboardWillHide(notification: Notification) {
UIView.animate(withDuration: 0.3) {
self.view.transform = CGAffineTransform(translationX: 0, y: 0)
}
}
It works fine in the sense that the view moves up and down when a text field gains the first responder.
However, when a key is pressed, the view moves again and defeats the purpose of moving it up on the first place.
I made a little GIF to better describe this undesired behavior, the first time the keyboard appears and disappears shows the correct behavior, the second time, when a key is pressed, shows the undesired one.
So, the question is, is there a way I could prevent the view movement when a key is pressed? I would like the view to stay "up" when the user is using the keyboard to insert text.
You should move view app, not transform it, use:
self.view.frame.origin.y -= kbSize.height
transforming should behave like it is right now
Set your UIView as a Outlet and insert this code in viewDidLoad
yourView.translatesAutoresizingMaskIntoConstraints = true
I have looked around and found this post about moving a view when a keyboard appears. It works great and moves the keyboard anytime I click in a UITextField. The issue is that I have three UITextFields, and the keyboard should only move when it is going to present over a UITextField. I looked at the Apple documentation on this as well, and found some useful information but I am still not getting the desired functionality.
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
var aRect = self.view.frame;
aRect.size.height -= keyboardSize.size.height
if self.view.frame.origin.y == 0{
if aRect.contains(activeField.frame.origin){
self.view.frame.origin.y -= keyboardSize.height
}
}
}
}
func textFieldDidBeginEditing(_ textField: UITextField) {
activeField = textField
}
func textFieldDidEndEditing(_ textField: UITextField) {
activeField = nil
}
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
}
}
}
From the Apple documentation I just took the piece where I create the aRect, and then check if the points intersect with the contains function. I would expect this to then make the view move only when the keyboard were to overlap with a textfield, and keep the view in place otherwise. For some reason that I don't fully understand, this is not the case. The keyboard will move the view in the case where any textfield is clicked (even though for some it shouldn't). I have played around with it a bit now and tried debugging but have been unsuccessful. Any ideas?
EDIT: I did a little debugging and it seems that the aRect.contains(...) is returning true for when all textfields are clicked, but in reality it should not. Is contains the right method to be using?
I followed this way to manage such issue in TableView same way you can manage in your view Here is step by step code:
within viewDidLoad added registerForKeyboardNotifications()
Here is the method
func registerForKeyboardNotifications() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWasShown:", name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillBeHidden:", name: UIKeyboardWillHideNotification, object: nil)
}
Again define other method :
func keyboardWasShown(aNotification: NSNotification) {
let info = aNotification.userInfo as! [String: AnyObject],
kbSize = (info[UIKeyboardFrameBeginUserInfoKey] as! NSValue).CGRectValue().size,
contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: kbSize.height, right: 0)
electricalViewListTableview.contentInset = contentInsets
electricalViewListTableview.scrollIndicatorInsets = contentInsets
// If active text field is hidden by keyboard, scroll it so it's visible
// Your app might not need or want this behavior.
var aRect = self.view.frame
aRect.size.height -= kbSize.height
if let activeTF = activeField {
if !CGRectContainsPoint(aRect, activeTF.frame.origin) {
electricalViewListTableview.scrollRectToVisible(activeTF.frame, animated: true)
}
}
}
Keyboard Hiding Method :
func keyboardWillBeHidden(aNotification: NSNotification) {
let contentInsets = UIEdgeInsetsZero
electricalViewListTableview.contentInset = contentInsets
electricalViewListTableview.scrollIndicatorInsets = contentInsets
}
After this use UITextFieldDelegates method to keep track active textfield :
var activeField: UITextField?
func textFieldDidBeginEditing(textField: UITextField) {
self.activeField = textField
}
func textFieldDidEndEditing(textField: UITextField) {
self.activeField = textField
}
Hope it helps!
You have two main issues with your keyboardWillShow code.
You are using the wrong key to get the keyboard frame. You need UIKeyboardFrameEndUserInfoKey, not UIKeyboardFrameBeginUserInfoKey. You want to know where the keyboard will end up, not where it starts from.
Once you get the keyboard's frame, you need to convert it to local coordinates. It is given to you in screen coordinates.
Your updated code would be:
func keyboardWillShow(notification: NSNotification) {
if let keyboardScreenFrame = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
let keyboardLocalFrame = self.view.convert(keyboardScreenFrame, from: nil)
var aRect = self.view.frame;
aRect.size.height -= keyboardLocalFrame.size.height
if self.view.frame.origin.y == 0 {
if aRect.contains(activeField.frame.origin) {
self.view.frame.origin.y -= keyboardSize.height
}
}
}
}
You also have a big problem with your keyboardWillHide method. Since you keyboardWillShow method always shortens your view's frame, your keyboardWillHide method also needs to always restore the view's frame height.
If I were you, I wouldn't change the view's frame height in either method. Just adjust its origin as needed to make the text field visible.
Try IQKeyboardManager . It automatically manages text fields to make them visible. You just need to add it to your project, and no need to write even one line of code. A piece from it's documentation:
Often while developing an app, We ran into an issues where the iPhone
keyboard slide up and cover the UITextField/UITextView.
IQKeyboardManager allows you to prevent issues of the keyboard sliding
up and cover UITextField/UITextView without needing you to enter any
code and no additional setup required. To use IQKeyboardManager you
simply need to add source files to your project.
EDIT: In addition to Rmaddy's answer, I can say you should consider changing if aRect.contains(activeField.frame.origin) to if !aRect.contains(activeField.frame), because the first check will return true even if the top of your textfield is in the frame of the view, and also, you should be checking if it doesn't contain the frame of your textfield, then change the frame of the view.
And, I'm not totally sure, but, maybe it would be better if you move your activeField = textField code to the textFieldShouldBeginEditing delegate method.
I am trying to offset UITextField whenever the keyboard is active, it works well, until I tried the Emoji-layout. Is there a way to detect the type of Keyboard-input, so I can find out the height-difference?
Thanks
Instead of using the UIKeyboardDidShowNotification/UIKeyboardDidHideNotification observers, use the UIKeyboardWillChangeFrameNotification observer, that is fired of each event: Keyboard hiding, keyboard showing and keyboard changing frame.
Like this:
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(keyboardAction), name: UIKeyboardWillChangeFrameNotification, object: nil)
You can use the keyboard notifications
func getKeyboardHeight() {
let defaultCenter = NSNotificationCenter.defaultCenter()
defaultCenter.addObserver(self, selector: "keyboardWillChangeFrame:", name: UIKeyboardWillChangeFrameNotification, object: nil)
}
func keyboardWillChangeFrame(notification : NSNotification){
let keyboardFrame = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
let keyboardheight = keyboardFrame.height
}
And from the obtained height you can adjust the textfield's frame.
see the images.
image 1 before emoji is selected.
image 2 after emoji is selected
Thanks guys for your help, i have found the answer:
I was using this line :
if let keyboardSize = (notifcation.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue().size{
I changed to the key-value of notifcation.userInfo to UIKeyboardFrameEndUserInfoKey, and that fixed the issue.