-[UIScrollView zoomToRect:animated:] weird behavio

2019-04-10 15:51发布

问题:

Can anyone precisely describe the behavior of -[UIScrollView zoomToRect:animated:] ? This method really seems to do some complicated stuff, but Apple's documentation of it is very sparse.

I am getting unpredictable behavior of this method when the content size is smaller than the size of the scroll view in width and/or height. In some cases, this method causes the scroll view to have a negative content offset when it should be 0. Passing slightly different rects, it leaves the content offset at 0 like I would expect.

To demonstrate this weird behavior, I set up an example project with a scroll view of size (200, 200) containing a content view of size (100, 100). I would expect that zooming to rect ((0, 0), (200, 200)) of the content view should leave the content in the top left (i.e. nothing should happen). However, it actually causes the content to scroll to the bottom right of the scroll view's bounds (content offset (-100, -100)). Why does this happen?

Here is the code in my example project:

@implementation RootViewController

- (void)loadView {
    self.view = [[UIView alloc] init];

    self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
    self.scrollView.delegate = self;
    self.scrollView.backgroundColor = [UIColor whiteColor];
    self.scrollView.minimumZoomScale = .5;
    self.scrollView.maximumZoomScale = 4;

    self.contentView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    self.contentView.backgroundColor = [UIColor redColor];
    [self.contentView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap)]];

    [self.scrollView addSubview:self.contentView];
    self.scrollView.contentSize = self.contentView.frame.size;
    [self.view addSubview:self.scrollView];
}

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
    return self.contentView;
}

- (void)handleTap {
    [self.scrollView zoomToRect:CGRectMake(0, 0, 200, 200) animated:YES]; // Try changing to 199, 199 to get completely different behavior
}

@end

Thanks for any insights! Right now I am guessing UIScrollView just isn't designed to display content smaller than its own size. My use case is that I may have a content view that is wider than the scroll view but shorter in height, and I need to be able to scroll to either the right or left end of this content view programmatically.

回答1:

Unless someone has a better answer, I'm going to conclude that calling -zoomToRect:animated: has undefined results when the resulting contentSize is smaller than the bounds size in either dimension. In other words, the content should be larger than the scroll view if you want to be safe.



回答2:

If anyone is wondering, this is what I may do in this case. I'm going to see if it plays well with the unit tests for the scroll view subclass I'm implementing in my real life project. However, this doesn't answer the original question of "Can anyone precisely describe the behavior of -[UIScrollView zoomToRect:animated:] ?" so I'm still hoping for more answers.

Well, here goes (I hate hackery like this):

@implementation TBScrollView // Subclass of UIScrollView

- (void)setContentOffset:(CGPoint)contentOffset {
    contentOffset.x = MAX(contentOffset.x, 0);
    contentOffset.y = MAX(contentOffset.y, 0);
    [super setContentOffset:contentOffset];
}

- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated {
    contentOffset.x = MAX(contentOffset.x, 0);
    contentOffset.y = MAX(contentOffset.y, 0);
    [super setContentOffset:contentOffset animated:animated];
}

@end

EDIT: Unfortunately this has the unfortunate side effect that you can no longer bounce scrolling on the left side for larger images where that should be allowed (requires negative content offset). I'm going to look for another solution to my use case.

EDIT 2: I got around the above-mentioned side effect by only disabling negative content offsets while a -zoomToRect:animated: animation is taking place. I declared a BOOL property, set it to YES before calling -zoomToRect:animated:, and set it back to NO afterwards if animated was NO. Otherwise, I set the property to NO during -scrollViewDidEndScrollingAnimation: (called if -zoomToRect:animated: didn't cause any change in scale) and -scrollViewDidEndZooming:withView:atScale: (called if -zoomToRect:animated: did cause a change in scale). This is kind of a hacky solution and I worry that it may break with a future iOS release, but at least I have it all supported by unit tests :)