On iPhone, I found when keyboard shows up, the system will set contentInset of tableView to UIEdgeInsets(0, 0, 216, 0) , because the keyboard height is 216. I think this is convienent when design an app only for the newest iOS, in the past, I have to calculate tableView size when keyboard came up by myself.
But I have to support iOS5, so I wanna know how to disable this automatic "favor" for me ? If I set
self.searchTipsController.tableView.contentInset = UIEdgeInsetsZero; , the scroll indicator will not show. At last I have to detect system version to do handle it separately.
And I want to know from what version this feature begins? 6.0 or 6.1?
- (void) keyboardSizeChange:(NSNotification *)aNotification
{
NSDictionary* d = [aNotification userInfo];
CGRect r = [[d objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
if(r.origin.y<[UIScreen mainScreen].bounds.size.height)
{
CGRect convertRect = [self.view convertRect:r fromView:[UIApplication sharedApplication].keyWindow];
CGRect viewBounds = self.view.bounds;
CGRect tipsFrame = CGRectMake(0, 0, viewBounds.size.width, convertRect.origin.y);
if(SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"6.1"))
{
}else
{
self.searchTipsController.view.frame = tipsFrame;
self.searchTipsController.tableView.contentInset = UIEdgeInsetsZero;
}
}
}
I change my code, when keyboard raises, remove the tableview then add it back, this time it looks right..
- (void) keyboardSizeChange:(NSNotification *)aNotification
{
NSDictionary* d = [aNotification userInfo];
CGRect r = [[d objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
if(r.origin.y<[UIScreen mainScreen].bounds.size.height)
{
CGRect convertRect = [self.view convertRect:r fromView:[UIApplication sharedApplication].keyWindow];
CGRect viewBounds = self.view.bounds;
CGRect tipsFrame = CGRectMake(0, 0, viewBounds.size.width, convertRect.origin.y);
self.searchTipsController.view.frame = tipsFrame;
self.searchTipsController.tableView.contentInset = UIEdgeInsetsZero;
[self.searchTipsController.view removeFromSuperview];
[self.view addSubview:self.searchTipsController.view];
}
}
But I still hope some one can answer my question, thanks.
Related
For my keyboards to move up to uncover UITextField in my iOS app, I used to implement this answer: https://stackoverflow.com/a/6908258/3855618 on iOS7 and 8 and it has worked perfectly for now. However on iOS 9.1, it doesn't work anymore.
To be more accurate, even if the background view does move up, the UITextField doesn't.
Any idea of what has changed so much since iOS9 and iOS 9.1?
The answer you have linked is not recommended. You should not set the view controller view's frame directly, especially not if you are using auto layout. Instead of changing the view's frame you should add a scrollview as a subview to the view, and adjust the content inset when the keyboard is shown or hidden.
From the official apple doc:
When asked to display the keyboard, the system slides it in from the bottom of the screen and positions it over your app’s content. Because it is placed on top of your content, it is possible for the keyboard to be placed on top of the text object that the user wanted to edit. When this happens, you must adjust your content so that the target object remains visible.
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). 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:UIKeyboardFrameBeginUserInfoKey] 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;
}
Zero lines of Code
Devoid of hacks, kludges, workaround and listeners.
The present question has been asked over and over since the dawn of iOS time. No answer on StackOverflow survived more than 2 iOS iterations. Rightly so, because the UIKit keeps changing from underneath your feet. There exists a design as opposed to implementation solution to this ancient problem. Use a UITableViewController.
Use a UITableViewController
When a UITableView is managed by a UITableViewController, the scrolling is managed automatically for you. Never tinker with UIKeyboardWillShowNotification, ever again. Merely create static or dynamic UITableViewCells to layout your interface, add UITextView or UITextField as needed ; merely becoming first responder will scroll the the proper location.
#availability(iOS, introduced=2.0)
Notes
Works on all iOS since 2.0.
Quote: «Waste no time optimizing a poor algorithm ; pick a better one»
See https://stackoverflow.com/a/32390936/218152.
We need to take keyboard frame from notification. When get reference of scrollView, tableView, etc. Convert low border of view to window`s coordinates. When determine how much keyboard covers our view, and if difference is greater than 0, we can add inset below.
Try this code:
- (void)subscribeKeyboardNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)unsubscribeKeyboardNotifications
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)keyboardWillShow:(NSNotification *)aNotification
{
CGRect keyBoardFrame = [[[aNotification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
UIWindow *keyWindow = [[[UIApplication sharedApplication] delegate] window];
UIScrollView *someScrollView = ......
CGPoint tableViewBottomPoint = CGPointMake(0, CGRectGetMaxY([someScrollView bounds]));
CGPoint convertedTableViewBottomPoint = [someScrollView convertPoint:tableViewBottomPoint
toView:keyWindow];
CGFloat keyboardOverlappedSpaceHeight = convertedTableViewBottomPoint.y - keyBoardFrame.origin.y;
if (keyboardOverlappedSpaceHeight > 0)
{
UIEdgeInsets tableViewInsets = UIEdgeInsetsMake(0, 0, keyboardOverlappedSpaceHeight, 0);
[someScrollView setContentInset:tableViewInsets];
}
}
- (void)keyboardWillHide:(NSNotification *)aNotification
{
UIEdgeInsets tableViewInsets = UIEdgeInsetsZero;
UIScrollView *someScrollView = ......
[someScrollView setContentInset:tableViewInsets];
}
Add all UITextField on UIScrollView and use TPKeyboardAvoiding
I'm usually listening to keyboard notifications and make according changes to layout constraints. See my other answer for more details and a sample project.
Try this code that I have used in my previous projects:
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
[self didBeginEditingIn:textField];
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
[self didEndEditing];
}
static const CGFloat KEYBOARD_ANIMATION_DURATION = 0.3;
static const CGFloat MINIMUM_SCROLL_FRACTION = 0.2;
static const CGFloat MAXIMUM_SCROLL_FRACTION = 0.8;
static const CGFloat PORTRAIT_KEYBOARD_HEIGHT = 216+100;
static const CGFloat LANDSCAPE_KEYBOARD_HEIGHT = 162+100;
- (void)didBeginEditingIn:(UIView *)view
{
CGRect textFieldRect = [self.view.window convertRect:view.bounds fromView:view];
CGRect viewRect = [self.view.window convertRect:self.view.bounds fromView:self.view];
CGFloat midline = textFieldRect.origin.y + 0.5* textFieldRect.size.height;
CGFloat numerator = midline - viewRect.origin.y- MINIMUM_SCROLL_FRACTION * viewRect.size.height;
CGFloat denominator = (MAXIMUM_SCROLL_FRACTION - MINIMUM_SCROLL_FRACTION)* viewRect.size.height;
CGFloat heightFraction = numerator / denominator;
if (heightFraction < 0.0)
{
heightFraction = 0.0;
}
else if (heightFraction > 1.0)
{
heightFraction = 1.0;
}
UIInterfaceOrientation orientation =
[[UIApplication sharedApplication] statusBarOrientation];
if (orientation == UIInterfaceOrientationPortrait ||
orientation == UIInterfaceOrientationPortraitUpsideDown)
{
_animatedDistance = floor(PORTRAIT_KEYBOARD_HEIGHT * heightFraction);
}
else
{
_animatedDistance = floor(LANDSCAPE_KEYBOARD_HEIGHT * heightFraction);
}
CGRect viewFrame = self.view.frame;
viewFrame.origin.y -= _animatedDistance;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:KEYBOARD_ANIMATION_DURATION];
[self.view setFrame:viewFrame];
[UIView commitAnimations];
}
- (void)didEndEditing
{
CGRect viewFrame = self.view.frame;
viewFrame.origin.y += _animatedDistance;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:KEYBOARD_ANIMATION_DURATION];
[self.view setFrame:viewFrame];
[UIView commitAnimations];
}
i followed the doc from #Istvan to the apple site, and there are a lot of stuff missing to make it work:
1. Set your .h document to <UITextFieldDelegate> (to be able to work with "activefield")
2. In the viewDidLoad, set the delegates to your UITextfields, and set the height of your scrollview content with a bigger height (in my case i've setted 500 more):
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;
CGFloat screenHeight = screenRect.size.height + 500;
_scrollView.contentSize = CGSizeMake(screenWidth, screenHeight);
And now it's all working...
So I'm not sure why my code isn't working. When the keyboard appears it puts the toolBar above the keyboard, but much higher than the height of the keyboard. Also the quick text bar in ios8 messes it up even more when I toggle it on and off, and the toolBar doesn't adjust correctly. Also when I close out the keyboard the first time the toolBar goes back to its original position, but then after once it doesn't go back down all the way where it was originally. I logged out self.yPositionStore and it never changes, which is why I don't understand why it doesn't always go back to the same spot. I've literally been working on this code all day trying to find a solution to my textfield getting hidden by the keyboard and it's giving me a headache. someone PLEASE help me.
- (void)viewDidLoad {
[super viewDidLoad];
self.yPositionStore = self.toolBar.frame.origin.y;
}
- (void)keyboardWasShown:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
CGRect newFrame = self.toolBar.frame;
newFrame.origin.y = kbSize.height;
self.toolBar.frame = newFrame;
}
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
CGRect newFrame = self.toolBar.frame;
newFrame.origin.y = self.yPositionStore;
self.toolBar.frame = newFrame;
}
There was problem in keyboardwasshown method. you have to subtract toolbar height and keyboard height from main screen height to find exact y value for toolbar.
- (void)viewDidLoad {
[super viewDidLoad];
self.yPositionStore = self.toolBar.frame.origin.y;
}
- (void)keyboardWasShown:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
CGRect newFrame = self.toolBar.frame;
newFrame.origin.y = [UIScreen mainscreen].bounds.size.height - kbSize.height - newFrame.size.height;
self.toolBar.frame = newFrame;
}
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
CGRect newFrame = self.toolBar.frame;
newFrame.origin.y = self.yPositionStore;
self.toolBar.frame = newFrame;
}
ok so for the most part this winded up working
- (void)keyboardWasShown:(NSNotification*)aNotification
{
self.yPositionStore = self.toolBar.frame.origin.y;
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
CGRect newFrame = self.toolBar.frame;
newFrame.origin.y = [UIScreen mainScreen].bounds.size.height - kbSize.height - newFrame.size.height;
self.toolBar.frame = newFrame;
}
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
CGRect newFrame = self.toolBar.frame;
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
newFrame.origin.y += kbSize.height - newFrame.size.height;
self.toolBar.frame = newFrame;
}
After the first time the keyboard is hidden, the tool bar goes down about a pixel further than it's suppose to, but it still looks good. The other problem I'm still having is on keyboardWasShown, the toolbar is repositioning itself but it's a little bit delayed, and I'm not sure how to fix that. Now off to trying out how to reposition a tableview with the keyboard!
I have a ViewController with UITextView taking up the whole view, with a navigation bar on top. Almost like Apple's "Notes" app. What I'm trying to achieve is to keep the textview's cursor visible when editing start, or when editing.
I was able to get the cursor's CGPoint, but I'm having difficult time calculating the scroll point. How can I achieve this?
Thanks
When textview starts editing
- (void)keyboardDidShow:(NSNotification*)aNotification {
// Keyboard
NSDictionary *info = [aNotification userInfo];
CGRect keyPadFrame = [[UIApplication sharedApplication].keyWindow convertRect:[[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue] fromView:self.view];
CGSize keyboardSize = keyPadFrame.size;
kbSize = keyboardSize;
[self scrollToCursor];
}
When textview is editing
- (void)textViewDidChange:(UITextView *)textView {
// Scroll to cursor
[self scrollToCursor];
}
Scroll to cursor method
- (void)scrollToCursor {
// View
CGRect viewBounds = self.view.bounds;
CGRect visibleViewBounds = CGRectMake(viewBounds.origin.x,
viewBounds.origin.y + (self.navigationController.navigationBar.frame.size.height + [UIApplication sharedApplication].statusBarFrame.size.height),
viewBounds.size.width,
viewBounds.size.height - (kbSize.height + self.navigationController.navigationBar.frame.size.height + [UIApplication sharedApplication].statusBarFrame.size.height));
// TextView
CGPoint textViewOrigin = [self.view convertRect:self.noteTextView.frame fromView:self.noteTextView.superview].origin;
// Cursor
CGPoint textViewCursor = [self.noteTextView caretRectForPosition:self.noteTextView.selectedTextRange.start].origin;
CGPoint cursorPoint = CGPointMake((textViewCursor.x + textViewOrigin.x), (textViewCursor.y - self.noteTextView.contentOffset.y));
// Scroll to point
if (!CGRectContainsPoint(visibleViewBounds, CGPointMake(cursorPoint.x, cursorPoint.y + 25/*25 for cursor's height*/))) {
[self.noteTextView setContentOffset:CGPointMake(0, 0)/*How to calculate??*/ animated:YES];
}
}
This is not tested at all, but would be my first attempt at it.
Get the keyboard height by listening to the KeyboardWillChangeFrameNotification
CGRect keyboardFrame;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillChange:) name:UIKeyboardWillChangeFrameNotification object:nil];
- (void)keyboardWillChange:(NSNotification *)notification {
keyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
keyboardFrame = [self.view convertRect:keyboardRect fromView:nil];
}
That will give you the keyboard height.
Then get the screen height:
CGRect screenBound = [[UIScreen mainScreen] bounds];
CGSize screenSize = screenBound.size;
CGFloat screenHeight = screenSize.height;
Then if you know the CGPoint of the cursor, do something like this:
CGFloat keyboardTop = (screenHeight - (keyboardFrame.size.height + <padding if you want it>));
if (currentCursorPosition.y > keyboardTop)
{
[self.noteTextView setContentOffset:CGPointMake(0, (cursorPoint.y - (viewBounds.size.height - kbSize.height)) + self.noteTextView.contentOffset.y + 25);
}
Ideally this should keep the cursor at the top of the keyboard and then scroll as you move the cursor down.
For your sanity, just use a library.
Here is a good one: https://github.com/hackiftekhar/IQKeyboardManager
Carthage:
github "hackiftekhar/IQKeyboardManager"
CocoaPods:
pod 'IQKeyboardManagerSwift', '6.3.0'
or
pod 'IQKeyboardManager', '3.3.7' #iOS7
This worked before ios7 when someone tapped on anything that could become first responder inside a UIScrollView. Now it does not - UITextFields/Views still can show under the keyboard.
Code:
- (void)keyboardWasShown:(NSNotification*)notification{
//Some similar questions mentioned this might work, but made no difference for me
self.automaticallyAdjustsScrollViewInsets=NO;
NSDictionary* info = [notification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
float height = 0.0;
if (UIDeviceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
height = kbSize.width;
} else {
height = kbSize.height;
}
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, height, 0.0);
[UIView animateWithDuration:.25
delay:0
options:(UIViewAnimationOptionAllowUserInteraction)
animations:^
{
self.editorScrollView.contentInset = contentInsets;
self.editorScrollView.scrollIndicatorInsets = contentInsets;
}
completion:^(BOOL finished)
{
}];
}
Currently, with this code nothing takes place when a uitextfield/view is assigned first responder status. The insets don't seem to change - I perhaps could use contentOffset but I would have to find the origin view's Y who just become first responder to do that.
Like I said, before ios7 this code worked (no textfield/view would be hidden behind the keyboard when assigned first responder status). I seem to be missing something obvious or perhaps there is a better way of doing this in ios7?
A better way to detect keyboard changing and frame.
The key point is to convert keyboard frame: CGRect keyboardFrameInsideView = [self.view convertRect:keyboardFrame fromView:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardFrameWillChange:)
name:UIKeyboardWillShowNotification
object:nil];
- (void)keyboardFrameWillChange:(NSNotification *)notification
{
CGRect keyboardFrame;
[[notification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardFrame];
CGRect keyboardFrameInsideView = [self.view convertRect:keyboardFrame fromView:nil];
CGRect r = self.bodyView.frame;
r.size.height = CGRectGetMinY(keyboardFrameInsideView) - r.origin.y;
self.bodyView.frame = r;
}
How can I check if the keyboard is covering a first responder inside a UIScrollView which may or may not be a UITableView? Note the UIScrollView will not necessarily cover the entire viewController's view and may be contained in a modal view (UIModalPresentationFormSheet).
I'm using this modified code from Apple's reference documentation and example, but CGRectContainsPoint will return false even when the keyboard is clearly covering the first responder. It's obvious I'm not using convertRect:toView correctly.
Also, Apple's code does not take into account that the view is not full-screen, so setting the scrollView's contentInset to the full height of the keyboard isn't a great solution -- it should only be inset for the portion of the keyboard covering the firstResponder.
- (void)keyboardWasShown:(NSNotification*)aNotification
{
// self.scrollView can be a tableView or not. need to handle both
UIView *firstResponderView = [self.scrollView findFirstResponder];
if (!firstResponderView)
return;
NSDictionary* info = [aNotification userInfo];
CGRect rect = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
// convertRect:toView? convertRect:fromView? Always confusing
CGRect kbRect = [self.scrollView convertRect:rect toView:nil];
CGRect viewRect = [self.scrollView convertRect:firstResponderView.bounds toView:nil];
// doesn't work. convertRect misuse is certainly to blame
if (!CGRectContainsPoint(kbRect, firstResponderView.frame.origin))
return;
// Only inset to the portion which the keyboard covers?
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbRect.size.height, 0.0);
self.scrollView.contentInset = contentInsets;
self.scrollView.scrollIndicatorInsets = contentInsets;
}
Without further testing or having a deep look at the logic, this line seems odd:
CGRect kbRect = [self.scrollView convertRect:rect toView:nil];
The keyboard rect (that is included in the notification) is in window coordinates and you probably want to convert it into the scroll view coordinate system. [viewA convertRect:rect toView:viewB] converts rect from viewA's coordinate system to viewB's coordinate system, so you are actually doing the opposite of what you should be doing (as you suspected).
What I'm usually doing is this:
- (void)keyboardWillShow:(NSNotification *)aNotification
{
NSDictionary *info = [aNotification userInfo];
CGRect kbRect = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
kbRect = [self.view.window convertRect:kbRect toView:self.view]; // convert to local coordinate system, otherwise it is in window coordinates and does not consider interface orientation
_keyboardSize = kbRect.size; // store for later use
[UIView animateWithDuration:0.25 animations:^{
UIEdgeInsets insets = UIEdgeInsetsMake(0.0f, 0.0f, MAX(0.0f, CGRectGetMaxY(_tableView.frame) - CGRectGetMinY(kbRect)), 0.0f); // NB: _tableView is a direct subview of self.view, thus _tableView.frame and kbRect are in the same coordinate system
_tableView.contentInset = insets;
_tableView.scrollIndicatorInsets = insets;
[self scrollToActiveTextField]; // here I adapt the content offset to ensure that the active text field is fully visible
}];
}