Xcode fetch keyboard plus suggestions height - swift

I'm having an issue getting the correct height of the keyboard. It does not return the keyboard plus suggestions height.
Below is some sample code
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeShown(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
Then I have a utility method which returns keyboard frame from notification. Code looks like this
var userInfo = notification.userInfo!
var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
if keyboardFrame.size.height <= 0 { // to fix bug on iOS 11
keyboardFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
}
keyboardFrame = view.convert(keyboardFrame, from: nil)
The keyboard is not returning the keyboard height + suggestions
I require this as I'm doing some UI testing on iPhone 5 and on certain screens the keyboard clips the textfield. So I have this code to check if it overlaps
// fetches keyboard frame
let keyboardFrame = UtilityMethods.getKeyboardFrame(notification: notification, view: view)
let fieldFrame = lastNameField!.frame
// check if keyboard frame overlaps with field frame
if(keyboardFrame.intersects(fieldFrame)){
let newY = view.frame.origin.y - (fieldFrame.origin.y - keyboardFrame.height)
UtilityMethods.animateViewMoving(viewToAnimate: view, newYValue: newY)
}
But because it is not returning the height of the keyboard + suggestions, the if statement fails. The only way this works is if I select another text field while keyboard is open, that then becomes first responder, then it returns keyboard height properly.
You can see when I print out the keyboard height
So when select fextfield, this height is printed out
216.0
Then if I select second text field, this height is then printed out
253.0
How do I make sure I'm always getting the keybaord height that includes the suggestions?
Any help would be much appreciated.

Related

scrollView problem with UITextView when keyboard hide/show notification

i have multiple textField and textView in scrollView in my viewController. i handle keyboard show and hide with these codes:
i added these line of code in viewDidLoad:
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name:UIResponder.keyboardWillChangeFrameNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name:UIResponder.keyboardWillHideNotification, object: nil)
and also these 2 function:
#objc func keyboardWillShow(notification:NSNotification){
guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardScreenEndFrame = keyboardValue.cgRectValue
let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
let bottom = keyboardViewEndFrame.height - view.safeAreaInsets.bottom + 16
self.scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: bottom , right: 0)
}
#objc func keyboardWillHide(notification:NSNotification){
self.scrollView.contentInset = UIEdgeInsets.zero
}
everything is ok when i start to edit a textField. but it does not work with textView and has problem to scroll to active textView.
how can i fix it?
The reason of this issue is explained here, so if you want to use UITextView inside a UIScrollView then uncheck the Scrolling Enabled from right menu inspector or set it False from the code.

table views only scrolls to textfields bottom edge

The bottommost cell of my table view is a cell with a textField. When the user taps it, I want to scroll it so that the cell is right above the keyboard.
When I call the scrollRectToVisible(...) with animated false everything works as expected, but when animated is set to true the table scrolls the cell only so far, that the bottom of the textField is right above the keyboard (See left picture). Yet the bottonInsets should be correct, since I can scroll the cell the last bit manually and the cell sits right how it should (See right picture).
I think the table view scrolling the textField's bottom edge above the keyboard is the default behavior of a table view, but I'm afraid I don't know why it seems to override my own scrolling when I want it animated.
Left picture:
The textFields bottom edge right above the keyboard (I kept the border style so you can see it better).
Right picture:
How I want it. Cell's bottom edge right above the keyboard.
func repositionTextfieldCell(in tableView: UITableView) {
guard let textFieldCell = tableView.bottommostCell() else { return }
guard let keyboardRect = activeKeyboardRect else { return }
// - Adjust insets
var bottomInset = keyboardRect.size.height
tableView.contentInset.bottom = bottomInset
tableView.scrollIndicatorInsets.bottom = bottomInset
// - Make cell visible
let x = textFieldCell.frame.minX
let y = textFieldCell.frame.maxY
tableView.scrollRectToVisible(CGRect(origin: CGPoint(x: x, y: y),
size: CGSize(width: 1, height: 1)), animated: true)
}
add this in viewDidLoad() and create a NSlayout constraint for tableview bottom.
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillShow),
name: NSNotification.Name.UIKeyboardWillShow,
object: nil
)
create the function
#objc func keyboardWillShow(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
tableBottomConstraint.constant = self.view.frame.height - keyboardHeight
}
}
repeat the process to reset the tableBottomConstraint.constant = 0 in keyboardWillHide() method.
I could fix the problem.
The behavior seems to be depended on were scrollRectToVisible(...) is called. The behavior I described in the question occurs when scrollRectToVisible(...) is called in keyboardDidShow(...).
However when you call scrollRectToVisible(...) in keyboardWillShow(...) and set animated to false the cell / rect is pushed up by the keyboard sliding in. Which I think looks great.

