How can I check if the keyboard is blocking my view?
I have UIButton that is in the centre of the screen. I also have a UITextfield that makes the keyboard appears. When I run the app on iPhone 4 the keyboard block the button, but on other models, it doesn't. I have a method that scrolls up the view when the keyboard appears. But I only want to scroll up in case the view is blocked. I can check the model of the iPhone and then decide if to scroll or not, but I thought checking if the Button is blocked would be better. How can I do it?
I solved this problem in an app like so:
Embed the UI for that screen in a UIScrollView, but configure it so that scrolling is not enabled (scrollEnabled property from code, checkbox if you're using a storyboard).
When the keyboard notification is received, get the frame from the button, then call scrollRectToVisible:animated: on the scroll view. It'll move the content the minimum amount necessary to make the button visible, which will be not at all if the screen is big enough.
Using the UIKeyboardWillShowNotification, you can get the height of the keyboard like this:
NSValue *keyboardRect = [notification.userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey];
CGFloat keyboardHeight = MIN(keyboardRect.CGRectValue.size.width, keyboardRect.CGRectValue.size.height);
The get the relevant "lowest" point of your button. (like buttonMaxY = CGRectGetMaxY(yourButton.frame)).
Use the scroll methods you have implemented, but scroll only if necessary: keyboardHeight+buttonMaxY > the height of the screen.
When keyboard is about to appear, a UIKeyboardWillShowNotification notification is posted with the frame of the keyboard. You can calculate and see if the text field frame intersects with the keyboard frame, and scroll.
See documentation on keyboard notifications here.
Following Leo Natan's explanation, you can try something like:
func keyboardWillShow(notification: Notification) {
guard let keyboardFrame = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? CGRect,
let viewFrame = myView.frame else {
log.error("I cannot calculate keyboard & view frame")
return
}
if keyboardFrame.intersects(viewFrame) {
// view covered by Keyboard
}
}
pay attention to use UIKeyboardFrameEndUserInfoKey, you can check this link for further details.
Related
In UIScrollView, there is a feature named "Keyboard Dismiss interactively"
By using such option, this enables me to implement the following drag down to hide keyboard
However, the keyboard dismiss operation only kick start, when the UIScrollView drag action touches keyboard edge.
What I would like to achieve is, the keyboard dismiss operation kick start, when the UIScrollView drag operation touches the bottom toolbar edge.
What I wish to achieve (Same as WhatsApp)
As you can see from the video, the keyboard dismiss operation will kick start, when the drag operation touches the bottom bar edge, even before touching keyboard edge.
May I know, what technique WhatsApp is using, to achieve such behavior?
Side note
You may notice our bottom toolbar does move along with keyboard. This is because there is a bottom constraint for bottom toolbar's bottom with Safe Area's bottom.
We adjust the bottom constraint's constant value, by installing a gesture recognizer in global Window. This is the code snippet to achieve such technique.
#objc private func didPan(_ sender: UIPanGestureRecognizer){
if keyboardHeight > 0 {
let mainScrollView = editable.mainScrollView
let isScrolling = (mainScrollView.isDragging || mainScrollView.isDecelerating)
if isScrolling {
if let mainScrollViewGlobalOrigin = mainScrollView.globalOrigin {
let point = sender.location(in: sender.view!)
// Take safe area into consideration, like iPhone 12 Pro Max.
let key = UIWindow.key
let bottomSafeArea = key?.safeAreaInsets.bottom ?? 0
let dy = point.y - (
mainScrollViewGlobalOrigin.y +
mainScrollView.frame.height +
toolbarHeightLayoutConstraint.constant +
bottomSafeArea -
bottomLayoutConstraint.constant -
self.keyboardHeight
)
if dy > 0 {
bottomLayoutConstraint.constant = -(keyboardHeight - dy)
}
}
}
}
}
The reason that WhatsApp behaves like this is that their view is considered to be part of the keyboard, so when the swipe gesture reaches their custom view it will begin interactive dismissal.
To achieve this yourself all you need to do is provide the toolbar view as the inputAccessoryView for your view controller. You won't need the constraints for positioning as the keyboard window would then control your toolbar's position.
There is also inputAccessoryViewController for the times where your toolbar may not be a UIView, but instead an entire UIViewController.
The views in either of these properties will only be visible when the keyboard is visible, so to get around that you'll still want to put it into your view hierarchy, but remove/add it based on becoming/resigning first responder.
EDIT: Also, you should be using UIApplication.keyboardDidChangeFrameNotification to detect when the keyboard changes size/position/etc and allow you to adjust insets/positions of views appropriately. In modern iOS there are plenty of ways the keyboard can change size while open, and observing that notification is the correct way to handle the keyboard size.
I have a screen with various elements, including a UITextField, and I want to show coach marks on the screen (I'm using the Instructions library). However, I need to freeze interactions with the elements when the coach marks are displayed (so the user can't type stuff in the text field, for example).
My problem is with the keyboard (which is always displayed in this screen): if I just set the text field's isEnabled property to false, or set isUserInteractionEnabled = false, the field resigns first responder and the keyboard disappears, which is unnatural for this screen. If, however, I use the textField:shouldChangeCharactersIn range: delegate method and return false, the keyboard remains visible, but enabled. So when the coach marks appear, the user can tap around the keyboard. While this has no effect on the text field, it's still weird and annoying.
So how do I temporarily keep the keyboard up, but have it disabled (preferably behind the coach marks blur view)? I saw this, but that doesn't provide a solution that I think Apple would accept.
Thanks,
Yariv.
You can add a UIButton dynamically when the keyboard is visible. You can add keyboardDidShow & keyboardDidHide observer to know when the keyboard is up and visible.
You might face a problem related to keyboard being top most on screen but, you can deal with it by making the newly added UIButton the top most on your current view/window.
Don't forget to set userInteractionEnabled to false for that UIButton
Comment to your question by #MikeAlter would be the best (and clean) solution. But, that also means that you don't have any control with navigating/interacting to other controls on your screen.
EDIT
Keyboard has specific region on screen covered when shown. My solution suggested that you can add a UIButton on your view with the exact rectangle/frame same as of keyboard. A view/button that covers the keyboard overall. So, your keyboard will not take any user interaction.
The following code can get you size of keyboard. You can add a UIButton of same size in exactly same place which will make your keyboard inactive as user interaction will be taken care by the button itself.
- (void)keyboardDidShow:(NSNotification *)notification {
CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
int height = MIN(keyboardSize.height,keyboardSize.width);
int width = MAX(keyboardSize.height,keyboardSize.width);
... Add UIButton here with the CGRect of keyboard
}
You need to register to UIKeyboardDidShowNotification for above to work, like following:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardDidShow:)
name:UIKeyboardDidShowNotification
object:nil];
If you want to make keyboard behind coach marks blur view then add coach marks blur view to last window:
UIApplication.shared.windows.last?.addSubview(coachMarksBlurView)
UIApplication.shared.keyWindow?.bringSubview(toFront: coachMarksBlurView)
And if you want to block keyboard you can use these two functions:
func addBlockView(){
view.addSubview(viewBlock)
viewBlock.frame = CGRect.init(x: 0, y: 0, w:
UIScreen.main.bounds.size.width, h:
UIScreen.main.bounds.size.height)
viewBlock.backgroundColor = UIColor.clear
UIApplication.shared.windows.last?.addSubview(viewBlock)
UIApplication.shared.keyWindow?.bringSubview(toFront: viewBlock)
}
func removeBlockView(){
viewBlock.removeFromSuperview()
}
I have a UIViewController that displays a form with several text fields. In order to prevent the text fields from getting blocked by the keyboard, I resize the controller's view when the keyboard appears and disappears.
However, when the keyboard is up, the user presses the home button, and then returns to the app, the controller's view will be resized again to the size it was before the keyboard was up and the keyboard will still be showing.
What's causing my controller's view to be resized on return from background, and how can I prevent it?
Maybe you need to nest a UIView,for example
_backgroundView = [UIView new];
_backgroundView.backgroundColor = [UIColor whiteColor];
_backgroundView.frame = CGRectZero;
[self.view addSubview:_backgroundView];
[_backgroundView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.top.mas_equalTo(self.view);
make.height.mas_equalTo(self.view.mas_height);
}];
then you need add your custom UIView to this backgroundView.
as you said,UIViewController's view will be resized after return from background. so you can nest a UIView of the same size as self.view,and add your custom UIView to this UIView.
In order to prevent the text fields from getting blocked by the keyboard, you can resize this backgroundView when the keyboard appears and disappears. and this time when you click the home button to enter the background or return from background,self.view won't be resized and backgroundView won't be resized too.
Although it is a bit of a hassle, this will solve your problem and will not affect the user experience anymore. And if you have a better solution, please let me know
It sounds like you are setting the frame and not using autolayout. When the view reappears viewDidLayoutSubviews gets called and your frame gets recalculated obliterating your previous change. You can either:
1) Move your frame to viewDidLayoutSubviews and change its size only if the keyboard is showing.
2) Use autolayout and simply pull up your bottom constraint .constant by an amount equal to your keyboard height.
In both cases you should call layoutIfNeeded to trigger autolayout/viewDidLayoutSubviews when the keyboard appears/disappears. This behavior is a good example of why you should not manipulate your frames outside of viewDidLayoutSubviews except for transitory animations.
My app has two views, one in the top half of the screen, one in the bottom half of the screen. The top half view is called mainView, and the bottom half is called tableView. I have a UITextField in the mainView at the bottom of the mainView. So, I am getting the difference between the tableView's height and the keyboard so I can move the mainView up/down enough so that the keyboard will be directly below the uitextfield. The code I am using below works on iphone 4s and iphone 5, but anything bigger leaves a little bit of whitespace between the textfield and the keyboard. How can I fix this?
func keyboardShown(notification: NSNotification) {
let info = notification.userInfo!
let value: AnyObject = info[UIKeyboardFrameEndUserInfoKey]!
let rawFrame = value.CGRectValue
let keyboardFrameHeight = view.convertRect(rawFrame, fromView: nil).height
let bottomSpace = tableView.bounds.height
let spaceDifference = (keyboardFrameHeight - bottomSpace)
view.layoutIfNeeded()
self.mainViewProportionalHeight.constant -= spaceDifference
animLayout(0.5)
}
To reiterate:
mainView = top half of the view
tableView = bottom half of the view
There is a UITextField attached to the bottom of the mainView, so that the bottom of the mainview = bottom of the mainView. Basically, i am trying to align the mainView right above the keyboard without any whitespace in between.
The easiest way I've found to solve your problem is with the CocoaPod, IQKeyboardManager.
You just have to add it to your podfile and then run pod install and it takes care of positioning your keyboard right below whatever text field your writing in. (Even ones that the keyboard would normally cover)
Here's a link to it: https://cocoapods.org/?q=iqkeyboardmanager
I am trying to show a UITextField on top of the iPad keyboard.
I was able to get the height of the keyboard when it was presented with the notification.
However, in iPad, by change the language input of the keyboard -> most likely to Japanese, the height of the keyboard changed because a text-hypothesis area was shown on top of the keyboard, that caused my UITextfield hidden by that area....
Does anybody know how can I get the height changed notification or any other way?
The answer is that when you switch languages, the UIKeyboardDidShowNotification fires for each change, so you always get the updated height.
See my answer here on how to set up responses to the showing and hiding, and getting the height.
Swift
The UIKeyboardDidShowNotification won't fire anymore the keyboard size change.
Use UIKeyboardWillChangeFrameNotification instead:
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(instance.keyboardWillChange(_:)), name:UIKeyboardWillChangeFrameNotification, object: nil)
at the function:
let targetSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue()
Important: this event will be fired also when keyboard will open and will hide, can replace both UIKeyboardWillShowNotification and UIKeyboardWillHideNotification if only sizes are needed