Setting contentInset of UIScrollView not working - iphone

I am using Apple's proposed way of revealing a UITextfield, which gets hidden by the keyboard when selected (see listing 5-1): https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html#//apple_ref/doc/uid/TP40009542-CH5-SW1
This is not working for me though because every time I set the conentInset my scrollView automatically scrolls up just a few pixels and thats it. Even if I remove the scrollRectToVisible call my scrollView still moves up. If I set the contentInset in viewDidLoad everything works as excepted.
Note: I am currently not using autolayout. If I turn autolayout on nothing happen at all.
Any suggestions?

Set the contentInset,setContentOffset and scrollIndicatorInsets
Example:
/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Method is used to handle the keyboard visibility.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
- (void)keyboardWasShown:(NSNotification *)notification
{
#try
{
// Step 1: Get the size of the keyboard.
CGSize keyboardSizePotriat = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
CGSize keyboardSize = {keyboardSizePotriat.height,keyboardSizePotriat.width};
// Step 2: Adjust the bottom content inset of your scroll view by the keyboard height.
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize.height, 0.0);
self.theScrollView.contentInset = contentInsets;
self.theScrollView.scrollIndicatorInsets = contentInsets;
// Modify the scrollPoint as per your screen.
CGPoint scrollPoint = CGPointMake(0.0, self.activeTextField.frame.origin.y - (keyboardSize.height - 45));
[self.theScrollView setContentOffset:scrollPoint animated:YES];
}
#catch (NSException *exception)
{
NSLog(#"%s\n exception: Name- %# Reason->%#", __PRETTY_FUNCTION__,[exception name],[exception reason]);
}
}
Check out the link below for more details
iOS SDK: Keeping Content From Underneath the Keyboard

Related

scroll textfield covered by keyboard

Im working on getting my UIscroll to scroll when ever a textfield is blocked by the keyboard by following this documentation
http://developer.apple.com/library/ios/#documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html#//apple_ref/doc/uid/TP40009542-CH5-SW7
However sadly...there is a variable mentioned, activeField and i cannot figure out how it is declared. I would really like if some probably advise how/where it is declared or even a solution to scrolling when the keyboard is activated will help.
Thank you
To answer your specific question, since the Apple Documentation is only ever using activeField for size data, you can simply declare it as a private global UIView *activeField and it will work for textFields, textViews, etc all the same.
However, their code actually doesn't work very well at all. I had to make some changes to their code to get mine to work correctly. This code is basically theirs with some minor tweaks to handle all of the cases listed below:
1) If you have a smaller scrollView nested inside of a view, not full screen scrollView and it still works.
2) If you want to move text fields down to the keyboard focus area as well as move them up from behind the keyboard.
3) Works on textViews and textFields of various sizes
4) If the keyboard is currently showing, it will move any newly tapped fields into the focus area
5) Works if you have your content already scrolled and invoke the keyboard
6) Works on all keyboard sizes for all devices (no hardcoded constants)
First off, create private variables for these:
UIView *_activeField;
CGFloat _keyboardHeight;
BOOL _isShowingKeyboard;
Next, just cut and paste this code into your ViewController (it looks like a lot but it's not that bad).
#pragma mark TextFieldKeyboardScrolling
- (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)adjustInputFieldsForKeyboard {
CGFloat keyBoardTopInScrollView = self.view.frame.size.height - _keyboardHeight - self.scrollView.frame.origin.y;
CGFloat inputFieldBottomInVisibleScrollView = _activeField.frame.origin.y - self.scrollView.contentOffset.y + 30 /* small buffer for cursor size */;
CGPoint scrollPoint;
if (keyBoardTopInScrollView > inputFieldBottomInVisibleScrollView) {
scrollPoint = CGPointMake(0.0, self.scrollView.contentOffset.y - (keyBoardTopInScrollView - inputFieldBottomInVisibleScrollView));
} else {
scrollPoint = CGPointMake(0.0, self.scrollView.contentOffset.y + (inputFieldBottomInVisibleScrollView - keyBoardTopInScrollView));
}
[self.scrollView setContentOffset:scrollPoint animated:YES];
}
- (void)keyboardWasShown:(NSNotification*)aNotification {
_isShowingKeyboard = YES;
NSDictionary* info = [aNotification userInfo];
_keyboardHeight = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size.height;
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, _keyboardHeight, 0.0);
self.scrollView.contentInset = contentInsets;
self.scrollView.scrollIndicatorInsets = contentInsets;
[self adjustInputFieldsForKeyboard];
}
- (void)keyboardWillBeHidden:(NSNotification*)aNotification {
_isShowingKeyboard = NO;
UIEdgeInsets contentInsets = UIEdgeInsetsZero;
self.scrollView.contentInset = contentInsets;
self.scrollView.scrollIndicatorInsets = contentInsets;
}
#pragma mark UITextFieldDelegate
- (void)textFieldDidBeginEditing:(UITextField *)textField {
_activeField = textField;
if (_isShowingKeyboard) {
[self adjustInputFieldsForKeyboard];
}
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
_activeField = nil;
}
That's it, just call [self registerForKeyboardNotifications]; in your viewDidLoad method, hook up your scrollView outlet and textField/textView delegates in storyboard and you're all set.
The keyboard is only active when one of the texfields become active(Became first responder).So you should listen to UITextField delegate method to see when and which uitextfield became first responder.
- (void)textFieldDidBeginEditing:(UITextField *)textField
if you assign a tag to your textfield or make it ivar you can also understand which uitextfield has become active and also get its frame.
I don't use the scroll view or follow the above documentation.
Here is how without using the scroll view
In my
textFieldShouldBeginEditing
CGRect frame = self.view.frame;
frame.origin.y = <some negative value>;
self.view.frame = frame
and in my
textFieldShouldEndEditing
CGRect frame = self.view.frame;
frame.origin.y = <some positive value>; // absolute value of your above negative value
self.view.frame = frame
The above works for me. Remember these are the text field delegate methods. So you need to setup your delegate.

