|
@@ -1,7 +1,7 @@
|
|
|
/*
|
|
|
File: RSKImageScrollView.m
|
|
|
Abstract: Centers image within the scroll view and configures image sizing and display.
|
|
|
- Version: 1.3 modified by Ruslan Skorb on 8/24/14.
|
|
|
+ Version: 1.5 modified by Ruslan Skorb on 11/26/24.
|
|
|
|
|
|
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple
|
|
|
Inc. ("Apple") in consideration of your agreement to the following
|
|
@@ -42,6 +42,7 @@
|
|
|
POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
|
Copyright (C) 2012 Apple Inc. All Rights Reserved.
|
|
|
+ Copyright (C) 2014-present Ruslan Skorb. All Rights Reserved.
|
|
|
|
|
|
*/
|
|
|
|
|
@@ -55,7 +56,8 @@
|
|
|
@interface RSKImageScrollView () <UIScrollViewDelegate>
|
|
|
{
|
|
|
CGSize _imageSize;
|
|
|
-
|
|
|
+ UIImageView *_imageView;
|
|
|
+
|
|
|
CGPoint _pointToCenterAfterResize;
|
|
|
CGFloat _scaleToRestoreAfterResize;
|
|
|
}
|
|
@@ -70,11 +72,14 @@
|
|
|
if (self)
|
|
|
{
|
|
|
_aspectFill = NO;
|
|
|
+ _imageView = [[UIImageView alloc] init];
|
|
|
self.showsVerticalScrollIndicator = NO;
|
|
|
self.showsHorizontalScrollIndicator = NO;
|
|
|
self.scrollsToTop = NO;
|
|
|
self.decelerationRate = UIScrollViewDecelerationRateFast;
|
|
|
self.delegate = self;
|
|
|
+
|
|
|
+ [self addSubview:_imageView];
|
|
|
}
|
|
|
return self;
|
|
|
}
|
|
@@ -83,7 +88,7 @@
|
|
|
{
|
|
|
[super didAddSubview:subview];
|
|
|
|
|
|
- [self centerZoomView];
|
|
|
+ [self centerImageView];
|
|
|
}
|
|
|
|
|
|
- (void)setAspectFill:(BOOL)aspectFill
|
|
@@ -91,18 +96,91 @@
|
|
|
if (_aspectFill != aspectFill) {
|
|
|
_aspectFill = aspectFill;
|
|
|
|
|
|
- if (_zoomView) {
|
|
|
+ if (_imageView.image) {
|
|
|
[self setMaxMinZoomScalesForCurrentBounds];
|
|
|
|
|
|
if (self.zoomScale < self.minimumZoomScale) {
|
|
|
self.zoomScale = self.minimumZoomScale;
|
|
|
+ } else if (self.zoomScale > self.maximumZoomScale) {
|
|
|
+ self.zoomScale = self.maximumZoomScale;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+- (UIImage *)image
|
|
|
+{
|
|
|
+ return _imageView.image;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)setImage:(UIImage *)image
|
|
|
+{
|
|
|
+ _imageView.image = image;
|
|
|
+
|
|
|
+ if (CGSizeEqualToSize(_imageSize, CGSizeZero)) {
|
|
|
+ self.imageSize = image.size;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (UIColor *)imageViewBackgroundColor
|
|
|
+{
|
|
|
+ return _imageView.backgroundColor;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)setImageViewBackgroundColor:(UIColor *)imageViewBackgroundColor
|
|
|
+{
|
|
|
+ _imageView.backgroundColor = imageViewBackgroundColor;
|
|
|
+}
|
|
|
+
|
|
|
+- (id<UICoordinateSpace>)imageViewCoordinateSpace
|
|
|
+{
|
|
|
+ return [_imageView coordinateSpace];
|
|
|
+}
|
|
|
+
|
|
|
+- (CGRect)imageViewFrame
|
|
|
+{
|
|
|
+ return _imageView.frame;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)setImageSize:(CGSize)imageSize
|
|
|
+{
|
|
|
+ _imageSize = imageSize;
|
|
|
+
|
|
|
+ self.zoomScale = 1.0f;
|
|
|
+ _imageView.frame = CGRectMake(0.0f, 0.0f, imageSize.width, imageSize.height);
|
|
|
+ self.contentSize = imageSize;
|
|
|
+ [self setMaxMinZoomScalesForCurrentBounds];
|
|
|
+ [self setInitialZoomScale];
|
|
|
+ [self setInitialContentOffset];
|
|
|
+ [self centerImageView];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)setInitialZoomScaleAndContentOffsetAndCenterImageView
|
|
|
+{
|
|
|
+ [self setInitialZoomScale];
|
|
|
+ [self setInitialContentOffset];
|
|
|
+ [self centerImageView];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)setInitialZoomScaleAndContentOffsetAnimated:(BOOL)animated
|
|
|
+{
|
|
|
+ if (animated) {
|
|
|
+ UIViewAnimationOptions options = UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationCurveEaseInOut;
|
|
|
+ [UIView animateWithDuration:0.4f delay:0.0f options:options animations:^{
|
|
|
+ [self setInitialZoomScaleAndContentOffsetAndCenterImageView];
|
|
|
+ } completion:nil];
|
|
|
+ } else {
|
|
|
+ [self setInitialZoomScaleAndContentOffsetAndCenterImageView];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
- (void)setFrame:(CGRect)frame
|
|
|
{
|
|
|
+ if (CGSizeEqualToSize(self.contentSize, CGSizeZero)) {
|
|
|
+ [super setFrame:frame];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
BOOL sizeChanging = !CGSizeEqualToSize(frame.size, self.frame.size);
|
|
|
|
|
|
if (sizeChanging) {
|
|
@@ -115,98 +193,116 @@
|
|
|
[self recoverFromResizing];
|
|
|
}
|
|
|
|
|
|
- [self centerZoomView];
|
|
|
+ [self centerImageView];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)zoomToLocation:(CGPoint)location animated:(BOOL)animated
|
|
|
+{
|
|
|
+ CGPoint locationInImageView = [_imageView convertPoint:location fromView:self];
|
|
|
+ CGSize size = CGSizeMake(self.bounds.size.width / MIN(self.zoomScale * 5.0f, self.maximumZoomScale),
|
|
|
+ self.bounds.size.height / MIN(self.zoomScale * 5.0f, self.maximumZoomScale));
|
|
|
+ CGPoint origin = CGPointMake(locationInImageView.x - size.width * 0.5f,
|
|
|
+ locationInImageView.y - size.height * 0.5f);
|
|
|
+ CGRect rect = CGRectMake(origin.x, origin.y, size.width, size.height);
|
|
|
+
|
|
|
+ [self zoomToRect:rect animated:animated];
|
|
|
}
|
|
|
|
|
|
#pragma mark - UIScrollViewDelegate
|
|
|
|
|
|
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
|
|
|
{
|
|
|
- return _zoomView;
|
|
|
+ return _imageView;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
|
|
+{
|
|
|
+ if ([self.imageScrollViewDelegate respondsToSelector:@selector(imageScrollViewDidScroll)]) {
|
|
|
+ [self.imageScrollViewDelegate imageScrollViewDidScroll];
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- (void)scrollViewDidZoom:(__unused UIScrollView *)scrollView
|
|
|
{
|
|
|
- [self centerZoomView];
|
|
|
+ [self centerImageView];
|
|
|
}
|
|
|
|
|
|
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
|
|
|
{
|
|
|
- [self.imageScrollViewDelegate imageScrollViewWillBeginDragging];
|
|
|
+ if ([self.imageScrollViewDelegate respondsToSelector:@selector(imageScrollViewWillBeginDragging)]) {
|
|
|
+ [self.imageScrollViewDelegate imageScrollViewWillBeginDragging];
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
|
|
|
{
|
|
|
- [self.imageScrollViewDelegate imageScrollViewDidEndDragging:decelerate];
|
|
|
+ if ([self.imageScrollViewDelegate respondsToSelector:@selector(imageScrollViewDidEndDragging:)]) {
|
|
|
+ [self.imageScrollViewDelegate imageScrollViewDidEndDragging:decelerate];
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
|
|
|
{
|
|
|
- [self.imageScrollViewDelegate imageScrollViewDidEndDecelerating];
|
|
|
+ if ([self.imageScrollViewDelegate respondsToSelector:@selector(imageScrollViewDidEndDecelerating)]) {
|
|
|
+ [self.imageScrollViewDelegate imageScrollViewDidEndDecelerating];
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
|
|
|
{
|
|
|
- [self.imageScrollViewDelegate imageScrollViewWillBeginZooming];
|
|
|
+ if ([self.imageScrollViewDelegate respondsToSelector:@selector(imageScrollViewWillBeginZooming)]) {
|
|
|
+ [self.imageScrollViewDelegate imageScrollViewWillBeginZooming];
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale
|
|
|
{
|
|
|
- [self.imageScrollViewDelegate imageScrollViewDidEndZooming];
|
|
|
+ if ([self.imageScrollViewDelegate respondsToSelector:@selector(imageScrollViewDidEndZooming)]) {
|
|
|
+ [self.imageScrollViewDelegate imageScrollViewDidEndZooming];
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-#pragma mark - Center zoomView within scrollView
|
|
|
+#pragma mark - Center imageView within scrollView
|
|
|
|
|
|
-- (void)centerZoomView
|
|
|
+- (void)centerImageView
|
|
|
{
|
|
|
- // center zoomView as it becomes smaller than the size of the screen
|
|
|
+ // center imageView as it becomes smaller than the size of the screen
|
|
|
+
|
|
|
+ CGFloat top = 0.0f;
|
|
|
+ CGFloat left = 0.0f;
|
|
|
|
|
|
- CGFloat top = 0;
|
|
|
- CGFloat left = 0;
|
|
|
-
|
|
|
// center vertically
|
|
|
if (self.contentSize.height < CGRectGetHeight(self.bounds)) {
|
|
|
top = (CGRectGetHeight(self.bounds) - self.contentSize.height) * 0.5f;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// center horizontally
|
|
|
if (self.contentSize.width < CGRectGetWidth(self.bounds)) {
|
|
|
left = (CGRectGetWidth(self.bounds) - self.contentSize.width) * 0.5f;
|
|
|
}
|
|
|
|
|
|
- self.contentInset = UIEdgeInsetsMake(top, left, top, left);
|
|
|
+ UIEdgeInsets contentInset = UIEdgeInsetsMake(top, left, top, left);
|
|
|
+
|
|
|
+ if (!UIEdgeInsetsEqualToEdgeInsets(self.contentInset, contentInset)) {
|
|
|
+ self.contentInset = contentInset;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
#pragma mark - Configure scrollView to display new image
|
|
|
|
|
|
-- (void)displayImage:(UIImage *)image
|
|
|
+- (void)setMaxMinZoomScalesForCurrentBounds
|
|
|
{
|
|
|
- // clear view for the previous image
|
|
|
- [_zoomView removeFromSuperview];
|
|
|
- _zoomView = nil;
|
|
|
-
|
|
|
- // reset our zoomScale to 1.0 before doing any further calculations
|
|
|
- self.zoomScale = 1.0;
|
|
|
-
|
|
|
- // make views to display the new image
|
|
|
- _zoomView = [[UIImageView alloc] initWithImage:image];
|
|
|
- [self addSubview:_zoomView];
|
|
|
+ if (CGSizeEqualToSize(self.bounds.size, CGSizeZero)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- [self configureForImageSize:image.size];
|
|
|
-}
|
|
|
+ if (CGSizeEqualToSize(_imageSize, CGSizeZero)) {
|
|
|
+ self.maximumZoomScale = 1.0f;
|
|
|
+ self.minimumZoomScale = 1.0f;
|
|
|
|
|
|
-- (void)configureForImageSize:(CGSize)imageSize
|
|
|
-{
|
|
|
- _imageSize = imageSize;
|
|
|
- self.contentSize = imageSize;
|
|
|
- [self setMaxMinZoomScalesForCurrentBounds];
|
|
|
- [self setInitialZoomScale];
|
|
|
- [self setInitialContentOffset];
|
|
|
- self.contentInset = UIEdgeInsetsZero;
|
|
|
-}
|
|
|
-
|
|
|
-- (void)setMaxMinZoomScalesForCurrentBounds
|
|
|
-{
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
CGSize boundsSize = self.bounds.size;
|
|
|
|
|
|
// calculate min/max zoomscale
|
|
@@ -214,17 +310,17 @@
|
|
|
CGFloat yScale = boundsSize.height / _imageSize.height; // the scale needed to perfectly fit the image height-wise
|
|
|
|
|
|
CGFloat minScale;
|
|
|
- if (!self.aspectFill) {
|
|
|
- minScale = MIN(xScale, yScale); // use minimum of these to allow the image to become fully visible
|
|
|
- } else {
|
|
|
+ if (_aspectFill) {
|
|
|
minScale = MAX(xScale, yScale); // use maximum of these to allow the image to fill the screen
|
|
|
+ } else {
|
|
|
+ minScale = MIN(xScale, yScale); // use minimum of these to allow the image to become fully visible
|
|
|
}
|
|
|
|
|
|
CGFloat maxScale = MAX(xScale, yScale);
|
|
|
|
|
|
// Image must fit/fill the screen, even if its size is smaller.
|
|
|
- CGFloat xImageScale = maxScale*_imageSize.width / boundsSize.width;
|
|
|
- CGFloat yImageScale = maxScale*_imageSize.height / boundsSize.height;
|
|
|
+ CGFloat xImageScale = maxScale * _imageSize.width / boundsSize.width;
|
|
|
+ CGFloat yImageScale = maxScale * _imageSize.height / boundsSize.height;
|
|
|
|
|
|
CGFloat maxImageScale = MAX(xImageScale, yImageScale);
|
|
|
|
|
@@ -242,31 +338,27 @@
|
|
|
|
|
|
- (void)setInitialZoomScale
|
|
|
{
|
|
|
- CGSize boundsSize = self.bounds.size;
|
|
|
- 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 scale = MAX(xScale, yScale);
|
|
|
- self.zoomScale = scale;
|
|
|
+ if (self.zoomScale != self.minimumZoomScale) {
|
|
|
+ self.zoomScale = self.minimumZoomScale;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- (void)setInitialContentOffset
|
|
|
{
|
|
|
CGSize boundsSize = self.bounds.size;
|
|
|
- CGRect frameToCenter = self.zoomView.frame;
|
|
|
+ CGRect frameToCenter = _imageView.frame;
|
|
|
|
|
|
- CGPoint contentOffset;
|
|
|
+ CGPoint contentOffset = self.contentOffset;
|
|
|
if (CGRectGetWidth(frameToCenter) > boundsSize.width) {
|
|
|
contentOffset.x = (CGRectGetWidth(frameToCenter) - boundsSize.width) * 0.5f;
|
|
|
- } else {
|
|
|
- contentOffset.x = 0;
|
|
|
}
|
|
|
if (CGRectGetHeight(frameToCenter) > boundsSize.height) {
|
|
|
contentOffset.y = (CGRectGetHeight(frameToCenter) - boundsSize.height) * 0.5f;
|
|
|
- } else {
|
|
|
- contentOffset.y = 0;
|
|
|
}
|
|
|
|
|
|
- [self setContentOffset:contentOffset];
|
|
|
+ if (!CGPointEqualToPoint(self.contentOffset, contentOffset)) {
|
|
|
+ self.contentOffset = contentOffset;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
@@ -276,13 +368,13 @@
|
|
|
|
|
|
- (void)prepareToResize
|
|
|
{
|
|
|
- if (_zoomView == nil) {
|
|
|
+ if (_imageView == nil) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
CGPoint boundsCenter = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
|
|
|
- _pointToCenterAfterResize = [self convertPoint:boundsCenter toView:self.zoomView];
|
|
|
-
|
|
|
+ _pointToCenterAfterResize = [self convertPoint:boundsCenter toView:_imageView];
|
|
|
+
|
|
|
_scaleToRestoreAfterResize = self.zoomScale;
|
|
|
|
|
|
// If we're at the minimum zoom scale, preserve that by returning 0, which will be converted to the minimum
|
|
@@ -293,7 +385,7 @@
|
|
|
|
|
|
- (void)recoverFromResizing
|
|
|
{
|
|
|
- if (_zoomView == nil) {
|
|
|
+ if (_imageView == nil) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
@@ -306,12 +398,12 @@
|
|
|
// Step 2: restore center point, first making sure it is within the allowable range.
|
|
|
|
|
|
// 2a: convert our desired center point back to our own coordinate space
|
|
|
- CGPoint boundsCenter = [self convertPoint:_pointToCenterAfterResize fromView:self.zoomView];
|
|
|
-
|
|
|
+ CGPoint boundsCenter = [self convertPoint:_pointToCenterAfterResize fromView:_imageView];
|
|
|
+
|
|
|
// 2b: calculate the content offset that would yield that center point
|
|
|
- CGPoint offset = CGPointMake(boundsCenter.x - self.bounds.size.width / 2.0,
|
|
|
- boundsCenter.y - self.bounds.size.height / 2.0);
|
|
|
-
|
|
|
+ CGPoint offset = CGPointMake(boundsCenter.x - self.bounds.size.width * 0.5f,
|
|
|
+ boundsCenter.y - self.bounds.size.height * 0.5f);
|
|
|
+
|
|
|
// 2c: restore offset, adjusted to be within the allowable range
|
|
|
CGPoint maxOffset = [self maximumContentOffset];
|
|
|
CGPoint minOffset = [self minimumContentOffset];
|
|
@@ -322,6 +414,15 @@
|
|
|
realMaxOffset = MIN(maxOffset.y, offset.y);
|
|
|
offset.y = MAX(minOffset.y, realMaxOffset);
|
|
|
|
|
|
+ if (self.contentSize.height < self.bounds.size.height) {
|
|
|
+
|
|
|
+ offset.y = -(self.bounds.size.height - self.contentSize.height) * 0.5f;
|
|
|
+ }
|
|
|
+ if (self.contentSize.width < self.bounds.size.width) {
|
|
|
+
|
|
|
+ offset.x = -(self.bounds.size.width - self.contentSize.width) * 0.5f;
|
|
|
+ }
|
|
|
+
|
|
|
self.contentOffset = offset;
|
|
|
}
|
|
|
|