Scroll to selected textField with AQGridView (similar to tableView) - objective-c

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.

Related

Pop up tutorial page like Photosynth's

I want to create something that is basically a clone of what photosynth does for their tutorial page. A small "?" button pops up what looks like a new view in a frame that is slightly smaller than the first view, so that you can still see the first view around the edges.
It's a little tough to see from the pic above, but the part around the edges is the old view that the tutorial display popped up over.
My first guess is that I need to use a container view somehow, but I can't find anything on the web about exactly how to do this. I can currently create a container view, hook it up to a new view controller via a segue, and do whatever I want in that new view controller, but the container view is always visible on the view it is contained within. Any help?
BTW, I'm using storyboarding with ARC.
You can add a transparent view to the key window, add a tap gesture recognizer that would dismiss it and the subviews to show the content:
#define OVERLAY_TAG 997
-(void)showTutorial
{
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIView *overlay = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
overlay.backgroundColor = [UIColor clearColor];
overlay.userInteractionEnabled = YES;
[keyWindow addSubview:overlay];
UITapGestureRecognizer * tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(dismissTutorial)];
CGFloat border = 10;
CGRect frame = overlay.bounds;
// 20 is the status bar height (sorry for using the number)
frame = CGRectMake(border, border + 20, frame.size.width - border * 2, frame.size.height - border * 2 - 20);
// the black view in the example is probably a scroll view
UIView *blackView = [[UIView alloc] initWithFrame:frame];
blackView.backgroundColor = [UIColor blackColor];
blackView.alpha = 0.0;
[overlay addSubview:dimView];
// add all the subviews for your tutorial
// make it appear with an animation
[UIView animateWithDuration:0.3
animations:^{dimView.alpha = 1;}
completion:^(BOOL finished){[overlay addGestureRecognizer:tapRecognizer];}];
}
-(void)dismissTutorial
{
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIView *overlay = [keyWindow viewWithTag:OVERLAY_TAG];
[UIView animateWithDuration:0.3
animations:^{
overlay.alpha = 0.0;
}
completion:^(BOOL finished){
[overlay removeFromSuperview];
}];
}
This way you would remove the tutorial with a simple tap but you can use a button for instance.

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.

IOS Managing the Keyboard - Move UIScrollView Content up

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.

How to show a UIDatePicker over the tab bar, in an orientation-friendly way?

I want to slide in a UIDatePicker when my user taps on my table view date field, exactly as in the standard contact app, when the user taps on the birthday field. With one additional killer detail:
It's in a tab bar application and I want the UIDatePicker to slide over the tab bar. And still rotates when the user puts her phone in the lanscape orientation.
The way I show the UIDatePicker is to insert in the view, and then animate its position:
[self.view addSubview: self.pickerView];
When I do this, the UIDatePicker is shown, but doesn't cover the tabbar.
I can also add it to the window:
[self.view.window addSubview: self.pickerView];
The UIDatePicker correctly slides over the tab bar, but then, it doesn't follow the orientation.
The only semi-acceptable way I found, is to do without the tab bar altogether, by using
detailViewController.hidesBottomBarWhenPushed = YES;
But it's not what I want: I want the tab bar in the detail view. I only want it to go away while picking the date. I can't put the UIDatePicker in its own controller with hidesBottomBarWhenPushed = YES as this would entirely cover the screen, and I'd rather have the detail view still partially visible.
Any suggestion welcome.
Here is the full code I use to show the UIDatePicker, copied over from some sample code:
- (void) doPickDate
{
NSDate *initialDateForPicker = [(ExpenseData*) self.displayedObject displayDate];
self.pickerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
self.pickerView.datePickerMode = UIDatePickerModeDate;
self.pickerView.date = initialDateForPicker;
// check if our date picker is already on screen
if (self.pickerView.superview == nil)
{
[self.view addSubview: self.pickerView];
// size up the picker view to our screen and compute the start/end frame origin for our slide up animation
// compute the start frame
CGRect screenRect = [[UIScreen mainScreen] applicationFrame];
CGSize pickerSize = [self.pickerView sizeThatFits:CGSizeZero];
CGRect startRect = CGRectMake(0.0,
screenRect.origin.y + screenRect.size.height,
pickerSize.width, pickerSize.height);
self.pickerView.frame = startRect;
// compute the end frame
CGRect pickerRect = CGRectMake(0.0,
screenRect.origin.y + screenRect.size.height - pickerSize.height,
pickerSize.width,
pickerSize.height);
// start the slide up animation
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.3];
// we need to perform some post operations after the animation is complete
[UIView setAnimationDelegate:self];
self.pickerView.frame = pickerRect;
// shrink the table vertical size to make room for the date picker
CGRect newFrame = self.tableView.frame;
newFrame.size.height -= self.pickerView.frame.size.height - 49 /* tab bar height */;
self.tableView.frame = newFrame;
[UIView commitAnimations];
// add the "Done" button to the nav bar
self.navigationItem.rightBarButtonItem = self.doneButton;
}
}
You should set the UIDatePicker object as the inputView of that particular text field. All the animations and presentation stuff will be taken care of.
Hiding the cursor
There is no method to hide the cursor. The only mechanism I could think of was to use the leftView property and set it to a label.
CGRect frame = self.textField.bounds;
UILabel * theLabel = [[[UILabel alloc] initWithFrame:frame] autorelease];
theLabel.userInteractionEnabled = YES;
theLabel.backgroundColor = [UIColor clearColor];
UITapGestureRecognizer * tap = [[[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(tap:)] autorelease];
[theLabel addGestureRecognizer:tap];
self.textField.leftViewMode = UITextFieldViewModeAlways;
self.textField.leftView = theLabel;
self.textField.clipsToBounds = YES;
and handle the tap using,
- (void)tap:(UIGestureRecognizer *)gesture {
[self.textField becomeFirstResponder];
}
Now this doesn't work for the rounded rect button as the cursor blinks on the right corner no matter what the label's frame is. It hides the cursor for other border styles. You can also use the label to your advantage by setting your values as its text.
UILabel * theLabel = textField.leftView;
theLabel.text = #"Appropriate Value from Picker";

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)
}
}