I have a view in UIScrollView in which the user can zoom into.
The view has the same size as the UIScrollView frame. However, the subviews of that view are bigger and centered. It's a container with same size as UIScrollView with centered content.
When zooming out, UIScrollView changes the scale of the content with an awkward anchor point to the upper left, rather than a centered one.
Is there a way to change this behavior such that when zooming in or out the zoom happens relative to the center rather than the upper left corner?
The behavior can be changed to zooming around the center, but the technique is tricky.
Suppose you have a scroll view of size (300,300) with a single zoomable subview, self.imageView
, located initially at (0,0,300,300). Now you want to zoom out. What is happening is that the contentOffset
always remains (0,0) as you zoom out. This leads to the appearance that you are zooming with the anchor point at top-left corner.
However, changing the anchor point on any views will not help here; this is because the zooming behavior of the scroll view is not just a zoom transformation applied to some subview. The appearance of content in scroll view is controlled by contentInset
and contentOffset
, which are separate variables maintained by the scroll view. You need to modify the behavior of the content offset and the content inset during zoom.
One solution is to adjust the contentInset
dynamically as the scrollview scrolls. The purpose is to keep the image always centered in the visible area of the scroll view.
When initializing the scroll view, prepare its frame like this:
- (void) viewDidLoad {
//// .......
self.scrollView.zoomScale = 1;
self.scrollView.minimumZoomScale = fminf(1, fminf(self.scrollView.frame.size.width/(self.imageView.image.size.width+1), self.scrollView.frame.size.height/(self.imageView.image.size.height+1)));
CGFloat leftMargin = (self.scrollView.frame.size.width - self.imageView.image.size.width)*0.5;
CGFloat topMargin = (self.scrollView.frame.size.height - self.imageView.image.size.height)*0.5;
self.imageView.frame = CGRectMake(0, 0, self.imageView.image.size.width, self.imageView.image.size.height);
[self assignInsetsOnScroller]; // this has to be done before settings contentOffset!
self.scrollView.contentOffset = CGPointMake(fmaxf(0,-leftMargin), fmaxf(0,-topMargin));
self.scrollView.contentSize = CGSizeMake(fmaxf(self.imageView.image.size.width, self.scrollView.frame.size.width+1), fmaxf(self.imageView.image.size.height, self.scrollView.frame.size.height+1));
}
Set up a delegate for the scroll view, override scrollViewDidScroll:
, and do something like this:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[self assignInsetsOnScroller];
}
- (void) assignInsetsOnScroller {
CGFloat leftMargin = (scrollView.frame.size.width - self.imageView.frame.size.width)*0.5;
CGFloat topMargin = (scrollView.frame.size.height - self.imageView.frame.size.height)*0.5;
scrollView.contentInset = UIEdgeInsetsMake(fmaxf(0, topMargin), fmaxf(0, leftMargin), 0, 0);
}
Each pinch gesture will give rise to a scrolling action. The result is that the insets are adjusted dynamically, so that the imageView
always appears to be in the center of the scroll view.
Swift 5: It's much simpler. No need to do any setup — just set the contentOffset
inside scrollViewDidScroll
.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let leftMargin = (scrollView.bounds.width - imageView.frame.width) * 0.5
let topMargin = (scrollView.bounds.height - imageView.frame.height) * 0.5
scrollView.contentInset = UIEdgeInsets(top: topMargin, left: leftMargin, bottom: 0, right: 0)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With