Can I override scroll view's automatic behavior to scroll to the first responder?
I have a UITextField inside a UIScrollView (a few levels deep). I am watching UIKeyboardDidShowNotification, and also calling the same code when I manually change the first responder (I might change to a different text field without momentarily hiding the keyboard). In that code I use scrollRectToVisible:animated:
to make sure the UITextField is visible.
I was having a huge headache debugging why that was acting funny, but I realized 开发者_高级运维now that UIScrollView automatically ensures that the first responder is within its bounds. I am changing the frame of the UIScrollView so that none of it is hidden behind the keyboard.
However, my code can be slightly smarter than their code, because I want to show not only the UITextField, but some nearby related views as well. I try to show those views if they will fit; if not whatever, I try to show as much of them as I can but at least ensure that the UITextField is visible. So I want to keep my custom code.
The automatic behavior interferes with my code. What I see is the scroll view gently scroll up so that the bottom edge of my content is visible, then it snaps down to where my code told it to position.
Is there anyway to stop the UIScrollView from doing its default capability of scrolling the first responder into view?
More Info
On reviewing the documentation I read that they advise to change the scroll view's contentInset instead of frame. I changed that and eliminated some unpredictable behavior, but it didn't fix this particular problem.
I don't think posting all the code would necessarily be that useful. But here is the critical call and the values of important properties at that time. I will just write 4-tuples for CGRects; I mean (x, y, width, height).
[scrollView scrollRectToVisible:(116.2, 71.2, 60, 243) animated:YES];
scrollView.bounds == (0, 12, 320, 361)
scrollView.contentInset == UIEdgeInsetsMake(0, 0, 118, 0)
textField.frame == (112.2, 222.6, 24, 24)
converted to coordinates of the immediate subview of scrollView == (134.2, 244.6, 24, 24)
converted to coordinates of scrollView == (134.2, 244.6, 24, 24)
So the scroll view bottom edge is really at y == 243 because of the inset.
The requested rectangle extends to y == 314.2.
The text field extends to y == 268.6.
Both are out of bounds. scrollRectToVisible is trying to fix one of those problems. The standard UIScrollView / UITextField behavior is trying to fix the other. They don't come up with quite the same solution.
I didn't test this particular situation, but I've managed to prevent a scrollview from bouncing at the top and bottom by subclassing the scrollview and overriding setContentOffset: and setContentOffset:animated:. The scrollview calls this at every scroll movement, so I'm fairly certain they will be called when scrolling to the textfield.
You can use the delegate method textFieldDidBeginEditing: to determine when the scroll is allowed.
In code:
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
self.blockingTextViewScroll = YES;
}
-(void)setContentOffset:(CGPoint)contentOffset
{
if(self.blockingTextViewScroll)
{
self.blockingTextViewScroll = NO;
}
else
{
[super setContentOffset:contentOffset];
}
}
-(void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated
{
if(self.blockingTextViewScroll)
{
self.blockingTextViewScroll = NO;
}
else
{
[super setContentOffset:contentOffset animated:animated];
}
}
If your current scroll behaviour works with a setContentOffset: override, just place it inside the else blocks (or preferably, in a method you call from the else blocks).
In my project I have succeeded to achieve this by performing my scroll only after some delay.
- (void)keyboardWillShow:(NSNotification *)note
{
NSDictionary *userInfo = note.userInfo;
CGRect keyboardFrame = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
UIEdgeInsets contentInsets = self.tableView.contentInset;
contentInsets.bottom += keyboardFrame.size.height;
[self.tableView setContentInset:contentInsets];
[self performSelector:@selector(scrollToEditableCell) withObject:nil afterDelay:0];
}
Also there is other possibility to make your view with additional views to be first responder and fool scroll view where to scroll. Haven't tested this yet.
This may turn out to be useless, but have you tried setting scrollView.userInteractionEnabled
to NO before calling scrollrectToVisible:
& then setting it back to YES? It may prevent the automatic scrolling behavior.
Try changing the view autoresizing to UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin
. The default is FlexibleTopMargin
so maybe thats the reason. btw scrollRectToVisible:
is using the scrollView.contentSize
.
The other thing you can try to change the scrollView
size first and then apply the scrollRectToVisible:
change. First frame change, then content change. (Maybe observe the keyboard did appear event)
The automatic scrolling behavior seems to be especially buggy starting in iOS 14. I alleviated the problem by subclassing UIScrollView and overriding setContentOffset
to do nothing. Here is the bases of my code.
class ManualScrollView: UIScrollView {
/// Use this function to set the content offset. This will forward the call to
/// super.setContentOffset(:animated:)
/// - Parameters:
/// - contentOffset: A point (expressed in points) that is offset from the content view’s origin.
/// - animated: true to animate the transition at a constant velocity to the new offset, false to make the transition immediate.
func forceContentOffset(_ contentOffset: CGPoint, animated: Bool) {
super.setContentOffset(contentOffset, animated: animated)
}
/// This function has be overriden to do nothing to block system calls from changing the
/// content offset at undesireable times.
///
/// Instead call forceContentOffset(:animated:)
override func setContentOffset(_ contentOffset: CGPoint, animated: Bool) {
}
}
This works but you have to deal with reimplementing many of the scroll views behaviors and methods that you normally get for free. Since scrollRectToView
and scrollToView
both use setContentOffset
you also have to reimplement these if you want them to work.
精彩评论