With the iOS SDK:
I have a UIView
with UITextField
s that bring up a keyboard. I need it to be able to:
Allow scrolling of the contents of the
UIScrollView
to see the other text fields once the keyboard is brought upAutomatically "jump" (by scrolling up) or shortening
I know that I need a UIScrollView
. I've tried changing the class of my UIView
to a UIScrollView
but I'm still unable to scroll the textboxes up or down.
Do I need both a UIView
and a UIScrollView
? Does one go inside the other?
What needs to be implemented in order to automatically scroll to the active text field?
Ideally as much of the setup of the components as possible will be done in Interface Builder. I'd like to only write code for what needs it.
Note: the UIView
(or UIScrollView
) that I'm working with is brought up by a tabbar (UITabBar
), which needs to function as normal.
Edit: I am adding the scroll bar just for when the keyboard comes up. Even though it's not needed, I feel like it provides a better interface because then the user can scroll and change textboxes, for example.
I've got it working where I change the frame size of the UIScrollView
when the keyboard goes up and down. I'm simply using:
-(void)textFieldDidBeginEditing:(UITextField *)textField {
//Keyboard becomes visible
scrollView.frame = CGRectMake(scrollView.frame.origin.x,
scrollView.frame.origin.y,
scrollView.frame.size.width,
scrollView.frame.size.height - 215 + 50); //resize
}
-(void)textFieldDidEndEditing:(UITextField *)textField {
//keyboard will hide
scrollView.frame = CGRectMake(scrollView.frame.origin.x,
scrollView.frame.origin.y,
scrollView.frame.size.width,
scrollView.frame.size.height + 215 - 50); //resize
}
However, this doesn't automatically "move up" or center the lower text fields in the visible area, which is what I would really like.
You will only need a
ScrollView
if the contents you have now do not fit in the iPhone screen. (If you are adding theScrollView
as the superview of the components. just to make theTextField
scroll up when keyboard comes up, then it's not needed.)For showing the
textfields
without being hidden by the keyboard, the standard way is to move up/down the view having textfields whenever the keyboard is shown.
Here is some sample code:
#define kOFFSET_FOR_KEYBOARD 80.0
-(void)keyboardWillShow {
// Animate the current view out of the way
if (self.view.frame.origin.y >= 0)
{
[self setViewMovedUp:YES];
}
else if (self.view.frame.origin.y < 0)
{
[self setViewMovedUp:NO];
}
}
-(void)keyboardWillHide {
if (self.view.frame.origin.y >= 0)
{
[self setViewMovedUp:YES];
}
else if (self.view.frame.origin.y < 0)
{
[self setViewMovedUp:NO];
}
}
-(void)textFieldDidBeginEditing:(UITextField *)sender
{
if ([sender isEqual:mailTf])
{
//move the main view, so that the keyboard does not hide it.
if (self.view.frame.origin.y >= 0)
{
[self setViewMovedUp:YES];
}
}
}
//method to move the view up/down whenever the keyboard is shown/dismissed
-(void)setViewMovedUp:(BOOL)movedUp
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.3]; // if you want to slide up the view
CGRect rect = self.view.frame;
if (movedUp)
{
// 1. move the view's origin up so that the text field that will be hidden come above the keyboard
// 2. increase the size of the view so that the area behind the keyboard is covered up.
rect.origin.y -= kOFFSET_FOR_KEYBOARD;
rect.size.height += kOFFSET_FOR_KEYBOARD;
}
else
{
// revert back to the normal state.
rect.origin.y += kOFFSET_FOR_KEYBOARD;
rect.size.height -= kOFFSET_FOR_KEYBOARD;
}
self.view.frame = rect;
[UIView commitAnimations];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// register for keyboard notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide)
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// unregister for keyboard notifications while not visible.
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillHideNotification
object:nil];
}
textFieldDidBeginEditing
section.One thing to consider is whether you ever want to use a UITextField
on its own. I haven’t come across any well-designed iPhone apps that actually use UITextFields
outside of UITableViewCells
.
It will be some extra work, but I recommend you implement all data entry views a table views. Add a UITextView
to your UITableViewCells
.
UITableView
is sadly the only way to go. Keyboard notifications are brittle and have changed overtime. Sample code on Stack Overflow: stackoverflow.com/a/32390936/218152I'm not sure if moving the view up is the correct approach, I did it in a differente way, resizing the UIScrollView. I explained it in details on a little article
RPDP's code successfully moves the text field out of the way of the keyboard. But when you scroll to the top after using and dismissing the keyboard, the top has been scrolled up out of the view. This is true for the Simulator and the device. To read the content at the top of that view, one has to reload the view.
Isn't his following code supposed to bring the view back down?
else
{
// revert back to the normal state.
rect.origin.y += kOFFSET_FOR_KEYBOARD;
rect.size.height -= kOFFSET_FOR_KEYBOARD;
}
To bring back to original view state, add:
-(void)textFieldDidEndEditing:(UITextField *)sender
{
//move the main view, so that the keyboard does not hide it.
if (self.view.frame.origin.y < 0)
{
[self setViewMovedUp:NO];
}
}
Little fix that works for many UITextFields
#pragma mark UIKeyboard handling
#define kMin 150
-(void)textFieldDidBeginEditing:(UITextField *)sender
{
if (currTextField) {
[currTextField release];
}
currTextField = [sender retain];
//move the main view, so that the keyboard does not hide it.
if (self.view.frame.origin.y + currTextField.frame.origin. y >= kMin) {
[self setViewMovedUp:YES];
}
}
//method to move the view up/down whenever the keyboard is shown/dismissed
-(void)setViewMovedUp:(BOOL)movedUp
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.3]; // if you want to slide up the view
CGRect rect = self.view.frame;
if (movedUp)
{
// 1. move the view's origin up so that the text field that will be hidden come above the keyboard
// 2. increase the size of the view so that the area behind the keyboard is covered up.
rect.origin.y = kMin - currTextField.frame.origin.y ;
}
else
{
// revert back to the normal state.
rect.origin.y = 0;
}
self.view.frame = rect;
[UIView commitAnimations];
}
- (void)keyboardWillShow:(NSNotification *)notif
{
//keyboard will be shown now. depending for which textfield is active, move up or move down the view appropriately
if ([currTextField isFirstResponder] && currTextField.frame.origin.y + self.view.frame.origin.y >= kMin)
{
[self setViewMovedUp:YES];
}
else if (![currTextField isFirstResponder] && currTextField.frame.origin.y + self.view.frame.origin.y < kMin)
{
[self setViewMovedUp:NO];
}
}
- (void)keyboardWillHide:(NSNotification *)notif
{
//keyboard will be shown now. depending for which textfield is active, move up or move down the view appropriately
if (self.view.frame.origin.y < 0 ) {
[self setViewMovedUp:NO];
}
}
- (void)viewWillAppear:(BOOL)animated
{
// register for keyboard notifications
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification object:self.view.window];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification object:self.view.window];
}
- (void)viewWillDisappear:(BOOL)animated
{
// unregister for keyboard notifications while not visible.
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
}
rect.origin.y=+currTextField.frame.origin.y
working fine thank youHere is the hack solution I came up with for a specific layout. This solution is similar to Matt Gallagher solution in that is scrolls a section into view. I am still new to iPhone development, and am not familiar with how the layouts work. Thus, this hack.
My implementation needed to support scrolling when clicking in a field, and also scrolling when the user selects next on the keyboard.
I had a UIView with a height of 775. The controls are spread out basically in groups of 3 over a large space. I ended up with the following IB layout.
UIView -> UIScrollView -> [UI Components]
Here comes the hack
I set the UIScrollView height to 500 units larger then the actual layout (1250). I then created an array with the absolute positions I need to scroll to, and a simple function to get them based on the IB Tag number.
static NSInteger stepRange[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 140, 140, 140, 140, 140, 410
};
NSInteger getScrollPos(NSInteger i) {
if (i < TXT_FIELD_INDEX_MIN || i > TXT_FIELD_INDEX_MAX) {
return 0 ;
return stepRange[i] ;
}
Now all you need to do is use the following two lines of code in textFieldDidBeginEditing and textFieldShouldReturn (the latter one if you are creating a next field navigation)
CGPoint point = CGPointMake(0, getScrollPos(textField.tag)) ;
[self.scrollView setContentOffset:point animated:YES] ;
An example.
- (void) textFieldDidBeginEditing:(UITextField *)textField
{
CGPoint point = CGPointMake(0, getScrollPos(textField.tag)) ;
[self.scrollView setContentOffset:point animated:YES] ;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
NSInteger nextTag = textField.tag + 1;
UIResponder* nextResponder = [textField.superview viewWithTag:nextTag];
if (nextResponder) {
[nextResponder becomeFirstResponder];
CGPoint point = CGPointMake(0, getScrollPos(nextTag)) ;
[self.scrollView setContentOffset:point animated:YES] ;
}
else{
[textField resignFirstResponder];
}
return YES ;
}
This method does not 'scroll back' as other methods do. This was not a requirement. Again this was for a fairly 'tall' UIView, and I did not have days to learn the internal layout engines.
I was also having a lot of issue with a UIScrollView
composing of multiple UITextFields
, of which, one or more of them would get obscured by the keyboard when they are being edited.
Here are some things to consider if your UIScrollView
is not properly scrolling.
1) Ensure that your contentSize is greater than the UIScrollView
frame size. The way to understand UIScrollViews
is that the UIScrollView
is like a viewing window on the content defined in the contentSize. So when in order for the UIScrollview
to scroll anywhere, the contentSize must be greater than the UIScrollView
. Else, there is no scrolling required as everything defined in the contentSize is already visible. BTW, default contentSize = CGSizeZero
.
2) Now that you understand that the UIScrollView
is really a window into your "content", the way to ensure that the keyboard is not obscuring your UIScrollView's
viewing "window" would be to resize the UIScrollView
so that when the keyboard is present, you have the UIScrollView
window sized to just the original UIScrollView
frame.size.height minus the height of the keyboard. This will ensure that your window is only that small viewable area.
3) Here's the catch: When I first implemented this I figured I would have to get the CGRect
of the edited textfield and call UIScrollView's
scrollRecToVisible method. I implemented the UITextFieldDelegate
method textFieldDidBeginEditing
with the call to the scrollRecToVisible
method. This actually worked with a strange side effect that the scrolling would snap the UITextField
into position. For the longest time I couldn't figure out what it was. Then I commented out the textFieldDidBeginEditing
Delegate method and it all work!!(???). As it turned out, I believe the UIScrollView
actually implicitly brings the currently edited UITextField
into the viewable window implicitly. My implementation of the UITextFieldDelegate
method and subsequent call to the scrollRecToVisible
was redundant and was the cause of the strange side effect.
So here are the steps to properly scroll your UITextField
in a UIScrollView
into place when the keyboard appears.
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
[super viewDidLoad];
// register for keyboard notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:self.view.window];
// register for keyboard notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:self.view.window];
keyboardIsShown = NO;
//make contentSize bigger than your scrollSize (you will need to figure out for your own use case)
CGSize scrollContentSize = CGSizeMake(320, 345);
self.scrollView.contentSize = scrollContentSize;
}
- (void)keyboardWillHide:(NSNotification *)n
{
NSDictionary* userInfo = [n userInfo];
// get the size of the keyboard
CGSize keyboardSize = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
// resize the scrollview
CGRect viewFrame = self.scrollView.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 - kTabBarHeight);
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[self.scrollView setFrame:viewFrame];
[UIView commitAnimations];
keyboardIsShown = NO;
}
- (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 (keyboardIsShown) {
return;
}
NSDictionary* userInfo = [n userInfo];
// get the size of the keyboard
CGSize keyboardSize = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
// resize the noteView
CGRect viewFrame = self.scrollView.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 - kTabBarHeight);
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[self.scrollView setFrame:viewFrame];
[UIView commitAnimations];
keyboardIsShown = YES;
}
- Register for the keyboard notifications at
viewDidLoad
- Unregister for the keyboard nofitications at
viewDidUnload
- Ensure that the
contentSize
is set and greater than yourUIScrollView
atviewDidLoad
- Shrink the
UIScrollView
when the keyboard is present - Revert back the
UIScrollView
when the keyboard goes away. - Use an ivar to detect if the keyboard is already shown on the screen since the keyboard notifications are sent each time a
UITextField
is tabbed even if the keyboard is already present to avoid shrinking theUIScrollView
when it's already shrunk
One thing to note is that the UIKeyboardWillShowNotification
will fire even when the keyboard is already on the screen when you tab on another UITextField
. I took care of this by using an ivar to avoid resizing the UIScrollView
when the keyboard is already on the screen. Inadvertently resizing the UIScrollView
when the keyboard is already there would be disastrous!
Hope this code saves some of you a lot of headache.
UIKeyboardBoundsUserInfoKey
is deprecated. 2. keyboardSize is in "screen coordinates", so your viewFrame calculations will fail if the frame is rotated or scaled.CGSize keyboardSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
instead of the deprecated UIKeyboardBoundsUserInfoKey
[[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size
should be [[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size
. Great solution though!This document details a solution to this problem. Look at the source code under 'Moving Content That Is Located Under the Keyboard'. It's pretty straightforward.
EDIT: Noticed there's a wee glitch in the example. You will probably want to listen for UIKeyboardWillHideNotification
instead of UIKeyboardDidHideNotification
. Otherwise the scroll view behind of the keyboard will be clipped for the duration of the keyboard closing animation.
@user271753
To get your view back to original add:
-(BOOL)textFieldShouldReturn:(UITextField *)textField{
[textField resignFirstResponder];
[self setViewMovedUp:NO];
return YES;
}
Shiun said "As it turned out, I believe the UIScrollView actually implicitly brings the currently edited UITextField into the viewable window implicitly" This seems to be true for iOS 3.1.3, but not 3.2, 4.0, or 4.1. I had to add an explicit scrollRectToVisible in order to make the UITextField visible on iOS >= 3.2.
[UITextField scrollTextFieldToVisibleIfNecessary]
method which in turn calls [UIScrollView scrollRectToVisible]
when [UITextField becomeFirstResponder]
is called. See github.com/leopatras/ios_textfields_on_scrollview. If constraints and view controllers are setup properly there is actually no need to call scrollRectToVisible
explicitly (at least since IOS 11).It's actually best just to use Apple's implementation, as provided in the docs. However, the code they provide is faulty. Replace the portion found in keyboardWasShown:
just below the comments to the following:
NSDictionary* info = [aNotification userInfo];
CGRect keyPadFrame=[[UIApplication sharedApplication].keyWindow convertRect:[[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue] fromView:self.view];
CGSize kbSize =keyPadFrame.size;
CGRect activeRect=[self.view convertRect:activeField.frame fromView:activeField.superview];
CGRect aRect = self.view.bounds;
aRect.size.height -= (kbSize.height);
CGPoint origin = activeRect.origin;
origin.y -= backScrollView.contentOffset.y;
if (!CGRectContainsPoint(aRect, origin)) {
CGPoint scrollPoint = CGPointMake(0.0,CGRectGetMaxY(activeRect)-(aRect.size.height));
[backScrollView setContentOffset:scrollPoint animated:YES];
}
The problems with Apple's code are these:
(1) They always calculate if the point is within the view's frame, but it's a ScrollView
, so it may already have scrolled and you need to account for that offset:
origin.y -= scrollView.contentOffset.y
(2) They shift the contentOffset by the height of the keyboard, but we want the opposite (we want to shift the contentOffset
by the height that is visible on the screen, not what isn't):
activeField.frame.origin.y-(aRect.size.height)
UIKeyboardFrameEndUserInfoKey
instead of UIKeyboardFrameBeginUserInfoKey
when getting the keyboard size, as this will pick up things such as custom keyboard changes and toggling predictive text on/off.self.scrollView.contentOffset = self.currentSVoffset;
When UITextField
is in a UITableViewCell
scrolling should be setup automatically.
If it is not it is probably because of incorrect code/setup of the tableview.
For example when i reloaded my long table with one UITextField
at the bottom as follows,
-(void) viewWillAppear:(BOOL)animated
{
[self.tableview reloadData];
}
then my textfield at the bottom was obscured by the keyboard which appeared when I clicked inside the textfield.
To fix this I had to do this -
-(void) viewWillAppear:(BOOL)animated
{
//add the following line to fix issue
[super viewWillAppear:animated];
[self.tableview reloadData];
}
viewWillAppear
is not called. And reloadData
doesn't make obscured rows become visible.I've put together a universal, drop-in UIScrollView
, UITableView
and even UICollectionView
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' tis: solution for moving text fields out of the way of the keyboard
In textFieldDidBeginEditting
and in textFieldDidEndEditing
call the function [self animateTextField:textField up:YES]
like so:
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
[self animateTextField:textField up:YES];
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
[self animateTextField:textField up:NO];
}
-(void)animateTextField:(UITextField*)textField up:(BOOL)up
{
const int movementDistance = -130; // tweak as needed
const float movementDuration = 0.3f; // tweak as needed
int movement = (up ? movementDistance : -movementDistance);
[UIView beginAnimations: @"animateTextField" context: nil];
[UIView setAnimationBeginsFromCurrentState: YES];
[UIView setAnimationDuration: movementDuration];
self.view.frame = CGRectOffset(self.view.frame, 0, movement);
[UIView commitAnimations];
}
I hope this code will help you.
In Swift 2
func animateTextField(textField: UITextField, up: Bool)
{
let movementDistance:CGFloat = -130
let movementDuration: Double = 0.3
var movement:CGFloat = 0
if up
{
movement = movementDistance
}
else
{
movement = -movementDistance
}
UIView.beginAnimations("animateTextField", context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(movementDuration)
self.view.frame = CGRectOffset(self.view.frame, 0, movement)
UIView.commitAnimations()
}
func textFieldDidBeginEditing(textField: UITextField)
{
self.animateTextField(textField, up:true)
}
func textFieldDidEndEditing(textField: UITextField)
{
self.animateTextField(textField, up:false)
}
SWIFT 3
func animateTextField(textField: UITextField, up: Bool)
{
let movementDistance:CGFloat = -130
let movementDuration: Double = 0.3
var movement:CGFloat = 0
if up
{
movement = movementDistance
}
else
{
movement = -movementDistance
}
UIView.beginAnimations("animateTextField", context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(movementDuration)
self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)
UIView.commitAnimations()
}
func textFieldDidBeginEditing(textField: UITextField)
{
self.animateTextField(textField: textField, up:true)
}
func textFieldDidEndEditing(textField: UITextField)
{
self.animateTextField(textField: textField, up:false)
}
[UIView animateWithDuration: animations:^{ }];
?It doesn't require a scroll view to be able to move the view frame. You can change the frame of a viewcontroller's
view so that the entire view moves up just enough to put the firstresponder text field above the keyboard. When I ran into this problem I created a subclass of UIViewController
that does this. It observes for the keyboard will appear notification and finds the first responder subview and (if needed) it animates the main view upward just enough so that the first responder is above the keyboard. When the keyboard hides, it animates the view back where it was.
To use this subclass make your custom view controller a subclass of GMKeyboardVC and it inherits this feature (just be sure if you implement viewWillAppear
and viewWillDisappear
they must call super). The class is on github.
Been searching for a good tutorial for beginners on the subject, found the best tutorial here.
In the MIScrollView.h
example at the bottom of the tutorial be sure to put a space at
@property (nonatomic, retain) id backgroundTapDelegate;
as you see.
You need to add scrollview programmatically with specific frame size. You have to add UIScrollViewDelegate
in .h file. You have to enable scrollview for that you need to write following in viewDidLoad().
scrollview.scrollEnabled=YES;
scrollview.delegate=self;
scrollview.frame = CGRectMake(x,y,width,height);
//---set the content size of the scroll view---
[scrollview setContentSize:CGSizeMake(height,width)];
This way, you can add your x, y, width and height values.
A much, much more elegant solution is to use a UIView
subclass (though this isn't always appropriate) and recalculate all your subviews on a parent's frame change (and be smart: only recalculate them if the new frame size has changed, i.e. use CGRectEqualToRect
to compare the new frame when you override setFrame
and BEFORE you call [super setFrame:frame_]
). The only catch to this is that the UIViewController
you intend to use should probably listen to keyboard events (or, you could do it in the UIView
itself, for handy encapsulation). But only the UIKeyboardWillShowNotification
and UIKeyboardWillHideNotification
. This is just so it will look smooth (if you wait for CG to call it, you will get a moment of choppiness).
This has the advantage of building a UIView
subclass that does the right thing, anyway.
The naive implementation would be to override drawRect:
(don't), a better one would be to just use layoutSubviews
(and then in the UIViewController
, or whatnot you can call [view setNeedsLayout
] in a SINGLE method that is called for either show or hide).
This solution gets away from hardcoding a keyboard offset (which will change if they are not in split, etc) and also means that your view could be a subview of many other views and still respond properly.
Don't hardcode something like that unless there is no other solution. The OS gives you enough info, if you've done things right, that you just need to redraw intelligently (based on your new frame
size). This is much cleaner and the way you should do things. (There may be an even better approach, though.)
Cheers.
Try this:
-(void)textFieldDidBeginEditing:(UITextField *)sender
{
if ([sender isEqual:self.m_Sp_Contact])
{
[self.m_Scroller setContentOffset:CGPointMake(0, 105)animated:YES];
}
}
Please follow these steps ,it might be helpful. Put one view then put your textfield on that view and detect the event by delegate when your keyboard is coming up,at that time instant animate the view up(You can assign some position for that view also),then your view would be going up to that position.Do the same thing for animating the view down.
Thanks
As per the docs, as of iOS 3.0, the UITableViewController
class automatically resizes and repositions its table view when there is in-line editing of text fields. I think it's not sufficient to put the text field inside a UITableViewCell
as some have indicated.
From the docs:
A table view controller supports inline editing of table view rows; if, for example, rows have embedded text fields in editing mode, it scrolls the row being edited above the virtual keyboard that is displayed.
There so many solutions, but I've spend some hours before it start works. So, I put this code here (just paste to the project, any modifications needn't):
@interface RegistrationViewController : UIViewController <UITextFieldDelegate>{
UITextField* activeField;
UIScrollView *scrollView;
}
@end
- (void)viewDidLoad
{
[super viewDidLoad];
scrollView = [[UIScrollView alloc] initWithFrame:self.view.frame];
//scrool view must be under main view - swap it
UIView* natView = self.view;
[self setView:scrollView];
[self.view addSubview:natView];
CGSize scrollViewContentSize = self.view.frame.size;
[scrollView setContentSize:scrollViewContentSize];
[self registerForKeyboardNotifications];
}
- (void)viewDidUnload {
activeField = nil;
scrollView = nil;
[self unregisterForKeyboardNotifications];
[super viewDidUnload];
}
- (void)registerForKeyboardNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShown:)
name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification object:nil];
}
-(void)unregisterForKeyboardNotifications
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillShowNotification
object:nil];
// unregister for keyboard notifications while not visible.
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)keyboardWillShown:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
CGRect frame = self.view.frame;
frame.size.height -= kbSize.height;
CGPoint fOrigin = activeField.frame.origin;
fOrigin.y -= scrollView.contentOffset.y;
fOrigin.y += activeField.frame.size.height;
if (!CGRectContainsPoint(frame, fOrigin) ) {
CGPoint scrollPoint = CGPointMake(0.0, activeField.frame.origin.y + activeField.frame.size.height - frame.size.height);
[scrollView setContentOffset:scrollPoint animated:YES];
}
}
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
[scrollView setContentOffset:CGPointZero animated:YES];
}
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
activeField = textField;
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
activeField = nil;
}
-(BOOL) textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return YES;
}
P.S: I hope the code help somebody make desired effect quickly. (Xcode 4.5)
Lots of answers here, but this works and is much shorter than most:
- (void)textFieldDidBeginEditing:(UITextField *)sender
{
UIScrollView *scrollView = (UIScrollView *)self.view; // assuming this method is pasted into the UIScrollView's controller
const double dontHardcodeTheKeyboardHeight = 162;
double textY = [sender convertPoint:CGPointMake(0, 0) toView:scrollView].y;
if (textY - scrollView.contentOffset.y + sender.frame.size.height > self.view.frame.size.height - dontHardcodeTheKeyboardHeight)
[scrollView setContentOffset:CGPointMake(0.0, textY - 10) animated:YES];
}
Just using TextFields:
1a) Using Interface Builder
: Select All TextFields => Edit => Embed In => ScrollView
1b) Manually embed TextFields in UIScrollView called scrollView
2) Set UITextFieldDelegate
3) Set each textField.delegate = self;
(or make connections in Interface Builder
)
4) Copy / Paste:
- (void)textFieldDidBeginEditing:(UITextField *)textField {
CGPoint scrollPoint = CGPointMake(0, textField.frame.origin.y);
[scrollView setContentOffset:scrollPoint animated:YES];
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
[scrollView setContentOffset:CGPointZero animated:YES];
}
textField
is already visible.CGPointMake(0, textField.frame.origin.y);
to CGPointMake(0, textField.frame.origin.y + scrollView.contentInset.top);
You can do by using textfield delegate methods also. Check below code. It's working for me when placed textfield on scroll view.
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
if(textField == answer)
{
CGPoint cPoint = textField.frame.origin;
[scrollView setContentOffset:CGPointMake(0, cPoint.y - 100) animated:YES];
}
}
Note: You have to change cPoint.y - 100 value according to your view.
Here is a free library for keyboard handling Keyboard-Handling-in-iPhone-Applications. You need write just one line of code:
[AutoScroller addAutoScrollTo:scrollView];
This is awesome to handle keyboard in forms
There are already a lot of answers, but still none of the solutions above had all the fancy positioning stuff required for a "perfect" bug-free, backwards compatible and flicker-free animation. (bug when animating frame/bounds and contentOffset together, different interface orientations, iPad split keyboard, ...)
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];
}
UIApplication.shared.sendAction(...)
. Here's the Swift 3 version of your answer (minus willHide portion), with the sendAction
implemented: gist.github.com/xaphod/7aab1302004f6e933593a11ad8f5a72dPlease follow these steps.
1) Declare following variable in .h file.
{
CGFloat animatedDistance;
}
2) Declare following constants in .m file.
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;
static const CGFloat LANDSCAPE_KEYBOARD_HEIGHT = 162;
3) Use UITextField delegate to move up/down keyboard.
-(void) textFieldDidBeginEditing:(UITextField *)textField
{
if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
CGRect textFieldRect = [self.view.window convertRect:textField.bounds fromView:textField];
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)
{
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) textFieldDidEndEditing:(UITextField *)textField
{
if(UI_USER_INTERFACE_IDIOM()==UIUserInterfaceIdiomPhone)
{
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];
}
}
For Universal Solution, Here was my approach for implementing IQKeyboardManager.
Step1:- I Added global notifications of UITextField
, UITextView
, and UIKeyboard
in a singleton class. I call it IQKeyboardManager.
Step2:- If found UIKeyboardWillShowNotification
, UITextFieldTextDidBeginEditingNotification
or UITextViewTextDidBeginEditingNotification
notifications, I try to get topMostViewController
instance from the UIWindow.rootViewController
hierarchy. In order to properly uncover UITextField
/UITextView
on it, topMostViewController.view
's frame needs to be adjusted.
Step3:- I calculated expected move distance of topMostViewController.view
with respect to first responded UITextField
/UITextView
.
Step4:- I moved topMostViewController.view.frame
up/down according to the expected move distance.
Step5:- If found UIKeyboardWillHideNotification
, UITextFieldTextDidEndEditingNotification
or UITextViewTextDidEndEditingNotification
notification, I again try to get topMostViewController
instance from the UIWindow.rootViewController
hierarchy.
Step6:- I calculated disturbed distance of topMostViewController.view
which needs to be restored to it's original position.
Step7:- I restored topMostViewController.view.frame
down according to the disturbed distance.
Step8:- I instantiated singleton IQKeyboardManager class instance on app load, so every UITextField
/UITextView
in the app will adjust automatically according to the expected move distance.
That's all IQKeyboardManager do for you with NO LINE OF CODE really!! only need to drag and drop related source file to project. IQKeyboardManager also support Device Orientation, Automatic UIToolbar Management, KeybkeyboardDistanceFromTextField and much more than you think.