I have an app where you have textfields and one textview but when I get the keyboard it hides the lower textfields. How would I do it.
I have tried:
.m:
- (void)textFieldDidBeginEditing:(UITextField *)sender {
CGSize content = _scrollView.contentSize;
_scrollView.contentSize = CGSizeMake(content.width, content.height + 200);
svos = _scrollView.contentOffset;
CGPoint pt;
CGRect rc = [sender bounds];
rc = [sender convertRect:rc toView:_scrollView];
pt = rc.origin;
pt.x = 0;
pt.y -= 200;
[_scrollView setContentOffset:pt animated:YES];
}
- (IBAction)textFieldShouldReturn:(UITextField *)textField {
CGSize content = _scrollView.contentSize;
_scrollView.contentSize = CGSizeMake(content.width, content.height - 200);
[_scrollView setContentOffset:svos animated:YES];
[textField resignFirstResponder];
}
.h:
CGPoint svos;
Although the bottom text fields are still hidden it does scroll to the visible ones
You have obtained the origin of the sender textfield but only move up by 60, thus, the lower textfields are covered by the keyboard. You will need to know the height of the keyboard and calculate the distance to move up. Check this out. It has much of the answer so I will not explain again.
To scroll to the bottom textfield inside a scrollview, add these lines in textFieldDidBeginEditing:
CGSize content = _scrollview.contentSize;
_scrollview.contentSize = CGSizeMake(content.width, content.height + 200);
This will extend your contentSize programmatically so you can scroll to the last textfield and allow the keyboard to cover the empty space.
In textFieldDidEndEditing or textFieldShouldReturn, in your case, add these:
CGSize content = _scrollview.contentSize;
_scrollview.contentSize = CGSizeMake(content.width, content.height - 200);
I used an arbitrary 200 as example. You will need to figure out how much you want.
A drop-in universal solution for moving text fields out of the way of the keyboard in iOS
https://github.com/michaeltyson/TPKeyboardAvoiding
It works perfect for me.
Simply you need to copy the required classes(TPKeyboardAvoidingScrollView or TPKeyboardAvoidingTableView) in your project and in your interface builder file you have to change the UIScrollView class to TPKeyboardAvoidingScrollView or UITableView to TPKeyboardAvoidingTableView, the remaining things will be handled by these classes.
Have you seen the documentation about managing the keyboard? (apple documentation) There is an example of what i think you are working on. Hope it helps.
Related
I've a viewController composed by an UIImageView that fills the whole view and two textField located at the center of the view. All this is located inside a ScrollView.
I use Storyboard and I disabled the autolayout.
When I click on a textField, and thus opens the keyboard, I'd like that the scroll is displaced directly on textField. How can I do that?
Consider that your TextField outlet is named txtField then implement UITextField Delegate using
txtField.delegate = self;
Considering that your ScrollView outlet is named scrollView. Implement the Textfield Delegate method:
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
NSLog(#"%#",NSStringFromCGRect(textField.frame));
[scrollView scrollRectToVisible:textField.frame animated:YES];
}
Hope this helps.
Do let me know if you need more help.
I usually use this successfully:
http://developer.apple.com/library/ios/#documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html
Add your controller as an observer of the keyboard notifications. When you receive the notification, resize the scroll view frame and / or set the content offset (depending on the exact effect you want and content size you have).
Here are several options that I've used before:
Apple's Doco. See the section titled Moving Content That Is Located Under the Keyboard
Cocoa With Love article, Sliding UITextFields around to avoid the keyboard
If you have several text fields or other form elements in your view, you could alternatively use something like EZForm. It does automatic repositioning of views to keep input fields visible, and also handles input validation and other useful form-related stuff.
You can convert the text field coordinate system into Scroll view coordinate system. Use the below code
-(void)rearrangeView{
// previous
if (txtActiveField == txtUsername) {
CGPoint pt;
CGRect rc = [txtUsername bounds];
rc = [txtUsername convertRect:rc toView:scrollViewLogin];
pt = rc.origin;
pt.x = 0;
pt.y -= 40;
[scrollViewLogin setContentOffset:pt animated:YES];
}
else if (txtActiveField == txtPassword) {
// [self.txtEmail becomeFirstResponder];
CGPoint pt;
CGRect rc = [txtPassword bounds];
rc = [txtPassword convertRect:rc toView:scrollViewLogin];
pt = rc.origin;
pt.x = 0;
pt.y -= 40;
[scrollViewLogin setContentOffset:pt animated:YES];
}
}
#pragma mark- UITextField Delegate
-(BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
txtActiveField = textField;
//txtActiveField.placeholder = #"";
[self rearrangeView];
return YES;
}
Here txtActiveField is instance variable of type UItextField
I have a UITextView inside of a UIScrollView that is configured offscreen when the code below runs in my ViewController's viewDidLoad. Unfortunately, if the "eventDetails" text is particularly long nothing is displayed inside the UITextView until I interact with it (e.g click inside and drag for example).
My question: How to have it so the text is displayed in the UITextView WITHOUT forcing the user to interact with the UITextView first?
Here is the code:
UITextView *txtDetails = [[UITextView alloc] initWithFrame:CGRectMake(-4, yOffset, page3ScrollView.frame.size.width, 0)];
[txtDetails setEditable:NO];
[txtDetails setScrollEnabled:NO];
[txtDetails setFont:[UIFont systemFontOfSize:12]];
[txtDetails resizeAndSetTextWithMaxSize:CGSizeMake(txtDetails.frame.size.width, 999999999) forText:eventDetails withAdditionalHeightOf:16.f];
[page3ScrollView addSubview:txtDetails];
CGRect frame = txtDetails.frame;
frame.size.height = [txtDetails contentSize].height;
txtDetails.frame = frame;
[page3ScrollView setContentSize:CGSizeMake(page3ScrollView.frame.size.width, txtDetails.frame.origin.y + txtDetails.frame.size.height)];
Thanks
I was able to get this working by setting the UITextView's text ONLY when needed (e.g. is about to come on screen). Below is the relevant code:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sv
{
if (sv == scrollView) [self updatePagedView];
}
- (void)updatePagedView
{
int currentPage = pageControl.currentPage;
// *** loadDetailsPage3 is where I set the text ***
if (currentPage == 2) {
[self loadDetailsPage3];
}
}
If anyone has a better solution or needs more explanation just hit me up in the comments.
I've had the same issue and it's taken me several hours to find a suitable fix for it... Here's what I came up with...
-(void)DidBecomeActive {
if (!DidUpdateBounds) {
DidUpdateBounds = true; // instance variable set to false on load
NSString *oldtext = uiTextView.text;
//The trick is (1) removing it from it's superview
// (2) resetting it's 'text' property
// (3) re-adding it to the view WHILE it's on-screen.
[uiTextView removeFromSuperview];
uiTextView.text = oldtext;
[self.view addSubview:uiTextView];
[self.view setNeedsDisplay];
}
}
I'm using CoreAnimation to switch to this view. So right before I execute that code, I call this
//The 0,-300,480,300 is bounds of the UIView my offscreen UITextview is on
[self.view.layer setBounds:CGRectMake(0, -300, 480, 300)];
// SetNeedsDisplay, then call my method above to
//FORCIBILY redrew the UITextView while it's effectively on-screen
[self.view setNeedsDisplay];
[TextLogVC DidBecomeActive];
//CoreAnimation then goes here
This solves the issue. After setting the layer.bounds it redraws the control(UITextView) and it thinks it's onscreen. Then CoreAnimation takes care of animating from my current UIView to the new UIView and all of the text in the uiTextView is displayed throughout the animation.
Looks like the views that you are setting up are not refreshed.
And when you interact with the views then they are refreshed and your changes are rendered to the display...
Try adding [txtDetails setNeedsDisplay]; [page3ScrollView setNeedsDisplay]; after your code in viewDidLoad.
If it will help then maybe we'll find some more elegant solution...
In the context of the iPhone:
I have a UIScrollView containing a UIImage. When the user taps the screen inside the UIImage, a UITextField is added where the user touched. The user can edit this UITextField, and the text field will automatically resize itself based on whether text was added or deleted.
When the a UITextField being edited increases its width, the scrollview automatically scrolls to show the increased width.
The problem comes in because the automatic scrolling of the textfield doesn't respect the y-value of the screen
For example, say the user added a text field to the bottom of the image. When they go to edit that text field, the keyboard will show, hiding the text field. I have code in place to scroll the screen to show the text field. The problem comes in when the user enters so much text that that text field extends past the edge of the screen. When this happens, the screen scrolls horizontally to fit the wider text, but also vertically - the vertical scrolling ends up hiding the textfield, basically nullifying anything I did to show the text field.
Code to show the text field if it's hidden by the keyboard:
- (void)keyboardWasShown:(NSNotification*)notification
{
NSDictionary* info = [notification userInfo];
CGSize keyboardSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
self.offset = self.contentOffset;
CGRect frame = self.frame;
// self.activeField is the name of the field that is the current first responder - this just adds a little bit of padding
frame.size.height -= keyboardSize.height + (self.activeField.frame.size.height * 2);
if (!CGRectContainsPoint(frame, self.activeField.frame.origin)) {
CGPoint scrollPoint = CGPointMake(self.offset.x, self.activeField.frame.origin.y - keyboardSize.height + (activeField.frame.size.height * 2));
[self setContentOffset:scrollPoint animated:YES];
}
}
Here is the code to increase the size of the text field:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString *newString = [textField.text stringByReplacingCharactersInRange:range withString:string];
CGSize stringSize = [string sizeWithFont:textField.font];
CGSize newSize = [newString sizeWithFont:textField.font];
// Make textField wider if we're close to running up against it
if (newSize.width > (textField.frame.size.width - self.widthOffset)) {
CGRect textFieldFrame = textField.frame;
if (stringSize.width > self.widthOffset) {
textFieldFrame.size.width += stringSize.width;
}
textFieldFrame.size.width += self.widthOffset;
textField.frame = textFieldFrame;
}
// Shrink the textField if there is too much wasted space
if ((textField.frame.size.width - newSize.width) > self.widthOffset) {
CGRect textFieldFrame = textField.frame;
textFieldFrame.size.width = newSize.width + self.widthOffset;
textField.frame = textFieldFrame;
}
return YES;
}
The question is: How do I get the UIScrollView to respect the y-value of itself when automatically scrolling?
Basically setFrame of UIScrollView will readjust the scrollview offset, done by _adjustContentOffsetIfNecessary. As that method is private and not documented, there is very little we can guess on how the adjustment will happen.
There are two ways to stop the unnecessary scrolling or wrong offset being set:
1) reset the UIScrollView offset after applying setFrame. This you can do, if you are modifying the frame of UIScrollView intentionally based on some calculations.
CGPoint oldOffset = [scrollView contentOffset];
scrollView.frame = CGRectMake(newFrame);
[scrollView setContentOffset:oldOffset];
2) apply offset changes without animation. In your keyboardWasShown , change
[self setContentOffset:scrollPoint animated:YES]; to
[self setContentOffset:scrollPoint animated:NO];
Reason: when more than one offset is applied with animation on, the result offset is ambiguous. Here the internal method(_adjustContentOffsetIfNecessary) applies an offset change and the other one is done by your code. You can notice this if you try to log all the offsets being applied in the UIScrollView delegate method:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSLog(#" offset: %#", NSStringFromCGPoint(scrollView.conentOffset))
}
Let me know if this helps.
One possible workaround could be to respond to the scrollViewDidScroll delegate method check to see if the UITextField is hidden again, then re-scroll if necessary. Seems like a bit of a hack, but it sounds like the UIScrollView auto scrolling behavior is what's getting in your way, and if there's no way to directly affect it, the only option is to work around it. There is also the disadvantage, however, that if you do this then it appears to scroll twice.
If the auto-scrolling behavior happens only when the UITextField expands beyond the edge of the screen, you could also move the field to stay completely visible if it looks like it's going to expand beyond the edge of the screen.
Instead of changing the content offset, change the scroll view's display frame.
- (void)keyboardWill:(NSNotification*)notification
{
CGSize keyboardSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
// Update the scroll view's frame to the region not covered by the keyboard.
self.frame = CGRectMake(self.fullSizedFrame.origin.x,
self.fullSizedFrame.origin.y,
self.fullSizedFrame.size.width,
self.fullSizedFrame.size.height - keyboardSize.height);
}
- (void)keyboardWillHide:(NSNotification*)notification
{
// Set the frame back to the original frame before the keyboard was shown.
self.frame = self.fullSizedFrame;
}
If you don't allow the user to change screen orientation, then fullSizedFrame could be set to the original frame of the view when it is first displayed. If changes in orientation are allowed, you'll need to calculate the appropriate value for fullSizedFrame based on the orientation.
I have a view which is similar to the notes app - i.e. typing on a lined piece of paper. To make the text and the paper scroll simultaneously, I have disabled the UITextView's scrolling, and instead placed both my UITextView and my UIImageView inside a UIScrollView.
The only problem with this is that, when the user types, the text disappears below the keyboard, because obviously the UIScrollView does not know to scroll to the cursor position.
Is there any simple way I can retrieve the cursor position and tell the UIScrollView to scroll there?
---EDIT---
Starting from something similar here (where someone was trying to do something similar with a UITableView), I have managed to make a growing, editable UITextView with a fixed background that almost scrolls perfectly. The only issues now are:
There is a slight judder as the text moves up if the user types particularly fast.
If the user hides the keyboard, selects text at the bottom of the screen, and then shows the keyboard again, they have to type a couple of letters before the text becomes visible again - it doesn't scroll up immediately.
When the user hides the keyboard, the animation as the scroll view's frame fills the screen doesn't feel quite right somehow.
Here is the code - I'd be really grateful if anyone can refine it further...
#import "NoteEditViewController.h"
#import "RLWideLabelTableCell.h"
#implementation NoteEditViewController
#synthesize keyboardSize;
#synthesize keyboardHideDuration;
#synthesize scrollView;
#synthesize noteTextView;
//
// Dealloc and all that stuff
//
- (void)loadView
{
[super loadView];
UIScrollView *aScrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
self.scrollView = aScrollView; [aScrollView release];
self.scrollView.contentSize = CGSizeMake(self.view.frame.size.width, noteTextView.frame.size.height);
[self.view addSubview:scrollView];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Get notified when keyboard is shown. Don't need notification when hidden because we are
// using textViewDidEndEditing so we can start animating before the keyboard disappears.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
// Add the Done button so we can test dismissal of the keyboard
UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self
action:#selector(doneButton:)];
self.navigationItem.rightBarButtonItem = doneButton; [doneButton release];
// Add the background image that will scroll with the text
CGRect noteImageFrame = CGRectMake(self.view.bounds.origin.x,
noteTitleImageFrame.size.height,
self.view.bounds.size.width, 500);
UIView *backgroundPattern = [[UIView alloc] initWithFrame:noteImageFrame];
backgroundPattern.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"Notepaper-iPhone-Line"]];
[self.scrollView addSubview:backgroundPattern];
[self.view sendSubviewToBack:backgroundPattern];
[backgroundPattern release];
// Add the textView
CGRect textViewFrame = CGRectMake(noteImageFrame.origin.x+27,
noteImageFrame.origin.y-3,
noteImageFrame.size.width-35,
noteImageFrame.size.height);
RLTextView *textView = [[RLTextView alloc] initWithFrame:textViewFrame];
self.noteTextView = textView; [textView release];
self.noteTextView.font = [UIFont fontWithName:#"Cochin" size:21];
self.noteTextView.backgroundColor = [UIColor clearColor];
self.noteTextView.delegate = self;
self.noteTextView.scrollEnabled = NO;
[self.scrollView addSubview:self.noteTextView];
}
- (void)doneButton:(id)sender
{
[self.view endEditing:TRUE];
}
// When the keyboard is shown, the UIScrollView's frame shrinks so that it fits in the
// remaining space
- (void)keyboardWasShown:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
float kbHideDuration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
self.keyboardHideDuration = kbHideDuration;
self.keyboardSize = kbSize;
self.scrollView.frame = CGRectMake(self.view.bounds.origin.x,
self.view.bounds.origin.y,
self.view.bounds.size.width,
self.view.bounds.size.height - kbSize.height);
}
// When the user presses 'done' the UIScrollView expands to the size of its superview
// again, as the keyboard disappears.
- (void)textViewDidEndEditing:(UITextView *)textView
{
[UIScrollView animateWithDuration:keyboardHideDuration animations:^{self.scrollView.frame = self.view.bounds;}];
}
// This method needs to get called whenever there is a change of cursor position in the text box
// That means both textViewDidChange: and textViewDidChangeSelection:
- (void)scrollToCursor
{
// if there is a selection cursor…
if(noteTextView.selectedRange.location != NSNotFound) {
NSLog(#"selectedRange: %d %d", noteTextView.selectedRange.location, noteTextView.selectedRange.length);
// work out how big the text view would be if the text only went up to the cursor
NSRange range;
range.location = noteTextView.selectedRange.location;
range.length = noteTextView.text.length - range.location;
NSString *string = [noteTextView.text stringByReplacingCharactersInRange:range withString:#""];
CGSize size = [string sizeWithFont:noteTextView.font constrainedToSize:noteTextView.bounds.size lineBreakMode:UILineBreakModeWordWrap];
// work out where that position would be relative to the textView's frame
CGRect viewRect = noteTextView.frame;
int scrollHeight = viewRect.origin.y + size.height;
CGRect finalRect = CGRectMake(1, scrollHeight, 1, 1);
// scroll to it
[self.scrollView scrollRectToVisible:finalRect animated:YES];
}
}
// Whenever the text changes, the textView's size is updated (so it grows as more text
// is added), and it also scrolls to the cursor.
- (void)textViewDidChange:(UITextView *)textView
{
noteTextView.frame = CGRectMake(noteTextView.frame.origin.x,
noteTextView.frame.origin.y,
noteTextView.frame.size.width,
noteTextView.contentSize.height);
self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width,
noteTextView.frame.size.height+200);
[self scrollToCursor];
}
// The textView scrolls to the cursor whenever the user changes the selection point.
- (void)textViewDidChangeSelection:(UITextView *)aTextView
{
[self scrollToCursor];
}
// PROBLEM - the textView does not scroll until the user starts typing - just selecting
// it is not enough.
- (void)textViewDidBeginEditing:(UITextView *)textView
{
[self scrollToCursor];
}
Cool that you found my post about it, glad it was helpful!
I believe you may not be seeing the bottom line because of this line:
CGRect finalRect = CGRectMake(1, scrollHeight, 1, 1);
You're creating a 1x1 point box. A single line of text might be something like 20 or 30 points tall (depending on font size). So if you're scrolling this point to visible, it may only be showing the very top pixel of the bottom line - making the bottom line effectively invisible! If you make finalRect a little taller so it covers the whole line, it might work better:
CGRect finalRect = CGRectMake(1, scrollHeight, 1, 30);
Also, you may be calling your scrollRectToVisible code multiple times at once, which can cause "judders". In my code, I only run scrollRectToVisible from textViewDidChangeSelection, and resize the UITextView (if needed) in textViewDidChange. UIScrollView (and by inheritance UITableView) has built-in support to scroll the actively selected element to be visible, which in my testing worked well when simply resizing the UITextView while typing (but not when selecting a specific point inside with a touch).
There is no easy way to find the screen coordinates for any text or cursor in a UITextView.
What you should do is registering for UIKeyboardWillShowNotification and UIKeyboardWillShowNotification. And in the callbacks you adjust the size or contentInsets of the UIScrollView to adjust for the size of the keyboard.
The size of the keyboard, and even the animation duration is provided in the notifications userInfo, so you can do it in a nice animated fashion.
You find more information and sample code here: http://developer.apple.com/library/ios/#documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html
Not strictly an answer to your question, but here's a different approach to the notes lined background trick: http://www.cocoanetics.com/2010/03/stuff-you-learn-from-reverse-engineering-notes-app/
I've used it and it works well.
I have a UIScrollView with a few UITextFields on it. When the user edits a text field it scrolls so that the UITextField is centered. The problem I am running into is that the UIScrollView is scrolling to the correct spot but at the last frame of the animation it is jumps to 300,300. It works fine in 2.2.1 but not in 3.0 beta 5. It always jumps to exactly 300,300 too. The strange thing is that when I call the returnScrollAfterEdit method that moves the scroll view the same way it works just fine. Any ideas what could cause this?
- (void)scrollViewToCenterOfScreen:(UIView *)field withKeyboard:(bool)withKeyboard {
CGFloat viewCenterY = field.center.y;
CGRect applicationFrame = [[UIScreen mainScreen] applicationFrame];
CGFloat availableHeight = applicationFrame.size.height - 215;
CGFloat y = viewCenterY - availableHeight / 2.0;
NSLog([NSString stringWithFormat:#"w:%f h%f availH:%f y:%f x:%f", applicationFrame.size.width, applicationFrame.size.height, availableHeight, y, self.contentOffset.x]);
if (y
I'm not sure if this is exactly what you are looking for but I had some crazy issues with UITextView as well. It would scroll to the bottom of the frame if I had selected any text in the view. I fixed this by turning off the scrollEnabled before adding the text to the UITextView.
So the code would look something like this:
[textarea setText:#""]; //blank out the text to scroll back to the top
[textarea setScrollEnabled:NO]; //disable to avoid scrolling
[textarea setText:#"new text here"];
[textarea setScrollEnabled:YES];
[textarea resignFirstResponder]; //to drop annoying blue
I hope this helps.
Fixed with the release version of 3.0