Scrolling UITextField into view when keyboard is shown - iphone

I'm writing an app targeting iOS 6.1. I'm trying to follow this Apple doc on how to scroll a UITextField into view if it is obscured by the keyboard. The problem is that Apple's documented algorithm for calculating the scroll point doesn't work so well. My algorithm for calculating the scroll point works only slightly better, but is off by 70 pixels. What is the correct way to calculate the scroll point?
Below you can see, from left to right, my view before the keyboard is shown, my view after scrolling using Apple's algorithm to calculate the scroll point, and my view after scrolling using my algorithm to calculate the scroll point. (Each square in that grid is 25 pixels by 25 pixels.)
And here is the code I am using. Note the #if APPLE_WAY block.
- (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);
self.view.contentInset = contentInsets;
self.view.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, self.activeField.frame.origin) ) {
#if APPLE_WAY
CGPoint scrollPoint = CGPointMake(0.0, self.activeField.frame.origin.y-kbSize.height);
#else
CGFloat offset = self.activeField.frame.origin.y;
//TODO: Why is this off by 70?
offset = offset - 70;
CGPoint scrollPoint = CGPointMake(0.0, offset);
#endif
[self.view setContentOffset:scrollPoint animated:YES];
}
}

You simply need to set the offset to the height of the keyboard:
CGFloat offset = kbSize.height;
CGPoint scrollPoint = CGPointMake(0.0, offset);
[self.view setContentOffset:scrollPoint animated:YES];
hope this helps

There are a few ways to do this. Given your situation, I would do something like this:
- (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);
self.scrollView.contentInset = contentInsets;
self.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, self.activeField.frame.origin) ) {
CGPoint scrollPoint = CGPointMake(0.0, self.activeField.frame.origin.y-kbSize.height);
[self.scrollView setContentOffset:scrollPoint animated:YES];
}
}
- (void)keyboardWillBeHidden:(NSNotification *)aNotification
{
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);
self.scrollView.contentInset = contentInsets;
self.scrollView.scrollIndicatorInsets = contentInsets;
CGRect aRect = self.view.frame;
aRect.size.height -= kbSize.height;
if (!CGRectContainsPoint(aRect, self.activeField.frame.origin) ) {
CGPoint scrollPoint = CGPointMake(0, 0);
[self.scrollView setContentOffset:scrollPoint animated:YES];
}
}
Then, in your viewDidLoad method:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification object:nil];

