IOS Managing the Keyboard - Move UIScrollView Content up - objective-c

I am using the example in the IOS Developer Library for managing the keyboard.
Text, Web and Edition Programming Guide
I have created a view using the IB. Its a simple UI that has a UIScrollView, UITextView, UIButton, and a UITextField. I placed the UIScrollView on my view and then added all the other controls as children of this scrollview. The scrollview is exposed to the viewcontroller via a IBOutlet "scrollView".
The follow code executes with the user sets focus to the textField but I never see a scrollbar appear and the scrollbar's contents are not moved. Should I be able to see the scrollbar by default? Can someone tell me what I'm missing?
-(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 application might not need or want this behavior.
CGRect aRect = self.view.frame;
aRect.size.height -= kbSize.height;
if (!CGRectContainsPoint(aRect, activeField.frame.origin) ) {
CGPoint scrollPoint = CGPointMake(0.0, activeField.frame.origin.y-kbSize.height);
[scrollView setContentOffset:scrollPoint animated:YES];
}
}
Again, I'm taking this code directly from the IOS Programming guide in the link. The UI layout looks like a basic chat window. I would like to move the "input" field up while the soft keyboard is visible. Thanks!
Update
It seems that I needed to add some padding to actually see the controls located at the bottom of the scrollview.
CGPoint scrollPoint = CGPointMake(0.0, (activeField.frame.origin.y - kbSize.height) + 10.0);
How come I don't see scrollbars?

IF YOU HAVE PARENT VIEW AS SCROLL THEN JUST USE : UITextFielddelegate and set the methods
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
if(textField == self.txtUserName)
{
[self.txtPassword becomeFirstResponder];
}
else if(textField == self.txtPassword){
[self.txtPassword resignFirstResponder];
CGPoint bottomOffset = CGPointMake(0, 0);
[scrView setContentOffset:bottomOffset animated:YES];
[textField resignFirstResponder];
}
return YES;
}
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
if (textField == self.txtUserName)
{
CGPoint bottomOffset = CGPointMake(0, 80);
[scrView setContentOffset:bottomOffset animated:YES];
}
if (textField == self.txtPassword)
{
CGPoint bottomOffset = CGPointMake(0, 135);
[scrView setContentOffset:bottomOffset animated:YES];
}
}

Scrollbars should show up only on user interaction. That's not the case here since your programmatically setting the inset of your scrollview.
If you want to show the scroll bars, i believe UIScrollView defines a flashScrollIndicators method, that should show the scroll bars.

Related

UIScrollView return to its original place

I have the following code:
float yOffset = activeTextView.frame.origin.y - keyboardSize.height + 55;
CGPoint scrollPoint = CGPointMake(0.0, yOffset);
[scrollView setContentOffset:scrollPoint animated:YES];
This animates the scrollView in - (void)keyboardWasShown:(NSNotification *)notification
I am trying to return the scrollView to it's original location after the hiding the keyboard like this:
- (void) keyboardWillHide:(NSNotification *)notification {
UIEdgeInsets contentInsets = UIEdgeInsetsZero;
scrollView.contentInset = contentInsets;
scrollView.scrollIndicatorInsets = contentInsets;
}
But it doesn't work!
How can I return the UIScrollView and actually the whole screen to its original location so the user will see what he saw before animation of the scrollview?
In your keyboardWasShown: method, you're setting the contentOffset property ([scrollView setContentOffset:] is equivalent to scrollView.contentOffset). However, in keyboardWillHide:, you're setting contentInset, which is something completely different (essentially, it's the amount of internal padding of the scroll view's content). Try
scrollView.contentOffset = CGPointZero; // non-animated by default
or
[scrollView setContentOffset:CGPointZero animated:YES]; // animated
Also, as NSResponder mentioned, make sure your keyboardWillHide: method is being called.

regarding scrolling inside UIScrollView

