Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 394
Hooray Im Helping Avatar asked Jun 04 '11 16:06

Hooray Im Helping


2 Answers

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.

like image 75
Tatvamasi Avatar answered Sep 23 '22 05:09

Tatvamasi


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.

like image 35
Tom Hamming Avatar answered Sep 21 '22 05:09

Tom Hamming