It's easy when what you've got is a scroll view, because you don't even have to scroll; all you have to do is adjust the contentInset and scrollIndicatorInsets to get out of the keyboard's way, and the rest happens automatically:
- (void) keyboardShow: (NSNotification*) n {
self->_oldContentInset = self.scrollView.contentInset;
self->_oldIndicatorInset = self.scrollView.scrollIndicatorInsets;
self->_oldOffset = self.scrollView.contentOffset;
NSDictionary* d = [n userInfo];
CGRect r = [[d objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
r = [self.scrollView convertRect:r fromView:nil];
CGRect f = self.fr.frame;
CGFloat y =
CGRectGetMaxY(f) + r.size.height -
self.scrollView.bounds.size.height + 5;
if (r.origin.y < CGRectGetMaxY(f))
[self.scrollView setContentOffset:CGPointMake(0, y) animated:YES];
UIEdgeInsets insets;
insets = self.scrollView.contentInset;
insets.bottom = r.size.height;
self.scrollView.contentInset = insets;
insets = self.scrollView.scrollIndicatorInsets;
insets.bottom = r.size.height;
self.scrollView.scrollIndicatorInsets = insets;
}
I give a bunch of different strategies, with code, in this section of my book:
http://www.apeth.com/iOSBook/ch23.html#_keyboard_covers_text_field
All of that is backed by downloadable projects that you can grab at my github site.

Related

Why does the view scroll up when keyboard is present for a textview but not for a textfield?

I have a textfield and a textview in a scroll view. When I begin to edit the textview the view scrolls up appropriately so the keyboard does not hide the text. The textfield, however, does not scroll up but is hidden by the keyboard. I have set the delegates of the textfields and textviews to the view controllers. The code I have so far is below. Thanks for the help in advance.
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
activeField = textField;
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
activeField = nil;
}
- (void)registerForKeyboardNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification object:nil];
}
- (void)keyboardWasShown:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
CGRect kbRect = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
kbRect = [self.view convertRect:kbRect toView:nil];
CGSize kbSize = kbRect.size;
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);
pageScrollView.contentInset = contentInsets;
pageScrollView.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);
[pageScrollView setContentOffset:scrollPoint animated:YES];
}
}
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
UIEdgeInsets contentInsets = UIEdgeInsetsZero;
pageScrollView.contentInset = contentInsets;
pageScrollView.scrollIndicatorInsets = contentInsets;
}
michael tyson created TPKeyboardAvoiding which is very easy to use you can get it from github. https://github.com/michaeltyson/TPKeyboardAvoiding
I do not know why it works for your textview, but for a scrollview with textfields I am doing this manually via
// Called when the UIKeyboardDidShowNotification is sent.
- (void)keyboardWillBeShown:(NSNotification*)aNotification
{
if(keyboardShown) return;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
[UIView setAnimationDelegate:self];
NSDictionary* info = [aNotification userInfo];
// Get the size of the keyboard.
NSValue* keyboardFrameValue = [info objectForKey:UIKeyboardFrameEndUserInfoKey];
CGRect keyboardRectWrtScreen = [keyboardFrameValue CGRectValue];
CGRect keyboardRectWrtView = [scrollView convertRect:[[scrollView window] convertRect:keyboardRectWrtScreen fromWindow:nil] fromView: nil];
float keyboardHeight = keyboardRectWrtView.size.height;
scrollView.frame = CGRectMake(scrollView.frame.origin.x, scrollView.frame.origin.y, scrollView.frame.size.width,scrollView.frame.size.height-keyboardHeight);
if(activeTextField) {
CGRect rect = [contentView convertRect:[activeTextField bounds] fromView:activeTextField];
rect.origin.y -= 25;
rect.size.height += 50;
[scrollView scrollRectToVisible:rect animated:YES];
}
[UIView commitAnimations];
keyboardShown = YES;
}
as you see, I am manually moving the scroll view to the visible part, then ensuring that the text field is in the center of the scroll view.

Generic UITableView keyboard resizing algorithm

