ios – UIScrollView automatically scrolls UIImageView to top on pinch-to-zoom


I’m having issues with UIScrollView when doing pinch-to-zoom UIImageView. Implementation is relatively simple which you can find in many places on the Internet. Double tap to zoom in and out works perfectly fine. But pinch to zoom rarely works as expected. The main problem is that sometimes (like in 50% of cases) UIImageView is scrolled to the top although content offset is set to have padding. Easy to reproduce on horizontal images when slightly zoomed in by pinching the image. Appreciate any help.

#import "ViewController.h"
#import <Photos/Photos.h>
#import <PhotosUI/PhotosUI.h>

@interface ViewController () <UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIScrollViewDelegate> {
  UIImageView *_imageView;
  UIScrollView *_scrollView;
}

@property (nonatomic, readonly) BOOL isZoomed;

@end

@implementation ViewController

- (BOOL) isZoomed {
  return _scrollView.zoomScale > _scrollView.minimumZoomScale;
}

- (void) viewDidLayoutSubviews {
  [super viewDidLayoutSubviews];
  
  _scrollView.frame = self.view.bounds;
}

- (void) viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view.
  
  _scrollView = [[UIScrollView alloc] initWithFrame: self.view.bounds];
  
  _scrollView.backgroundColor = UIColor.clearColor;
  _scrollView.showsVerticalScrollIndicator = NO;
  _scrollView.showsHorizontalScrollIndicator = NO;
  
  _scrollView.delegate = self;
  _scrollView.minimumZoomScale = 1.0;
  _scrollView.maximumZoomScale = 5.0;
  _scrollView.clipsToBounds = YES;
  _scrollView.scrollsToTop = NO;
  _scrollView.contentInset = UIEdgeInsetsZero;
  _scrollView.translatesAutoresizingMaskIntoConstraints = NO;
  _scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
  
  [_scrollView setAutoresizesSubviews: NO];
  
  _imageView = [[UIImageView alloc] initWithFrame: self.view.bounds];
  
  _imageView.backgroundColor = UIColor.clearColor;
  _imageView.translatesAutoresizingMaskIntoConstraints = NO;
  _imageView.contentMode = UIViewContentModeScaleAspectFit;
  _imageView.autoresizingMask = UIViewAutoresizingNone;
  
  [_imageView setUserInteractionEnabled: YES];
  
  [_scrollView addSubview: _imageView];
  [self.view addSubview: _scrollView];
  
  [self.view sendSubviewToBack: _scrollView];
  
  
  UITapGestureRecognizer *_zoomGesture = [[UITapGestureRecognizer alloc] initWithTarget: self action: @selector(onEditorDoubleTap:)];
  
  _zoomGesture.numberOfTapsRequired = 2;
  
  [_scrollView addGestureRecognizer: _zoomGesture];
}

- (void) resetZoom {
  if (!self.isZoomed) {
    return;
  }
    
  [_scrollView setZoomScale: _scrollView.minimumZoomScale animated: YES];
}

- (void) onEditorDoubleTap: (UITapGestureRecognizer *)gestureRecognizer {
  if (gestureRecognizer.state != UIGestureRecognizerStateEnded) {
    return;
  }
  
  if (self.isZoomed) {
    [self resetZoom];
  } else {
    CGPoint location = [gestureRecognizer locationInView: gestureRecognizer.view];
    CGPoint pointInView = [gestureRecognizer.view convertPoint: location toView: _imageView];
    CGFloat newZoomScale = (_scrollView.zoomScale > _scrollView.minimumZoomScale) ? _scrollView.minimumZoomScale : _scrollView.maximumZoomScale;
    
    CGFloat width = _scrollView.frame.size.width / newZoomScale;
    CGFloat height = _scrollView.frame.size.height / newZoomScale;
    CGFloat x = pointInView.x - (width / 2.0);
    CGFloat y = pointInView.y - (height / 2.0);
    CGRect rectToZoomTo = CGRectMake(x, y, width, height);
    
    [_scrollView zoomToRect: rectToZoomTo animated: YES];
  }
}

- (IBAction) openPhoto {
  UIImagePickerController *picker = [[UIImagePickerController alloc] init];
  
  picker.delegate = self;
  picker.allowsEditing = NO;
  picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
  
  [self presentViewController:picker animated:YES completion:nil];
}

- (void)imagePickerController: (UIImagePickerController *) picker didFinishPickingMediaWithInfo: (NSDictionary *) info {
  UIImage *image = info[UIImagePickerControllerOriginalImage];
  
  _imageView.image = image;
  
  _imageView.transform = CGAffineTransformIdentity;
  _imageView.frame = CGRectMake(0, 0, image.size.width, image.size.height);
  
  _scrollView.frame = self.view.bounds;
  _scrollView.contentSize = image.size;
  
  CGSize scrollViewSize = _scrollView.frame.size;
  CGFloat widthScale = scrollViewSize.width / image.size.width;
  CGFloat heightScale = scrollViewSize.height / image.size.height;
  
  _scrollView.minimumZoomScale = MIN(widthScale, heightScale);
  _scrollView.maximumZoomScale = _scrollView.minimumZoomScale * 5.0;
  _scrollView.zoomScale = _scrollView.minimumZoomScale;
  
  [self.view bringSubviewToFront: _scrollView];
  
  __weak __typeof__(self) weakSelf = self;
  
  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
    [weakSelf centerImageAnimated: YES];
  });
  
  [picker dismissViewControllerAnimated: YES completion: nil];
}

- (void) centerImageAnimated: (BOOL) animated {
  CGSize scrollViewSize = _scrollView.frame.size;
  CGFloat imageWidth = _scrollView.contentSize.width;
  CGFloat imageHeigth = _scrollView.contentSize.height;
  
  if (imageWidth > scrollViewSize.width && imageHeigth > scrollViewSize.height) {
    return;
  }
  
  CGRect zoomFrame = _imageView.frame;
  
  zoomFrame.origin.x = zoomFrame.size.width < scrollViewSize.width ? (scrollViewSize.width - zoomFrame.size.width) / 2 : 0;
  zoomFrame.origin.y = zoomFrame.size.height < scrollViewSize.height ? (scrollViewSize.height - zoomFrame.size.height) / 2 : 0;
  _imageView.frame = zoomFrame;
  
  CGFloat horizontalPadding = (scrollViewSize.width - imageWidth) * 0.5f;
  CGFloat verticalPadding = (scrollViewSize.height - imageHeigth) * 0.5f;
    
  [_scrollView setContentOffset: CGPointMake(-horizontalPadding, -verticalPadding) animated: animated];
}

#pragma mark - UIScrollViewDelegate

- (UIView *) viewForZoomingInScrollView: (UIScrollView *) scrollView {
  return _imageView;
}

- (void) scrollViewDidZoom: (UIScrollView *) scrollView {
  [self centerImageAnimated: NO];
}

@end

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img