CARVIEW |
Select Language
HTTP/2 301
location: https://raw.githubusercontent.com/dblock/ARTiledImageView/1.1.1/Classes/ARTiledImageScrollView.m
accept-ranges: bytes
age: 0
date: Sun, 12 Oct 2025 13:46:43 GMT
via: 1.1 varnish
x-served-by: cache-bom-vanm7210061-BOM
x-cache: MISS
x-cache-hits: 0
x-timer: S1760276802.476306,VS0,VE1497
vary: Accept-Encoding
x-fastly-request-id: d88c7f66af4a0b3059924fe841e4eceb8b237b9a
content-length: 0
HTTP/2 200
cache-control: max-age=300
content-security-policy: default-src 'none'; style-src 'unsafe-inline'; sandbox
content-type: text/plain; charset=utf-8
etag: W/"9b0d36d37a8150c5d0c4ce4dbfff88522328691a81ca8ed3bc4c7845d2b22bec"
strict-transport-security: max-age=31536000
x-content-type-options: nosniff
x-frame-options: deny
x-xss-protection: 1; mode=block
x-github-request-id: EADC:2C7CCA:270CF5:61885C:68EBB144
content-encoding: gzip
accept-ranges: bytes
date: Sun, 12 Oct 2025 13:46:44 GMT
via: 1.1 varnish
x-served-by: cache-bom-vanm7210068-BOM
x-cache: MISS
x-cache-hits: 0
x-timer: S1760276804.028839,VS0,VE334
vary: Authorization,Accept-Encoding
access-control-allow-origin: *
cross-origin-resource-policy: cross-origin
x-fastly-request-id: e377c2dfa81c285a085ea15604df7158f6557639
expires: Sun, 12 Oct 2025 13:51:44 GMT
source-age: 0
content-length: 2027
//
// ARTiledImageViewScrollView.m
// ARTiledImageView
//
// Created by Daniel Doubrovkine on 3/10/14.
// Copyright (c) 2014 Artsy. All rights reserved.
//
#import "ARTiledImageScrollView.h"
#import "ARTiledImageView.h"
#import "ARTiledImageViewDataSource.h"
#import
const CGFloat ARTiledImageScrollViewDefaultZoomStep = 1.5f;
@interface ARTiledImageScrollView ()
@property (nonatomic, readonly) ARTiledImageView *tiledImageView;
@property (nonatomic, readonly) UIImageView *backgroundImageView;
@end
@implementation ARTiledImageScrollView
- (void)setDataSource:(NSObject *)dataSource
{
_dataSource = dataSource;
[self setup];
}
- (void)setup
{
_tiledImageView = [[ARTiledImageView alloc] initWithDataSource:self.dataSource];
[self addSubview:self.tiledImageView];
[self setMaxMinZoomScalesForCurrentBounds];
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.delegate = self;
UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTap:)];
[doubleTap setNumberOfTapsRequired:2];
[self addGestureRecognizer:doubleTap];
UITapGestureRecognizer *twoFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTwoFingerTap:)];
[twoFingerTap setNumberOfTouchesRequired:2];
[self addGestureRecognizer:twoFingerTap];
[self.panGestureRecognizer addTarget:self action:@selector(mapPanGestureHandler:)];
}
- (void)setDisplayTileBorders:(BOOL)displayTileBorders
{
self.tiledImageView.displayTileBorders = displayTileBorders;
_displayTileBorders = displayTileBorders;
}
- (void)setMaxMinZoomScalesForCurrentBounds
{
CGSize boundsSize = self.bounds.size;
CGSize imageSize = [self.dataSource imageSizeForImageView:nil];
// Calculate min/max zoomscale.
CGFloat xScale = boundsSize.width / imageSize.width; // the scale needed to perfectly fit the image width-wise
CGFloat yScale = boundsSize.height / imageSize.height; // the scale needed to perfectly fit the image height-wise
CGFloat minScale = MAX(xScale, yScale); // use minimum of these to allow the image to become fully visible
CGFloat maxScale = 1.0;
// Don't let minScale exceed maxScale.
// If the image is smaller than the screen, we don't want to force it to be zoomed.
if (minScale > maxScale) {
minScale = maxScale;
}
self.maximumZoomScale = maxScale * 0.6;
self.minimumZoomScale = minScale;
self.originalSize = imageSize;
self.contentSize = boundsSize;
}
- (void)zoomToFit:(BOOL)animate
{
[self setZoomScale:self.minimumZoomScale animated:animate];
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
self.backgroundImageView.frame = self.tiledImageView.frame;
if (self.tileZoomLevel != self.tiledImageView.currentZoomLevel) {
_tileZoomLevel = self.tiledImageView.currentZoomLevel;
[self tileZoomLevelDidChange];
}
}
- (void)tileZoomLevelDidChange
{
}
- (void)setBackgroundImageURL:(NSURL *)backgroundImageURL
{
UIImageView *backgroundImageView = [[UIImageView alloc] initWithFrame:self.tiledImageView.frame];
[self insertSubview:backgroundImageView belowSubview:self.tiledImageView];
[backgroundImageView setImageWithURL:backgroundImageURL];
_backgroundImageView = backgroundImageView;
_backgroundImageURL = backgroundImageURL;
}
- (void)setBackgroundImage:(UIImage *)backgroundImage
{
UIImageView *backgroundImageView = [[UIImageView alloc] initWithFrame:self.tiledImageView.frame];
[self insertSubview:backgroundImageView belowSubview:self.tiledImageView];
[backgroundImageView setImage:backgroundImage];
_backgroundImageView = backgroundImageView;
_backgroundImage = backgroundImage;
}
- (void)mapPanGestureHandler:(UIPanGestureRecognizer *)panGesture
{
if (panGesture.state == UIGestureRecognizerStateBegan) {
_centerPoint = CGPointZero;
}
}
- (void)centerOnPoint:(CGPoint)point animated:(BOOL)animate
{
CGFloat x = (point.x * self.zoomScale) - (self.frame.size.width / 2.0f);
CGFloat y = (point.y * self.zoomScale) - (self.frame.size.height / 2.0f);
[self setContentOffset:CGPointMake(round(x), round(y)) animated:animate];
_centerPoint = point;
}
- (CGPoint)zoomRelativePoint:(CGPoint)point
{
CGFloat x = (self.contentSize.width / self.originalSize.width) * point.x;
CGFloat y = (self.contentSize.height / self.originalSize.height) * point.y;
return CGPointMake(round(x), round(y));
}
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
BOOL zoomedOut = self.zoomScale == self.minimumZoomScale;
if (!CGPointEqualToPoint(self.centerPoint, CGPointZero) && !zoomedOut) {
[self centerOnPoint:self.centerPoint animated:NO];
}
}
#pragma mark - UIScrollViewDelegate
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.tiledImageView;
}
- (CGFloat)zoomLevel
{
return self.zoomScale / self.maximumZoomScale;
}
#pragma mark - Tap to Zoom
- (void)handleDoubleTap:(UIGestureRecognizer *)gestureRecognizer
{
// Double tap zooms in, but returns to normal zoom level if it reaches max zoom.
if (self.zoomScale >= self.maximumZoomScale) {
[self setZoomScale:self.minimumZoomScale animated:YES];
} else {
// The location tapped becomes the new center
CGPoint tapCenter = [gestureRecognizer locationInView:self.tiledImageView];
CGFloat newScale = MIN(self.zoomScale * (self.zoomStep ? : ARTiledImageScrollViewDefaultZoomStep), self.maximumZoomScale);
CGRect maxZoomRect = [self rectAroundPoint:tapCenter atZoomScale:newScale];
[self zoomToRect:maxZoomRect animated:YES];
}
}
- (CGRect)rectAroundPoint:(CGPoint)point atZoomScale:(CGFloat)zoomScale
{
// Define the shape of the zoom rect.
CGSize boundsSize = self.bounds.size;
// Modify the size according to the requested zoom level.
// For example, if we're zooming in to 0.5 zoom, then this will increase the bounds size by a factor of two.
CGSize scaledBoundsSize = CGSizeMake(boundsSize.width / zoomScale, boundsSize.height / zoomScale);
return CGRectMake(point.x - scaledBoundsSize.width / 2,
point.y - scaledBoundsSize.height / 2,
scaledBoundsSize.width,
scaledBoundsSize.height);
}
- (void)handleTwoFingerTap:(UIGestureRecognizer *)gestureRecognizer
{
// Two-finger tap zooms out, but returns to normal zoom level if it reaches min zoom.
CGFloat newScale = self.zoomScale <= self.minimumZoomScale ? self.maximumZoomScale : self.zoomScale / (self.zoomStep ? : ARTiledImageScrollViewDefaultZoomStep);
[self setZoomScale:newScale animated:YES];
}
@end