UI Elements Moving When Character Typed In Text Field

When a user selects the bottom text field all elements in the view move up by the height of the keyboard until the user dismisses the keyboard.
The problem occurs after the user selects the bottom text field. Initially the elements all move up correctly. However, when the first character is typed by the user all of the elements move back down to the original position. As a result, the user can no longer see the text field they are editing. Once the user dismisses the keyboard they see the elements go up where they should have been, and then come right back down to the original position.
I have narrowed down what is causing the issue, but I do not understand why it is happening. My project can be found on GitHub at https://github.com/JMNolan/memesicle
I am still very new to Swift and fairly new to coding in general, so all help is really appreciated. I have also included the code snippets below that are relevant to this process for anyone who does not want to use GitHub
This is what moves the elements up the height of the keyboard
//moves the view up when the keyboard appears to keep the text field
visible
#objc func keyboardWillShow (notification: NSNotification){
if bottomTextFieldActive == true{
keyboardHeight = getKeyboardHeight(notification:
notification)
imagePickerView.frame.origin.y -= keyboardHeight
topText.frame.origin.y -= keyboardHeight
bottomText.frame.origin.y -= keyboardHeight
toolbar.frame.origin.y -= keyboardHeight
}
}
This is what moves the elements back down the height of the keyboard when the keyboard is dismissed
//moves the view down when the keyboard is dismissed to show the full view again
#objc func keyboardWillHide (notification: NSNotification){
imagePickerView.frame.origin.y += keyboardHeight
topText.frame.origin.y += keyboardHeight
bottomText.frame.origin.y += keyboardHeight
toolbar.frame.origin.y += keyboardHeight
//shareButton.frame.origin.y += keyboardHeight
print("keyboard just hid")
}
This is what calls the function above
func subscribeToKeyboardNotifications(){
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)
}
This is how I get the keyboardHeight used when moving the elements up and down the height of the keyboard.
//get the height of the keyboard to determine how far up to move the view when editing the bottom text field
func getKeyboardHeight(notification: NSNotification) -> CGFloat {
let userinfo = notification.userInfo
let keyboardSize = userinfo![UIKeyboardFrameEndUserInfoKey] as!NSValue
return keyboardSize.cgRectValue.height
}
The reason your screen elements are jumping around when you type in the UITextField is because of your view constraints. When you add constraints to your view, you kind of forfeit the ability to later move the subviews around by altering their frames - each time the view recalculates the location of its subviews, it will go back to using the original values as provided by the constraints and ignore the frame changes you made.
Since you've got a few different screen elements you want to move, the easiest way to solve this is to put all your subviews (text field, image view, etc) inside a UIScrollView and then resize the scroll view's content inset property when the keyboard comes up. This would completely replace your code that changes the frame sizes. Apple has a help document about how to do this, but the important parts are below:
Adjusting your content typically involves temporarily resizing one or more views and positioning them so that the text object remains visible. The simplest way to manage text objects with the keyboard is to embed them inside a UIScrollView object or one of its subclasses, like UITableView. Note that UITableViewController automatically resizes and repositions its table view when there is inline editing of text fields (to learn more, see View Controllers and Navigation-Based Apps).
When the keyboard is displayed, all you have to do is reset the content area of the scroll view and scroll the desired text object into position. Thus, in response to a UIKeyboardDidShowNotification, your handler method would do the following:
Get the size of the keyboard.
Adjust the bottom content inset of your scroll view by the keyboard height.
Scroll the target text field into view.
// Called when the UIKeyboardDidShowNotification is sent.
- (void)keyboardWasShown:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);
scrollView.contentInset = contentInsets;
scrollView.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.
CGRect aRect = self.view.frame;
aRect.size.height -= kbSize.height;
if (!CGRectContainsPoint(aRect, activeField.frame.origin) ) {
[self.scrollView scrollRectToVisible:activeField.frame animated:YES];
}
}
// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
UIEdgeInsets contentInsets = UIEdgeInsetsZero;
scrollView.contentInset = contentInsets;
scrollView.scrollIndicatorInsets = contentInsets;
}
I faced the same issue, and after read shocking's answer, I found a solution. Thank shocking
I overrode updateViewConstraints() method, and inside, I updated constraint for TextView bottom to View bottom, with constant = keyboard height.
override func updateViewConstraints() {
writeCommentView.autoPinEdge(toSuperviewEdge: .bottom, withInset: keyboardHeight) //I use PureLayout library
super.view.updateConstraints()
}
And then, I call updateViewConstraints() in keyboardWillShow(), keyboardWillHide()
#objc func keyboardWillShow(notification: NSNotification) {
//Code for move up textview here
self.updateViewConstraints()
}