I've searched a lot for code that resizes the table view to accomodate for keyboard showing and hiding, but almost every single post i came across assumes that the table view is taking the entire view of its view controller. I have an iPad application where the table view is only taking part of the screen. What's the correct way to resize the table view in this case? (all the code in the posts i've mentioned above fails)
The following code does what you want and works with any device and any layout. The code is courtesy of the Sensible TableView framework (with permission to copy and use).
- (void)keyboardWillShow:(NSNotification *)aNotification
{
if(keyboardShown)
return;
keyboardShown = YES;
// Get the keyboard size
UIScrollView *tableView;
if([self.tableView.superview isKindOfClass:[UIScrollView class]])
tableView = (UIScrollView *)self.tableView.superview;
else
tableView = self.tableView;
NSDictionary *userInfo = [aNotification userInfo];
NSValue *aValue = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
CGRect keyboardRect = [tableView.superview convertRect:[aValue CGRectValue] fromView:nil];
// Get the keyboard's animation details
NSTimeInterval animationDuration;
[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];
UIViewAnimationCurve animationCurve;
[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&animationCurve];
// Determine how much overlap exists between tableView and the keyboard
CGRect tableFrame = tableView.frame;
CGFloat tableLowerYCoord = tableFrame.origin.y + tableFrame.size.height;
keyboardOverlap = tableLowerYCoord - keyboardRect.origin.y;
if(self.inputAccessoryView && keyboardOverlap>0)
{
CGFloat accessoryHeight = self.inputAccessoryView.frame.size.height;
keyboardOverlap -= accessoryHeight;
tableView.contentInset = UIEdgeInsetsMake(0, 0, accessoryHeight, 0);
tableView.scrollIndicatorInsets = UIEdgeInsetsMake(0, 0, accessoryHeight, 0);
}
if(keyboardOverlap < 0)
keyboardOverlap = 0;
if(keyboardOverlap != 0)
{
tableFrame.size.height -= keyboardOverlap;
NSTimeInterval delay = 0;
if(keyboardRect.size.height)
{
delay = (1 - keyboardOverlap/keyboardRect.size.height)*animationDuration;
animationDuration = animationDuration * keyboardOverlap/keyboardRect.size.height;
}
[UIView animateWithDuration:animationDuration delay:delay
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{ tableView.frame = tableFrame; }
completion:^(BOOL finished){ [self tableAnimationEnded:nil finished:nil contextInfo:nil]; }];
}
}
- (void)keyboardWillHide:(NSNotification *)aNotification
{
if(!keyboardShown)
return;
keyboardShown = NO;
UIScrollView *tableView;
if([self.tableView.superview isKindOfClass:[UIScrollView class]])
tableView = (UIScrollView *)self.tableView.superview;
else
tableView = self.tableView;
if(self.inputAccessoryView)
{
tableView.contentInset = UIEdgeInsetsZero;
tableView.scrollIndicatorInsets = UIEdgeInsetsZero;
}
if(keyboardOverlap == 0)
return;
// Get the size & animation details of the keyboard
NSDictionary *userInfo = [aNotification userInfo];
NSValue *aValue = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
CGRect keyboardRect = [tableView.superview convertRect:[aValue CGRectValue] fromView:nil];
NSTimeInterval animationDuration;
[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];
UIViewAnimationCurve animationCurve;
[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&animationCurve];
CGRect tableFrame = tableView.frame;
tableFrame.size.height += keyboardOverlap;
if(keyboardRect.size.height)
animationDuration = animationDuration * keyboardOverlap/keyboardRect.size.height;
[UIView animateWithDuration:animationDuration delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{ tableView.frame = tableFrame; }
completion:nil];
}
- (void) tableAnimationEnded:(NSString*)animationID finished:(NSNumber *)finished contextInfo:(void *)context
{
// Scroll to the active cell
if(self.activeCellIndexPath)
{
[self.tableView scrollToRowAtIndexPath:self.activeCellIndexPath atScrollPosition:UITableViewScrollPositionNone animated:YES];
[self.tableView selectRowAtIndexPath:self.activeCellIndexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
}
}
Notes:
a. The above two methods have been added to the notification center using the following code:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
b. The ivars used above has been declared like this:
BOOL keyboardShown;
CGFloat keyboardOverlap;
c. 'self.activeCellIndexPath' is always set to the indexPath of the cell owning the currently active UITextField/UITextView.
Enjoy! :)
I've found the easiest solution to be this one(I'm not fan of using subviews for this kind of stuff):
register for keyboard frame change notification(idealy register in viewWillAppear: and unregister in viewWillDisappear:):
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardDidChangeFrame:) name:UIKeyboardDidChangeFrameNotification object:nil];
and then in method:
- (void)keyboardDidChangeFrame:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
CGRect kbFrame = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGRect kbIntersectFrame = [window convertRect:CGRectIntersection(window.frame, kbFrame) toView:self.scrollView];
kbIntersectFrame = CGRectIntersection(self.bounds, kbIntersectFrame);
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbIntersectFrame.size.height, 0.0);
self.scrollView.contentInset = contentInsets;
self.scrollView.scrollIndicatorInsets = contentInsets;
}
or if You want to get rid of the "jump" after changing contentInset:
- (void)keyboardDidChangeFrame:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
CGRect kbFrame = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGRect kbIntersectFrame = [window convertRect:CGRectIntersection(window.frame, kbFrame) toView:self.scrollView];
kbIntersectFrame = CGRectIntersection(self.scrollView.bounds, kbIntersectFrame);
// get point before contentInset change
CGPoint pointBefore = self.scrollView.contentOffset;
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbIntersectFrame.size.height, 0.0);
self.scrollView.contentInset = contentInsets;
self.scrollView.scrollIndicatorInsets = contentInsets;
// get point after contentInset change
CGPoint pointAfter = self.scrollView.contentOffset;
// avoid jump by settings contentOffset
self.scrollView.contentOffset = pointBefore;
// and now animate smoothly
[self.scrollView setContentOffset:pointAfter animated:YES];
}
The simple solution is to add my extension UIViewController+Keyboard.swift to your project, with a single line setupKeyboardNotifcationListenerForScrollView(tableView) it will auto resize automatically. No need to subclass anything, just an extension! Its open source at SingleLineKeyboardResize
Simple solution - register to receive keyboard notifications on init or viewDidLoad with:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
then when the notification is received, you can use the given rect of the keyboard to adjust the frame of your tableView:
- (void)keyboardWillShow:(NSNotification *)notification
{
// Get the size of the keyboard.
CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
CGRect newTableFrame = _myTableView.frame;
//Here make adjustments to the tableview frame based on the value in keyboard size
...
_myTableView.frame = newTableFrame;
}
- (void)keyboardWillHide:(NSNotification *)notification
{
//Here change the table frame back to what it originally was.
}
Check out this project, it's drag and drop so just declare the tablview as type TPKeyboardAvoidingTableView
Here is the keyboard method:
func keyboardControl(notification: NSNotification, isShowing: Bool) {
var userInfo = notification.userInfo!
let keyboardRect = userInfo[UIKeyboardFrameEndUserInfoKey]!.CGRectValue
let curve = userInfo[UIKeyboardAnimationCurveUserInfoKey]!.unsignedIntValue
let convertedFrame = self.view.convertRect(keyboardRect, fromView: nil)
let heightOffset = self.view.bounds.size.height - convertedFrame.origin.y
let options = UIViewAnimationOptions(rawValue: UInt(curve) << 16)
let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey]!.doubleValue
//your UITableView bottom constrant
self.tableViewMarginBottomConstraint.constant = heightOffset
var contentInsets = UIEdgeInsetsZero
if isShowing {
contentInsets = UIEdgeInsetsMake(0.0, 0.0, (keyboardRect.size.height), 0.0)
}
UIView.animateWithDuration(
duration,
delay: 0,
options: options.union(.LayoutSubviews).union(.BeginFromCurrentState),
animations: {
self.listTableView.contentInset = contentInsets
self.listTableView.scrollIndicatorInsets = contentInsets
self.listTableView.scrollBottomToLastRow()
self.view.layoutIfNeeded()
},
completion: { bool in
})
}
Here is the UITableView extension:
extension UITableView {
func totalRows() -> Int {
var i = 0
var rowCount = 0
while i < self.numberOfSections {
rowCount += self.numberOfRowsInSection(i)
i++
}
return rowCount
}
var lastIndexPath: NSIndexPath {
get { return NSIndexPath(forRow: self.totalRows()-1, inSection: 0) }
}
func scrollBottomToLastRow() {
self.scrollToRowAtIndexPath(self.lastIndexPath, atScrollPosition: .Bottom, animated: false)
}
}
You can achieve what you're looking for by using IQKeyboardManager, it's a codeless library, you just have to add it to your Podfile: pod 'IQKeyboardManager' and that's it, it will hand the scrolling effect when the keyboard is shown even if the UITextField/UITextView is not part of a scrollView/tableView.

