Swiftgram/submodules/LegacyComponents/Sources/TGPhotoCropScrollView.m
2020-02-22 15:38:54 +04:00

858 lines
33 KiB
Objective-C

#import "TGPhotoCropScrollView.h"
#import "LegacyComponentsInternal.h"
#import <LegacyComponents/TGPhotoEditorAnimation.h>
#import <LegacyComponents/TGPhotoEditorUtils.h>
typedef struct {
CGPoint tl, tr, bl, br;
} TGPhotoCropRectangle;
@interface TGPhotoCropScrollView () <UIGestureRecognizerDelegate>
{
UIView *_wrapperView;
UILongPressGestureRecognizer *_pressGestureRecognizer;
UIPanGestureRecognizer *_panGestureRecognizer;
UIPinchGestureRecognizer *_pinchGestureRecognizer;
UIView *_snapshotClipView;
UIImageView *_snapshotView;
UIImageView *_paintingImageView;
UIImage *_paintingImage;
bool _beganInteraction;
bool _endedInteraction;
bool _fitted;
bool _isTracking;
CGPoint _touchCenter;
CGPoint _pinchCenter;
CGFloat _pinchStartScale;
CGFloat _rotationStartScale;
bool _mirrored;
}
@end
@implementation TGPhotoCropScrollView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self != nil)
{
self.backgroundColor = [UIColor blackColor];
_maximumZoomScale = 10.0f;
_wrapperView = [[UIView alloc] initWithFrame:CGRectZero];
if (iosMajorVersion() >= 7)
_wrapperView.layer.allowsEdgeAntialiasing = true;
[self addSubview:_wrapperView];
_pressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handlePress:)];
_pressGestureRecognizer.delegate = self;
_pressGestureRecognizer.minimumPressDuration = 0.1f;
[self addGestureRecognizer:_pressGestureRecognizer];
_panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
_panGestureRecognizer.delegate = self;
[self addGestureRecognizer:_panGestureRecognizer];
_pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)];
_pinchGestureRecognizer.delegate = self;
[self addGestureRecognizer:_pinchGestureRecognizer];
}
return self;
}
#pragma mark - Content
- (void)setHidden:(BOOL)hidden
{
[super setHidden:hidden];
_snapshotClipView.hidden = hidden;
}
- (void)setContentSize:(CGSize)contentSize
{
_contentSize = contentSize;
_wrapperView.frame = CGRectMake(0, 0, contentSize.width, contentSize.height);
}
- (CGFloat)contentRotation
{
return [[_wrapperView.layer valueForKeyPath:@"transform.rotation.z"] floatValue];
}
- (CGFloat)contentScale
{
return [[_wrapperView.layer valueForKeyPath:@"transform.scale.x"] floatValue];
}
- (CGAffineTransform)cropTransform
{
CATransform3D transform3d = _wrapperView.layer.transform;
CGAffineTransform currentTransform = CGAffineTransformMake(transform3d.m11, transform3d.m12,
transform3d.m21, transform3d.m22,
transform3d.m41, transform3d.m42);
return currentTransform;
}
- (void)setContentRotation:(CGFloat)contentRotation
{
[self setContentRotation:contentRotation maximize:false resetting:false];
}
- (void)setContentRotation:(CGFloat)contentRotation maximize:(bool)maximize resetting:(bool)resetting
{
CGPoint rotationCenter = [self convertPoint:CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2) toView:_wrapperView];
CGPoint delta = CGPointMake(rotationCenter.x - _wrapperView.bounds.size.width / 2,
rotationCenter.y - _wrapperView.bounds.size.height / 2);
CATransform3D transform = CATransform3DTranslate(_wrapperView.layer.transform, delta.x, delta.y, 0.0f);
transform = CATransform3DRotate(transform, contentRotation - self.contentRotation, 0.0f, 0.0f, 1.0f);
transform = CATransform3DTranslate(transform, -delta.x, -delta.y, 0.0f);
_wrapperView.layer.transform = transform;
_snapshotView.superview.layer.transform = transform;
if (!resetting)
[self fitContentInsideBoundsAllowScale:true maximize:maximize animated:false completion:nil];
}
- (void)setContentMirrored:(bool)mirrored
{
_mirrored = mirrored;
_contentView.transform = CGAffineTransformMakeScale(mirrored ? -1.0f : 1.0f, 1.0f);
}
- (void)setContentView:(UIView *)contentView
{
[_contentView removeFromSuperview];
_contentView = contentView;
[_wrapperView addSubview:contentView];
_contentView.frame = CGRectMake(0, 0, _contentSize.width, _contentSize.height);
if (_mirrored)
_contentView.transform = CGAffineTransformMakeScale(-1.0f, 1.0f);
[self resetAndSetBounds:true];
}
- (CGRect)availableRect
{
if (ABS(self.contentRotation) < FLT_EPSILON)
return CGRectMake(0, 0, _contentSize.width, _contentSize.height);
else
return self.zoomedRect;
}
- (CGRect)zoomedRect
{
CGSize rotatedContentSize = TGRotatedContentSize(_contentSize, self.contentRotation);
CGSize rotationScaleRatios = CGSizeMake(rotatedContentSize.width / _contentSize.width,
rotatedContentSize.height / _contentSize.height);
UIView *convertView = [[UIView alloc] initWithFrame:CGRectMake((self.bounds.size.width - _contentSize.width) / 2,
(self.bounds.size.height - _contentSize.height) / 2,
_contentSize.width,
_contentSize.height)];
CGAffineTransform transform = CGAffineTransformMakeScale(_wrapperView.frame.size.width / _contentSize.width / rotationScaleRatios.width, _wrapperView.frame.size.height / _contentSize.height / rotationScaleRatios.height);
convertView.transform = transform;
convertView.frame = CGRectOffset(convertView.frame, _wrapperView.frame.origin.x - convertView.frame.origin.x, _wrapperView.frame.origin.y - convertView.frame.origin.y);
[self addSubview:convertView];
CGRect rect = [self convertRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height) toView:convertView];
[convertView removeFromSuperview];
return rect;
}
- (void)translateContentViewWithOffset:(CGPoint)offset
{
CGFloat scale = self.contentScale;
offset.x /= scale;
offset.y /= scale;
CGPoint xComp = CGPointMake(CGSin((CGFloat)M_PI_2 + self.contentRotation) * offset.x,
CGCos((CGFloat)M_PI_2 + self.contentRotation) * offset.x);
CGPoint yComp = CGPointMake(CGCos((CGFloat)M_PI_2 - self.contentRotation) * offset.y,
CGSin((CGFloat)M_PI_2 - self.contentRotation) * offset.y);
_wrapperView.transform = CGAffineTransformTranslate(_wrapperView.transform, xComp.x + yComp.x, xComp.y + yComp.y);
_snapshotView.superview.transform = _wrapperView.transform;
}
- (void)fitContentInsideBoundsAllowScale:(bool)allowScale animated:(bool)animated completion:(void (^)(void))completion
{
[self fitContentInsideBoundsAllowScale:allowScale maximize:false animated:animated completion:completion];
}
- (void)fitContentInsideBoundsAllowScale:(bool)allowScale maximize:(bool)maximize animated:(bool)animated completion:(void (^)(void))completion
{
CGRect boundsRect = [self boundingBoxForRect:self.bounds withRotation:self.contentRotation];
CGRect initialRect = CGRectMake(0, 0, _contentSize.width, _contentSize.height);
CGPoint initialOffset = CGPointMake((self.bounds.size.width - _contentSize.width) / 2, (self.bounds.size.height - _contentSize.height) / 2);
CGAffineTransform currentTransform = self.cropTransform;
CGPoint centerOffset = CGPointMake(_wrapperView.center.x - CGRectGetMidX(boundsRect), _wrapperView.center.y - CGRectGetMidY(boundsRect));
CGPoint xComp = CGPointMake(CGSin((CGFloat)M_PI_2 + self.contentRotation) * centerOffset.x,
CGCos((CGFloat)M_PI_2 + self.contentRotation) * centerOffset.x);
CGPoint yComp = CGPointMake(CGCos((CGFloat)M_PI_2 - self.contentRotation) * centerOffset.y,
CGSin((CGFloat)M_PI_2 - self.contentRotation) * centerOffset.y);
CGFloat contentScale = self.contentScale;
TGPhotoCropRectangle r2 = [self applyTransform:CGAffineTransformTranslate(currentTransform,
(initialOffset.x + xComp.x + yComp.x) / contentScale,
(initialOffset.y + xComp.y + yComp.y) / contentScale) toRect:initialRect];
CGAffineTransform t = CGAffineTransformMakeTranslation(_contentSize.width / 2, _contentSize.height / 2);
t = CGAffineTransformRotate(t, -self.contentRotation);
t = CGAffineTransformTranslate(t, -_contentSize.width / 2, -_contentSize.height / 2);
TGPhotoCropRectangle r3 = [self applyTransform:t toRectangle:r2];
__block CGRect contentRect = [self CGRectFromRectangle:r3];
__block CGPoint targetTranslation = [[_wrapperView.layer valueForKeyPath:@"transform.translation"] CGPointValue];
__block CGFloat targetScale = contentScale;
__block CATransform3D targetTransform = _wrapperView.layer.transform;
void (^fitScaleBlock)(CGFloat) = ^(CGFloat ratio)
{
CGSize scaledSize = CGSizeMake(contentRect.size.width * ratio, contentRect.size.height * ratio);
CGPoint scaledOffset = CGPointMake((contentRect.size.width - scaledSize.width) / 2, (contentRect.size.height - scaledSize.height) / 2);
contentRect = CGRectMake(contentRect.origin.x + scaledOffset.x, contentRect.origin.y + scaledOffset.y, scaledSize.width, scaledSize.height);
targetTransform = CATransform3DScale(targetTransform, ratio, ratio, 1.0f);
targetScale *= ratio;
};
void (^fitTranslationBlock)(void) = ^
{
CGPoint contentTL = CGPointMake(CGRectGetMinX(contentRect), CGRectGetMinY(contentRect));
CGPoint contentBR = CGPointMake(CGRectGetMaxX(contentRect), CGRectGetMaxY(contentRect));
CGPoint frameTL = CGPointMake(CGRectGetMinX(boundsRect), CGRectGetMinY(boundsRect));
CGPoint frameBR = CGPointMake(CGRectGetMaxX(boundsRect), CGRectGetMaxY(boundsRect));
if (contentTL.x > frameTL.x)
frameTL.x = contentTL.x;
if (contentTL.y > frameTL.y)
frameTL.y = contentTL.y;
if (contentBR.x < frameBR.x)
frameTL.x += contentBR.x - frameBR.x;
if (contentBR.y < frameBR.y)
frameTL.y += contentBR.y - frameBR.y;
CGRect validBoundsRect = CGRectMake(frameTL.x, frameTL.y, boundsRect.size.width, boundsRect.size.height);
CGPoint delta = CGPointMake(CGRectGetMidX(boundsRect) - CGRectGetMidX(validBoundsRect),
CGRectGetMidY(boundsRect) - CGRectGetMidY(validBoundsRect));
targetTransform = CATransform3DTranslate(targetTransform, delta.x / targetScale, delta.y / targetScale, 0.0f);
CGPoint xComp = CGPointMake(CGSin((CGFloat)M_PI_2 - self.contentRotation) * delta.x,
CGCos((CGFloat)M_PI_2 - self.contentRotation) * delta.x);
CGPoint yComp = CGPointMake(CGCos((CGFloat)M_PI_2 + self.contentRotation) * delta.y,
CGSin((CGFloat)M_PI_2 + self.contentRotation) * delta.y);
targetTranslation.x += xComp.x + yComp.x;
targetTranslation.y += xComp.y + yComp.y;
};
void (^applyBlock)(void) = ^
{
if (animated)
{
CGPoint translation = [[_wrapperView.layer valueForKeyPath:@"transform.translation"] CGPointValue];
POPSpringAnimation *translationAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerTranslationXY];
translationAnimation.fromValue = [NSValue valueWithCGPoint:translation];
translationAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(targetTranslation.x, targetTranslation.y)];
translationAnimation.springSpeed = 7;
translationAnimation.springBounciness = 1;
POPSpringAnimation *scaleAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
scaleAnimation.fromValue = [NSValue valueWithCGSize:CGSizeMake(self.contentScale, self.contentScale)];
scaleAnimation.toValue = [NSValue valueWithCGSize:CGSizeMake(targetScale, targetScale)];
scaleAnimation.springSpeed = 7;
scaleAnimation.springBounciness = 1;
POPSpringAnimation *snapshotTranslationAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerTranslationXY];
snapshotTranslationAnimation.fromValue = [NSValue valueWithCGPoint:translation];
snapshotTranslationAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(targetTranslation.x, targetTranslation.y)];
snapshotTranslationAnimation.springSpeed = 7;
snapshotTranslationAnimation.springBounciness = 1;
POPSpringAnimation *snapshotScaleAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
snapshotScaleAnimation.fromValue = [NSValue valueWithCGSize:CGSizeMake(self.contentScale, self.contentScale)];
snapshotScaleAnimation.toValue = [NSValue valueWithCGSize:CGSizeMake(targetScale, targetScale)];
snapshotScaleAnimation.springSpeed = 7;
snapshotScaleAnimation.springBounciness = 1;
_animating = true;
[TGPhotoEditorAnimation performBlock:^(__unused bool allFinished)
{
_animating = false;
if (completion != nil)
completion();
} whenCompletedAllAnimations:@[ translationAnimation, scaleAnimation ]];
[_wrapperView.layer pop_addAnimation:translationAnimation forKey:@"translation"];
[_wrapperView.layer pop_addAnimation:scaleAnimation forKey:@"scale"];
_snapshotView.superview.layer.transform = _wrapperView.layer.transform;
[_snapshotView.superview.layer pop_addAnimation:snapshotTranslationAnimation forKey:@"translation"];
[_snapshotView.superview.layer pop_addAnimation:snapshotScaleAnimation forKey:@"scale"];
}
else
{
_wrapperView.layer.transform = targetTransform;
_snapshotView.superview.layer.transform = targetTransform;
if (completion != nil)
completion();
}
};
if (!CGRectContainsRect(contentRect, boundsRect))
{
if (allowScale && (boundsRect.size.width > contentRect.size.width || boundsRect.size.height > contentRect.size.height))
fitScaleBlock(boundsRect.size.width / TGScaleToSize(boundsRect.size, contentRect.size).width);
fitTranslationBlock();
applyBlock();
}
else
{
if (maximize && _rotationStartScale > FLT_EPSILON)
{
CGFloat ratio = boundsRect.size.width / TGScaleToSize(boundsRect.size, contentRect.size).width;
CGFloat newScale = self.contentScale * ratio;
if (newScale < _rotationStartScale)
ratio = 1.0f;
fitScaleBlock(ratio);
fitTranslationBlock();
}
applyBlock();
}
}
- (TGPhotoCropRectangle)rectangleFromCGRect:(CGRect)rect
{
return (TGPhotoCropRectangle)
{
.tl = (CGPoint){ rect.origin.x, rect.origin.y },
.tr = (CGPoint){ CGRectGetMaxX(rect), rect.origin.y },
.br = (CGPoint){ CGRectGetMaxX(rect), CGRectGetMaxY(rect) },
.bl = (CGPoint){ rect.origin.x, CGRectGetMaxY(rect) }
};
}
- (CGRect)CGRectFromRectangle:(TGPhotoCropRectangle)rect
{
return (CGRect)
{
.origin = rect.tl,
.size = (CGSize){ .width = rect.tr.x - rect.tl.x, .height = rect.bl.y - rect.tl.y }
};
}
- (CGRect)boundingBoxForRect:(CGRect)rect withRotation:(CGFloat)rotation
{
CGAffineTransform t = CGAffineTransformMakeTranslation(CGRectGetMidX(rect), CGRectGetMidY(rect));
t = CGAffineTransformRotate(t, rotation);
t = CGAffineTransformTranslate(t, -CGRectGetMidX(rect), -CGRectGetMidY(rect));
return CGRectApplyAffineTransform(rect, t);
}
- (TGPhotoCropRectangle)applyTransform:(CGAffineTransform)transform toRect:(CGRect)rect
{
CGAffineTransform t = CGAffineTransformMakeTranslation(CGRectGetMidX(rect), CGRectGetMidY(rect));
t = CGAffineTransformConcat(transform, t);
t = CGAffineTransformTranslate(t, -CGRectGetMidX(rect), -CGRectGetMidY(rect));
TGPhotoCropRectangle r = [self rectangleFromCGRect:rect];
return (TGPhotoCropRectangle)
{
.tl = CGPointApplyAffineTransform(r.tl, t),
.tr = CGPointApplyAffineTransform(r.tr, t),
.br = CGPointApplyAffineTransform(r.br, t),
.bl = CGPointApplyAffineTransform(r.bl, t)
};
}
- (TGPhotoCropRectangle)applyTransform:(CGAffineTransform)t toRectangle:(TGPhotoCropRectangle)r
{
return (TGPhotoCropRectangle)
{
.tl = CGPointApplyAffineTransform(r.tl, t),
.tr = CGPointApplyAffineTransform(r.tr, t),
.br = CGPointApplyAffineTransform(r.br, t),
.bl = CGPointApplyAffineTransform(r.bl, t)
};
}
- (bool)isTracking
{
return _animating || _isTracking;
}
#pragma mark - Gesture Recognizing
- (void)handlePress:(UILongPressGestureRecognizer *)gestureRecognizer
{
switch (gestureRecognizer.state)
{
case UIGestureRecognizerStateBegan:
{
if (!_beganInteraction && self.didBeginChanging != nil)
self.didBeginChanging();
_isTracking = true;
_endedInteraction = false;
_beganInteraction = true;
_fitted = false;
[self _stopAllContentAnimations];
}
break;
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
{
_beganInteraction = false;
if (!_endedInteraction && self.didEndChanging != nil)
self.didEndChanging();
if (!_fitted)
{
[self fitContentInsideBoundsAllowScale:true animated:true completion:nil];
_fitted = true;
}
_isTracking = false;
_endedInteraction = true;
}
break;
default:
break;
}
}
- (void)handlePan:(UIPanGestureRecognizer *)gestureRecognizer
{
CGPoint translation = [gestureRecognizer translationInView:_wrapperView];
//CGPoint velocity = [gestureRecognizer velocityInView:self];
switch (gestureRecognizer.state)
{
case UIGestureRecognizerStateBegan:
{
if (!_beganInteraction && self.didBeginChanging != nil)
self.didBeginChanging();
_isTracking = true;
_endedInteraction = false;
_beganInteraction = true;
_fitted = false;
[self _stopAllContentAnimations];
}
break;
case UIGestureRecognizerStateChanged:
{
_wrapperView.layer.transform = CATransform3DTranslate(_wrapperView.layer.transform, translation.x, translation.y, 0);
_snapshotView.superview.layer.transform = _wrapperView.layer.transform;
[gestureRecognizer setTranslation:CGPointZero inView:self];
}
break;
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
{
_beganInteraction = false;
if (!_endedInteraction && self.didBeginChanging != nil)
self.didEndChanging();
if (!_fitted)
{
[self fitContentInsideBoundsAllowScale:true animated:true completion:nil];
_fitted = true;
}
_isTracking = false;
_endedInteraction = true;
}
break;
default:
break;
}
}
- (void)handlePinch:(UIPinchGestureRecognizer *)gestureRecognizer
{
CGFloat scale = gestureRecognizer.scale;
CGFloat contentScale = self.contentScale;
switch (gestureRecognizer.state)
{
case UIGestureRecognizerStateBegan:
{
_pinchCenter = _touchCenter;
_pinchStartScale = contentScale;
if (!_beganInteraction && self.didBeginChanging != nil)
self.didBeginChanging();
_isTracking = true;
_endedInteraction = false;
_beganInteraction = true;
_fitted = false;
[self _stopAllContentAnimations];
}
case UIGestureRecognizerStateChanged:
{
CGPoint delta = CGPointMake(_pinchCenter.x - _wrapperView.bounds.size.width / 2.0f, _pinchCenter.y - _wrapperView.bounds.size.height / 2.0f);
if (_pinchStartScale / self.minimumZoomScale * scale > self.maximumZoomScale)
scale = self.maximumZoomScale / _pinchStartScale * self.minimumZoomScale;
CGFloat size = _contentSize.width * _pinchStartScale;
CGFloat newSize = size * scale;
CGFloat constrainedSize = MAX(self.minimumZoomScale * _contentSize.width,
MIN(newSize, self.maximumZoomScale * _contentSize.width));
CGFloat sizeDimension = self.maximumZoomScale * _contentSize.width - self.minimumZoomScale * _contentSize.width;
CGFloat rubberBandedSize = TGRubberBandDistance(newSize - constrainedSize, sizeDimension);
CGFloat finalSize = MAX(self.minimumZoomScale * _contentSize.width * 0.25f, constrainedSize + rubberBandedSize);
CATransform3D transform = CATransform3DTranslate(_wrapperView.layer.transform, delta.x, delta.y, 0.0f);
CGFloat scale = finalSize / (_contentSize.width * contentScale);
transform = CATransform3DScale(transform, scale, scale, 1.0f);
transform = CATransform3DTranslate(transform, -delta.x, -delta.y, 0);
_wrapperView.layer.transform = transform;
_snapshotView.superview.layer.transform = transform;
}
break;
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
{
_beganInteraction = false;
if (!_endedInteraction && self.didEndChanging != nil)
self.didEndChanging();
if (!_fitted)
{
[self fitContentInsideBoundsAllowScale:true animated:true completion:nil];
_fitted = true;
}
_isTracking = false;
_endedInteraction = true;
}
break;
default:
break;
}
}
- (void)handleTouches:(NSSet *)touches
{
_touchCenter = CGPointZero;
if (touches.count < 2)
return;
[touches enumerateObjectsUsingBlock:^(UITouch *touch, __unused BOOL *stop)
{
CGPoint location = [touch locationInView:_wrapperView];
_touchCenter = CGPointMake(_touchCenter.x + location.x, _touchCenter.y + location.y);
}];
_touchCenter = CGPointMake(_touchCenter.x / touches.count, _touchCenter.y / touches.count);
}
- (void)touchesBegan:(NSSet *)__unused touches withEvent:(UIEvent *)event
{
[self handleTouches:event.allTouches];
}
- (void)touchesMoved:(NSSet *)__unused touches withEvent:(UIEvent *)event
{
[self handleTouches:event.allTouches];
}
- (void)touchesEnded:(NSSet *)__unused touches withEvent:(UIEvent *)event
{
[self handleTouches:event.allTouches];
}
- (void)touchesCancelled:(NSSet *)__unused touches withEvent:(UIEvent *)event
{
[self handleTouches:event.allTouches];
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)__unused gestureRecognizer
{
bool shouldBegin = true;
if (self.shouldBeginChanging)
shouldBegin = self.shouldBeginChanging();
return shouldBegin;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)__unused gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)__unused otherGestureRecognizer
{
return YES;
}
#pragma mark - Zoom
- (void)resetAndSetBounds:(bool)setBounds
{
if (CGSizeEqualToSize(_contentSize, CGSizeZero) || CGSizeEqualToSize(self.frame.size, CGSizeZero))
return;
if (setBounds)
{
_wrapperView.center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
_wrapperView.bounds = CGRectMake(0, 0, _contentSize.width, _contentSize.height);
}
CGFloat sourceAspect = _contentSize.height / _contentSize.width;
CGFloat cropAspect = self.frame.size.height / self.frame.size.width;
CGFloat scale = 1.0f;
if(sourceAspect > cropAspect)
scale = self.frame.size.width / _contentSize.width;
else
scale = self.frame.size.height / _contentSize.height;
_minimumZoomScale = scale;
_wrapperView.layer.transform = CATransform3DMakeScale(scale, scale, 1);
_snapshotView.superview.layer.transform = _wrapperView.layer.transform;
}
- (void)reset
{
[self resetAndSetBounds:true];
}
- (void)resetAnimatedWithFrame:(CGRect)frame completion:(void (^)(void))completion
{
CGRect bounds = CGRectMake(0, 0, frame.size.width, frame.size.height);
CGFloat sourceAspect = _contentSize.height / _contentSize.width;
CGFloat cropAspect = frame.size.height / frame.size.width;
CGFloat scale = 1.0f;
if(sourceAspect > cropAspect)
scale = frame.size.width / _contentSize.width;
else
scale = frame.size.height / _contentSize.height;
POPSpringAnimation *centerAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewCenter];
centerAnimation.fromValue = [NSValue valueWithCGPoint:_wrapperView.center];
centerAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds))];
centerAnimation.springSpeed = 7;
centerAnimation.springBounciness = 1;
POPSpringAnimation *translationAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerTranslationXY];
translationAnimation.fromValue = [_wrapperView.layer valueForKeyPath:@"transform.translation"];
translationAnimation.toValue = [NSValue valueWithCGPoint:CGPointZero];
translationAnimation.springSpeed = 7;
translationAnimation.springBounciness = 1;
CGFloat fromScale = self.contentScale;
POPSpringAnimation *scaleAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
scaleAnimation.fromValue = [NSValue valueWithCGSize:CGSizeMake(fromScale, fromScale)];
scaleAnimation.toValue = [NSValue valueWithCGSize:CGSizeMake(scale, scale)];
scaleAnimation.springSpeed = 7;
scaleAnimation.springBounciness = 1;
POPSpringAnimation *rotationAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerRotation];
rotationAnimation.fromValue = @(self.contentRotation);
rotationAnimation.toValue = @0;
rotationAnimation.springSpeed = 7;
rotationAnimation.springBounciness = 1;
_wrapperView.userInteractionEnabled = false;
[TGPhotoEditorAnimation performBlock:^(__unused bool allFinished)
{
_wrapperView.userInteractionEnabled = true;
if (completion != nil)
completion();
} whenCompletedAllAnimations:@[ centerAnimation, translationAnimation, scaleAnimation, rotationAnimation ]];
[_wrapperView pop_addAnimation:centerAnimation forKey:@"position"];
[_wrapperView.layer pop_addAnimation:translationAnimation forKey:@"translation"];
[_wrapperView.layer pop_addAnimation:scaleAnimation forKey:@"scale"];
[_wrapperView.layer pop_addAnimation:rotationAnimation forKey:@"rotation"];
}
- (void)zoomToRect:(CGRect)rect withFrame:(CGRect)frame animated:(bool)animated completion:(void (^)(void))completion
{
CGFloat contentRotation = self.contentRotation;
if (!animated)
[self resetAndSetBounds:true];
CGFloat sourceAspect = rect.size.height / rect.size.width;
CGFloat cropAspect = frame.size.height / frame.size.width;
CGFloat scale = 1.0f;
if(sourceAspect > cropAspect)
scale = frame.size.width / rect.size.width;
else
scale = frame.size.height / rect.size.height;
CGSize rotatedContentSize = TGRotatedContentSize(_contentSize, contentRotation);
CGRect bounds = CGRectMake(0, 0, frame.size.width, frame.size.height);
if (animated)
{
POPSpringAnimation *centerAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewCenter];
centerAnimation.fromValue = [NSValue valueWithCGPoint:_wrapperView.center];
centerAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds))];
centerAnimation.springSpeed = 7;
centerAnimation.springBounciness = 1;
POPSpringAnimation *translationAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerTranslationXY];
translationAnimation.fromValue = [_wrapperView.layer valueForKeyPath:@"transform.translation"];
translationAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake((rotatedContentSize.width / 2 - CGRectGetMidX(rect)) * scale, (rotatedContentSize.height / 2 - CGRectGetMidY(rect)) * scale)];
translationAnimation.springSpeed = 7;
translationAnimation.springBounciness = 1;
CGFloat fromScale = self.contentScale;
POPSpringAnimation *scaleAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
scaleAnimation.fromValue = [NSValue valueWithCGSize:CGSizeMake(fromScale, fromScale)];
scaleAnimation.toValue = [NSValue valueWithCGSize:CGSizeMake(scale, scale)];
scaleAnimation.springSpeed = 7;
scaleAnimation.springBounciness = 1;
_wrapperView.userInteractionEnabled = false;
[TGPhotoEditorAnimation performBlock:^(__unused bool allFinished)
{
_wrapperView.userInteractionEnabled = true;
if (completion != nil)
completion();
} whenCompletedAllAnimations:@[ centerAnimation, translationAnimation, scaleAnimation ]];
[_wrapperView pop_addAnimation:centerAnimation forKey:@"position"];
[_wrapperView.layer pop_addAnimation:translationAnimation forKey:@"translation"];
[_wrapperView.layer pop_addAnimation:scaleAnimation forKey:@"scale"];
}
else
{
_wrapperView.center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DScale(transform, scale, scale, 1.0f);
transform = CATransform3DTranslate(transform, (rotatedContentSize.width / 2 - CGRectGetMidX(rect)), (rotatedContentSize.height / 2 - CGRectGetMidY(rect)), 0);
transform = CATransform3DRotate(transform, contentRotation, 0.0f, 0.0f, 1.0f);
_wrapperView.layer.transform = transform;
_snapshotView.superview.layer.transform = _wrapperView.layer.transform;
_snapshotView.superview.center = _wrapperView.center;
if (completion != nil)
completion();
}
}
#pragma mark -
- (void)storeRotationStartValues
{
if (_rotationStartScale < FLT_EPSILON)
_rotationStartScale = self.contentScale;
}
- (void)resetRotationStartValues
{
_rotationStartScale = 0.0f;
}
#pragma mark - Misc
- (void)setPaintingImage:(UIImage *)paintingImage
{
_paintingImage = paintingImage;
}
- (UIView *)setSnapshotViewEnabled:(bool)enabled
{
if (enabled)
{
[_snapshotClipView removeFromSuperview];
[_snapshotView removeFromSuperview];
_snapshotClipView = [[UIView alloc] initWithFrame:self.bounds];
_snapshotClipView.clipsToBounds = true;
_snapshotClipView.userInteractionEnabled = false;
CGRect contentFrame = CGRectMake(0, 0, _contentSize.width, _contentSize.height);
UIView *snapshotWrapperView = [[UIView alloc] initWithFrame:contentFrame];
[_snapshotClipView addSubview:snapshotWrapperView];
_snapshotView = [[UIImageView alloc] initWithFrame:contentFrame];
_snapshotView.image = self.imageView.image;
_snapshotView.transform = CGAffineTransformMakeScale(_mirrored ? -1.0f : 1.0f, 1.0f);
[snapshotWrapperView addSubview:_snapshotView];
_paintingImageView = [[UIImageView alloc] initWithFrame:contentFrame];
_paintingImageView.image = _paintingImage;
[snapshotWrapperView addSubview:_paintingImageView];
snapshotWrapperView.center = _wrapperView.center;
snapshotWrapperView.layer.transform = _wrapperView.layer.transform;
}
else
{
[_snapshotView removeFromSuperview];
_snapshotView = nil;
[_snapshotClipView removeFromSuperview];
_snapshotClipView = nil;
}
return _snapshotClipView;
}
- (void)_stopAllContentAnimations
{
[_wrapperView.layer pop_removeAnimationForKey:@"translation"];
[_wrapperView.layer pop_removeAnimationForKey:@"scale"];
_animating = false;
}
@end