view move up in particular textfield delegate

I have to move the UIView in only last UITextField in Swift 3.0 on mentioned below delegate method using tag,
func textFieldDidBeginEditing(_ textField: UITextField) {
if (textField.tag == 4){
//UIView Up
}
}
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
if (textField.tag == 4){
//UIView Down
}
return true
}
I tried many codes but none of them are working like notification,..etc.
You need to add Observers into the NotificationCenter for listening to both when Keyboard goes up and down (i'll assume your textfield outlet is lastTextField for this example to work but this obviously have to be adapted to whatever name you've had provide for it)
IBOutlet weak var passwordTextField: UITextField!
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: .UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: .UIKeyboardWillHide, object: nil)
(Code above can be added in viewDidLoad())
Then you add methods to be executed when those notifications arrive, like this:
func keyboardWillShow(_ notification:Notification) {
if view.frame.origin.y >= 0 && lastTextField.isFirstResponder {
view.frame.origin.y -= getKeyboardHeight(notification)
}
}
func keyboardWillHide(_ notification:Notification) {
if view.frame.origin.y < 0 {
view.frame.origin.y += getKeyboardHeight(notification)
}
}
Validations within those methods prevent double execution like moving up/down twice when moving between textfields without resigning first responder which is common in cases like your (i assume your doing this for a form hence the clarification you only need it for the fourth textfield). Notice i'm only doing validation in for the specified textfield (with its outlet lastTextField) in the keyboardWillShow method, this in case you move thor another textfield while the keyboard is shown and resign responder from it in which case, even though it isn't the original place where you started, the view will return to its original place when the keyboard is hidden.
You'll also need a method for getting keyboard's height, this one can help with that:
func getKeyboardHeight(_ notification:Notification) -> CGFloat {
let userInfo = notification.userInfo
let keyboardSize = userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue // of CGRect
return keyboardSize.cgRectValue.height
}
Let me know how it goes but i just tested this same code on my app and it works so you should be fine.
PS: pay close attention to the storyboard (if you're using it) and that delegate for textfields are set up properly.
The problem you are trying to remedy is rather complicated, because it requires you to:
Find the textField which is firstResponder
Calculate where that TextField is relative to it's superViews
Determine the distance for the animation, so that the containing
superview doesnt jump out of the window, or jumps too
much/repeatedly
Animate the proper superView.
As you can see.. it's quite the algorithm. But luckily, I can help. However, this only works for a hierarchy which has the following layout:
superView (view in the case of UIViewController) > (N)containerSubviews > textFields
where N is an integer
or the following:
superView (view in the case of UIViewController) > textFields
The idea is to animate superView, based on which textField is firstResponser, and to calculate if it's position inside of the SCREEN implies that it either partially/totally obstructed by the Keyboard or that it is not positioned the way you want for editing. The advantage to this, over simply moving up the superView when the keyboard is shown in an arbitrary manner, is that your textField might not be positioned properly (ie; obstructed by the statusbar), and in the case where your textfields are in a ScrollView/TableView or CollectionView, you can simply scroll the texfield into the place you want instead. This allows you to compute that desired location.
First you need a method which will parse through a given superView, and look for which of it's subViews isFirstResponder:
func findActiveTextField(subviews : [UIView], textField : inout UITextField?) {
for view in subviews {
if let tf = view as? UITextField {
guard !tf.isFirstResponder else {
textField = tf; break
return
}
} else if !subviews.isEmpty {
findActiveTextField(subviews: view.subviews, textField: &textField)
}
}
}
Second, to aleviate the notification method, also make a method to manage the actual animation:
func moveFromDisplace(view: UIView, keyboardheight: CGFloat, comp: #escaping (()->())) {
//You check to see if the view passed is a textField.
if let texty = view as? UITextField {
//Ideally, you set some variables to animate with.
//Next step, you determine which textField you're using.
if texty == YourTextFieldA {
UIView.animate(withDuration: 0.5, animations: {
self./*the proper superView*/.center.y = //The value needed
})
comp()
return
}
if texty == YourTextFieldB {
// Now, since you know which textField is FirstResponder, you can calculate for each textField, if they will be cropped out by the keyboard or not, and to animate the main view up accordingly, or not if the textField is visible at the time the keyboard is called.
UIView.animate(withDuration: 0.5, animations: {
self./*the proper superView*/.center.y = //The Value needed
})
comp()
return
}
}
}
Finally, the method which is tied to the notification for the keyboardWillShow key; in this case, i have a UIViewController, with an optional view called profileFlow containing a bunch of UITextFields
func searchDisplace(notification: NSNotification) {
guard let userInfo:NSDictionary = notification.userInfo as NSDictionary else { return }
guard let keyboardFrame:NSValue = userInfo.value(forKey: UIKeyboardFrameEndUserInfoKey) as? NSValue else { return }
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
let keybheight = keyboardHeight
var texty : UITextField? //Here is the potential textfield
var search : UISearchBar? //In my case, i also look out for searchBars.. So ignore this.
guard let logProfile = profileFlow else { return }
findActiveTextField(subviews: [logProfile], textField: &texty)
//Check if the parsing method found anything...
guard let text = texty else {
//Found something.. so determine if it should be animated..
moveFromDisplace(view: searchy, keybheight: keybheight, comp: {
value in
search = nil
})
return
}
//Didn't find anything..
}
Finally, you tie in this whole logic to the notification:
NotificationCenter.default.addObserver(self, selector: #selector(searchDisplace(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
I can't provide more content to the code, since it all depends on your view hierarchy, and how you want things to animate. So it's up to you to figure that out.
On a side note, usually, if you have so many textfields that to lay them out properly means they overstep the length of the screen.. it's probable that you could simplify your layout. A way to make this algorithm better would be to make sure you have all your textfields in one containing view, which again can become heavy for when, say, you use AutoLayout constraints. Odds are if you're in this situation, you can probably afford to add a flow of several views etc.
There is also the fact that i've never really needed to use this for iPhone views, more for iPad views, and even then for large forms only (e-commerce). So perhaps if you're not in that category, it might be worth reviewing your layout.
Another approach to this, is to use my approach, but to instead check for specific textFields right in the findActiveTextField() method if you only have a handful of textfields, and to animate things within findActiveTextField() as well if you know all of the possible positions they can be in.
Either way, i use inout parameters in this case, something worth looking into if you ask me.

How to set textview height to space left when keyboard appears (Swift)

I have been searching for ages for a way to set the height of my textview to the leftover space after the keyboard appears. The keyboard never hides and is up permanently.
How am I able to place the textview in the space at the top of the screen when the keyboard is up.
I am desperate for an answer as nothing I have found has worked, and I also need it in Swift, not Objective-C as I think one of the reasons I haven't been able to get it working is because I have been translating the code wrong.
Thanks!
Finally found a solution.
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)
textView.contentInset = contentInsets
}
}
This is how you find the height of the onscreen keyboard:
onscreen keyboard height stackoverflow
next I assume your textView is covering the whole view of parent viewController.
make your viewController conform to UITextViewDelegate and implement this method ->
func textViewDidBeginEditing(textView: UITextView) -> Bool{
textView.bounds.size.height = view.bounds.size.height - KEYBOARDHEIGHT(method from that link of stackoverflow)
}
also in the viewDidLoad :
yourTextView.delegate = self