I have a scroll view with some content. After loading the contents I am using the following code to move to the top of the scroll view
[scrollViewCustom setContentOffset:CGPointMake(0.0, 0.0) animated:NO];
It works. With one exception :
I have a UITextView. If I add some text to it, then the scroll view never goes back to the top with this code. I have to manually scroll to the top.
I have even tried to put the same code after adding text to the UITextView.
Can anyone kindly help out ? Thanks.
NB:So, heres the solution :
Basically UITextView derives from UIScrollView and as such, whenever there is some text there, it tries to scroll to make the text visible. I have replaced UITextView to UILabel and it works like a charm !
Instead of
[scrollViewCustom setContentOffset:CGPointMake(0.0, 0.0) animated:NO];
Use
[scroll scrollRectToVisible:CGRectMake(0,0, 100,100) animated:NO];
- (void)viewDidLoad{
scroll.contentSize=CGSizeMake(320, 400);
}
- (void)scrollViewToCenterOfScreen:(UIView *)theView {
CGFloat viewCenterY = theView.center.y;
CGRect applicationFrame = [[UIScreen mainScreen] applicationFrame];
CGFloat availableHeight;
availableHeight = applicationFrame.size.height - 300;
CGFloat z = viewCenterY - availableHeight / 2.0;
if (z < 0) {
z = 0;
}
[scroll setContentOffset:CGPointMake(0, z) animated:YES];
}
-(void)textViewDidBeginEditing:(UITextView *)textView{
[self scrollViewToCenterOfScreen:scroll];
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
if (range.length==0) {
[scroll setContentOffset:CGPointMake(0, 0) animated:YES];
return NO;
}
}
Just two steps:
// define the area that is initially visible
scrollViewCustom.frame = CGRectMake(0, 5, 354, 500);
// then define how much a user can scroll it
[scrollViewCustom setContentSize:CGSizeMake(354, 960)];
In addition,clear the background color of scroll view to show the text.
scrollViewCustom.backgroundColor = [UIColor clearColor];

UIScrollView not receiving scroll action