Date Picker controller need to set the selected value iphone

am using scrollview for text field when i press that text field the view scrolling down can anyone pls help me why its happening ?
This code am using for scroll
- (void)keyboardWasShown:(NSNotification*)aNotification {
if (keyboardShown)
return;
//static const float deviceHeight = 480;
static const float keyboardHeight = 216;
static const float gap = 5; //gap between the top of keyboard and the control
//Find the controls absolute position in the 320*480 window - it could be nested in other views
CGPoint absolute = [activeField.superview convertPoint:activeField.frame.origin toView:nil];
//NSLog(#"Keyborad Shown %f : %f",absolute.y,(keyboardHeight + gap);
if (absolute.y > (keyboardHeight + gap)) {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.3f]; //this is speed of keyboard
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
CGRect bkgndRect = activeField.superview.frame;
bkgndRect.size.height += kbSize.height;
[activeField.superview setFrame:bkgndRect];
[scrollview setContentOffset:CGPointMake(0.0, activeField.frame.origin.y-92) animated:YES];
[UIView commitAnimations];
}
keyboardShown = YES;
}
// Called when the UIKeyboardDidHideNotification is sent
- (void)keyboardWasHidden:(NSNotification*)aNotification {
//[UIView beginAnimations:nil context:nil];
//[UIView setAnimationDuration:0.003f]; //this is speed of keyboard
NSDictionary* info = [aNotification userInfo];
// Get the size of the keyboard.
NSValue* aValue = [info objectForKey:UIKeyboardFrameEndUserInfoKey];
CGSize keyboardSize = [aValue CGRectValue].size;
// Reset the height of the scroll view to its original value
CGRect viewFrame = [scrollview frame];
viewFrame.size.height += keyboardSize.height;
scrollview.frame = viewFrame;
//[UIView commitAnimations];
keyboardShown = NO;
}
- (void)textFieldDidBeginEditing:(UITextField *)textField {
activeField = textField;
}
Try This....
This code my be helpful for you to scroll your scrollview up.
(BOOL)textViewShouldBeginEditing:(UITextView *)textView {
[scrView setContentOffset:CGPointMake(0, textView.frame.origin.y - 20)];
return YES;
}

