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.
Related
I know that there are several questions on this topic, but i have not been able to find any on [SWIFT 5.0] or more, and would really appreciate your help.
I have a UITextFeild at the bottom of the screen which gets hidden every time the user clicks on it. Is there a recent method on which i can solve this hiding issue, by either the textFeild following the keyboard to the top or a sample field appearing on top of the keyboard. Any help would be greatly appreciated, thank you!
You can use the following code:
First, add these two lines of code in your controller viewDidLoad() method:
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
Second, add these two methods inside your controller:
#objc func keyboardWillShow(notification: NSNotification) {
if yourTextfield.isEditing == true
{
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
self.view.frame.origin.y -= keyboardSize.height
}
}
}
#objc func keyboardWillHide(notification: NSNotification) {
if self.view.frame.origin.y != 0 {
self.view.frame.origin.y = 64
}
}
Enjoy :).
If you use AutoLayout you may try Keyboard and KeyboardLayoutGuide from my collection of handy Swift extensions and classes called SwiftToolkit
Add this code to your controller viewDidLoad() method:
let keyboardGuide = KeyboardLayoutGuide()
view.addLayoutGuide(keyboardGuide)
/**
Your textField vertical position constraint
must have lower priority than this constraint
*/
keyboardGuide.topAnchor.constraint(greaterThanOrEqualTo: textField.bottomAnchor, constant: 8).isActive = true
When the keyboard appears it will push your UITextField up like this:
This is my view controller.
I am trying to move the textFields up while the keyboard is covering textFields of the bottom of the screen. Here is the code I've done :
override func viewDidLoad() {
super.viewDidLoad()
txtFName.delegate = self
txtLName.delegate = self
txtCompany.delegate = self
txtStreet1.delegate = self
txtStreet2.delegate = self
txtTown.delegate = self
txtPin.delegate = self
txtPhone.delegate = self
txtEmail.delegate = self
NotificationCenter.default.addObserver(
self,
selector: #selector(self.keyBoardWillChange(notification:)),
name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(self.keyBoardWillChange(notification:)),
name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(self.keyBoardWillChange(notification:)),
name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
deinit {
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
print("Return Tapped")
txtFName.resignFirstResponder()
txtLName.resignFirstResponder()
txtCompany.resignFirstResponder()
txtStreet1.resignFirstResponder()
txtStreet2.resignFirstResponder()
txtTown.resignFirstResponder()
txtPin.resignFirstResponder()
txtPhone.resignFirstResponder()
txtEmail.resignFirstResponder()
view.frame.origin.y = 0
return true
}
#objc func keyBoardWillChange(notification: Notification) {
print("Keyboard will show: \(notification.name.rawValue)")
view.frame.origin.y = -250
}
Now while I am tapping on any of the textFields, the whole view is moving up. "txtFName", "txtLName".. these textFields are not being visible.
I want to move up the view only when I would tap on "txtPin", "txtPhone", "txtEmail". Rest textfields would remain in the default position even when the keyboard appears.
what the required changes are?
Generally you can use your isFirstResponder property on your text field. When true, it means that this is the text field you wish to focus on and move upwards when it starts being edited.
#objc private func keyBoardWillChange(notification: Notification) {
let possibleTextFields: [UITextField] = [txtLName, txtCompany, txtStreet1, txtStreet2, txtTown, txtPin, txtPhone, txtEmail]
let focusedTextField: UITextField? = possibleTextFields.first { $0.isFirstResponder }
...
}
Now that you have that you would still need to calculate the offset that is needed to move your view
Getting the frame of keyboard:
guard let info = notification.userInfo else { return }
guard let value: NSValue = info[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
let newFrame = value.cgRectValue
Computing the difference
There are tools to check frames which allow you to convert frames to different coordinate systems (views)
private func getVerticalDifference(keyboardFrame: CGRect, viewToFocus: UIView, panel: UIView) -> CGFloat {
let keyboardInPanel = panel.convert(keyboardFrame, from: nil)
let viewInPanel = panel.convert(viewToFocus.bounds, from: viewToFocus)
return viewInPanel.maxY - keyboardInPanel.minY
}
I suggest you use the view of your view controller for your panel parameter it should be something that is not being changed (Which is not the case in your current code. Avoid changing frame of the view of your view controller).
Applying the difference:
To apply the difference I suggest that you use constraints. Put all your text fields on a single "panel" view. This view may best also put into a scroll view so user may scroll through it on smaller devices. Now panel (or scroll view) can have a low priority (500 for instance) bottom constraint to view controller. Then another high priority bottom constraint set to greaterThan meaning that bottom will be fixed to "greater than X" where X can be setup later.
Now you can drag an outlet to your code from this greaterThan constraint. And then all you need to do is
bottomConstraintOutlet.constant = max(0.0, getVerticalDifference(...))
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 written a functionscrollToVisible() to scroll text in UItextview because some part of text is covered by the keyboard, or the cursor isn't in visible. But UItextview can scroll the text automatically when the cursor is not in the whole view but not visible, it can still be covered by keyboard by automatically scroll.The UItextview's auto scroll can interrupt my scrollToVisible().
Thus, can I ban the UItexview to scroll automatically? Or another way to solve "keyboard cover" problem?
My scrollToVisible() function
func scrollToVisible()
{
let cursortop = self.EditArea.convert(self.EditArea.caretRect(for: (self.EditArea.selectedTextRange?.start)!).origin, to: self.view)
var cursorbottom = cursortop
cursorbottom.y += self.EditArea.caretRect(for: (self.EditArea.selectedTextRange?.start)!).height
let bottom = UIScreen.main.bounds.size.height - self.EditArea.textContainerInset.bottom
var contentOffset = self.EditArea.contentOffset
if cursortop.y <= 85
{
contentOffset.y = contentOffset.y - 85 + cursortop.y
self.EditArea.setContentOffset(contentOffset, animated: true)
}
else if cursorbottom.y >= bottom
{
contentOffset.y = contentOffset.y - bottom + cursorbottom.y
self.EditArea.setContentOffset(contentOffset, animated: true)
}
}
PS:this EditArea is the textview
I have a similar problem: when you open the keyboard, the text view is not adjusted and the cursor hides behind the keyboard (or as you say "covers" the cursor). So if I hit enter to start a new line, it also doesn't visibly auto scroll (actually it does, but it's behind the keyboard). I found a solution, which works perfectly for me on this website: https://www.hackingwithswift.com/example-code/uikit/how-to-adjust-a-uiscrollview-to-fit-the-keyboard
Solution extracted from above website using swift 4:
Subscribe for the events when the keyboard appears and disappears in your viewDidLoad() function:
// For avoiding that the text cursor disappears behind the keyboard, adjust the text for it
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: .UIKeyboardWillHide, object: nil)
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: .UIKeyboardWillChangeFrame, object: nil)
Adjust the textview using this function, add it anywhere in your class:
// Adjusts the textView, so that the text cursor does not disappear behind the keyboard
#objc func adjustForKeyboard(notification: Notification) {
let userInfo = notification.userInfo!
let keyboardScreenEndFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
if notification.name == Notification.Name.UIKeyboardWillHide {
textView.contentInset = UIEdgeInsets.zero
} else {
textView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height, right: 0)
}
textView.scrollIndicatorInsets = textView.contentInset
let selectedRange = textView.selectedRange
textView.scrollRangeToVisible(selectedRange)
}
Here is my solution, where you need not worry to handle any textfield/textview in the app by writing just one line of code in app delegate
If you are using pods, the you can add "IQKeyboardManager" pods by just adding the following pods
pod 'IQKeyboardManagerSwift'
and add this line in didFinishLaunchingWithOptions in app delegate
IQKeyboardManager.sharedManager().enable = true
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.