IOS: add imageview in a scrollview to have zoom
- Set your view controller up as a
<UIScrollViewDelegate>
- Draw your
UIScrollView
the size you want for the rectangle at the center of the view. Set the max zoom in the inspector to something bigger than 1. Like 4 or 10. - Right click on the scroll view and connect the delegate to your view controller.
- Draw your
UIImageView
in theUIScrollView
and set it up with whatever image you want. Make it the same size as theUIScrollView
. - Ctrl + drag form you
UIImageView
to the.h
of your View controller to create anIBOutlet
for theUIImageView
, call it something clever likeimageView
. Add this code:
-(UIView *) viewForZoomingInScrollView:(UIScrollView *)scrollView{ return self.imageView;}
Run the app and pinch and pan til your heart's content.
Download this and this files. You'll need them to handle touches.
Add to your view the scrollView delegate <UIScrollViewDelegate>
and declare the outlets:
@property (nonatomic, retain) IBOutlet UIScrollView *imageScrollView; @property (nonatomic, retain) UIImageView *imageView;
Import the downloaded file inside the screen and do:
#import "TapDetectingImageView.h"#define ZOOM_STEP 2.0@interface myView (UtilityMethods)- (CGRect)zoomRectForScale:(float)scale withCenter:(CGPoint)center;@end@implementation myView@synthesize imageScrollView, imageView;- (void)viewDidLoad{ [super viewDidLoad]; //Setting up the scrollView imageScrollView.bouncesZoom = YES; imageScrollView.delegate = self; imageScrollView.clipsToBounds = YES; //Setting up the imageView imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"myImage.png"]]; imageView.userInteractionEnabled = YES; imageView.autoresizingMask = ( UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin); //Adding the imageView to the scrollView as subView [imageScrollView addSubview:imageView]; imageScrollView.contentSize = CGSizeMake(imageView.bounds.size.width, imageView.bounds.size.height); imageScrollView.decelerationRate = UIScrollViewDecelerationRateFast; //UITapGestureRecognizer set up UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)]; UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTap:)]; UITapGestureRecognizer *twoFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTwoFingerTap:)]; [doubleTap setNumberOfTapsRequired:2]; [twoFingerTap setNumberOfTouchesRequired:2]; //Adding gesture recognizer [imageView addGestureRecognizer:doubleTap]; [imageView addGestureRecognizer:twoFingerTap]; [singleTap release]; [doubleTap release]; [twoFingerTap release]; // calculate minimum scale to perfectly fit image width, and begin at that scale float minimumScale = 1.0;//This is the minimum scale, set it to whatever you want. 1.0 = default imageScrollView.maximumZoomScale = 4.0; imageScrollView.minimumZoomScale = minimumScale; imageScrollView.zoomScale = minimumScale; [imageScrollView setContentMode:UIViewContentModeScaleAspectFit]; [imageView sizeToFit]; [imageScrollView setContentSize:CGSizeMake(imageView.frame.size.width, imageView.frame.size.height)];}- (void)scrollViewDidZoom:(UIScrollView *)aScrollView { CGFloat offsetX = (imageScrollView.bounds.size.width > imageScrollView.contentSize.width)? (imageScrollView.bounds.size.width - imageScrollView.contentSize.width) * 0.5 : 0.0; CGFloat offsetY = (imageScrollView.bounds.size.height > imageScrollView.contentSize.height)? (imageScrollView.bounds.size.height - imageScrollView.contentSize.height) * 0.5 : 0.0; imageView.center = CGPointMake(imageScrollView.contentSize.width * 0.5 + offsetX, imageScrollView.contentSize.height * 0.5 + offsetY);}- (void)viewDidUnload { self.imageScrollView = nil; self.imageView = nil;}#pragma mark UIScrollViewDelegate methods- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { return imageView;}#pragma mark TapDetectingImageViewDelegate methods- (void)handleDoubleTap:(UIGestureRecognizer *)gestureRecognizer { // zoom in float newScale = [imageScrollView zoomScale] * ZOOM_STEP; if (newScale > self.imageScrollView.maximumZoomScale){ newScale = self.imageScrollView.minimumZoomScale; CGRect zoomRect = [self zoomRectForScale:newScale withCenter:[gestureRecognizer locationInView:gestureRecognizer.view]]; [imageScrollView zoomToRect:zoomRect animated:YES]; } else{ newScale = self.imageScrollView.maximumZoomScale; CGRect zoomRect = [self zoomRectForScale:newScale withCenter:[gestureRecognizer locationInView:gestureRecognizer.view]]; [imageScrollView zoomToRect:zoomRect animated:YES]; }}- (void)handleTwoFingerTap:(UIGestureRecognizer *)gestureRecognizer { // two-finger tap zooms out float newScale = [imageScrollView zoomScale] / ZOOM_STEP; CGRect zoomRect = [self zoomRectForScale:newScale withCenter:[gestureRecognizer locationInView:gestureRecognizer.view]]; [imageScrollView zoomToRect:zoomRect animated:YES];}#pragma mark Utility methods- (CGRect)zoomRectForScale:(float)scale withCenter:(CGPoint)center { CGRect zoomRect; // the zoom rect is in the content view's coordinates. // At a zoom scale of 1.0, it would be the size of the imageScrollView's bounds. // As the zoom scale decreases, so more content is visible, the size of the rect grows. zoomRect.size.height = [imageScrollView frame].size.height / scale; zoomRect.size.width = [imageScrollView frame].size.width / scale; // choose an origin so as to get the right center. zoomRect.origin.x = center.x - (zoomRect.size.width / 2.0); zoomRect.origin.y = center.y - (zoomRect.size.height / 2.0); return zoomRect;}
Done!
Basically what this code do is to add the imageView
as subview of the imageScrollView
.
Then, it adds the TapDetecting
class methods to the scrollView, in order to recognize the number of taps - the pinch the user do and add zoom functionalities.
You can set the minimumScale
of the image, if you leave 1.0
the image should be displayed as-it-is (if you set it a little bit lower it's being scaled), and the maximumZoomScale
, i suggest you to leave it to 4, it's fine!
Now, you can load images programmatically from there.
The last thing you have to do is to insert a UIScrollView
inside your xib file and link it to imageScrollView
. You'll have the image at the perfect center, you can double tap on it to zoom, pinch to zoom as you set up in code.
With Swift 4 and iOS 11, you can use one of the two following solutions in order to solve your problem.
#1. Using insets
ViewController.swift
import UIKitfinal class ViewController: UIViewController { private let scrollView = ImageScrollView(image: UIImage(named: "image")!) override func viewDidLoad() { view.backgroundColor = .black view.addSubview(scrollView) scrollView.frame = view.frame scrollView.autoresizingMask = [.flexibleWidth, .flexibleHeight] }}
ImageScrollView.swift
import UIKitfinal class ImageScrollView: UIScrollView { private let imageView = UIImageView() override var frame: CGRect { didSet { if frame.size != oldValue.size { setZoomScale() } } } required init(image: UIImage) { super.init(frame: .zero) imageView.image = image imageView.sizeToFit() addSubview(imageView) contentSize = imageView.bounds.size contentInsetAdjustmentBehavior = .never // Adjust content according to safe area if necessary showsVerticalScrollIndicator = false showsHorizontalScrollIndicator = false alwaysBounceHorizontal = true alwaysBounceVertical = true delegate = self } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - Helper methods func setZoomScale() { let widthScale = frame.size.width / imageView.bounds.width let heightScale = frame.size.height / imageView.bounds.height let minScale = min(widthScale, heightScale) minimumZoomScale = minScale zoomScale = minScale }}
extension ImageScrollView: UIScrollViewDelegate { func viewForZooming(in scrollView: UIScrollView) -> UIView? { return imageView } func scrollViewDidZoom(_ scrollView: UIScrollView) { let imageViewSize = imageView.frame.size let scrollViewSize = scrollView.bounds.size let verticalInset = imageViewSize.height < scrollViewSize.height ? (scrollViewSize.height - imageViewSize.height) / 2 : 0 let horizontalInset = imageViewSize.width < scrollViewSize.width ? (scrollViewSize.width - imageViewSize.width) / 2 : 0 scrollView.contentInset = UIEdgeInsets(top: verticalInset, left: horizontalInset, bottom: verticalInset, right: horizontalInset) }}
#2. Using Auto Layout
ViewController.swift
import UIKitfinal class ViewController: UIViewController { private let scrollView = ImageScrollView(image: UIImage(named: "image")!) override func viewDidLoad() { view.backgroundColor = .black view.addSubview(scrollView) scrollView.translatesAutoresizingMaskIntoConstraints = false scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true } override func viewDidLayoutSubviews() { scrollView.setZoomScale() }}
ImageScrollView.swift
import UIKitfinal class ImageScrollView: UIScrollView { private let imageView = UIImageView() private var imageViewBottomConstraint = NSLayoutConstraint() private var imageViewLeadingConstraint = NSLayoutConstraint() private var imageViewTopConstraint = NSLayoutConstraint() private var imageViewTrailingConstraint = NSLayoutConstraint() required init(image: UIImage) { super.init(frame: .zero) imageView.image = image imageView.sizeToFit() addSubview(imageView) imageView.translatesAutoresizingMaskIntoConstraints = false imageViewLeadingConstraint = imageView.leadingAnchor.constraint(equalTo: leadingAnchor) imageViewTrailingConstraint = imageView.trailingAnchor.constraint(equalTo: trailingAnchor) imageViewTopConstraint = imageView.topAnchor.constraint(equalTo: topAnchor) imageViewBottomConstraint = imageView.bottomAnchor.constraint(equalTo: bottomAnchor) NSLayoutConstraint.activate([imageViewLeadingConstraint, imageViewTrailingConstraint, imageViewTopConstraint, imageViewBottomConstraint]) contentInsetAdjustmentBehavior = .never // Adjust content according to safe area if necessary showsVerticalScrollIndicator = false showsHorizontalScrollIndicator = false alwaysBounceHorizontal = true alwaysBounceVertical = true delegate = self } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - Helper methods func setZoomScale() { let widthScale = frame.size.width / imageView.bounds.width let heightScale = frame.size.height / imageView.bounds.height let minScale = min(widthScale, heightScale) minimumZoomScale = minScale zoomScale = minScale }}
extension ImageScrollView: UIScrollViewDelegate { func viewForZooming(in scrollView: UIScrollView) -> UIView? { return imageView } func scrollViewDidZoom(_ scrollView: UIScrollView) { let yOffset = max(0, (bounds.size.height - imageView.frame.height) / 2) imageViewTopConstraint.constant = yOffset imageViewBottomConstraint.constant = yOffset let xOffset = max(0, (bounds.size.width - imageView.frame.width) / 2) imageViewLeadingConstraint.constant = xOffset imageViewTrailingConstraint.constant = xOffset layoutIfNeeded() }}
Sources: