#import "TGPhotoCropScrollView.h" #import "LegacyComponentsInternal.h" #import #import typedef struct { CGPoint tl, tr, bl, br; } TGPhotoCropRectangle; @interface TGPhotoCropScrollView () { 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