UIKit: UIScrollView automatically scrolling when a subview increases its width past the edge of the screen

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.

How can I make a UIScrollView scroll to a UITextView's cursor position?

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.

iPhone - How may I scroll a UITextField and resize a UITextView when keybord appears

I have a UIScrollView inside which I have a UITextField and a UITextView.
I want to prevent the keyboard to hide the field we are working on.
If my user tap on the TextField, I want it to scroll to top of the screen.
If my user tap on the TextView, I want it to resize to the empty place left on the top of the keyboard.
My keyboard has an accessory view (a toolbar).
For now, I catch the UIKeyboardWillShowNotification notification, and I use this code, that does not really work (the textview is resized, but is still behind the keyboard) :
This code worked well when I hadn't the UIScrollView.
- (void)keyboardWillShow:(NSNotification *)notification {
if ([self.noteView isFirstResponder] == NO) return;
/*
Reduce the size of the text view so that it's not obscured by the keyboard.
Animate the resize so that it's in sync with the appearance of the keyboard.
*/
NSDictionary *userInfo = [notification userInfo];
NSValue* aValue = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey]; // Get the origin of the keyboard when it's displayed.
NSValue *animationDurationValue = [userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey]; // Get the duration of the animation.
// Get the top of the keyboard as the y coordinate of its origin in self's view's coordinate system. The bottom of the text view's frame should align with the top of the keyboard's final position.
CGRect keyboardRect = [aValue CGRectValue];
keyboardRect = [self.view convertRect:keyboardRect fromView:nil];
CGFloat keyboardTop = keyboardRect.origin.y;
CGRect newTextViewFrame = self.view.bounds;
newTextViewFrame.size.height = keyboardTop - self.view.frame.origin.y; // Using bounds here does not help
NSTimeInterval animationDuration;
[animationDurationValue getValue:&animationDuration];
// Animate the resize of the text view's frame in sync with the keyboard's appearance.
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:animationDuration];
self.noteView.frame = newTextViewFrame;
[UIView commitAnimations];
}
So could you help me to :
make this work for the textview ?
make the Textview go back to its original position ?
How may I do to scroll the TextField without any resizing feature ?
Thank you
Could you just programmatically scroll the uiscrollview using this:
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
Not sure if this will work for your application or not.

Iphone -ScrollView smearing the view and the text

I registerd for the keyboard show event, and implemented the method like this :
-(void) KeyboardDidShow:(NSNotification*)notif{
if (KeyboardVisible)
{
NSLog(#"Keyboard is already visible");
return;
}
NSDictionary* info = [notif userInfo];
NSValue* value = [info objectForKey:UIKeyboardBoundsUserInfoKey];
CGSize keyboardSize = [value CGRectValue].size;
CGRect ViewFrame = self.view.frame;
ViewFrame.size.height-=keyboardSize.height;
scrollView.frame = ViewFrame;
KeyboardVisible = YES;
}
Within my view I have 2 textViews.
The problem I have is :
When I put the cursor within the textView and the keyboard pops, If I play with the view cursor up and down all text is getting smeared and it looks like the 2 textviews mix.
Is there something wrong with this code ? (It is actually taken from a book i'm reading).
Worth mentioning is that initially the view was implemented without scrolling, and then scrolling abilities were added using the "Embed objects in Scroll View" command.
Is self.view the same as scrollView? You should probably be using the scroll view's frame as the starting point of your change:
CGRect ViewFrame = scrollView.frame;
instead of
CGRect ViewFrame = self.view.frame;
Also double check your xib file to ensure that both of your text views are subviews of the scroll view and not sibling views. Perhaps you only embedded one of the text views?