Why did my TableView get shifted up?

I have a method that gets called when the keyboard is shown. It's working on other view. Somehow the TableView gets shifted up.
-(void) keyboardDidShow:(NSNotification *) notification {
// -- return if keyboard is still shown
MyManager *sharedManager = [MyManager sharedManager];
if ([sharedManager.keyboardIsShown isEqualToString:#"YES"]) return;
NSDictionary* info = [notification userInfo];
//---obtain the size of the keyboard---
NSValue *aValue = [info objectForKey:UIKeyboardFrameEndUserInfoKey];
CGRect keyboardRect = [self.view convertRect:[aValue CGRectValue] fromView:nil];
//---resize the scroll view (with keyboard)---
CGRect viewFrame = [scrollView frame];
viewFrame.size.height -= keyboardRect.size.height;
scrollView.frame = viewFrame;
//---scroll to the current text field---
CGRect textFieldRect = [currentTextField frame];
[scrollView scrollRectToVisible:textFieldRect animated:YES];
sharedManager.keyboardIsShown = #"YES";
}
Setting myTableView.autoresizingMask = 0; did the trick.

How do I get UITextView to scroll properly when the keyboard is visible

I have a UITextView sitting on top of a UIView, and if I tap on it to open it for editing, then the keyboard is blocking the bottom of the view and I can not see it even though I can write in this area. Can I tell the UITextView to have a different scroll area or what is the solution?
A better solution, specially for iOS 7, would be to adjust the content inset property of the textview instead of its frame, this way, the keyboard will blur the text that falls behinds it like in any other iOS 7 app. You'll also have to adjust the scroll indicators to match.
Expanding Lindemann's answer,
- (void)keyboardWasShown:(NSNotification*)notification {
NSDictionary* info = [notification userInfo];
CGSize keyboardSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
self.textView.contentInset = UIEdgeInsetsMake(0, 0, keyboardSize.height, 0);
self.textView.scrollIndicatorInsets = self.textView.contentInset;
}
- (void)keyboardWillBeHidden:(NSNotification*)notification {
self.textView.contentInset = UIEdgeInsetsZero;
self.textView.scrollIndicatorInsets = UIEdgeInsetsZero;
}
An easy solution is to implement the UITextViewDelegate Methods
- (void)textViewDidBeginEditing:(UITextView *)textView
and
- (void)textViewDidEndEditing:(UITextView *)textView
You can make the UITextView Frame smaller when the keyboard appears and make it full size again when the keyboard disappears...like this:
- (void)textViewDidBeginEditing:(UITextView *)textView {
self.textView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height/1.8);
}
- (void)textViewDidEndEditing:(UITextView *)textView {
self.textView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
}
EDIT
The solution above is bad...don't use it!!!
Now I think it's a better idea to resize the UITextView in proportion to the keyboard size and not with a fixed value...because the size of the keyboard can change when an other language become chosen or the device become rotated...of course -.-
At first you must register your UIViewController which displays your UITextView for receiving Keyboard Notifications:
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification object:nil];
}
Then you have to implement the two methods -keyboardWasShown: and
-keyboardWillBeHidden:.
The size of the actual keyboard is contained in the NSNotification object.
- (void)keyboardWasShown:(NSNotification*)notification {
NSDictionary* info = [notification userInfo];
CGSize keyboardSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
self.textView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height - keyboardSize.height);
}
- (void)keyboardWillBeHidden:(NSNotification*)notification {
self.textView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
}
If you want a Messages App style input, you can use a nested UITextView (allows for multiple lines of text). It will look like this in the end:
You start by laying out a view to hold all the child views. Here the background colour of the bottomView is set to match UIKeyboardAppearanceDark. It rests at the bottom of the screen.
bottomView = [UIView new];
bottomView.frame = CGRectMake(0, h-45, w, 45);
bottomView.backgroundColor = [UIColor colorWithRed:0.078 green:0.078 blue:0.078 alpha:1];
[self.view addSubview:bottomView];
Then, add in a simple background view styled like a typical UITextField, and add the UITextView as a subview to that. The inputTV (UITextView) takes its height based upon the size of the font. Also, all the padding is removed from inputTV using the textContainer variables.
inputTVBG = [UIImageView new];
inputTVBG.frame = CGRectMake(10, 8, w-90, 29);
inputTVBG.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.1f];
inputTVBG.layer.cornerRadius = 4.0f;
inputTVBG.userInteractionEnabled = true;
inputTVBG.clipsToBounds = true;
[bottomView addSubview:inputTVBG];
inputTV = [UITextView new];
inputTV.font = [UIFont systemFontOfSize:14.0f];
inputTV.frame = CGRectMake(5, 6, w-100, inputTV.font.lineHeight);
inputTV.backgroundColor = [UIColor clearColor];
inputTV.keyboardAppearance = UIKeyboardAppearanceDark;
inputTV.delegate = self;
inputTV.autocorrectionType = UITextAutocorrectionTypeNo;
inputTV.tintColor = [UIColor whiteColor];
inputTV.textColor = [UIColor whiteColor];
inputTV.textContainer.lineFragmentPadding = 0;
inputTV.textContainerInset = UIEdgeInsetsZero;
[inputTVBG addSubview:inputTV];
In the example above, I've included a label indicating how many letters are left (max / min characters) and a submit button.
lettersLeftLabel = [UILabel new];
lettersLeftLabel.frame = CGRectMake(w-70, 8, 60, 16);
lettersLeftLabel.font = [UIFont systemFontOfSize:12.0f];
lettersLeftLabel.textColor = [[UIColor whiteColor] colorWithAlphaComponent:0.5f];
lettersLeftLabel.alpha = 0.0f;
[bottomView addSubview:lettersLeftLabel];
submitButton = [UIButton new];
submitButton.frame = CGRectMake(w-70, 0, 60, 45);
[submitButton setTitle:#"SUBMIT" forState:UIControlStateNormal];
[submitButton setTitleColor:[_peacock.applePink colorWithAlphaComponent:0.5f] forState:UIControlStateNormal];
[submitButton addTarget:self action:#selector(submit) forControlEvents:UIControlEventTouchUpInside];
[submitButton.titleLabel setFont:[UIFont boldSystemFontOfSize:14.0f]];
[bottomView addSubview:submitButton];
Add this line early on in your code, so you get keyboard change updates:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
It calls the method below when a user clicks on the inputTV. Here it sets the variable 'keyboardHeight' used later on.
-(void)keyboardWillShow:(NSNotification *)n {
CGRect rect = [n.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGRect keyboardFrame = [self.view convertRect:rect fromView:nil];
keyboardHeight = keyboardFrame.size.height;
[self textViewDidChange:inputTV];
}
This is the main bit of code that takes care of all the movement and resizing of the inputTV.
-(void)textViewDidChange:(UITextView *)textView {
//1. letters and submit button vars
int numberOfCharacters = (int)textView.text.length;
int minCharacters = 50;
int maxCharacters = 400;
int remainingCharacters = maxCharacters-numberOfCharacters;
//2. if entered letters exceeds maximum, reset text and return
if (remainingCharacters <= 0){
textView.text = [textView.text substringToIndex:maxCharacters];
numberOfCharacters = maxCharacters;
}
//3. set height vars
inputTV.scrollEnabled = true;
float textHeight = textView.contentSize.height;
float lineHeight = roundf(textView.font.lineHeight);
float additionalHeight = textHeight - lineHeight;
float moveUpHeight = keyboardHeight + additionalHeight;
//4. default letter colour is weak white
UIColor * letterColour = [[UIColor whiteColor] colorWithAlphaComponent:0.5f];
if (numberOfCharacters < minCharacters){ //minimum threshold not met
lettersLeftLabel.text = [NSString stringWithFormat:#"%i", minCharacters-numberOfCharacters];
letterColour = [_peacock.applePink colorWithAlphaComponent:0.5f];
} else { //within range
lettersLeftLabel.text = [NSString stringWithFormat:#"%i/%i", numberOfCharacters, maxCharacters];
if (remainingCharacters<5){ //increase alpha towards the end of range
letterColour = [[UIColor whiteColor] colorWithAlphaComponent:1.0f - ((float)remainingCharacters/10)];
}
}
//5. hide/show letter label based on textView height
float letterAlpha = 0.0f; //default hide
if (additionalHeight > 0){ letterAlpha = 1.0f; } //if multiline, show
[UIView animateWithDuration:0.3f
delay:0.0f
options:UIViewAnimationOptionCurveEaseOut
animations:^{
lettersLeftLabel.alpha = letterAlpha;
lettersLeftLabel.textColor = letterColour;
}
completion:^(BOOL finished){
}];
//6. update submit colour based on minimum threshold
UIColor * submitColour = [_peacock.applePink colorWithAlphaComponent:0.5f];
bool enableSubmit = false;
if (numberOfCharacters >= minCharacters){
submitColour = _peacock.applePink;
enableSubmit = true;
}
[submitButton setEnabled:enableSubmit];
[UIView animateWithDuration:0.3f
delay:0.0f
options:UIViewAnimationOptionCurveEaseOut
animations:^{
[submitButton setTitleColor:submitColour forState:UIControlStateNormal];
}
completion:^(BOOL finished){
}];
//7. special case if you want to limit the frame size of the input TV to a specific number of lines
bool shouldEnableScroll = false;
int maxNumberOfLines = 5; //anything above this triggers the input TV to stay stationary and update its scroll
int actualNumberOfLines = textHeight / textView.font.lineHeight;
if (actualNumberOfLines >= maxNumberOfLines){ //recalculate vars for frames
textHeight = maxNumberOfLines * lineHeight;
additionalHeight = textHeight - lineHeight;
moveUpHeight = keyboardHeight + additionalHeight;
shouldEnableScroll = true;
}
//8. adjust frames of views
inputTV.frame = CGRectMake(5, 6, w-100, textHeight); //update immediately (parent view clips to bounds)
[UIView animateWithDuration:0.3f
delay:0.0f
options:UIViewAnimationOptionCurveEaseOut
animations:^{
bottomView.frame = CGRectMake(0, h-45-moveUpHeight, w, 45+additionalHeight);
inputTVBG.frame = CGRectMake(10, 8, w-90, lineHeight+additionalHeight+13);
submitButton.frame = CGRectMake(w-70, additionalHeight, 60, 45);
}
completion:^(BOOL finished){
inputTV.scrollEnabled = shouldEnableScroll; //default disable scroll here to avoid bouncing
}];
}
In the above method, this is what's happening:
If you want to set a minimum or maximum number of characters, you can do so here. You pull the the number of characters and store as an integer, and calculate how many characters are left.
If the user has reached the maximum number of characters, reset the textView text by stripping back to your max.
These vars are used to calculate how much you need to move your bottomView up and also for the resizing its subviews.
This method is just to change the colour / text of some UI elements. It's not strictly necessary.
This method brings the lettersLeftLabel onto view if you're using that. It's not necessary either.
This enables the submit button only if the minimum number of characters has been reached. It changes the colour as an indicator to the user.
If you want to limit the growth of the inputTV and surrounding elements, you can include this bit of code. It requires you to set the maximum number of lines you want to show. If the user exceeds the max, scroll is reenabled for the inputTV, otherwise it defaults to false (important to stop it bouncing).
This is the main resizing logic, moving the bottomView up and resizing its child views. The submit button needs to stay in the same position, so move it down as the bottomView grows.
NOTE: If you just want the barebones code, you only need to implement steps 3 and 8.
Apple has some code samples that deal with this exact situation.
I finally got it working. Here is my solution, can you guys spot any errors in my design?
#synthesize textView = _textView;
#synthesize callbackViewController = _callbackViewController;
-(void)keyboardWasShown:(NSNotification*)aNotification {
if(keyboardShown) {
return;
}
NSDictionary *info = [aNotification userInfo];
// Get the size of the keyboard.
NSValue *aValue = [info objectForKey:UIKeyboardFrameBeginUserInfoKey];
keyboardSize = [aValue CGRectValue].size;
// Resize the scroll view (which is the root view of the window)
CGRect viewFrame = [self.textView frame];
orientationAtShown = orientation;
if(orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown) {
viewFrame.size.height -= keyboardSize.height;
} else {
viewFrame.size.height -= keyboardSize.width;
}
self.textView.frame = viewFrame;
// Scroll the active text field into view.
//CGRect textFieldRect = [activeField frame];
[self.textView scrollRectToVisible:viewFrame animated:YES];
keyboardShown = YES;
}
-(void)keyboardWasHidden:(NSNotification*)aNotification {
if(!keyboardShown) {
return;
}
// Reset the height of the scroll view to its original value
CGRect viewFrame = [self.textView frame];
if(orientationAtShown == UIInterfaceOrientationPortrait || orientationAtShown == UIInterfaceOrientationPortraitUpsideDown) {
viewFrame.size.height += keyboardSize.height;
} else {
viewFrame.size.height += keyboardSize.width;
}
self.textView.frame = viewFrame;
keyboardShown = NO;
}
-(void)registerForKeyboardNotifications {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasHidden:)
name:UIKeyboardDidHideNotification object:nil];
}
-(void)viewWillAppear:(BOOL)animated {
keyboardShown = NO;
[self registerForKeyboardNotifications];
}
-(void)viewWillDisappear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
}
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
if(keyboardShown) {
[self keyboardWasHidden:nil];
}
orientation = interfaceOrientation;
CGRect viewFrame = [self.textView frame];
if(orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown) {
if(viewFrame.size.width > viewFrame.size.height) {
CGRect viewFrameFixed = CGRectMake(viewFrame.origin.x, viewFrame.origin.y, viewFrame.size.height, viewFrame.size.width);
self.textView.frame = viewFrameFixed;
}
} else {
if(viewFrame.size.width < viewFrame.size.height) {
CGRect viewFrameFixed = CGRectMake(viewFrame.origin.x, viewFrame.origin.y, viewFrame.size.height, viewFrame.size.width);
self.textView.frame = viewFrameFixed;
}
}
// Return YES for supported orientations
return YES;
}
#Alejandro above has the right idea, but his code does not work in landscape mode. I have amended his keyboardWasShown: method to work correctly in all orientations:
- (void)keyboardWasShown:(NSNotification *)notification {
if (self.textView != nil) {
NSDictionary* info = [notification userInfo];
CGRect keyboardRect = [self.textView convertRect:[[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue] fromView:nil];
CGSize keyboardSize = keyboardRect.size;
self.textView.contentInset = UIEdgeInsetsMake(0, 0, keyboardSize.height, 0);
self.textView.scrollIndicatorInsets = self.textView.contentInset;
}
}
Add Observer first in viewDidLoad.
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasHidden:)
name:UIKeyboardWillHideNotification object:nil];
}
Call the methods
- (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);
self.textView.contentInset = contentInsets;
self.textView.scrollIndicatorInsets = contentInsets;
// If active text field is hidden by keyboard, scroll it so it's visible
// Your app might not need or want this behavior.
CGRect aRect = self.view.frame;
aRect.size.height -= kbSize.height;
if (!CGRectContainsPoint(aRect, self.textView.frame.origin) ) {
[self.textView scrollRectToVisible:self.textView.frame animated:YES];
}
}
// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWasHidden:(NSNotification*)aNotification
{
UIEdgeInsets contentInsets = UIEdgeInsetsZero;
self.textView.contentInset = contentInsets;
self.textView.scrollIndicatorInsets = contentInsets;
}
if you have more then 1 textfield or you want to reduce your code then try this code
- (void)textFieldDidBeginEditing:(UITextField *)textField{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.35f];
CGRect frame = self.view.frame;
frame.origin.y = (self.view.frame.size.height - textField.frame.origin.y) - self.view.frame.size.height+60;
if (frame.origin.y<-162) {
frame.origin.y = -162;
}
[self.view setFrame:frame];
[UIView commitAnimations];
}
-(BOOL)textFieldShouldEndEditing:(UITextField *)textField{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.35f];
CGRect frame = self.view.frame;
frame.origin.y = 0;
[self.view setFrame:frame];
[UIView commitAnimations];
return YES;
}
Extending #alejandro & #Mani :
Th final answer:
- (void)keyboardWasShown:(NSNotification *)notification {
if (self.textView != nil) {
NSDictionary* info = [notification userInfo];
CGRect keyboardRect = [self.textNote convertRect:[[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue] fromView:nil];
CGSize keyboardSize = keyboardRect.size;
self.textView.contentInset = UIEdgeInsetsMake(0, 0, keyboardSize.height, 0);
self.textView.scrollIndicatorInsets = self.textView.contentInset;
}
}
- (void)keyboardWillBeHidden:(NSNotification*)notification {
self.textView.scrollIndicatorInsets = UIEdgeInsetsZero;
self.textView.scrollIndicatorInsets = UIEdgeInsetsZero;
}