In the app I am working on, my goal right now is to scroll content when the keyboard shows and to allow users to scroll while it is showing. I have tried a few different solutions and none have been able to achieve this yet.
I'm using storyboards in the app and here is the element hierarchy within the view controller:
View Controller
UIScrollView
UIView
Buttons/textfields/labels/UIPickerView
I first set the UIScrollView's content size to be the same size of the view that was inside of it holding all of the form elements. When that didn't work, I tried over-exagerating the height of the content manually setting the content size to be 320 x 2000. Again, that didn't work. I have user interaction enabled set to YES on the scroll view as well. This is the code I have in there at this point.
CGSize contentSize = CGSizeMake(320, 2000);
[self.scrollView setContentSize:contentSize];
Within the scroll view I had a button that sits behind the whole form that has an action to close the keyboard if a user touches outside of it. I disabled that to see if it may have been a conflict in events that would keep it from scrolling. Again, didn't work.
-(IBAction)closeKeyboard:(id)sender
{
if(![self isFirstResponder]){
[self.view endEditing:YES];
}
}
I even set up some observers to see if the keyboard is about to appear or disappear. The observers would adjust the height of the scroll view, not the content size, just the scroll view itself, based on where the keyboard was currently sitting. So at this point, the content in the scroll view would be much taller than the scroll view itself, but still no scrolling is happening.
Here is the code for my observers:
// adjust view based on keyboard
- (void)keyboardWillHide:(NSNotification *)n
{
NSDictionary* userInfo = [n userInfo];
// get the size of the keyboard
CGSize keyboardSize = [[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
// resize the scrollview
CGRect viewFrame = self.view.frame;
// I'm also subtracting a constant kTabBarHeight because my UIScrollView was offset by the UITabBar so really only the portion of the keyboard that is leftover pass the UITabBar is obscuring my UIScrollView.
//viewFrame.size.height += keyboardSize.height;
CGRect scrollRect = CGRectMake(viewFrame.origin.x, viewFrame.origin.y, viewFrame.size.width, viewFrame.size.height + keyboardSize.height + 100);
[UIScrollView beginAnimations:nil context:NULL];
[UIScrollView setAnimationBeginsFromCurrentState:YES];
[UIScrollView setAnimationDuration:0.3];
[self.scrollView setFrame:scrollRect];
self.scrollView.userInteractionEnabled = YES;
[UIScrollView commitAnimations];
keyboardShowing = false;
}
- (void)keyboardWillShow:(NSNotification *)n
{
// This is an ivar I'm using to ensure that we do not do the frame size adjustment on the UIScrollView if the keyboard is already shown. This can happen if the user, after fixing editing a UITextField, scrolls the resized UIScrollView to another UITextField and attempts to edit the next UITextField. If we were to resize the UIScrollView again, it would be disastrous. NOTE: The keyboard notification will fire even when the keyboard is already shown.
if (keyboardShowing) {
return;
}
NSDictionary* userInfo = [n userInfo];
// get the size of the keyboard
CGSize keyboardSize = [[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
// resize the noteView
CGRect viewFrame = self.view.frame;
// I'm also subtracting a constant kTabBarHeight because my UIScrollView was offset by the UITabBar so really only the portion of the keyboard that is leftover pass the UITabBar is obscuring my UIScrollView.
CGRect scrollRect = CGRectMake(viewFrame.origin.x, viewFrame.origin.y, viewFrame.size.width, viewFrame.size.height - keyboardSize.height - 100);
//scrollView.frame.size.height -= keyboardSize.height;
//viewFrame.size.height -= keyboardSize.height;
[UIScrollView beginAnimations:nil context:NULL];
[UIScrollView setAnimationBeginsFromCurrentState:YES];
[UIScrollView setAnimationDuration:0.3];
[self.scrollView setFrame:scrollRect];
self.scrollView.userInteractionEnabled = YES;
[UIScrollView commitAnimations];
keyboardShowing = YES;
}
I would not be surprised if this is one of those simple mistakes that keeps slipping my mind, but this sort of accessibility feature would be really nice to have in the app. Any help would be much appreciated, or even other possible solutions to the problem I am trying to solve would be great too.
It looks like you're using a gesture recognizer in IB to detect a tap outside event. Because this recognizer is in the highest view in the hierarchy, it overrides the scrollview's detectors. You might need to change it slightly depending on which areas you want to be able to tap on to close the keyboard.
This is the UIResponder class reference. It lists all of the UIResponder events that your view controller automatically inherits. What might fix your problem is subclassing your UIScrollView and adding the keyboard closing code to it. Also, make sure that you set it to the first responder.
Final code that worked:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
mouseSwiped = NO;
UITouch *touch = [touches anyObject];
if ([touch tapcount] == 1)
for(UIView *view in self.view.subviews){
if([view isKindOfClass:[UITextField class]]){
[view resignFirstResponder];
}
}
}
}

Scroll to selected textField with AQGridView (similar to tableView)

I'm trying to create a simple note block application. I'm using AQGridView to display note blocks in a grid. Every note block have a editable UITextField property in a custom subclass of AQGridViewCell (similar to TableViewCell). And I have some problems with keyboard handling. I have tried to follow this guide, http://developer.apple.com/library/ios/#documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html. I have implemented the methods keyboardWillShow and keyboardWillBeHidden like this:
- (void)keyboardWillShow:(NSNotification *)notification
{
// Animates the done button.
[UIView beginAnimations:nil context:NULL];
// Display a done button
UIBarButtonItem * doneButton = [[UIBarButtonItem alloc]initWithTitle:#"Done" style:UIBarButtonItemStyleDone target:self action:#selector(hideKeyboard)];
[[self navigationItem] setRightBarButtonItem:doneButton];
NSDictionary* info = [notification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);
self.gridView.contentInset = contentInsets;
self.gridView.scrollIndicatorInsets = contentInsets;
// If active text field is hidden by keyboard, scroll it so it's visible
// Your application might not need or want this behavior.
CGRect aRect = self.view.frame;
aRect.size.height -= kbSize.height;
NSLog(#"%f", activeField.frame.origin.y);
if (!CGRectContainsPoint(aRect, activeField.frame.origin) ) {
CGPoint scrollPoint = CGPointMake(0.0, activeField.frame.origin.y+kbSize.height);
[self.gridView setContentOffset:scrollPoint animated:YES];
}
}
// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillBeHidden:(NSNotification *)notification
{
// Remove the "done" button
self.navigationItem.rightBarButtonItem = self.editButtonItem;
UIEdgeInsets contentInsets = UIEdgeInsetsZero;
self.gridView.contentInset = contentInsets;
self.gridView.scrollIndicatorInsets = contentInsets;
}
I set the delegate of the textField in cellForItemAtIndex (similar to cellForRowAtIndexPath).
Currently, the scrolling to the right position doesn't work correctly. I think the problem have to do with the textField, because when I test:
// activeField is a UITextField property that I set in textFieldDidBeginEditing,
// and later set to nil in textFieldDidEndEditing.
NSLog(#"%f", activeField.frame.origin.y);
It always returns the same value, in my case 95, even if the selected textField isn't on the same row.
Tips on how to solve this, or other solutions, would be great!
I think I solved it by setting a tag to the cell's textField, and then getting the cell from that tag/index. And then calculating the position when the keyboard appears based on the cell's position instead of the textField inside the cell.

How programmatically move a UIScrollView to focus in a control above keyboard?

I have 6 UITextFields on my UIScrollView. Now, I can scroll by user request. But when the keyboard appear, some textfields are hidden.
That is not user-friendly.
How scroll programmatically the view so I get sure the keyboard not hide the textfield?
Here's what worked for me. Having an instance variable that holds the value of the UIScrollView's offset before the view is adjusted for the keyboard so you can restore the previous state after the UITextField returns:
//header
#interface TheViewController : UIViewController <UITextFieldDelegate> {
CGPoint svos;
}
//implementation
- (void)textFieldDidBeginEditing:(UITextField *)textField {
svos = scrollView.contentOffset;
CGPoint pt;
CGRect rc = [textField bounds];
rc = [textField convertRect:rc toView:scrollView];
pt = rc.origin;
pt.x = 0;
pt.y -= 60;
[scrollView setContentOffset:pt animated:YES];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[scrollView setContentOffset:svos animated:YES];
[textField resignFirstResponder];
return YES;
}
Finally, a simple fix:
UIScrollView* v = (UIScrollView*) self.view ;
CGRect rc = [textField bounds];
rc = [textField convertRect:rc toView:v];
rc.origin.x = 0 ;
rc.origin.y -= 60 ;
rc.size.height = 400;
[self.scroll scrollRectToVisible:rc animated:YES];
Now I think is only combine this with the link above and is set!
I've put together a universal, drop-in UIScrollView and UITableView subclass that takes care of moving all text fields within it out of the way of the keyboard.
When the keyboard is about to appear, the subclass will find the subview that's about to be edited, and adjust its frame and content offset to make sure that view is visible, with an animation to match the keyboard pop-up. When the keyboard disappears, it restores its prior size.
It should work with basically any setup, either a UITableView-based interface, or one consisting of views placed manually.
Here it is.
(For google: TPKeyboardAvoiding, TPKeyboardAvoidingScrollView, TPKeyboardAvoidingCollectionView.)
Editor's note: TPKeyboardAvoiding seems to be continually updated and fresh, as of 2014.
If you set the delegate of your text fields to a controller object in your program, you can have that object implement the textFieldDidBeginEditing: and textFieldShouldReturn: methods. The first method can then be used to scroll to your text field and the second method can be used to scroll back.
You can find code I have used for this in my blog: Sliding UITextViews around to avoid the keyboard. I didn't test this code for text views in a UIScrollView but it should work.
simple and best
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
// self.scrlViewUI.contentOffset = CGPointMake(0, textField.frame.origin.y);
[_scrlViewUI setContentOffset:CGPointMake(0,textField.center.y-90) animated:YES];
tes=YES;
[self viewDidLayoutSubviews];
}
The answers posted so far didn't work for me as I've a quite deep nested structure of UIViews. Also, the I had the problem that some of those answers were working only on certain device orientations.
Here's my solution, which will hopefully make you waste some less time on this.
My UIViewTextView derives from UIView, is a UITextView delegate and adds a UITextView after having read some parameters from an XML file for that UITextView (that XML part is left out here for clarity).
Here's the private interface definition:
#import "UIViewTextView.h"
#import <CoreGraphics/CoreGraphics.h>
#import <CoreGraphics/CGColor.h>
#interface UIViewTextView (/**/) {
#private
UITextView *tf;
/*
* Current content scroll view
* position and frame
*/
CGFloat currentScrollViewPosition;
CGFloat currentScrollViewHeight;
CGFloat kbHeight;
CGFloat kbTop;
/*
* contentScrollView is the UIScrollView
* that contains ourselves.
*/
UIScrollView contentScrollView;
}
#end
In the init method I have to register the event handlers:
#implementation UIViewTextView
- (id) initWithScrollView:(UIScrollView*)scrollView {
self = [super init];
if (self) {
contentScrollView = scrollView;
// ...
tf = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 241, 31)];
// ... configure tf and fetch data for it ...
tf.delegate = self;
// ...
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(keyboardWasShown:) name: UIKeyboardWillShowNotification object:nil];
[nc addObserver:self selector:#selector(keyboardWasHidden:) name: UIKeyboardWillHideNotification object:nil];
[self addSubview:tf];
}
return(self);
}
Once that's done, we need to handle the keyboard show event. This gets called before the textViewBeginEditing is called, so we can use it to find out some properties of the keyboard. In essence, we want to know the height of the keyboard. This, unfortunately, needs to be taken from its width property in landscape mode:
-(void)keyboardWasShown:(NSNotification*)aNotification {
NSDictionary* info = [aNotification userInfo];
CGRect kbRect = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
CGSize kbSize = kbRect.size;
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat sWidth = screenRect.size.width;
CGFloat sHeight = screenRect.size.height;
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if ((orientation == UIDeviceOrientationPortrait)
||(orientation == UIDeviceOrientationPortraitUpsideDown)) {
kbHeight = kbSize.height;
kbTop = sHeight - kbHeight;
} else {
//Note that the keyboard size is not oriented
//so use width property instead
kbHeight = kbSize.width;
kbTop = sWidth - kbHeight;
}
Next, we need to actually scroll around when we start editing. We do this here:
- (void) textViewDidBeginEditing:(UITextView *)textView {
/*
* Memorize the current scroll position
*/
currentScrollViewPosition = contentScrollView.contentOffset.y;
/*
* Memorize the current scroll view height
*/
currentScrollViewHeight = contentScrollView.frame.size.height;
// My top position
CGFloat myTop = [self convertPoint:self.bounds.origin toView:[UIApplication sharedApplication].keyWindow.rootViewController.view].y;
// My height
CGFloat myHeight = self.frame.size.height;
// My bottom
CGFloat myBottom = myTop + myHeight;
// Eventual overlap
CGFloat overlap = myBottom - kbTop;
/*
* If there's no overlap, there's nothing to do.
*/
if (overlap < 0) {
return;
}
/*
* Calculate the new height
*/
CGRect crect = contentScrollView.frame;
CGRect nrect = CGRectMake(crect.origin.x, crect.origin.y, crect.size.width, currentScrollViewHeight + overlap);
/*
* Set the new height
*/
[contentScrollView setFrame:nrect];
/*
* Set the new scroll position
*/
CGPoint npos;
npos.x = contentScrollView.contentOffset.x;
npos.y = contentScrollView.contentOffset.y + overlap;
[contentScrollView setContentOffset:npos animated:NO];
}
When we end editing, we do this to reset the scroll position:
- (void) textViewDidEndEditing:(UITextView *)textView {
/*
* Reset the scroll view position
*/
CGRect crect = contentScrollView.frame;
CGRect nrect = CGRectMake(crect.origin.x, crect.origin.y, crect.size.width, currentScrollViewHeight);
[contentScrollView setFrame:nrect];
/*
* Reset the scroll view height
*/
CGPoint npos;
npos.x = contentScrollView.contentOffset.x;
npos.y = currentScrollViewPosition;
[contentScrollView setContentOffset:npos animated:YES];
[tf resignFirstResponder];
// ... do something with your data ...
}
There's nothing left to do in the keyboard was hidden event handler; we leave it in anyway:
-(void)keyboardWasHidden:(NSNotification*)aNotification {
}
And that's it.
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#end
I know this is old, but still none of the solutions above had all the fancy positioning stuff required for that "perfect" bug-free, backwards compatible and flicker-free animation.
Let me share my solution (assuming you have set up UIKeyboardWill(Show|Hide)Notification):
// Called when UIKeyboardWillShowNotification is sent
- (void)keyboardWillShow:(NSNotification*)notification
{
// if we have no view or are not visible in any window, we don't care
if (!self.isViewLoaded || !self.view.window) {
return;
}
NSDictionary *userInfo = [notification userInfo];
CGRect keyboardFrameInWindow;
[[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardFrameInWindow];
// the keyboard frame is specified in window-level coordinates. this calculates the frame as if it were a subview of our view, making it a sibling of the scroll view
CGRect keyboardFrameInView = [self.view convertRect:keyboardFrameInWindow fromView:nil];
CGRect scrollViewKeyboardIntersection = CGRectIntersection(_scrollView.frame, keyboardFrameInView);
UIEdgeInsets newContentInsets = UIEdgeInsetsMake(0, 0, scrollViewKeyboardIntersection.size.height, 0);
// this is an old animation method, but the only one that retains compaitiblity between parameters (duration, curve) and the values contained in the userInfo-Dictionary.
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
[UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
_scrollView.contentInset = newContentInsets;
_scrollView.scrollIndicatorInsets = newContentInsets;
/*
* Depending on visual layout, _focusedControl should either be the input field (UITextField,..) or another element
* that should be visible, e.g. a purchase button below an amount text field
* it makes sense to set _focusedControl in delegates like -textFieldShouldBeginEditing: if you have multiple input fields
*/
if (_focusedControl) {
CGRect controlFrameInScrollView = [_scrollView convertRect:_focusedControl.bounds fromView:_focusedControl]; // if the control is a deep in the hierarchy below the scroll view, this will calculate the frame as if it were a direct subview
controlFrameInScrollView = CGRectInset(controlFrameInScrollView, 0, -10); // replace 10 with any nice visual offset between control and keyboard or control and top of the scroll view.
CGFloat controlVisualOffsetToTopOfScrollview = controlFrameInScrollView.origin.y - _scrollView.contentOffset.y;
CGFloat controlVisualBottom = controlVisualOffsetToTopOfScrollview + controlFrameInScrollView.size.height;
// this is the visible part of the scroll view that is not hidden by the keyboard
CGFloat scrollViewVisibleHeight = _scrollView.frame.size.height - scrollViewKeyboardIntersection.size.height;
if (controlVisualBottom > scrollViewVisibleHeight) { // check if the keyboard will hide the control in question
// scroll up until the control is in place
CGPoint newContentOffset = _scrollView.contentOffset;
newContentOffset.y += (controlVisualBottom - scrollViewVisibleHeight);
// make sure we don't set an impossible offset caused by the "nice visual offset"
// if a control is at the bottom of the scroll view, it will end up just above the keyboard to eliminate scrolling inconsistencies
newContentOffset.y = MIN(newContentOffset.y, _scrollView.contentSize.height - scrollViewVisibleHeight);
[_scrollView setContentOffset:newContentOffset animated:NO]; // animated:NO because we have created our own animation context around this code
} else if (controlFrameInScrollView.origin.y < _scrollView.contentOffset.y) {
// if the control is not fully visible, make it so (useful if the user taps on a partially visible input field
CGPoint newContentOffset = _scrollView.contentOffset;
newContentOffset.y = controlFrameInScrollView.origin.y;
[_scrollView setContentOffset:newContentOffset animated:NO]; // animated:NO because we have created our own animation context around this code
}
}
[UIView commitAnimations];
}
// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillHide:(NSNotification*)notification
{
// if we have no view or are not visible in any window, we don't care
if (!self.isViewLoaded || !self.view.window) {
return;
}
NSDictionary *userInfo = notification.userInfo;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:[[userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
[UIView setAnimationCurve:[[userInfo valueForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
// undo all that keyboardWillShow-magic
// the scroll view will adjust its contentOffset apropriately
_scrollView.contentInset = UIEdgeInsetsZero;
_scrollView.scrollIndicatorInsets = UIEdgeInsetsZero;
[UIView commitAnimations];
}
You may check it out: https://github.com/michaeltyson/TPKeyboardAvoiding (I used that sample for my apps). It is working so well. I hope that helps you.
Actually, here's a full tutorial on using TPKeyboardAvoiding, which may help someone
(1) download the zip file from the github link. add these four files to your Xcode project:
(2) build your beautiful form in IB. add a UIScrollView. sit the form items INSIDE the scroll view. (Note - extremely useful tip regarding interface builder: https://stackoverflow.com/a/16952902/294884)
(3) click on the scroll view. then at the top right, third button, you'll see the word "UIScrollView". using copy and paste, change it to "TPKeyboardAvoidingScrollView"
(4) that's it. put the app in the app store, and bill your client.
(Also, just click on the Inspector tab of the scroll view. You may prefer to turn on or off bouncing and the scroll bars - your preference.)
Personal comment - I strongly recommend using scroll view (or collection view) for input forms, in almost all cases. do not use a table view. it's problematic for many reasons. and quite simply, it's incredibly easier to use a scroll view. just lay it out any way you want. it is 100% wysiwyg in interface builder. hope it helps
This is my code, hope it will help you. It work ok in case you have many textfield
CGPoint contentOffset;
bool isScroll;
- (void)textFieldDidBeginEditing:(UITextField *)textField {
contentOffset = self.myScroll.contentOffset;
CGPoint newOffset;
newOffset.x = contentOffset.x;
newOffset.y = contentOffset.y;
//check push return in keyboar
if(!isScroll){
//180 is height of keyboar
newOffset.y += 180;
isScroll=YES;
}
[self.myScroll setContentOffset:newOffset animated:YES];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField{
//reset offset of content
isScroll = NO;
[self.myScroll setContentOffset:contentOffset animated:YES];
[textField endEditing:true];
return true;
}
we have a point contentOffset to save contentoffset of scrollview before keyboar show. Then we will scroll content for y about 180 (height of keyboar). when you touch return in keyboar, we will scroll content to old point(it is contentOffset). If you have many textfield, you don't touch return in keyboar but you touch another textfield, it will +180 . So we have check touch return
Use any of these,
CGPoint bottomOffset = CGPointMake(0, self.MainScrollView.contentSize.height - self.MainScrollView.bounds.size.height);
[self.MainScrollView setContentOffset:bottomOffset animated:YES];
or
[self.MainScrollView scrollRectToVisible:CGRectMake(0, self.MainScrollView.contentSize.height - self.MainScrollView.bounds.size.height-30, MainScrollView.frame.size.width, MainScrollView.frame.size.height) animated:YES];
I think it's better use keyboard notifications because you don't know if the first responder (the control with focus on) is a textField or a textView (or whatever). So juste create a category to find the first responder :
#import "UIResponder+FirstResponder.h"
static __weak id currentFirstResponder;
#implementation UIResponder (FirstResponder)
+(id)currentFirstResponder {
currentFirstResponder = nil;
[[UIApplication sharedApplication] sendAction:#selector(findFirstResponder:) to:nil from:nil forEvent:nil];
return currentFirstResponder;
}
-(void)findFirstResponder:(id)sender {
currentFirstResponder = self;
}
#end
then
-(void)keyboardWillShowNotification:(NSNotification*)aNotification{
contentScrollView.delegate=nil;
contentScrollView.scrollEnabled=NO;
contentScrollViewOriginalOffset = contentScrollView.contentOffset;
UIResponder *lc_firstResponder = [UIResponder currentFirstResponder];
if([lc_firstResponder isKindOfClass:[UIView class]]){
UIView *lc_view = (UIView *)lc_firstResponder;
CGRect lc_frame = [lc_view convertRect:lc_view.bounds toView:contentScrollView];
CGPoint lc_point = CGPointMake(0, lc_frame.origin.y-lc_frame.size.height);
[contentScrollView setContentOffset:lc_point animated:YES];
}
}
Eventually disable the scroll and set the delegate to nil then restore it to avoid some actions during the edition of the first responder. Like james_womack said, keep the original offset to restore it in a keyboardWillHideNotification method.
-(void)keyboardWillHideNotification:(NSNotification*)aNotification{
contentScrollView.delegate=self;
contentScrollView.scrollEnabled=YES;
[contentScrollView setContentOffset:contentScrollViewOriginalOffset animated:YES];
}
In Swift 1.2+ do something like this:
class YourViewController: UIViewController, UITextFieldDelegate {
override func viewDidLoad() {
super.viewDidLoad()
_yourTextField.delegate = self //make sure you have the delegate set to this view controller for each of your textFields so textFieldDidBeginEditing can be called for each one
...
}
func textFieldDidBeginEditing(textField: UITextField) {
var point = textField.convertPoint(textField.frame.origin, toView: _yourScrollView)
point.x = 0.0 //if your textField does not have an origin at 0 for x and you don't want your scrollView to shift left and right but rather just up and down
_yourScrollView.setContentOffset(point, animated: true)
}
func textFieldDidEndEditing(textField: UITextField) {
//Reset scrollview once done editing
scrollView.setContentOffset(CGPoint.zero, animated: true)
}
}