From 20864a4f7c453ca785e7edfea255ebad7af773d2 Mon Sep 17 00:00:00 2001 From: Jan de Vries Date: Fri, 25 Oct 2024 12:23:36 +0200 Subject: [PATCH 1/5] Improved accuracy by removing all floors and ceils and adding pixel rounding where necessary. --- .../Categories/UIView+Pixels.h | 38 ++++++ .../Categories/UIView+Pixels.m | 44 ++++++ .../TOCropViewController.m | 3 +- .../TOCropViewController/Views/TOCropView.m | 126 +++++++++--------- .../project.pbxproj | 18 +++ 5 files changed, 165 insertions(+), 64 deletions(-) create mode 100644 Objective-C/TOCropViewController/Categories/UIView+Pixels.h create mode 100644 Objective-C/TOCropViewController/Categories/UIView+Pixels.m diff --git a/Objective-C/TOCropViewController/Categories/UIView+Pixels.h b/Objective-C/TOCropViewController/Categories/UIView+Pixels.h new file mode 100644 index 00000000..e26cb9c0 --- /dev/null +++ b/Objective-C/TOCropViewController/Categories/UIView+Pixels.h @@ -0,0 +1,38 @@ +// +// UIView+Pixels.h +// +// Copyright 2024 Jan de Vries. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIView(TOPixels) + +///Round point value to nearest physical pixel +- (CGFloat)roundToNearestPixel:(CGFloat)val; + +///Check if two CGFloats (points) round to the same number of physical pixels +- (BOOL)pixelCount:(CGFloat)val1 equals:(CGFloat)val2; + +@end + +NS_ASSUME_NONNULL_END + diff --git a/Objective-C/TOCropViewController/Categories/UIView+Pixels.m b/Objective-C/TOCropViewController/Categories/UIView+Pixels.m new file mode 100644 index 00000000..fac41886 --- /dev/null +++ b/Objective-C/TOCropViewController/Categories/UIView+Pixels.m @@ -0,0 +1,44 @@ +// +// UIView+Pixels.m +// +// Copyright 2024 Jan de Vries. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import "UIView+Pixels.h" + +@implementation UIView (TOPixels) + +- (CGFloat)roundToNearestPixel:(CGFloat)val { + CGFloat screenScale = 2.0f; + if (self.window != nil && self.window.screen != nil) { + screenScale = self.window.screen.scale; + } + return roundf(val * screenScale) / screenScale; +} + +- (BOOL)pixelCount:(CGFloat)val1 equals:(CGFloat)val2 +{ + if (self.window == nil || self.window.screen == nil) { + return val1 == val2; + } + CGFloat screenScale = self.window.screen.scale; + return roundf(val1*screenScale) == roundf(val2*screenScale); +} + +@end diff --git a/Objective-C/TOCropViewController/TOCropViewController.m b/Objective-C/TOCropViewController/TOCropViewController.m index a8fb131f..10b3c573 100755 --- a/Objective-C/TOCropViewController/TOCropViewController.m +++ b/Objective-C/TOCropViewController/TOCropViewController.m @@ -25,6 +25,7 @@ #import "TOCropViewControllerTransitioning.h" #import "TOActivityCroppedImageProvider.h" #import "UIImage+CropRotate.h" +#import "UIView+Pixels.h" #import "TOCroppedImageAttributes.h" static const CGFloat kTOCropViewControllerTitleTopPadding = 14.0f; @@ -358,7 +359,7 @@ - (CGRect)frameForTitleLabelWithSize:(CGSize)size verticalLayout:(BOOL)verticalL } // Work out horizontal position - frame.origin.x = ceilf((viewWidth - frame.size.width) * 0.5f); + frame.origin.x = [self.view roundToNearestPixel:(viewWidth - frame.size.width) * 0.5f]; if (!verticalLayout) { frame.origin.x += x; } // Work out vertical position diff --git a/Objective-C/TOCropViewController/Views/TOCropView.m b/Objective-C/TOCropViewController/Views/TOCropView.m index 8a03c976..8a35a329 100755 --- a/Objective-C/TOCropViewController/Views/TOCropView.m +++ b/Objective-C/TOCropViewController/Views/TOCropView.m @@ -23,6 +23,7 @@ #import "TOCropView.h" #import "TOCropOverlayView.h" #import "TOCropScrollView.h" +#import "UIView+Pixels.h" #define TOCROPVIEW_BACKGROUND_COLOR [UIColor colorWithWhite:0.12f alpha:1.0f] @@ -286,7 +287,7 @@ - (void)layoutInitialImage // Work out the size of the image to fit into the content bounds scale = MIN(CGRectGetWidth(bounds)/imageSize.width, CGRectGetHeight(bounds)/imageSize.height); - CGSize scaledImageSize = (CGSize){floorf(imageSize.width * scale), floorf(imageSize.height * scale)}; + CGSize scaledImageSize = (CGSize){imageSize.width * scale, imageSize.height * scale}; // If an aspect ratio was pre-applied to the crop view, use that to work out the minimum scale the image needs to be to fit CGSize cropBoxSize = CGSizeZero; @@ -300,7 +301,7 @@ - (void)layoutInitialImage } //Whether aspect ratio, or original, the final image size we'll base the rest of the calculations off - CGSize scaledSize = (CGSize){floorf(imageSize.width * scale), floorf(imageSize.height * scale)}; + CGSize scaledSize = (CGSize){imageSize.width * scale, imageSize.height * scale}; // Configure the scroll view self.scrollView.minimumZoomScale = scale; @@ -309,8 +310,8 @@ - (void)layoutInitialImage //Set the crop box to the size we calculated and align in the middle of the screen CGRect frame = CGRectZero; frame.size = self.hasAspectRatio ? cropBoxSize : scaledSize; - frame.origin.x = floorf(bounds.origin.x + floorf((CGRectGetWidth(bounds) - frame.size.width) * 0.5f)); - frame.origin.y = floorf(bounds.origin.y + floorf((CGRectGetHeight(bounds) - frame.size.height) * 0.5f)); + frame.origin.x = bounds.origin.x + (CGRectGetWidth(bounds) - frame.size.width) * 0.5f; + frame.origin.y = bounds.origin.y + (CGRectGetHeight(bounds) - frame.size.height) * 0.5f; self.cropBoxFrame = frame; //set the fully zoomed out state initially @@ -321,8 +322,8 @@ - (void)layoutInitialImage // is in the center of the cropbox if (frame.size.width < scaledSize.width - FLT_EPSILON || frame.size.height < scaledSize.height - FLT_EPSILON) { CGPoint offset = CGPointZero; - offset.x = -floorf(CGRectGetMidX(bounds) - (scaledSize.width * 0.5f)); - offset.y = -floorf(CGRectGetMidY(bounds) - (scaledSize.height * 0.5f)); + offset.x = -(CGRectGetMidX(bounds) - (scaledSize.width * 0.5f)); + offset.y = -(CGRectGetMidY(bounds) - (scaledSize.height * 0.5f)); self.scrollView.contentOffset = offset; } @@ -355,10 +356,10 @@ - (void)performRelayoutForRotation self.scrollView.zoomScale *= scale; //Work out the centered, upscaled version of the crop rectangle - cropFrame.size.width = floorf(cropFrame.size.width * scale); - cropFrame.size.height = floorf(cropFrame.size.height * scale); - cropFrame.origin.x = floorf(contentFrame.origin.x + ((contentFrame.size.width - cropFrame.size.width) * 0.5f)); - cropFrame.origin.y = floorf(contentFrame.origin.y + ((contentFrame.size.height - cropFrame.size.height) * 0.5f)); + cropFrame.size.width = cropFrame.size.width * scale; + cropFrame.size.height = cropFrame.size.height * scale; + cropFrame.origin.x = contentFrame.origin.x + ((contentFrame.size.width - cropFrame.size.width) * 0.5f); + cropFrame.origin.y = contentFrame.origin.y + ((contentFrame.size.height - cropFrame.size.height) * 0.5f); self.cropBoxFrame = cropFrame; [self captureStateForImageRotation]; @@ -380,8 +381,8 @@ - (void)performRelayoutForRotation translatedContentOffset.y = self.scrollView.contentSize.height * normalizedCenter.y; CGPoint offset = CGPointZero; - offset.x = floorf(translatedContentOffset.x - newMidPoint.x); - offset.y = floorf(translatedContentOffset.y - newMidPoint.y); + offset.x = translatedContentOffset.x - newMidPoint.x; + offset.y = translatedContentOffset.y - newMidPoint.y; //Make sure it doesn't overshoot the top left corner of the crop box offset.x = MAX(-self.scrollView.contentInset.left, offset.x); @@ -420,8 +421,8 @@ - (void)updateCropBoxFrameWithGesturePoint:(CGPoint)point point.y = MAX(contentFrame.origin.y - self.cropViewPadding, point.y); //The delta between where we first tapped, and where our finger is now - CGFloat xDelta = ceilf(point.x - self.panOriginPoint.x); - CGFloat yDelta = ceilf(point.y - self.panOriginPoint.y); + CGFloat xDelta = point.x - self.panOriginPoint.x; + CGFloat yDelta = point.y - self.panOriginPoint.y; //Current aspect ratio of the crop box in case we need to clamp it CGFloat aspectRatio = (originFrame.size.width / originFrame.size.height); @@ -523,8 +524,8 @@ - (void)updateCropBoxFrameWithGesturePoint:(CGPoint)point CGFloat scale = (distance.x + distance.y) * 0.5f; - frame.size.width = ceilf(CGRectGetWidth(originFrame) * scale); - frame.size.height = ceilf(CGRectGetHeight(originFrame) * scale); + frame.size.width = CGRectGetWidth(originFrame) * scale; + frame.size.height = CGRectGetHeight(originFrame) * scale; frame.origin.x = originFrame.origin.x + (CGRectGetWidth(originFrame) - frame.size.width); frame.origin.y = originFrame.origin.y + (CGRectGetHeight(originFrame) - frame.size.height); @@ -558,8 +559,8 @@ - (void)updateCropBoxFrameWithGesturePoint:(CGPoint)point CGFloat scale = (distance.x + distance.y) * 0.5f; - frame.size.width = ceilf(CGRectGetWidth(originFrame) * scale); - frame.size.height = ceilf(CGRectGetHeight(originFrame) * scale); + frame.size.width = CGRectGetWidth(originFrame) * scale; + frame.size.height = CGRectGetHeight(originFrame) * scale; frame.origin.y = originFrame.origin.y + (CGRectGetHeight(originFrame) - frame.size.height); aspectVertical = YES; @@ -587,8 +588,8 @@ - (void)updateCropBoxFrameWithGesturePoint:(CGPoint)point CGFloat scale = (distance.x + distance.y) * 0.5f; - frame.size.width = ceilf(CGRectGetWidth(originFrame) * scale); - frame.size.height = ceilf(CGRectGetHeight(originFrame) * scale); + frame.size.width = CGRectGetWidth(originFrame) * scale; + frame.size.height = CGRectGetHeight(originFrame) * scale; frame.origin.x = CGRectGetMaxX(originFrame) - frame.size.width; aspectVertical = YES; @@ -617,8 +618,8 @@ - (void)updateCropBoxFrameWithGesturePoint:(CGPoint)point CGFloat scale = (distance.x + distance.y) * 0.5f; - frame.size.width = ceilf(CGRectGetWidth(originFrame) * scale); - frame.size.height = ceilf(CGRectGetHeight(originFrame) * scale); + frame.size.width = CGRectGetWidth(originFrame) * scale; + frame.size.height = CGRectGetHeight(originFrame) * scale; aspectVertical = YES; aspectHorizontal = YES; @@ -774,21 +775,21 @@ - (void)updateToImageCropFrame:(CGRect)imageCropframe self.scrollView.zoomScale = self.scrollView.minimumZoomScale * scale; CGSize contentSize = self.scrollView.contentSize; - self.scrollView.contentSize = CGSizeMake(floorf(contentSize.width), floorf(contentSize.height)); + self.scrollView.contentSize = CGSizeMake(contentSize.width, contentSize.height); // Work out the size and offset of the upscaled crop box CGRect frame = CGRectZero; - frame.size = (CGSize){floorf(scaledCropSize.width * scale), floorf(scaledCropSize.height * scale)}; + frame.size = (CGSize){scaledCropSize.width * scale, scaledCropSize.height * scale}; //set the crop box CGRect cropBoxFrame = CGRectZero; cropBoxFrame.size = frame.size; - cropBoxFrame.origin.x = floorf(CGRectGetMidX(bounds) - (frame.size.width * 0.5f)); - cropBoxFrame.origin.y = floorf(CGRectGetMidY(bounds) - (frame.size.height * 0.5f)); + cropBoxFrame.origin.x = CGRectGetMidX(bounds) - (frame.size.width * 0.5f); + cropBoxFrame.origin.y = CGRectGetMidY(bounds) - (frame.size.height * 0.5f); self.cropBoxFrame = cropBoxFrame; - frame.origin.x = ceilf((scaledOffset.x * scale) - self.scrollView.contentInset.left); - frame.origin.y = ceilf((scaledOffset.y * scale) - self.scrollView.contentInset.top); + frame.origin.x = (scaledOffset.x * scale) - self.scrollView.contentInset.left; + frame.origin.y = (scaledOffset.y * scale) - self.scrollView.contentInset.top; self.scrollView.contentOffset = frame.origin; } @@ -980,24 +981,24 @@ - (void)setCropBoxFrame:(CGRect)cropBoxFrame //clamp the cropping region to the inset boundaries of the screen CGRect contentFrame = self.contentBounds; - CGFloat xOrigin = ceilf(contentFrame.origin.x); + CGFloat xOrigin = contentFrame.origin.x; CGFloat xDelta = cropBoxFrame.origin.x - xOrigin; - cropBoxFrame.origin.x = floorf(MAX(cropBoxFrame.origin.x, xOrigin)); + cropBoxFrame.origin.x = MAX(cropBoxFrame.origin.x, xOrigin); if (xDelta < -FLT_EPSILON) //If we clamp the x value, ensure we compensate for the subsequent delta generated in the width (Or else, the box will keep growing) cropBoxFrame.size.width += xDelta; - CGFloat yOrigin = ceilf(contentFrame.origin.y); + CGFloat yOrigin = contentFrame.origin.y; CGFloat yDelta = cropBoxFrame.origin.y - yOrigin; - cropBoxFrame.origin.y = floorf(MAX(cropBoxFrame.origin.y, yOrigin)); + cropBoxFrame.origin.y = MAX(cropBoxFrame.origin.y, yOrigin); if (yDelta < -FLT_EPSILON) cropBoxFrame.size.height += yDelta; //given the clamped X/Y values, make sure we can't extend the crop box beyond the edge of the screen in the current state CGFloat maxWidth = (contentFrame.size.width + contentFrame.origin.x) - cropBoxFrame.origin.x; - cropBoxFrame.size.width = floorf(MIN(cropBoxFrame.size.width, maxWidth)); + cropBoxFrame.size.width = MIN(cropBoxFrame.size.width, maxWidth); CGFloat maxHeight = (contentFrame.size.height + contentFrame.origin.y) - cropBoxFrame.origin.y; - cropBoxFrame.size.height = floorf(MIN(cropBoxFrame.size.height, maxHeight)); + cropBoxFrame.size.height = MIN(cropBoxFrame.size.height, maxHeight); //Make sure we can't make the crop box too small cropBoxFrame.size.width = MAX(cropBoxFrame.size.width, kTOCropViewMinimumBoxSize); @@ -1005,8 +1006,13 @@ - (void)setCropBoxFrame:(CGRect)cropBoxFrame _cropBoxFrame = cropBoxFrame; - self.foregroundContainerView.frame = _cropBoxFrame; //set the clipping view to match the new rect - self.gridOverlayView.frame = _cropBoxFrame; //set the new overlay view to match the same region + CGRect pixelRoundedCropBoxFrame = CGRectMake([self roundToNearestPixel:cropBoxFrame.origin.x], + [self roundToNearestPixel:cropBoxFrame.origin.y], + [self roundToNearestPixel:cropBoxFrame.size.width], + [self roundToNearestPixel:cropBoxFrame.size.height]); + + self.foregroundContainerView.frame = pixelRoundedCropBoxFrame; //set the clipping view to match the new rect + self.gridOverlayView.frame = pixelRoundedCropBoxFrame; //set the new overlay view to match the same region // If the mask layer is present, adjust its transform to fit the new container view size if (self.croppingStyle == TOCropViewCroppingStyleCircular) { @@ -1025,12 +1031,6 @@ - (void)setCropBoxFrame:(CGRect)cropBoxFrame CGFloat scale = MAX(cropBoxFrame.size.height/imageSize.height, cropBoxFrame.size.width/imageSize.width); self.scrollView.minimumZoomScale = scale; - //make sure content isn't smaller than the crop box - CGSize size = self.scrollView.contentSize; - size.width = floorf(size.width); - size.height = floorf(size.height); - self.scrollView.contentSize = size; - //IMPORTANT: Force the scroll view to update its content after changing the zoom scale self.scrollView.zoomScale = self.scrollView.zoomScale; @@ -1065,21 +1065,21 @@ - (CGRect)imageCropFrame CGRect frame = CGRectZero; // Calculate the normalized origin - frame.origin.x = floorf((floorf(contentOffset.x) + edgeInsets.left) * (imageSize.width / contentSize.width)); + frame.origin.x = (contentOffset.x + edgeInsets.left) * (imageSize.width / contentSize.width); frame.origin.x = MAX(0, frame.origin.x); - frame.origin.y = floorf((floorf(contentOffset.y) + edgeInsets.top) * (imageSize.height / contentSize.height)); + frame.origin.y = (contentOffset.y + edgeInsets.top) * (imageSize.height / contentSize.height); frame.origin.y = MAX(0, frame.origin.y); // Calculate the normalized width - frame.size.width = ceilf(cropBoxFrame.size.width * scale); + frame.size.width = cropBoxFrame.size.width * scale; frame.size.width = MIN(imageSize.width, frame.size.width); // Calculate normalized height - if (floor(cropBoxFrame.size.width) == floor(cropBoxFrame.size.height)) { + if ([self pixelCount:cropBoxFrame.size.width equals:cropBoxFrame.size.height]) { frame.size.height = frame.size.width; } else { - frame.size.height = ceilf(cropBoxFrame.size.height * scale); + frame.size.height = cropBoxFrame.size.height * scale; } frame.size.height = MIN(imageSize.height, frame.size.height); @@ -1295,10 +1295,10 @@ - (void)moveCroppedContentToCenterAnimated:(BOOL)animated CGPoint focusPoint = (CGPoint){CGRectGetMidX(cropFrame), CGRectGetMidY(cropFrame)}; CGPoint midPoint = (CGPoint){CGRectGetMidX(contentRect), CGRectGetMidY(contentRect)}; - cropFrame.size.width = ceilf(cropFrame.size.width * scale); - cropFrame.size.height = ceilf(cropFrame.size.height * scale); - cropFrame.origin.x = contentRect.origin.x + ceilf((contentRect.size.width - cropFrame.size.width) * 0.5f); - cropFrame.origin.y = contentRect.origin.y + ceilf((contentRect.size.height - cropFrame.size.height) * 0.5f); + cropFrame.size.width = cropFrame.size.width * scale; + cropFrame.size.height = cropFrame.size.height * scale; + cropFrame.origin.x = contentRect.origin.x + (contentRect.size.width - cropFrame.size.width) * 0.5f; + cropFrame.origin.y = contentRect.origin.y + (contentRect.size.height - cropFrame.size.height) * 0.5f; //Work out the point on the scroll content that the focusPoint is aiming at CGPoint contentTargetPoint = CGPointZero; @@ -1416,13 +1416,13 @@ - (void)setAspectRatio:(CGSize)aspectRatio animated:(BOOL)animated CGPoint offset = self.scrollView.contentOffset; BOOL cropBoxIsPortrait = NO; - if ((NSInteger)aspectRatio.width == 1 && (NSInteger)aspectRatio.height == 1) + if (aspectRatio.width == aspectRatio.height) cropBoxIsPortrait = self.image.size.width > self.image.size.height; else cropBoxIsPortrait = aspectRatio.width < aspectRatio.height; if (cropBoxIsPortrait) { - CGFloat newWidth = floorf(cropBoxFrame.size.height * (aspectRatio.width/aspectRatio.height)); + CGFloat newWidth = cropBoxFrame.size.height * (aspectRatio.width/aspectRatio.height); CGFloat delta = cropBoxFrame.size.width - newWidth; cropBoxFrame.size.width = newWidth; offset.x += (delta * 0.5f); @@ -1451,7 +1451,7 @@ - (void)setAspectRatio:(CGSize)aspectRatio animated:(BOOL)animated } } else { - CGFloat newHeight = floorf(cropBoxFrame.size.width * (aspectRatio.height/aspectRatio.width)); + CGFloat newHeight = cropBoxFrame.size.width * (aspectRatio.height/aspectRatio.width); CGFloat delta = cropBoxFrame.size.height - newHeight; cropBoxFrame.size.height = newHeight; offset.y += (delta * 0.5f); @@ -1571,15 +1571,15 @@ - (void)rotateImageNinetyDegreesAnimated:(BOOL)animated clockwise:(BOOL)clockwis self.scrollView.zoomScale = self.cropBoxLastEditedZoomScale; } else { - newCropFrame.size = (CGSize){floorf(self.cropBoxFrame.size.height * scale), floorf(self.cropBoxFrame.size.width * scale)}; + newCropFrame.size = (CGSize){self.cropBoxFrame.size.height * scale, self.cropBoxFrame.size.width * scale}; //Re-adjust the scrolling dimensions of the scroll view to match the new size self.scrollView.minimumZoomScale *= scale; self.scrollView.zoomScale *= scale; } - newCropFrame.origin.x = floorf(CGRectGetMidX(contentBounds) - (newCropFrame.size.width * 0.5f)); - newCropFrame.origin.y = floorf(CGRectGetMidY(contentBounds) - (newCropFrame.size.height * 0.5f)); + newCropFrame.origin.x = CGRectGetMidX(contentBounds) - (newCropFrame.size.width * 0.5f); + newCropFrame.origin.y = CGRectGetMidY(contentBounds) - (newCropFrame.size.height * 0.5f); //If we're animated, generate a snapshot view that we'll animate in place of the real view UIView *snapshotView = nil; @@ -1625,8 +1625,8 @@ - (void)rotateImageNinetyDegreesAnimated:(BOOL)animated clockwise:(BOOL)clockwis //reapply the translated scroll offset to the scroll view CGPoint midPoint = {CGRectGetMidX(newCropFrame), CGRectGetMidY(newCropFrame)}; CGPoint offset = CGPointZero; - offset.x = floorf(-midPoint.x + cropTargetPoint.x); - offset.y = floorf(-midPoint.y + cropTargetPoint.y); + offset.x = -midPoint.x + cropTargetPoint.x; + offset.y = -midPoint.y + cropTargetPoint.y; offset.x = MAX(-self.scrollView.contentInset.left, offset.x); offset.y = MAX(-self.scrollView.contentInset.top, offset.y); offset.x = MIN(self.scrollView.contentSize.width - (newCropFrame.size.width - self.scrollView.contentInset.right), offset.x); @@ -1708,13 +1708,13 @@ - (void)checkForCanReset else if (self.scrollView.zoomScale > self.scrollView.minimumZoomScale + FLT_EPSILON) { //image has been zoomed in canReset = YES; } - else if ((NSInteger)floorf(self.cropBoxFrame.size.width) != (NSInteger)floorf(self.originalCropBoxSize.width) || - (NSInteger)floorf(self.cropBoxFrame.size.height) != (NSInteger)floorf(self.originalCropBoxSize.height)) + else if (![self pixelCount:self.cropBoxFrame.size.width equals:self.originalCropBoxSize.width] || + ![self pixelCount:self.cropBoxFrame.size.height equals:self.originalCropBoxSize.height]) { //crop box has been changed canReset = YES; } - else if ((NSInteger)floorf(self.scrollView.contentOffset.x) != (NSInteger)floorf(self.originalContentOffset.x) || - (NSInteger)floorf(self.scrollView.contentOffset.y) != (NSInteger)floorf(self.originalContentOffset.y)) + else if (![self pixelCount:self.scrollView.contentOffset.x equals:self.originalContentOffset.x] || + ![self pixelCount:self.scrollView.contentOffset.y equals:self.originalContentOffset.y]) { canReset = YES; } diff --git a/TOCropViewControllerExample.xcodeproj/project.pbxproj b/TOCropViewControllerExample.xcodeproj/project.pbxproj index 824b1d93..2512d935 100644 --- a/TOCropViewControllerExample.xcodeproj/project.pbxproj +++ b/TOCropViewControllerExample.xcodeproj/project.pbxproj @@ -98,6 +98,13 @@ 2F5062F31F53E31F00AA9F14 /* TOCropViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 22DB4D831B234CFA008B8466 /* TOCropViewController.m */; }; 2F5062F41F53E32800AA9F14 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 223424751ABBC0CD00BBC2B1 /* ViewController.m */; }; 2F5062F51F53E32D00AA9F14 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 223424771ABBC0CD00BBC2B1 /* Main.storyboard */; }; + 94189B982CC92BAA000C5263 /* UIView+Pixels.h in Headers */ = {isa = PBXBuildFile; fileRef = 94189B962CC92BAA000C5263 /* UIView+Pixels.h */; }; + 94189B992CC92BAA000C5263 /* UIView+Pixels.m in Sources */ = {isa = PBXBuildFile; fileRef = 94189B972CC92BAA000C5263 /* UIView+Pixels.m */; }; + 94189B9A2CC92BAA000C5263 /* UIView+Pixels.m in Sources */ = {isa = PBXBuildFile; fileRef = 94189B972CC92BAA000C5263 /* UIView+Pixels.m */; }; + 94189B9B2CC92BAA000C5263 /* UIView+Pixels.m in Sources */ = {isa = PBXBuildFile; fileRef = 94189B972CC92BAA000C5263 /* UIView+Pixels.m */; }; + 94189B9C2CC92BAA000C5263 /* UIView+Pixels.h in Headers */ = {isa = PBXBuildFile; fileRef = 94189B962CC92BAA000C5263 /* UIView+Pixels.h */; }; + 94189B9D2CC92BAA000C5263 /* UIView+Pixels.m in Sources */ = {isa = PBXBuildFile; fileRef = 94189B972CC92BAA000C5263 /* UIView+Pixels.m */; }; + 94189B9E2CC92BAA000C5263 /* UIView+Pixels.m in Sources */ = {isa = PBXBuildFile; fileRef = 94189B972CC92BAA000C5263 /* UIView+Pixels.m */; }; FE3E0E3A21098448004DAE93 /* TOCropViewConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 220C8E9F21062DD300A9B25D /* TOCropViewConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ @@ -217,6 +224,8 @@ 6AE313511C47DAA200894C5B /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/TOCropViewControllerLocalizable.strings; sourceTree = ""; }; 72B9DCDC25763340006E160C /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/TOCropViewControllerLocalizable.strings; sourceTree = ""; }; 88BBE90E22C206340073D22A /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/TOCropViewControllerLocalizable.strings; sourceTree = ""; }; + 94189B962CC92BAA000C5263 /* UIView+Pixels.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+Pixels.h"; sourceTree = ""; }; + 94189B972CC92BAA000C5263 /* UIView+Pixels.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+Pixels.m"; sourceTree = ""; }; A93782CF214A55F900CAE7EE /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/TOCropViewControllerLocalizable.strings; sourceTree = ""; }; A93782D3214A81A200CAE7EE /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/TOCropViewControllerLocalizable.strings; sourceTree = ""; }; CDC8C5751C667B9100BB86A4 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Main.strings; sourceTree = ""; }; @@ -335,6 +344,8 @@ children = ( 220C8EAB2106344D00A9B25D /* UIImage+CropRotate.h */, 220C8EAA2106344D00A9B25D /* UIImage+CropRotate.m */, + 94189B962CC92BAA000C5263 /* UIView+Pixels.h */, + 94189B972CC92BAA000C5263 /* UIView+Pixels.m */, ); path = Categories; sourceTree = ""; @@ -501,6 +512,7 @@ 144B8CD31D22CD650085D774 /* TOCroppedImageAttributes.h in Headers */, 144B8CD61D22CD650085D774 /* TOCropScrollView.h in Headers */, 144B8CD51D22CD650085D774 /* TOCropOverlayView.h in Headers */, + 94189B9C2CC92BAA000C5263 /* UIView+Pixels.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -510,6 +522,7 @@ files = ( 04262DA520F6FD1000024177 /* CropViewController.h in Headers */, 04262DA220F6FC4600024177 /* TOCropToolbar.h in Headers */, + 94189B982CC92BAA000C5263 /* UIView+Pixels.h in Headers */, 04262DA320F6FC4600024177 /* TOCropView.h in Headers */, 04262DA420F6FC4600024177 /* TOCropViewController.h in Headers */, 220C8EA82106341600A9B25D /* TOCropViewConstants.h in Headers */, @@ -799,6 +812,7 @@ 144B8CE01D22CD730085D774 /* TOCropToolbar.m in Sources */, 144B8CE11D22CD730085D774 /* TOCropView.m in Sources */, 144B8CE21D22CD730085D774 /* TOCropViewController.m in Sources */, + 94189B9D2CC92BAA000C5263 /* UIView+Pixels.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -825,6 +839,7 @@ 22DB4D9E1B234D4F008B8466 /* TOActivityCroppedImageProvider.m in Sources */, 22DB4D961B234D07008B8466 /* TOCropViewControllerTransitioning.m in Sources */, 22DB4D991B234D07008B8466 /* TOCropScrollView.m in Sources */, + 94189B9B2CC92BAA000C5263 /* UIView+Pixels.m in Sources */, 223DCEB61FBAA85D00F99209 /* TOCropViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -844,6 +859,7 @@ 22B68F9D1FFB380700601B1A /* TOCropViewController.m in Sources */, 2238CF251FC0269C0081B957 /* ViewController.swift in Sources */, 2238CF231FC0269C0081B957 /* AppDelegate.swift in Sources */, + 94189B9E2CC92BAA000C5263 /* UIView+Pixels.m in Sources */, 2238CF361FC029880081B957 /* CropViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -859,6 +875,7 @@ 22B68FA31FFB3C0800601B1A /* TOCropOverlayView.m in Sources */, 22B68FA41FFB3C0800601B1A /* TOCropScrollView.m in Sources */, 22B68FA51FFB3C0800601B1A /* TOCropToolbar.m in Sources */, + 94189B992CC92BAA000C5263 /* UIView+Pixels.m in Sources */, 22B68FA61FFB3C0800601B1A /* TOCropView.m in Sources */, 22B68FA71FFB3C0800601B1A /* TOCropViewController.m in Sources */, 22DEA39F1FC1293A000FA1CB /* CropViewController.swift in Sources */, @@ -872,6 +889,7 @@ 2236AB4A1F595960005C5098 /* ShareViewController.m in Sources */, 2F5062EC1F53E31F00AA9F14 /* TOActivityCroppedImageProvider.m in Sources */, 2F5062EF1F53E31F00AA9F14 /* TOCropOverlayView.m in Sources */, + 94189B9A2CC92BAA000C5263 /* UIView+Pixels.m in Sources */, 2F5062F21F53E31F00AA9F14 /* TOCropView.m in Sources */, 2F5062F41F53E32800AA9F14 /* ViewController.m in Sources */, 2F5062F31F53E31F00AA9F14 /* TOCropViewController.m in Sources */, From 0ccdba1679f75fe48a430fc91b5fd38e94951130 Mon Sep 17 00:00:00 2001 From: Jan de Vries Date: Fri, 25 Oct 2024 20:01:19 +0200 Subject: [PATCH 2/5] Fixed small issue that a plain rotate would sometimes result in an image with a thin black edge. --- Objective-C/TOCropViewController/Views/TOCropView.m | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Objective-C/TOCropViewController/Views/TOCropView.m b/Objective-C/TOCropViewController/Views/TOCropView.m index 8a35a329..fc802adf 100755 --- a/Objective-C/TOCropViewController/Views/TOCropView.m +++ b/Objective-C/TOCropViewController/Views/TOCropView.m @@ -1603,11 +1603,6 @@ - (void)rotateImageNinetyDegreesAnimated:(BOOL)animated clockwise:(BOOL)clockwis //Flip the content size of the scroll view to match the rotated bounds self.scrollView.contentSize = self.backgroundContainerView.frame.size; - //assign the new crop box frame and re-adjust the content to fill it - self.cropBoxFrame = newCropFrame; - [self moveCroppedContentToCenterAnimated:NO]; - newCropFrame = self.cropBoxFrame; - //work out how to line up out point of interest into the middle of the crop box cropTargetPoint.x *= scale; cropTargetPoint.y *= scale; @@ -1639,6 +1634,10 @@ - (void)rotateImageNinetyDegreesAnimated:(BOOL)animated clockwise:(BOOL)clockwis } self.scrollView.contentOffset = offset; + //assign the new crop box frame and re-adjust the content to fill it + self.cropBoxFrame = newCropFrame; + [self moveCroppedContentToCenterAnimated:NO]; + //If we're animated, play an animation of the snapshot view rotating, //then fade it out over the live content if (animated) { From 7fb9041ec908344546ad6058c4f73bfe0aef083c Mon Sep 17 00:00:00 2001 From: Jan de Vries Date: Sun, 27 Oct 2024 00:46:55 +0200 Subject: [PATCH 3/5] All f numbers, roundf() and epsilons have been replaced with double variants, because CGFloat and NSTimeInterval are actually doubles. --- .../Categories/UIImage+CropRotate.m | 2 +- .../Categories/UIView+Pixels.m | 6 +- .../TOCropViewControllerTransitioning.m | 12 +- .../TOCropViewController.m | 58 ++--- .../Views/TOCropOverlayView.m | 38 ++-- .../Views/TOCropToolbar.m | 44 ++-- .../TOCropViewController/Views/TOCropView.m | 214 +++++++++--------- .../ViewController.m | 10 +- .../TOCropViewControllerTests.m | 2 +- 9 files changed, 194 insertions(+), 192 deletions(-) diff --git a/Objective-C/TOCropViewController/Categories/UIImage+CropRotate.m b/Objective-C/TOCropViewController/Categories/UIImage+CropRotate.m index b2cad97d..8b2f4975 100644 --- a/Objective-C/TOCropViewController/Categories/UIImage+CropRotate.m +++ b/Objective-C/TOCropViewController/Categories/UIImage+CropRotate.m @@ -53,7 +53,7 @@ - (UIImage *)croppedImageWithFrame:(CGRect)frame angle:(NSInteger)angle circular // If an angle was supplied, rotate the entire canvas + coordinate space to match if (angle != 0) { // Rotation in radians - CGFloat rotation = angle * (M_PI/180.0f); + CGFloat rotation = angle * (M_PI/180.0); // Work out the new bounding size of the canvas after rotation CGRect imageBounds = (CGRect){CGPointZero, self.size}; diff --git a/Objective-C/TOCropViewController/Categories/UIView+Pixels.m b/Objective-C/TOCropViewController/Categories/UIView+Pixels.m index fac41886..c941de7f 100644 --- a/Objective-C/TOCropViewController/Categories/UIView+Pixels.m +++ b/Objective-C/TOCropViewController/Categories/UIView+Pixels.m @@ -25,11 +25,11 @@ @implementation UIView (TOPixels) - (CGFloat)roundToNearestPixel:(CGFloat)val { - CGFloat screenScale = 2.0f; + CGFloat screenScale = 2.0; if (self.window != nil && self.window.screen != nil) { screenScale = self.window.screen.scale; } - return roundf(val * screenScale) / screenScale; + return round(val * screenScale) / screenScale; } - (BOOL)pixelCount:(CGFloat)val1 equals:(CGFloat)val2 @@ -38,7 +38,7 @@ - (BOOL)pixelCount:(CGFloat)val1 equals:(CGFloat)val2 return val1 == val2; } CGFloat screenScale = self.window.screen.scale; - return roundf(val1*screenScale) == roundf(val2*screenScale); + return round(val1*screenScale) == round(val2*screenScale); } @end diff --git a/Objective-C/TOCropViewController/Models/TOCropViewControllerTransitioning.m b/Objective-C/TOCropViewController/Models/TOCropViewControllerTransitioning.m index 3f79f023..732a2184 100644 --- a/Objective-C/TOCropViewController/Models/TOCropViewControllerTransitioning.m +++ b/Objective-C/TOCropViewController/Models/TOCropViewControllerTransitioning.m @@ -27,7 +27,7 @@ @implementation TOCropViewControllerTransitioning - (NSTimeInterval)transitionDuration:(id )transitionContext { - return 0.45f; + return 0.45; } - (void)animateTransition:(id )transitionContext @@ -86,13 +86,13 @@ - (void)animateTransition:(id )transitionC } } - cropViewController.view.alpha = (self.isDismissing ? 1.0f : 0.0f); + cropViewController.view.alpha = (self.isDismissing ? 1.0 : 0.0); if (imageView) { - [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0f usingSpringWithDamping:1.0f initialSpringVelocity:0.7f options:0 animations:^{ + [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 usingSpringWithDamping:1.0 initialSpringVelocity:0.7 options:0 animations:^{ imageView.frame = self.toFrame; } completion:^(BOOL complete) { - [UIView animateWithDuration:0.25f animations:^{ - imageView.alpha = 0.0f; + [UIView animateWithDuration:0.25 animations:^{ + imageView.alpha = 0.0; }completion:^(BOOL complete) { [imageView removeFromSuperview]; }]; @@ -100,7 +100,7 @@ - (void)animateTransition:(id )transitionC } [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ - cropViewController.view.alpha = (self.isDismissing ? 0.0f : 1.0f); + cropViewController.view.alpha = (self.isDismissing ? 0.0 : 1.0); } completion:^(BOOL complete) { [self reset]; [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; diff --git a/Objective-C/TOCropViewController/TOCropViewController.m b/Objective-C/TOCropViewController/TOCropViewController.m index 10b3c573..df3665a2 100755 --- a/Objective-C/TOCropViewController/TOCropViewController.m +++ b/Objective-C/TOCropViewController/TOCropViewController.m @@ -28,8 +28,8 @@ #import "UIView+Pixels.h" #import "TOCroppedImageAttributes.h" -static const CGFloat kTOCropViewControllerTitleTopPadding = 14.0f; -static const CGFloat kTOCropViewControllerToolbarHeight = 44.0f; +static const CGFloat kTOCropViewControllerTitleTopPadding = 14.0; +static const CGFloat kTOCropViewControllerToolbarHeight = 44.0; @interface TOCropViewController () @@ -166,7 +166,7 @@ - (void)viewWillAppear:(BOOL)animated [self.cropView setBackgroundImageViewHidden:YES animated:NO]; // The title label will fade - self.titleLabel.alpha = animated ? 0.0f : 1.0f; + self.titleLabel.alpha = animated ? 0.0 : 1.0; } // If an initial aspect ratio was set before presentation, set it now once the rest of @@ -192,11 +192,11 @@ - (void)viewDidAppear:(BOOL)animated #if TARGET_OS_IOS [self setNeedsStatusBarAppearanceUpdate]; #endif - self.titleLabel.alpha = 1.0f; + self.titleLabel.alpha = 1.0; }; if (animated) { - [UIView animateWithDuration:0.3f animations:updateContentBlock]; + [UIView animateWithDuration:0.3 animations:updateContentBlock]; } else { updateContentBlock(); @@ -220,7 +220,7 @@ - (void)viewWillDisappear:(BOOL)animated // Set the transition flag again so we can defer the status bar self.inTransition = YES; #if TARGET_OS_IOS - [UIView animateWithDuration:0.5f animations:^{ [self setNeedsStatusBarAppearanceUpdate]; }]; + [UIView animateWithDuration:0.5 animations:^{ [self setNeedsStatusBarAppearanceUpdate]; }]; #endif // Restore the navigation controller to its state before we were presented @@ -283,12 +283,12 @@ - (CGRect)frameForToolbarWithVerticalLayout:(BOOL)verticalLayout CGRect frame = CGRectZero; if (!verticalLayout) { // In landscape laying out toolbar to the left frame.origin.x = insets.left; - frame.origin.y = 0.0f; + frame.origin.y = 0.0; frame.size.width = kTOCropViewControllerToolbarHeight; frame.size.height = CGRectGetHeight(self.view.frame); } else { - frame.origin.x = 0.0f; + frame.origin.x = 0.0; frame.size.width = CGRectGetWidth(self.view.bounds); frame.size.height = kTOCropViewControllerToolbarHeight; @@ -346,7 +346,7 @@ - (CGRect)frameForTitleLabelWithSize:(CGSize)size verticalLayout:(BOOL)verticalL { CGRect frame = (CGRect){CGPointZero, size}; CGFloat viewWidth = self.view.bounds.size.width; - CGFloat x = 0.0f; // Additional X offset in landscape mode + CGFloat x = 0.0; // Additional X offset in landscape mode // Adjust for landscape layout if (!verticalLayout) { @@ -359,7 +359,7 @@ - (CGRect)frameForTitleLabelWithSize:(CGSize)size verticalLayout:(BOOL)verticalL } // Work out horizontal position - frame.origin.x = [self.view roundToNearestPixel:(viewWidth - frame.size.width) * 0.5f]; + frame.origin.x = [self.view roundToNearestPixel:(viewWidth - frame.size.width) * 0.5]; if (!verticalLayout) { frame.origin.x += x; } // Work out vertical position @@ -381,14 +381,14 @@ - (void)adjustCropViewInsets if (!self.titleLabel.text.length) { if (self.verticalLayout) { if (self.toolbarPosition == TOCropViewControllerToolbarPositionTop) { - self.cropView.cropRegionInsets = UIEdgeInsetsMake(0.0f, 0.0f, insets.bottom, 0.0f); + self.cropView.cropRegionInsets = UIEdgeInsetsMake(0.0, 0.0, insets.bottom, 0.0); } else { // Add padding to the top otherwise - self.cropView.cropRegionInsets = UIEdgeInsetsMake(insets.top, 0.0f, 0.0, 0.0f); + self.cropView.cropRegionInsets = UIEdgeInsetsMake(insets.top, 0.0, 0.0, 0.0); } } else { - self.cropView.cropRegionInsets = UIEdgeInsetsMake(0.0f, 0.0f, insets.bottom, 0.0f); + self.cropView.cropRegionInsets = UIEdgeInsetsMake(0.0, 0.0, insets.bottom, 0.0); } return; @@ -495,7 +495,7 @@ - (void)_willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOri self.toolbar.frame = frame; [self.toolbar layoutIfNeeded]; - self.toolbar.alpha = 0.0f; + self.toolbar.alpha = 0.0; [self.cropView prepareforRotation]; self.cropView.frame = [self frameForCropViewWithVerticalLayout:!UIInterfaceOrientationIsPortrait(toInterfaceOrientation)]; @@ -515,7 +515,7 @@ - (void)_willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInt // On iOS 11, since these layout calls are done multiple times, if we don't aggregate from the // current state, the animation breaks. [UIView animateWithDuration:duration - delay:0.0f + delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations: ^{ @@ -524,8 +524,8 @@ - (void)_willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInt [self.cropView performRelayoutForRotation]; } completion:nil]; - self.toolbarSnapshotView.alpha = 0.0f; - self.toolbar.alpha = 1.0f; + self.toolbarSnapshotView.alpha = 0.0; + self.toolbar.alpha = 1.0; } - (void)_didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation @@ -652,25 +652,25 @@ - (void)setAspectRatioPreset:(TOCropViewControllerAspectRatioPreset)aspectRatioP aspectRatio = CGSizeZero; break; case TOCropViewControllerAspectRatioPresetSquare: - aspectRatio = CGSizeMake(1.0f, 1.0f); + aspectRatio = CGSizeMake(1.0, 1.0); break; case TOCropViewControllerAspectRatioPreset3x2: - aspectRatio = CGSizeMake(3.0f, 2.0f); + aspectRatio = CGSizeMake(3.0, 2.0); break; case TOCropViewControllerAspectRatioPreset5x3: - aspectRatio = CGSizeMake(5.0f, 3.0f); + aspectRatio = CGSizeMake(5.0, 3.0); break; case TOCropViewControllerAspectRatioPreset4x3: - aspectRatio = CGSizeMake(4.0f, 3.0f); + aspectRatio = CGSizeMake(4.0, 3.0); break; case TOCropViewControllerAspectRatioPreset5x4: - aspectRatio = CGSizeMake(5.0f, 4.0f); + aspectRatio = CGSizeMake(5.0, 4.0); break; case TOCropViewControllerAspectRatioPreset7x5: - aspectRatio = CGSizeMake(7.0f, 5.0f); + aspectRatio = CGSizeMake(7.0, 5.0); break; case TOCropViewControllerAspectRatioPreset16x9: - aspectRatio = CGSizeMake(16.0f, 9.0f); + aspectRatio = CGSizeMake(16.0, 9.0); break; case TOCropViewControllerAspectRatioPresetCustom: aspectRatio = self.customAspectRatio; @@ -995,7 +995,7 @@ - (void)doneButtonTapped UIImage *image = [self.image croppedImageWithFrame:cropFrame angle:angle circularClip:YES]; //Dispatch on the next run-loop so the animation isn't interuppted by the crop operation - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.03f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.03 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if (isCircularImageDelegateAvailable) { [self.delegate cropViewController:self didCropToCircularImage:image withRect:cropFrame angle:angle]; } @@ -1017,7 +1017,7 @@ - (void)doneButtonTapped } //Dispatch on the next run-loop so the animation isn't interuppted by the crop operation - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.03f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.03 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if (isDidCropToImageDelegateAvailable) { [self.delegate cropViewController:self didCropToImage:image withRect:cropFrame angle:angle]; } @@ -1293,12 +1293,12 @@ - (BOOL)statusBarHidden - (CGFloat)statusBarHeight { - CGFloat statusBarHeight = 0.0f; + CGFloat statusBarHeight = 0.0; statusBarHeight = self.view.safeAreaInsets.top; // We do need to include the status bar height on devices // that have a physical hardware inset, like an iPhone X notch - BOOL hardwareRelatedInset = self.view.safeAreaInsets.bottom > FLT_EPSILON + BOOL hardwareRelatedInset = self.view.safeAreaInsets.bottom > DBL_EPSILON && UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone; // Always have insetting on Mac Catalyst @@ -1309,7 +1309,7 @@ - (CGFloat)statusBarHeight // Unless the status bar is visible, or we need to account // for a hardware notch, always treat the status bar height as zero if (self.statusBarHidden && !hardwareRelatedInset) { - statusBarHeight = 0.0f; + statusBarHeight = 0.0; } return statusBarHeight; diff --git a/Objective-C/TOCropViewController/Views/TOCropOverlayView.m b/Objective-C/TOCropViewController/Views/TOCropOverlayView.m index 257432b0..34f59999 100644 --- a/Objective-C/TOCropViewController/Views/TOCropOverlayView.m +++ b/Objective-C/TOCropViewController/Views/TOCropOverlayView.m @@ -22,7 +22,7 @@ #import "TOCropOverlayView.h" -static const CGFloat kTOCropOverLayerCornerWidth = 20.0f; +static const CGFloat kTOCropOverLayerCornerWidth = 20.0; @interface TOCropOverlayView () @@ -93,10 +93,10 @@ - (void)layoutLines CGRect frame = CGRectZero; switch (i) { - case 0: frame = (CGRect){-1.0f,-1.0f,boundsSize.width+2.0f, 1.0f}; break; //top - case 1: frame = (CGRect){boundsSize.width,0.0f,1.0f,boundsSize.height}; break; //right - case 2: frame = (CGRect){-1.0f,boundsSize.height,boundsSize.width+2.0f,1.0f}; break; //bottom - case 3: frame = (CGRect){-1.0f,0,1.0f,boundsSize.height+1.0f}; break; //left + case 0: frame = (CGRect){-1.0, -1.0, boundsSize.width+2.0, 1.0}; break; //top + case 1: frame = (CGRect){boundsSize.width, 0.0, 1.0, boundsSize.height}; break; //right + case 2: frame = (CGRect){-1.0 ,boundsSize.height, boundsSize.width+2.0, 1.0}; break; //bottom + case 3: frame = (CGRect){-1.0, 0.0, 1.0, boundsSize.height+1.0}; break; //left } lineView.frame = frame; @@ -110,20 +110,20 @@ - (void)layoutLines CGRect verticalFrame = CGRectZero, horizontalFrame = CGRectZero; switch (i) { case 0: //top left - verticalFrame = (CGRect){-3.0f,-3.0f,3.0f,kTOCropOverLayerCornerWidth+3.0f}; - horizontalFrame = (CGRect){0,-3.0f,kTOCropOverLayerCornerWidth,3.0f}; + verticalFrame = (CGRect){-3.0, -3.0, 3.0, kTOCropOverLayerCornerWidth+3.0}; + horizontalFrame = (CGRect){0, -3.0, kTOCropOverLayerCornerWidth, 3.0}; break; case 1: //top right - verticalFrame = (CGRect){boundsSize.width,-3.0f,3.0f,kTOCropOverLayerCornerWidth+3.0f}; - horizontalFrame = (CGRect){boundsSize.width-kTOCropOverLayerCornerWidth,-3.0f,kTOCropOverLayerCornerWidth,3.0f}; + verticalFrame = (CGRect){boundsSize.width,-3.0, 3.0, kTOCropOverLayerCornerWidth+3.0}; + horizontalFrame = (CGRect){boundsSize.width-kTOCropOverLayerCornerWidth, -3.0, kTOCropOverLayerCornerWidth, 3.0}; break; case 2: //bottom right - verticalFrame = (CGRect){boundsSize.width,boundsSize.height-kTOCropOverLayerCornerWidth,3.0f,kTOCropOverLayerCornerWidth+3.0f}; - horizontalFrame = (CGRect){boundsSize.width-kTOCropOverLayerCornerWidth,boundsSize.height,kTOCropOverLayerCornerWidth,3.0f}; + verticalFrame = (CGRect){boundsSize.width,boundsSize.height-kTOCropOverLayerCornerWidth, 3.0, kTOCropOverLayerCornerWidth+3.0}; + horizontalFrame = (CGRect){boundsSize.width-kTOCropOverLayerCornerWidth, boundsSize.height, kTOCropOverLayerCornerWidth, 3.0}; break; case 3: //bottom left - verticalFrame = (CGRect){-3.0f,boundsSize.height-kTOCropOverLayerCornerWidth,3.0f,kTOCropOverLayerCornerWidth}; - horizontalFrame = (CGRect){-3.0f,boundsSize.height,kTOCropOverLayerCornerWidth+3.0f,3.0f}; + verticalFrame = (CGRect){-3.0, boundsSize.height-kTOCropOverLayerCornerWidth, 3.0, kTOCropOverLayerCornerWidth}; + horizontalFrame = (CGRect){-3.0, boundsSize.height, kTOCropOverLayerCornerWidth+3.0, 3.0}; break; } @@ -132,7 +132,7 @@ - (void)layoutLines } //grid lines - horizontal - CGFloat thickness = 1.0f / self.traitCollection.displayScale; + CGFloat thickness = 1.0 / self.traitCollection.displayScale; NSInteger numberOfLines = self.horizontalGridLines.count; CGFloat padding = (CGRectGetHeight(self.bounds) - (thickness*numberOfLines)) / (numberOfLines + 1); for (NSInteger i = 0; i < numberOfLines; i++) { @@ -163,22 +163,22 @@ - (void)setGridHidden:(BOOL)hidden animated:(BOOL)animated if (animated == NO) { for (UIView *lineView in self.horizontalGridLines) { - lineView.alpha = hidden ? 0.0f : 1.0f; + lineView.alpha = hidden ? 0.0 : 1.0; } for (UIView *lineView in self.verticalGridLines) { - lineView.alpha = hidden ? 0.0f : 1.0f; + lineView.alpha = hidden ? 0.0 : 1.0; } return; } - [UIView animateWithDuration:hidden?0.35f:0.2f animations:^{ + [UIView animateWithDuration:hidden? 0.35 : 0.2 animations:^{ for (UIView *lineView in self.horizontalGridLines) - lineView.alpha = hidden ? 0.0f : 1.0f; + lineView.alpha = hidden ? 0.0 : 1.0; for (UIView *lineView in self.verticalGridLines) - lineView.alpha = hidden ? 0.0f : 1.0f; + lineView.alpha = hidden ? 0.0 : 1.0; }]; } diff --git a/Objective-C/TOCropViewController/Views/TOCropToolbar.m b/Objective-C/TOCropViewController/Views/TOCropToolbar.m index cc5023d7..8782ac81 100644 --- a/Objective-C/TOCropViewController/Views/TOCropToolbar.m +++ b/Objective-C/TOCropViewController/Views/TOCropToolbar.m @@ -54,7 +54,7 @@ - (instancetype)initWithFrame:(CGRect)frame - (void)setup { self.backgroundView = [[UIView alloc] initWithFrame:self.bounds]; - self.backgroundView.backgroundColor = [UIColor colorWithWhite:0.12f alpha:1.0f]; + self.backgroundView.backgroundColor = [UIColor colorWithWhite:0.12 alpha:1.0]; [self addSubview:self.backgroundView]; // On iOS 9, we can use the new layout features to determine whether we're in an 'Arabic' style language mode @@ -75,11 +75,11 @@ - (void)setup { resourceBundle, nil) forState:UIControlStateNormal]; - [_doneTextButton setTitleColor:[UIColor colorWithRed:1.0f green:0.8f blue:0.0f alpha:1.0f] forState:UIControlStateNormal]; + [_doneTextButton setTitleColor:[UIColor colorWithRed:1.0 green:0.8 blue:0.0 alpha:1.0] forState:UIControlStateNormal]; if (@available(iOS 13.0, *)) { - [_doneTextButton.titleLabel setFont:[UIFont systemFontOfSize:17.0f weight:UIFontWeightMedium]]; + [_doneTextButton.titleLabel setFont:[UIFont systemFontOfSize:17.0 weight:UIFontWeightMedium]]; } else { - [_doneTextButton.titleLabel setFont:[UIFont systemFontOfSize:17.0f]]; + [_doneTextButton.titleLabel setFont:[UIFont systemFontOfSize:17.0]]; } [_doneTextButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside]; [_doneTextButton sizeToFit]; @@ -87,7 +87,7 @@ - (void)setup { _doneIconButton = [UIButton buttonWithType:UIButtonTypeSystem]; [_doneIconButton setImage:[TOCropToolbar doneImage] forState:UIControlStateNormal]; - [_doneIconButton setTintColor:[UIColor colorWithRed:1.0f green:0.8f blue:0.0f alpha:1.0f]]; + [_doneIconButton setTintColor:[UIColor colorWithRed:1.0 green:0.8 blue:0.0 alpha:1.0]]; [_doneIconButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:_doneIconButton]; @@ -102,7 +102,7 @@ - (void)setup { resourceBundle, nil) forState:UIControlStateNormal]; - [_cancelTextButton.titleLabel setFont:[UIFont systemFontOfSize:17.0f]]; + [_cancelTextButton.titleLabel setFont:[UIFont systemFontOfSize:17.0]]; [_cancelTextButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside]; [_cancelTextButton sizeToFit]; [self addSubview:_cancelTextButton]; @@ -178,12 +178,12 @@ - (void)layoutSubviews #endif if (verticalLayout == NO) { - CGFloat insetPadding = 10.0f; + CGFloat insetPadding = 10.0; // Work out the cancel button frame CGRect frame = CGRectZero; - frame.size.height = 44.0f; - frame.size.width = _showOnlyIcons ? 44.0f : MIN(self.frame.size.width / 3.0, self.cancelTextButton.frame.size.width); + frame.size.height = 44.0; + frame.size.width = _showOnlyIcons ? 44.0 : MIN(self.frame.size.width / 3.0, self.cancelTextButton.frame.size.width); //If normal layout, place on the left side, else place on the right if (self.reverseContentLayout == NO) { @@ -195,7 +195,7 @@ - (void)layoutSubviews (_showOnlyIcons ? self.cancelIconButton : self.cancelTextButton).frame = frame; // Work out the Done button frame - frame.size.width = _showOnlyIcons ? 44.0f : MIN(self.frame.size.width / 3.0, self.doneTextButton.frame.size.width); + frame.size.width = _showOnlyIcons ? 44.0 : MIN(self.frame.size.width / 3.0, self.doneTextButton.frame.size.width); if (self.reverseContentLayout == NO) { frame.origin.x = boundsSize.width - (frame.size.width + insetPadding); @@ -207,7 +207,7 @@ - (void)layoutSubviews // Work out the frame between the two buttons where we can layout our action buttons CGFloat x = self.reverseContentLayout ? CGRectGetMaxX((_showOnlyIcons ? self.doneIconButton : self.doneTextButton).frame) : CGRectGetMaxX((_showOnlyIcons ? self.cancelIconButton : self.cancelTextButton).frame); - CGFloat width = 0.0f; + CGFloat width = 0.0; if (self.reverseContentLayout == NO) { width = CGRectGetMinX((_showOnlyIcons ? self.doneIconButton : self.doneTextButton).frame) - CGRectGetMaxX((_showOnlyIcons ? self.cancelIconButton : self.cancelTextButton).frame); @@ -216,13 +216,13 @@ - (void)layoutSubviews width = CGRectGetMinX((_showOnlyIcons ? self.cancelIconButton : self.cancelTextButton).frame) - CGRectGetMaxX((_showOnlyIcons ? self.doneIconButton : self.doneTextButton).frame); } - CGRect containerRect = CGRectIntegral((CGRect){x,frame.origin.y,width,44.0f}); + CGRect containerRect = CGRectIntegral((CGRect){x,frame.origin.y,width,44.0}); #if TOCROPTOOLBAR_DEBUG_SHOWING_BUTTONS_CONTAINER_RECT containerView.frame = containerRect; #endif - CGSize buttonSize = (CGSize){44.0f,44.0f}; + CGSize buttonSize = (CGSize){44.0,44.0}; NSMutableArray *buttonsInOrderHorizontally = [NSMutableArray new]; if (!self.rotateCounterclockwiseButtonHidden) { @@ -244,23 +244,23 @@ - (void)layoutSubviews } else { CGRect frame = CGRectZero; - frame.size.height = 44.0f; - frame.size.width = 44.0f; - frame.origin.y = CGRectGetHeight(self.bounds) - 44.0f; + frame.size.height = 44.0; + frame.size.width = 44.0; + frame.origin.y = CGRectGetHeight(self.bounds) - 44.0; self.cancelIconButton.frame = frame; frame.origin.y = self.statusBarHeightInset; - frame.size.width = 44.0f; - frame.size.height = 44.0f; + frame.size.width = 44.0; + frame.size.height = 44.0; self.doneIconButton.frame = frame; - CGRect containerRect = (CGRect){0,CGRectGetMaxY(self.doneIconButton.frame),44.0f,CGRectGetMinY(self.cancelIconButton.frame)-CGRectGetMaxY(self.doneIconButton.frame)}; + CGRect containerRect = (CGRect){0,CGRectGetMaxY(self.doneIconButton.frame),44.0,CGRectGetMinY(self.cancelIconButton.frame)-CGRectGetMaxY(self.doneIconButton.frame)}; #if TOCROPTOOLBAR_DEBUG_SHOWING_BUTTONS_CONTAINER_RECT containerView.frame = containerRect; #endif - CGSize buttonSize = (CGSize){44.0f,44.0f}; + CGSize buttonSize = (CGSize){44.0,44.0}; NSMutableArray *buttonsInOrderVertically = [NSMutableArray new]; if (!self.rotateCounterclockwiseButtonHidden) { @@ -301,7 +301,7 @@ - (void)layoutToolbarButtons:(NSArray *)buttons withSameButtonSize:(CGSize)size origin.x += CGRectGetMinX(containerRect); if (@available(iOS 13.0, *)) { UIImage *image = button.imageView.image; - button.imageEdgeInsets = UIEdgeInsetsMake(0, 0, image.baselineOffsetFromBottom, 0); + button.imageEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, image.baselineOffsetFromBottom, 0.0); } } else { origin.y += CGRectGetMinY(containerRect); @@ -447,7 +447,7 @@ - (void)setCancelButtonColor:(UIColor *)cancelButtonColor { - (void)setDoneButtonColor:(UIColor *)doneButtonColor { // Set the default color when nil is specified if (doneButtonColor == nil) { - doneButtonColor = [UIColor colorWithRed:1.0f green:0.8f blue:0.0f alpha:1.0f]; + doneButtonColor = [UIColor colorWithRed:1.0 green:0.8 blue:0.0 alpha:1.0]; } if (doneButtonColor == _doneButtonColor) { return; } diff --git a/Objective-C/TOCropViewController/Views/TOCropView.m b/Objective-C/TOCropViewController/Views/TOCropView.m index fc802adf..d9f4d293 100755 --- a/Objective-C/TOCropViewController/Views/TOCropView.m +++ b/Objective-C/TOCropViewController/Views/TOCropView.m @@ -25,12 +25,12 @@ #import "TOCropScrollView.h" #import "UIView+Pixels.h" -#define TOCROPVIEW_BACKGROUND_COLOR [UIColor colorWithWhite:0.12f alpha:1.0f] +#define TOCROPVIEW_BACKGROUND_COLOR [UIColor colorWithWhite:0.12 alpha:1.0] -static const CGFloat kTOCropViewPadding = 14.0f; -static const NSTimeInterval kTOCropTimerDuration = 0.8f; -static const CGFloat kTOCropViewMinimumBoxSize = 42.0f; -static const CGFloat kTOMaximumZoomScale = 15.0f; +static const CGFloat kTOCropViewPadding = 14.0; +static const NSTimeInterval kTOCropTimerDuration = 0.8; +static const CGFloat kTOCropViewMinimumBoxSize = 42.0; +static const CGFloat kTOMaximumZoomScale = 15.0; /* When the user taps down to resize the box, this state is used to determine where they tapped and how to manipulate the box */ @@ -142,7 +142,7 @@ - (void)setup self.applyInitialCroppedImageFrame = NO; self.editing = NO; self.cropBoxResizeEnabled = !circularMode; - self.aspectRatio = circularMode ? (CGSize){1.0f, 1.0f} : CGSizeZero; + self.aspectRatio = circularMode ? (CGSize){1.0, 1.0} : CGSizeZero; self.resetAspectRatioEnabled = !circularMode; self.restoreImageCropFrame = CGRectZero; self.restoreAngle = 0; @@ -184,7 +184,7 @@ - (void)setup //Grey transparent overlay view self.overlayView = [[UIView alloc] initWithFrame:self.bounds]; self.overlayView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - self.overlayView.backgroundColor = [self.backgroundColor colorWithAlphaComponent:0.35f]; + self.overlayView.backgroundColor = [self.backgroundColor colorWithAlphaComponent:0.35]; self.overlayView.hidden = NO; self.overlayView.userInteractionEnabled = NO; [self addSubview:self.overlayView]; @@ -199,7 +199,7 @@ - (void)setup UIToolbar *toolbar = [[UIToolbar alloc] init]; toolbar.barStyle = UIBarStyleBlack; self.translucencyView = toolbar; - self.translucencyView.frame = CGRectInset(self.bounds, -1.0f, -1.0f); + self.translucencyView.frame = CGRectInset(self.bounds, -1.0, -1.0); } self.translucencyView.hidden = self.translucencyAlwaysHidden; self.translucencyView.userInteractionEnabled = NO; @@ -283,7 +283,7 @@ - (void)layoutInitialImage CGSize boundsSize = bounds.size; //work out the minimum scale of the object - CGFloat scale = 0.0f; + CGFloat scale = 0.0; // Work out the size of the image to fit into the content bounds scale = MIN(CGRectGetWidth(bounds)/imageSize.width, CGRectGetHeight(bounds)/imageSize.height); @@ -310,8 +310,8 @@ - (void)layoutInitialImage //Set the crop box to the size we calculated and align in the middle of the screen CGRect frame = CGRectZero; frame.size = self.hasAspectRatio ? cropBoxSize : scaledSize; - frame.origin.x = bounds.origin.x + (CGRectGetWidth(bounds) - frame.size.width) * 0.5f; - frame.origin.y = bounds.origin.y + (CGRectGetHeight(bounds) - frame.size.height) * 0.5f; + frame.origin.x = bounds.origin.x + (CGRectGetWidth(bounds) - frame.size.width) * 0.5; + frame.origin.y = bounds.origin.y + (CGRectGetHeight(bounds) - frame.size.height) * 0.5; self.cropBoxFrame = frame; //set the fully zoomed out state initially @@ -320,10 +320,10 @@ - (void)layoutInitialImage // If we ended up with a smaller crop box than the content, line up the content so its center // is in the center of the cropbox - if (frame.size.width < scaledSize.width - FLT_EPSILON || frame.size.height < scaledSize.height - FLT_EPSILON) { + if (frame.size.width < scaledSize.width - DBL_EPSILON || frame.size.height < scaledSize.height - DBL_EPSILON) { CGPoint offset = CGPointZero; - offset.x = -(CGRectGetMidX(bounds) - (scaledSize.width * 0.5f)); - offset.y = -(CGRectGetMidY(bounds) - (scaledSize.height * 0.5f)); + offset.x = -(CGRectGetMidX(bounds) - (scaledSize.width * 0.5)); + offset.y = -(CGRectGetMidY(bounds) - (scaledSize.height * 0.5)); self.scrollView.contentOffset = offset; } @@ -358,8 +358,8 @@ - (void)performRelayoutForRotation //Work out the centered, upscaled version of the crop rectangle cropFrame.size.width = cropFrame.size.width * scale; cropFrame.size.height = cropFrame.size.height * scale; - cropFrame.origin.x = contentFrame.origin.x + ((contentFrame.size.width - cropFrame.size.width) * 0.5f); - cropFrame.origin.y = contentFrame.origin.y + ((contentFrame.size.height - cropFrame.size.height) * 0.5f); + cropFrame.origin.x = contentFrame.origin.x + ((contentFrame.size.width - cropFrame.size.width) * 0.5); + cropFrame.origin.y = contentFrame.origin.y + ((contentFrame.size.height - cropFrame.size.height) * 0.5); self.cropBoxFrame = cropFrame; [self captureStateForImageRotation]; @@ -442,7 +442,7 @@ - (void)updateCropBoxFrameWithGesturePoint:(CGPoint)point xDelta = MAX(xDelta, 0); CGPoint scaleOrigin = (CGPoint){CGRectGetMaxX(originFrame), CGRectGetMidY(originFrame)}; frame.size.height = frame.size.width / aspectRatio; - frame.origin.y = scaleOrigin.y - (frame.size.height * 0.5f); + frame.origin.y = scaleOrigin.y - (frame.size.height * 0.5); } CGFloat newWidth = originFrame.size.width - xDelta; CGFloat newHeight = originFrame.size.height; @@ -459,7 +459,7 @@ - (void)updateCropBoxFrameWithGesturePoint:(CGPoint)point aspectHorizontal = YES; CGPoint scaleOrigin = (CGPoint){CGRectGetMinX(originFrame), CGRectGetMidY(originFrame)}; frame.size.height = frame.size.width / aspectRatio; - frame.origin.y = scaleOrigin.y - (frame.size.height * 0.5f); + frame.origin.y = scaleOrigin.y - (frame.size.height * 0.5); frame.size.width = originFrame.size.width + xDelta; frame.size.width = MIN(frame.size.width, contentFrame.size.height * aspectRatio); } @@ -477,7 +477,7 @@ - (void)updateCropBoxFrameWithGesturePoint:(CGPoint)point aspectVertical = YES; CGPoint scaleOrigin = (CGPoint){CGRectGetMidX(originFrame), CGRectGetMinY(originFrame)}; frame.size.width = frame.size.height * aspectRatio; - frame.origin.x = scaleOrigin.x - (frame.size.width * 0.5f); + frame.origin.x = scaleOrigin.x - (frame.size.width * 0.5); frame.size.height = originFrame.size.height + yDelta; frame.size.height = MIN(frame.size.height, contentFrame.size.width / aspectRatio); } @@ -496,7 +496,7 @@ - (void)updateCropBoxFrameWithGesturePoint:(CGPoint)point yDelta = MAX(0,yDelta); CGPoint scaleOrigin = (CGPoint){CGRectGetMidX(originFrame), CGRectGetMaxY(originFrame)}; frame.size.width = frame.size.height * aspectRatio; - frame.origin.x = scaleOrigin.x - (frame.size.width * 0.5f); + frame.origin.x = scaleOrigin.x - (frame.size.width * 0.5); frame.origin.y = originFrame.origin.y + yDelta; frame.size.height = originFrame.size.height - yDelta; } @@ -519,10 +519,10 @@ - (void)updateCropBoxFrameWithGesturePoint:(CGPoint)point yDelta = MAX(yDelta, 0); CGPoint distance; - distance.x = 1.0f - (xDelta / CGRectGetWidth(originFrame)); - distance.y = 1.0f - (yDelta / CGRectGetHeight(originFrame)); + distance.x = 1.0 - (xDelta / CGRectGetWidth(originFrame)); + distance.y = 1.0 - (yDelta / CGRectGetHeight(originFrame)); - CGFloat scale = (distance.x + distance.y) * 0.5f; + CGFloat scale = (distance.x + distance.y) * 0.5; frame.size.width = CGRectGetWidth(originFrame) * scale; frame.size.height = CGRectGetHeight(originFrame) * scale; @@ -554,10 +554,10 @@ - (void)updateCropBoxFrameWithGesturePoint:(CGPoint)point yDelta = MAX(yDelta, 0); CGPoint distance; - distance.x = 1.0f - ((-xDelta) / CGRectGetWidth(originFrame)); - distance.y = 1.0f - ((yDelta) / CGRectGetHeight(originFrame)); + distance.x = 1.0 - ((-xDelta) / CGRectGetWidth(originFrame)); + distance.y = 1.0 - ((yDelta) / CGRectGetHeight(originFrame)); - CGFloat scale = (distance.x + distance.y) * 0.5f; + CGFloat scale = (distance.x + distance.y) * 0.5; frame.size.width = CGRectGetWidth(originFrame) * scale; frame.size.height = CGRectGetHeight(originFrame) * scale; @@ -583,10 +583,10 @@ - (void)updateCropBoxFrameWithGesturePoint:(CGPoint)point case TOCropViewOverlayEdgeBottomLeft: if (self.aspectRatioLockEnabled) { CGPoint distance; - distance.x = 1.0f - (xDelta / CGRectGetWidth(originFrame)); - distance.y = 1.0f - (-yDelta / CGRectGetHeight(originFrame)); + distance.x = 1.0 - (xDelta / CGRectGetWidth(originFrame)); + distance.y = 1.0 - (-yDelta / CGRectGetHeight(originFrame)); - CGFloat scale = (distance.x + distance.y) * 0.5f; + CGFloat scale = (distance.x + distance.y) * 0.5; frame.size.width = CGRectGetWidth(originFrame) * scale; frame.size.height = CGRectGetHeight(originFrame) * scale; @@ -613,10 +613,10 @@ - (void)updateCropBoxFrameWithGesturePoint:(CGPoint)point if (self.aspectRatioLockEnabled) { CGPoint distance; - distance.x = 1.0f - ((-1 * xDelta) / CGRectGetWidth(originFrame)); - distance.y = 1.0f - ((-1 * yDelta) / CGRectGetHeight(originFrame)); + distance.x = 1.0 - ((-1 * xDelta) / CGRectGetWidth(originFrame)); + distance.y = 1.0 - ((-1 * yDelta) / CGRectGetHeight(originFrame)); - CGFloat scale = (distance.x + distance.y) * 0.5f; + CGFloat scale = (distance.x + distance.y) * 0.5; frame.size.width = CGRectGetWidth(originFrame) * scale; frame.size.height = CGRectGetHeight(originFrame) * scale; @@ -680,12 +680,12 @@ - (void)updateCropBoxFrameWithGesturePoint:(CGPoint)point frame.origin.y = MIN(frame.origin.y, CGRectGetMaxY(contentFrame) - minSize.height); //Once the box is completely shrunk, clamp its ability to move - if (clampMinFromLeft && frame.size.width <= minSize.width + FLT_EPSILON) { + if (clampMinFromLeft && frame.size.width <= minSize.width + DBL_EPSILON) { frame.origin.x = CGRectGetMaxX(originFrame) - minSize.width; } //Once the box is completely shrunk, clamp its ability to move - if (clampMinFromTop && frame.size.height <= minSize.height + FLT_EPSILON) { + if (clampMinFromTop && frame.size.height <= minSize.height + DBL_EPSILON) { frame.origin.y = CGRectGetMaxY(originFrame) - minSize.height; } @@ -708,7 +708,7 @@ - (void)resetLayoutToDefaultAnimated:(BOOL)animated _angle = 0; //Set the scroll to 1.0f to reset the transform scale - self.scrollView.zoomScale = 1.0f; + self.scrollView.zoomScale = 1.0; CGRect imageRect = (CGRect){CGPointZero, self.image.size}; @@ -741,8 +741,8 @@ - (void)resetLayoutToDefaultAnimated:(BOOL)animated [self setSimpleRenderMode:YES animated:NO]; //Perform an animation of the image zooming back out to its original size - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [UIView animateWithDuration:0.5f delay:0.0f usingSpringWithDamping:1.0f initialSpringVelocity:1.0f options:UIViewAnimationOptionBeginFromCurrentState animations:^{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [UIView animateWithDuration:0.5 delay:0.0 usingSpringWithDamping:1.0 initialSpringVelocity:1.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ [self layoutInitialImage]; } completion:^(BOOL complete) { [self setSimpleRenderMode:NO animated:YES]; @@ -753,7 +753,7 @@ - (void)resetLayoutToDefaultAnimated:(BOOL)animated - (void)toggleTranslucencyViewVisible:(BOOL)visible { if (self.dynamicBlurEffect == NO) { - self.translucencyView.alpha = visible ? 1.0f : 0.0f; + self.translucencyView.alpha = visible ? 1.0 : 0.0; } else { [(UIVisualEffectView *)self.translucencyView setEffect:visible ? self.translucencyEffect : nil]; @@ -784,8 +784,8 @@ - (void)updateToImageCropFrame:(CGRect)imageCropframe //set the crop box CGRect cropBoxFrame = CGRectZero; cropBoxFrame.size = frame.size; - cropBoxFrame.origin.x = CGRectGetMidX(bounds) - (frame.size.width * 0.5f); - cropBoxFrame.origin.y = CGRectGetMidY(bounds) - (frame.size.height * 0.5f); + cropBoxFrame.origin.x = CGRectGetMidX(bounds) - (frame.size.width * 0.5); + cropBoxFrame.origin.y = CGRectGetMidY(bounds) - (frame.size.height * 0.5); self.cropBoxFrame = cropBoxFrame; frame.origin.x = (scaledOffset.x * scale) - self.scrollView.contentInset.left; @@ -829,8 +829,8 @@ - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer CGPoint tapPoint = [gestureRecognizer locationInView:self]; CGRect frame = self.gridOverlayView.frame; - CGRect innerFrame = CGRectInset(frame, 22.0f, 22.0f); - CGRect outerFrame = CGRectInset(frame, -22.0f, -22.0f); + CGRect innerFrame = CGRectInset(frame, 22.0, 22.0); + CGRect outerFrame = CGRectInset(frame, -22.0, -22.0); if (CGRectContainsPoint(innerFrame, tapPoint) || !CGRectContainsPoint(outerFrame, tapPoint)) return NO; @@ -873,20 +873,20 @@ - (TOCropViewOverlayEdge)cropEdgeForPoint:(CGPoint)point CGRect frame = self.cropBoxFrame; //account for padding around the box - frame = CGRectInset(frame, -32.0f, -32.0f); + frame = CGRectInset(frame, -32.0, -32.0); //Make sure the corners take priority - CGRect topLeftRect = (CGRect){frame.origin, {64,64}}; + CGRect topLeftRect = (CGRect){frame.origin, {64.0,64.0}}; if (CGRectContainsPoint(topLeftRect, point)) return TOCropViewOverlayEdgeTopLeft; CGRect topRightRect = topLeftRect; - topRightRect.origin.x = CGRectGetMaxX(frame) - 64.0f; + topRightRect.origin.x = CGRectGetMaxX(frame) - 64.0; if (CGRectContainsPoint(topRightRect, point)) return TOCropViewOverlayEdgeTopRight; CGRect bottomLeftRect = topLeftRect; - bottomLeftRect.origin.y = CGRectGetMaxY(frame) - 64.0f; + bottomLeftRect.origin.y = CGRectGetMaxY(frame) - 64.0; if (CGRectContainsPoint(bottomLeftRect, point)) return TOCropViewOverlayEdgeBottomLeft; @@ -896,21 +896,21 @@ - (TOCropViewOverlayEdge)cropEdgeForPoint:(CGPoint)point return TOCropViewOverlayEdgeBottomRight; //Check for edges - CGRect topRect = (CGRect){frame.origin, {CGRectGetWidth(frame), 64.0f}}; + CGRect topRect = (CGRect){frame.origin, {CGRectGetWidth(frame), 64.0}}; if (CGRectContainsPoint(topRect, point)) return TOCropViewOverlayEdgeTop; CGRect bottomRect = topRect; - bottomRect.origin.y = CGRectGetMaxY(frame) - 64.0f; + bottomRect.origin.y = CGRectGetMaxY(frame) - 64.0; if (CGRectContainsPoint(bottomRect, point)) return TOCropViewOverlayEdgeBottom; - CGRect leftRect = (CGRect){frame.origin, {64.0f, CGRectGetHeight(frame)}}; + CGRect leftRect = (CGRect){frame.origin, {64.0, CGRectGetHeight(frame)}}; if (CGRectContainsPoint(leftRect, point)) return TOCropViewOverlayEdgeLeft; CGRect rightRect = leftRect; - rightRect.origin.x = CGRectGetMaxX(frame) - 64.0f; + rightRect.origin.x = CGRectGetMaxX(frame) - 64.0; if (CGRectContainsPoint(rightRect, point)) return TOCropViewOverlayEdgeRight; @@ -976,7 +976,7 @@ - (void)setCropBoxFrame:(CGRect)cropBoxFrame // Upon init, sometimes the box size is still 0 (or NaN), which can result in CALayer issues CGSize frameSize = cropBoxFrame.size; - if (frameSize.width < FLT_EPSILON || frameSize.height < FLT_EPSILON) { return; } + if (frameSize.width < DBL_EPSILON || frameSize.height < DBL_EPSILON) { return; } if (isnan(frameSize.width) || isnan(frameSize.height)) { return; } //clamp the cropping region to the inset boundaries of the screen @@ -984,13 +984,13 @@ - (void)setCropBoxFrame:(CGRect)cropBoxFrame CGFloat xOrigin = contentFrame.origin.x; CGFloat xDelta = cropBoxFrame.origin.x - xOrigin; cropBoxFrame.origin.x = MAX(cropBoxFrame.origin.x, xOrigin); - if (xDelta < -FLT_EPSILON) //If we clamp the x value, ensure we compensate for the subsequent delta generated in the width (Or else, the box will keep growing) + if (xDelta < -DBL_EPSILON) //If we clamp the x value, ensure we compensate for the subsequent delta generated in the width (Or else, the box will keep growing) cropBoxFrame.size.width += xDelta; CGFloat yOrigin = contentFrame.origin.y; CGFloat yDelta = cropBoxFrame.origin.y - yOrigin; cropBoxFrame.origin.y = MAX(cropBoxFrame.origin.y, yOrigin); - if (yDelta < -FLT_EPSILON) + if (yDelta < -DBL_EPSILON) cropBoxFrame.size.height += yDelta; //given the clamped X/Y values, make sure we can't extend the crop box beyond the edge of the screen in the current state @@ -1016,7 +1016,7 @@ - (void)setCropBoxFrame:(CGRect)cropBoxFrame // If the mask layer is present, adjust its transform to fit the new container view size if (self.croppingStyle == TOCropViewCroppingStyleCircular) { - CGFloat halfWidth = self.foregroundContainerView.frame.size.width * 0.5f; + CGFloat halfWidth = self.foregroundContainerView.frame.size.width * 0.5; self.foregroundContainerView.layer.cornerRadius = halfWidth; } @@ -1108,7 +1108,7 @@ - (void)setCroppingViewsHidden:(BOOL)hidden animated:(BOOL)animated _croppingViewsHidden = hidden; - CGFloat alpha = hidden ? 0.0f : 1.0f; + CGFloat alpha = hidden ? 0.0 : 1.0; if (animated == NO) { self.backgroundImageView.alpha = alpha; @@ -1123,7 +1123,7 @@ - (void)setCroppingViewsHidden:(BOOL)hidden animated:(BOOL)animated self.foregroundContainerView.alpha = alpha; self.backgroundImageView.alpha = alpha; - [UIView animateWithDuration:0.4f animations:^{ + [UIView animateWithDuration:0.4 animations:^{ [self toggleTranslucencyViewVisible:!hidden]; self.gridOverlayView.alpha = alpha; }]; @@ -1136,12 +1136,12 @@ - (void)setBackgroundImageViewHidden:(BOOL)hidden animated:(BOOL)animated return; } - CGFloat beforeAlpha = hidden ? 1.0f : 0.0f; - CGFloat toAlpha = hidden ? 0.0f : 1.0f; + CGFloat beforeAlpha = hidden ? 1.0 : 0.0; + CGFloat toAlpha = hidden ? 0.0 : 1.0; self.backgroundImageView.hidden = NO; self.backgroundImageView.alpha = beforeAlpha; - [UIView animateWithDuration:0.5f animations:^{ + [UIView animateWithDuration:0.5 animations:^{ self.backgroundImageView.alpha = toAlpha; }completion:^(BOOL complete) { if (hidden) { @@ -1172,10 +1172,10 @@ - (void)setGridOverlayHidden:(BOOL)gridOverlayHidden - (void)setGridOverlayHidden:(BOOL)gridOverlayHidden animated:(BOOL)animated { _gridOverlayHidden = gridOverlayHidden; - self.gridOverlayView.alpha = gridOverlayHidden ? 1.0f : 0.0f; + self.gridOverlayView.alpha = gridOverlayHidden ? 1.0 : 0.0; - [UIView animateWithDuration:0.4f animations:^{ - self.gridOverlayView.alpha = gridOverlayHidden ? 0.0f : 1.0f; + [UIView animateWithDuration:0.4 animations:^{ + self.gridOverlayView.alpha = gridOverlayHidden ? 0.0 : 1.0; }]; } @@ -1264,8 +1264,8 @@ - (void)setEditing:(BOOL)editing resetCropBox:(BOOL)resetCropbox animated:(BOOL) return; } - CGFloat duration = editing ? 0.05f : 0.35f; - CGFloat delay = editing? 0.0f : 0.35f; + CGFloat duration = editing ? 0.05 : 0.35; + CGFloat delay = editing? 0.0 : 0.35; if (self.croppingStyle == TOCropViewCroppingStyleCircular) { delay = 0.0f; @@ -1285,7 +1285,7 @@ - (void)moveCroppedContentToCenterAnimated:(BOOL)animated CGRect cropFrame = self.cropBoxFrame; // Ensure we only proceed after the crop frame has been setup for the first time - if (cropFrame.size.width < FLT_EPSILON || cropFrame.size.height < FLT_EPSILON) { + if (cropFrame.size.width < DBL_EPSILON || cropFrame.size.height < DBL_EPSILON) { return; } @@ -1297,8 +1297,8 @@ - (void)moveCroppedContentToCenterAnimated:(BOOL)animated cropFrame.size.width = cropFrame.size.width * scale; cropFrame.size.height = cropFrame.size.height * scale; - cropFrame.origin.x = contentRect.origin.x + (contentRect.size.width - cropFrame.size.width) * 0.5f; - cropFrame.origin.y = contentRect.origin.y + (contentRect.size.height - cropFrame.size.height) * 0.5f; + cropFrame.origin.x = contentRect.origin.x + (contentRect.size.width - cropFrame.size.width) * 0.5; + cropFrame.origin.y = contentRect.origin.y + (contentRect.size.height - cropFrame.size.height) * 0.5; //Work out the point on the scroll content that the focusPoint is aiming at CGPoint contentTargetPoint = CGPointZero; @@ -1330,14 +1330,14 @@ - (void)moveCroppedContentToCenterAnimated:(BOOL)animated // in order for the crop view to resize itself during iPad split screen events. // On the first run, even though scale is exactly 1.0f, performing this multiplication introduces // a floating point noise that zooms the image in by about 5 pixels. This fixes that issue. - if (scale < 1.0f - FLT_EPSILON || scale > 1.0f + FLT_EPSILON) { + if (scale < 1.0 - DBL_EPSILON || scale > 1.0 + DBL_EPSILON) { strongSelf.scrollView.zoomScale *= scale; strongSelf.scrollView.zoomScale = MIN(strongSelf.scrollView.maximumZoomScale, strongSelf.scrollView.zoomScale); } - // If it turns out the zoom operation would have exceeded the minizum zoom scale, don't apply + // If it turns out the zoom operation would have exceeded the minimum zoom scale, don't apply // the content offset - if (strongSelf.scrollView.zoomScale < strongSelf.scrollView.maximumZoomScale - FLT_EPSILON) { + if (strongSelf.scrollView.zoomScale < strongSelf.scrollView.maximumZoomScale - DBL_EPSILON) { offset.x = MIN(-CGRectGetMaxX(cropFrame)+strongSelf.scrollView.contentSize.width, offset.x); offset.y = MIN(-CGRectGetMaxY(cropFrame)+strongSelf.scrollView.contentSize.height, offset.y); strongSelf.scrollView.contentOffset = offset; @@ -1358,11 +1358,11 @@ - (void)moveCroppedContentToCenterAnimated:(BOOL)animated [self matchForegroundToBackground]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [UIView animateWithDuration:0.5f - delay:0.0f - usingSpringWithDamping:1.0f - initialSpringVelocity:1.0f + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [UIView animateWithDuration:0.5 + delay:0.0 + usingSpringWithDamping:1.0 + initialSpringVelocity:1.0 options:UIViewAnimationOptionBeginFromCurrentState animations:translateBlock completion:nil]; @@ -1384,7 +1384,7 @@ - (void)setSimpleRenderMode:(BOOL)simpleMode animated:(BOOL)animated return; } - [UIView animateWithDuration:0.25f animations:^{ + [UIView animateWithDuration:0.25 animations:^{ [self toggleTranslucencyViewVisible:!simpleMode]; }]; } @@ -1406,7 +1406,7 @@ - (void)setAspectRatio:(CGSize)aspectRatio animated:(BOOL)animated BOOL zoomOut = NO; // Passing in an empty size will revert back to the image aspect ratio - if (aspectRatio.width < FLT_EPSILON && aspectRatio.height < FLT_EPSILON) { + if (aspectRatio.width < DBL_EPSILON && aspectRatio.height < DBL_EPSILON) { aspectRatio = (CGSize){self.imageSize.width, self.imageSize.height}; zoomOut = YES; // Prevent from steadily zooming in when cycling between alternate aspectRatios and original } @@ -1425,9 +1425,9 @@ - (void)setAspectRatio:(CGSize)aspectRatio animated:(BOOL)animated CGFloat newWidth = cropBoxFrame.size.height * (aspectRatio.width/aspectRatio.height); CGFloat delta = cropBoxFrame.size.width - newWidth; cropBoxFrame.size.width = newWidth; - offset.x += (delta * 0.5f); + offset.x += (delta * 0.5); - if (delta < FLT_EPSILON) { + if (delta < DBL_EPSILON) { cropBoxFrame.origin.x = self.contentBounds.origin.x; //set to 0 to avoid accidental clamping by the crop frame sanitizer } @@ -1443,7 +1443,7 @@ - (void)setAspectRatio:(CGSize)aspectRatio animated:(BOOL)animated cropBoxFrame.size.height = newHeight; // Offset the Y position so it stays in the middle - offset.y += (delta * 0.5f); + offset.y += (delta * 0.5); // Clamp the width to the bounds width cropBoxFrame.size.width = boundsWidth; @@ -1454,9 +1454,9 @@ - (void)setAspectRatio:(CGSize)aspectRatio animated:(BOOL)animated CGFloat newHeight = cropBoxFrame.size.width * (aspectRatio.height/aspectRatio.width); CGFloat delta = cropBoxFrame.size.height - newHeight; cropBoxFrame.size.height = newHeight; - offset.y += (delta * 0.5f); + offset.y += (delta * 0.5); - if (delta < FLT_EPSILON) { + if (delta < DBL_EPSILON) { cropBoxFrame.origin.y = self.contentBounds.origin.y; } @@ -1472,7 +1472,7 @@ - (void)setAspectRatio:(CGSize)aspectRatio animated:(BOOL)animated cropBoxFrame.size.width = newWidth; // Offset the Y position so it stays in the middle - offset.x += (delta * 0.5f); + offset.x += (delta * 0.5); // Clamp the width to the bounds height cropBoxFrame.size.height = boundsHeight; @@ -1500,10 +1500,10 @@ - (void)setAspectRatio:(CGSize)aspectRatio animated:(BOOL)animated return; } - [UIView animateWithDuration:0.5f + [UIView animateWithDuration:0.5 delay:0.0 - usingSpringWithDamping:1.0f - initialSpringVelocity:0.7f + usingSpringWithDamping:1.0 + initialSpringVelocity:0.7 options:UIViewAnimationOptionBeginFromCurrentState animations:translateBlock completion:nil]; @@ -1539,7 +1539,7 @@ - (void)rotateImageNinetyDegreesAnimated:(BOOL)animated clockwise:(BOOL)clockwis _angle = newAngle; //Convert the new angle to radians - CGFloat angleInRadians = 0.0f; + CGFloat angleInRadians = 0.0; switch (newAngle) { case 90: angleInRadians = M_PI_2; break; case -90: angleInRadians = -M_PI_2; break; @@ -1578,8 +1578,8 @@ - (void)rotateImageNinetyDegreesAnimated:(BOOL)animated clockwise:(BOOL)clockwis self.scrollView.zoomScale *= scale; } - newCropFrame.origin.x = CGRectGetMidX(contentBounds) - (newCropFrame.size.width * 0.5f); - newCropFrame.origin.y = CGRectGetMidY(contentBounds) - (newCropFrame.size.height * 0.5f); + newCropFrame.origin.x = CGRectGetMidX(contentBounds) - (newCropFrame.size.width * 0.5); + newCropFrame.origin.y = CGRectGetMidY(contentBounds) - (newCropFrame.size.height * 0.5); //If we're animated, generate a snapshot view that we'll animate in place of the real view UIView *snapshotView = nil; @@ -1602,6 +1602,11 @@ - (void)rotateImageNinetyDegreesAnimated:(BOOL)animated clockwise:(BOOL)clockwis //Flip the content size of the scroll view to match the rotated bounds self.scrollView.contentSize = self.backgroundContainerView.frame.size; + + //assign the new crop box frame and re-adjust the content to fill it + self.cropBoxFrame = newCropFrame; + [self moveCroppedContentToCenterAnimated:NO]; + newCropFrame = self.cropBoxFrame; //work out how to line up out point of interest into the middle of the crop box cropTargetPoint.x *= scale; @@ -1629,14 +1634,11 @@ - (void)rotateImageNinetyDegreesAnimated:(BOOL)animated clockwise:(BOOL)clockwis //if the scroll view's new scale is 1 and the new offset is equal to the old, will not trigger the delegate 'scrollViewDidScroll:' //so we should call the method manually to update the foregroundImageView's frame - if (offset.x == self.scrollView.contentOffset.x && offset.y == self.scrollView.contentOffset.y && scale == 1) { + if (offset.x == self.scrollView.contentOffset.x && offset.y == self.scrollView.contentOffset.y && scale == 1.0) { [self matchForegroundToBackground]; } self.scrollView.contentOffset = offset; - - //assign the new crop box frame and re-adjust the content to fill it - self.cropBoxFrame = newCropFrame; - [self moveCroppedContentToCenterAnimated:NO]; + [self moveCroppedContentToCenterAnimated:NO]; //Prevents uncropped rotation sometimes causing a black line on the edge. //If we're animated, play an animation of the snapshot view rotating, //then fade it out over the live content @@ -1649,7 +1651,7 @@ - (void)rotateImageNinetyDegreesAnimated:(BOOL)animated clockwise:(BOOL)clockwis self.translucencyView.hidden = YES; self.gridOverlayView.hidden = YES; - [UIView animateWithDuration:0.45f delay:0.0f usingSpringWithDamping:1.0f initialSpringVelocity:0.8f options:UIViewAnimationOptionBeginFromCurrentState animations:^{ + [UIView animateWithDuration:0.45 delay:0.0 usingSpringWithDamping:1.0 initialSpringVelocity:0.8 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ CGAffineTransform transform = CGAffineTransformRotate(CGAffineTransformIdentity, clockwise ? M_PI_2 : -M_PI_2); transform = CGAffineTransformScale(transform, scale, scale); snapshotView.transform = transform; @@ -1659,15 +1661,15 @@ - (void)rotateImageNinetyDegreesAnimated:(BOOL)animated clockwise:(BOOL)clockwis self.translucencyView.hidden = self.translucencyAlwaysHidden; self.gridOverlayView.hidden = NO; - self.backgroundContainerView.alpha = 0.0f; - self.gridOverlayView.alpha = 0.0f; + self.backgroundContainerView.alpha = 0.0; + self.gridOverlayView.alpha = 0.0; - self.translucencyView.alpha = 1.0f; + self.translucencyView.alpha = 1.0; - [UIView animateWithDuration:0.45f animations:^{ - snapshotView.alpha = 0.0f; - self.backgroundContainerView.alpha = 1.0f; - self.gridOverlayView.alpha = 1.0f; + [UIView animateWithDuration:0.45 animations:^{ + snapshotView.alpha = 0.0; + self.backgroundContainerView.alpha = 1.0; + self.gridOverlayView.alpha = 1.0; } completion:^(BOOL complete) { self.rotateAnimationInProgress = NO; [snapshotView removeFromSuperview]; @@ -1704,7 +1706,7 @@ - (void)checkForCanReset if (self.angle != 0) { //Image has been rotated canReset = YES; } - else if (self.scrollView.zoomScale > self.scrollView.minimumZoomScale + FLT_EPSILON) { //image has been zoomed in + else if (self.scrollView.zoomScale > self.scrollView.minimumZoomScale + DBL_EPSILON) { //image has been zoomed in canReset = YES; } else if (![self pixelCount:self.cropBoxFrame.size.width equals:self.originalCropBoxSize.width] || @@ -1727,8 +1729,8 @@ - (CGRect)contentBounds CGRect contentRect = CGRectZero; contentRect.origin.x = self.cropViewPadding + self.cropRegionInsets.left; contentRect.origin.y = self.cropViewPadding + self.cropRegionInsets.top; - contentRect.size.width = CGRectGetWidth(self.bounds) - ((self.cropViewPadding * 2) + self.cropRegionInsets.left + self.cropRegionInsets.right); - contentRect.size.height = CGRectGetHeight(self.bounds) - ((self.cropViewPadding * 2) + self.cropRegionInsets.top + self.cropRegionInsets.bottom); + contentRect.size.width = CGRectGetWidth(self.bounds) - ((self.cropViewPadding * 2.0) + self.cropRegionInsets.left + self.cropRegionInsets.right); + contentRect.size.height = CGRectGetHeight(self.bounds) - ((self.cropViewPadding * 2.0) + self.cropRegionInsets.top + self.cropRegionInsets.bottom); return contentRect; } @@ -1742,7 +1744,7 @@ - (CGSize)imageSize - (BOOL)hasAspectRatio { - return (self.aspectRatio.width > FLT_EPSILON && self.aspectRatio.height > FLT_EPSILON); + return (self.aspectRatio.width > DBL_EPSILON && self.aspectRatio.height > DBL_EPSILON); } @end diff --git a/Objective-C/TOCropViewControllerExample/ViewController.m b/Objective-C/TOCropViewControllerExample/ViewController.m index 3ff95c28..bc539531 100644 --- a/Objective-C/TOCropViewControllerExample/ViewController.m +++ b/Objective-C/TOCropViewControllerExample/ViewController.m @@ -163,11 +163,11 @@ - (void)layoutImageView if (self.imageView.image == nil) return; - CGFloat padding = 20.0f; + CGFloat padding = 20.0; CGRect viewFrame = self.view.bounds; - viewFrame.size.width -= (padding * 2.0f); - viewFrame.size.height -= ((padding * 2.0f)); + viewFrame.size.width -= (padding * 2.0); + viewFrame.size.height -= ((padding * 2.0)); CGRect imageFrame = CGRectZero; imageFrame.size = self.imageView.image.size; @@ -178,8 +178,8 @@ - (void)layoutImageView CGFloat scale = MIN(viewFrame.size.width / imageFrame.size.width, viewFrame.size.height / imageFrame.size.height); imageFrame.size.width *= scale; imageFrame.size.height *= scale; - imageFrame.origin.x = (CGRectGetWidth(self.view.bounds) - imageFrame.size.width) * 0.5f; - imageFrame.origin.y = (CGRectGetHeight(self.view.bounds) - imageFrame.size.height) * 0.5f; + imageFrame.origin.x = (CGRectGetWidth(self.view.bounds) - imageFrame.size.width) * 0.5; + imageFrame.origin.y = (CGRectGetHeight(self.view.bounds) - imageFrame.size.height) * 0.5; self.imageView.frame = imageFrame; } else { diff --git a/Objective-C/TOCropViewControllerTests/TOCropViewControllerTests.m b/Objective-C/TOCropViewControllerTests/TOCropViewControllerTests.m index 57f98510..21f9be72 100644 --- a/Objective-C/TOCropViewControllerTests/TOCropViewControllerTests.m +++ b/Objective-C/TOCropViewControllerTests/TOCropViewControllerTests.m @@ -19,7 +19,7 @@ @implementation TOCropViewControllerTests - (void)testViewControllerInstance { //Create a basic image - UIGraphicsBeginImageContextWithOptions((CGSize){10, 10}, NO, 1.0f); + UIGraphicsBeginImageContextWithOptions((CGSize){10, 10}, NO, 1.0); CGContextFillRect(UIGraphicsGetCurrentContext(), (CGRect){0,0,10,10}); UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); From 2dfb0af0f1f52f86d9e0248d7ccf608af5518aa8 Mon Sep 17 00:00:00 2001 From: Jan de Vries Date: Sun, 17 Nov 2024 11:03:21 +0100 Subject: [PATCH 4/5] Fully fledged flip implementation --- .../Categories/UIImage+CropRotate.h | 2 + .../Categories/UIImage+CropRotate.m | 6 +- .../Categories/UIView+Pixels.h | 7 +- .../Categories/UIView+Pixels.m | 18 +- .../Models/TOActivityCroppedImageProvider.h | 3 +- .../Models/TOActivityCroppedImageProvider.m | 8 +- .../Models/TOCroppedImageAttributes.h | 3 +- .../Models/TOCroppedImageAttributes.m | 4 +- .../Resources/arrow.counterclockwise@2x.png | Bin 0 -> 1163 bytes ...angle.left.righttriangle.right.fill@2x.png | Bin 0 -> 1207 bytes ...triangle.up.righttriangle.down.fill@2x.png | Bin 0 -> 1154 bytes .../Resources/aspectratio.fill@2x.png | Bin 0 -> 621 bytes .../Resources/checkmark@2x.png | Bin 0 -> 827 bytes .../Resources/rotate.left.fill@2x.png | Bin 0 -> 823 bytes .../Resources/rotate.right.fill@2x.png | Bin 0 -> 816 bytes .../Resources/xmark@2x.png | Bin 0 -> 604 bytes .../TOCropViewController.h | 110 ++++++-- .../TOCropViewController.m | 119 +++++++-- .../Views/TOCropToolbar.h | 6 + .../Views/TOCropToolbar.m | 249 +++++++----------- .../TOCropViewController/Views/TOCropView.h | 34 ++- .../TOCropViewController/Views/TOCropView.m | 127 +++++++-- .../include/UIView+Pixels.h | 1 + .../ViewController.m | 12 +- README.md | 11 +- .../CropViewController.swift | 142 ++++++++-- .../ViewController.swift | 14 +- .../project.pbxproj | 90 ++++++- 28 files changed, 691 insertions(+), 275 deletions(-) create mode 100644 Objective-C/TOCropViewController/Resources/arrow.counterclockwise@2x.png create mode 100644 Objective-C/TOCropViewController/Resources/arrow.trianglehead.left.and.right.righttriangle.left.righttriangle.right.fill@2x.png create mode 100644 Objective-C/TOCropViewController/Resources/arrow.trianglehead.up.and.down.righttriangle.up.righttriangle.down.fill@2x.png create mode 100644 Objective-C/TOCropViewController/Resources/aspectratio.fill@2x.png create mode 100644 Objective-C/TOCropViewController/Resources/checkmark@2x.png create mode 100644 Objective-C/TOCropViewController/Resources/rotate.left.fill@2x.png create mode 100644 Objective-C/TOCropViewController/Resources/rotate.right.fill@2x.png create mode 100644 Objective-C/TOCropViewController/Resources/xmark@2x.png create mode 120000 Objective-C/TOCropViewController/include/UIView+Pixels.h diff --git a/Objective-C/TOCropViewController/Categories/UIImage+CropRotate.h b/Objective-C/TOCropViewController/Categories/UIImage+CropRotate.h index 1ae9e977..8d96a4f4 100644 --- a/Objective-C/TOCropViewController/Categories/UIImage+CropRotate.h +++ b/Objective-C/TOCropViewController/Categories/UIImage+CropRotate.h @@ -29,9 +29,11 @@ NS_ASSUME_NONNULL_BEGIN /// Crops a portion of an existing image object and returns it as a new image /// @param frame The region inside the image (In image pixel space) to crop /// @param angle If any, the angle the image is rotated at as well +/// @param flip Whether to flip (mirror) the image /// @param circular Whether the resulting image is returned as a square or a circle - (nonnull UIImage *)croppedImageWithFrame:(CGRect)frame angle:(NSInteger)angle + flip:(BOOL)flip circularClip:(BOOL)circular; @end diff --git a/Objective-C/TOCropViewController/Categories/UIImage+CropRotate.m b/Objective-C/TOCropViewController/Categories/UIImage+CropRotate.m index 8b2f4975..134a80ce 100644 --- a/Objective-C/TOCropViewController/Categories/UIImage+CropRotate.m +++ b/Objective-C/TOCropViewController/Categories/UIImage+CropRotate.m @@ -31,7 +31,7 @@ - (BOOL)hasAlpha alphaInfo == kCGImageAlphaPremultipliedFirst || alphaInfo == kCGImageAlphaPremultipliedLast); } -- (UIImage *)croppedImageWithFrame:(CGRect)frame angle:(NSInteger)angle circularClip:(BOOL)circular +- (UIImage *)croppedImageWithFrame:(CGRect)frame angle:(NSInteger)angle flip:(BOOL)flip circularClip:(BOOL)circular { UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat new]; format.opaque = !self.hasAlpha && !circular; @@ -46,6 +46,10 @@ - (UIImage *)croppedImageWithFrame:(CGRect)frame angle:(NSInteger)angle circular CGContextAddEllipseInRect(context, (CGRect){CGPointZero, frame.size}); CGContextClip(context); } + + // Flip image when applicable + CGContextTranslateCTM(context, flip ? frame.size.width : 0, 0); + CGContextScaleCTM(context, flip ? -1 : 1, 1); // Offset the origin (Which is the top left corner) to start where our cropping origin is CGContextTranslateCTM(context, -frame.origin.x, -frame.origin.y); diff --git a/Objective-C/TOCropViewController/Categories/UIView+Pixels.h b/Objective-C/TOCropViewController/Categories/UIView+Pixels.h index e26cb9c0..eddff707 100644 --- a/Objective-C/TOCropViewController/Categories/UIView+Pixels.h +++ b/Objective-C/TOCropViewController/Categories/UIView+Pixels.h @@ -27,10 +27,13 @@ NS_ASSUME_NONNULL_BEGIN @interface UIView(TOPixels) ///Round point value to nearest physical pixel -- (CGFloat)roundToNearestPixel:(CGFloat)val; +- (CGFloat)roundToNearestPixel:(CGFloat)point NS_SWIFT_NAME(roundToNearestPixel(point:)); ///Check if two CGFloats (points) round to the same number of physical pixels -- (BOOL)pixelCount:(CGFloat)val1 equals:(CGFloat)val2; +- (BOOL)pixelCountOf:(CGFloat)point1 equals:(CGFloat)point2; + +///Works like CGRectIntegral() but rounds values to the nearest physical pixel +- (CGRect)CGRectIntegralRetina:(CGRect)rect; @end diff --git a/Objective-C/TOCropViewController/Categories/UIView+Pixels.m b/Objective-C/TOCropViewController/Categories/UIView+Pixels.m index c941de7f..f734b215 100644 --- a/Objective-C/TOCropViewController/Categories/UIView+Pixels.m +++ b/Objective-C/TOCropViewController/Categories/UIView+Pixels.m @@ -24,21 +24,29 @@ @implementation UIView (TOPixels) -- (CGFloat)roundToNearestPixel:(CGFloat)val { +- (CGFloat)roundToNearestPixel:(CGFloat)point { CGFloat screenScale = 2.0; if (self.window != nil && self.window.screen != nil) { screenScale = self.window.screen.scale; } - return round(val * screenScale) / screenScale; + return round(point * screenScale) / screenScale; } -- (BOOL)pixelCount:(CGFloat)val1 equals:(CGFloat)val2 +- (BOOL)pixelCountOf:(CGFloat)point1 equals:(CGFloat)point2 { if (self.window == nil || self.window.screen == nil) { - return val1 == val2; + return point1 == point2; } CGFloat screenScale = self.window.screen.scale; - return round(val1*screenScale) == round(val2*screenScale); + return round(point1*screenScale) == round(point2*screenScale); +} + +- (CGRect)CGRectIntegralRetina:(CGRect)rect +{ + return CGRectMake([self roundToNearestPixel:rect.origin.x], + [self roundToNearestPixel:rect.origin.y], + [self roundToNearestPixel:rect.size.width], + [self roundToNearestPixel:rect.size.height]); } @end diff --git a/Objective-C/TOCropViewController/Models/TOActivityCroppedImageProvider.h b/Objective-C/TOCropViewController/Models/TOActivityCroppedImageProvider.h index f6199e47..58f20ecc 100644 --- a/Objective-C/TOCropViewController/Models/TOActivityCroppedImageProvider.h +++ b/Objective-C/TOCropViewController/Models/TOActivityCroppedImageProvider.h @@ -29,9 +29,10 @@ NS_ASSUME_NONNULL_BEGIN @property (nonnull, nonatomic, readonly) UIImage *image; @property (nonatomic, readonly) CGRect cropFrame; @property (nonatomic, readonly) NSInteger angle; +@property (nonatomic, readonly) BOOL flipped; @property (nonatomic, readonly) BOOL circular; -- (nonnull instancetype)initWithImage:(nonnull UIImage *)image cropFrame:(CGRect)cropFrame angle:(NSInteger)angle circular:(BOOL)circular; +- (nonnull instancetype)initWithImage:(nonnull UIImage *)image cropFrame:(CGRect)cropFrame angle:(NSInteger)angle flipped:(BOOL)flipped circular:(BOOL)circular; @end diff --git a/Objective-C/TOCropViewController/Models/TOActivityCroppedImageProvider.m b/Objective-C/TOCropViewController/Models/TOActivityCroppedImageProvider.m index 578e4a24..76939b94 100644 --- a/Objective-C/TOCropViewController/Models/TOActivityCroppedImageProvider.m +++ b/Objective-C/TOCropViewController/Models/TOActivityCroppedImageProvider.m @@ -28,6 +28,7 @@ @interface TOActivityCroppedImageProvider () @property (nonatomic, strong, readwrite) UIImage *image; @property (nonatomic, assign, readwrite) CGRect cropFrame; @property (nonatomic, assign, readwrite) NSInteger angle; +@property (nonatomic, assign, readwrite) BOOL flipped; @property (nonatomic, assign, readwrite) BOOL circular; @property (atomic, strong) UIImage *croppedImage; @@ -36,12 +37,13 @@ @interface TOActivityCroppedImageProvider () @implementation TOActivityCroppedImageProvider -- (instancetype)initWithImage:(UIImage *)image cropFrame:(CGRect)cropFrame angle:(NSInteger)angle circular:(BOOL)circular +- (instancetype)initWithImage:(UIImage *)image cropFrame:(CGRect)cropFrame angle:(NSInteger)angle flipped:(BOOL)flipped circular:(BOOL)circular { if (self = [super initWithPlaceholderItem:[UIImage new]]) { _image = image; _cropFrame = cropFrame; _angle = angle; + _flipped = flipped; _circular = circular; } @@ -63,12 +65,12 @@ - (id)activityViewController:(UIActivityViewController *)activityViewController - (id)item { //If the user didn't touch the image, just forward along the original - if (self.angle == 0 && CGRectEqualToRect(self.cropFrame, (CGRect){CGPointZero, self.image.size})) { + if (self.angle == 0 && !self.flipped && CGRectEqualToRect(self.cropFrame, (CGRect){CGPointZero, self.image.size})) { self.croppedImage = self.image; return self.croppedImage; } - UIImage *image = [self.image croppedImageWithFrame:self.cropFrame angle:self.angle circularClip:self.circular]; + UIImage *image = [self.image croppedImageWithFrame:self.cropFrame angle:self.angle flip:self.flipped circularClip:self.circular]; self.croppedImage = image; return self.croppedImage; } diff --git a/Objective-C/TOCropViewController/Models/TOCroppedImageAttributes.h b/Objective-C/TOCropViewController/Models/TOCroppedImageAttributes.h index d7a8a8a9..b2bfd7cf 100644 --- a/Objective-C/TOCropViewController/Models/TOCroppedImageAttributes.h +++ b/Objective-C/TOCropViewController/Models/TOCroppedImageAttributes.h @@ -28,10 +28,11 @@ NS_ASSUME_NONNULL_BEGIN @interface TOCroppedImageAttributes : NSObject @property (nonatomic, readonly) NSInteger angle; +@property (nonatomic, readonly) BOOL flipped; @property (nonatomic, readonly) CGRect croppedFrame; @property (nonatomic, readonly) CGSize originalImageSize; -- (instancetype)initWithCroppedFrame:(CGRect)croppedFrame angle:(NSInteger)angle originalImageSize:(CGSize)originalSize; +- (instancetype)initWithCroppedFrame:(CGRect)croppedFrame angle:(NSInteger)angle flipped:(BOOL)flipped originalImageSize:(CGSize)originalSize; @end diff --git a/Objective-C/TOCropViewController/Models/TOCroppedImageAttributes.m b/Objective-C/TOCropViewController/Models/TOCroppedImageAttributes.m index 97ddaf32..54a16378 100644 --- a/Objective-C/TOCropViewController/Models/TOCroppedImageAttributes.m +++ b/Objective-C/TOCropViewController/Models/TOCroppedImageAttributes.m @@ -25,6 +25,7 @@ @interface TOCroppedImageAttributes () @property (nonatomic, assign, readwrite) NSInteger angle; +@property (nonatomic, assign, readwrite) BOOL flipped; @property (nonatomic, assign, readwrite) CGRect croppedFrame; @property (nonatomic, assign, readwrite) CGSize originalImageSize; @@ -32,10 +33,11 @@ @interface TOCroppedImageAttributes () @implementation TOCroppedImageAttributes -- (instancetype)initWithCroppedFrame:(CGRect)croppedFrame angle:(NSInteger)angle originalImageSize:(CGSize)originalSize +- (instancetype)initWithCroppedFrame:(CGRect)croppedFrame angle:(NSInteger)angle flipped:(BOOL)flipped originalImageSize:(CGSize)originalSize { if (self = [super init]) { _angle = angle; + _flipped = flipped; _croppedFrame = croppedFrame; _originalImageSize = originalSize; } diff --git a/Objective-C/TOCropViewController/Resources/arrow.counterclockwise@2x.png b/Objective-C/TOCropViewController/Resources/arrow.counterclockwise@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c1ecd2d1d6ab02717909acfae860b3dc23eefa14 GIT binary patch literal 1163 zcmV;61a$j}P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91CZGcV1ONa40RR91G5`Po01=WA82|tS^+`lQR9Fe^nOlfWVHC&BxEth} z`?yQVC7BcwqIgo`Me@Rn2PBP5CPFDZcpxPuQ))z#2Of<}kq1T*8s_5i;x=X6M~vTp zoTJa0z4!U{-sgClRsZ(4ZvVB;x5wV=+q!hggS54^?E>FG3s|2A%JdX8asasU6{Syl z<`fJxu+QztS&v9Tn=X|mb~}x@%k$}&Q)JUch1-d4J7~haJ1U{m9S|mIUl0LRR-kg5t1K)d=k+6 zGAp-zmuavveDb|wAR7vHfUTgfR}w+Q9tKwC`{oxcj*iM~SLl^0=<}{D zSYr_11udq)bm_%(I1StY`a3xax|+a(sk|qDLH@)PnAEP*$Q|IyIulq>mBJU@;;b7~ zP`7fHsXW{0g;u|r0_`KZ8vAY>1B_m1RilzCU!wXPp5KFmDd4D@^BqlJ;2Gr5URss`2h(+}I( z185#{r5U9#=fTWRfPXm9Co5O#v%j#Ihg>U4b=Hu%4&+*4Z24CALugO$RsR&BWzANa zhg@kzne$Izo)mbO0(*$o1U$Gy35+Lzl|~GC{Rju2X1F)NigvK05H$z{B|OAFvSUVk z3p|?L8i5s^zIul;;#>jj7K=nvYN|Pj{N+Gnw-x=#%nfZS>mVrXYoWg690NL;dkgVn zKotm+jd92hg|T4QA>uU94%qixdkyS539SVBcBUN(lXCB-vVkz^c&}ZG68%fA12y0Z zxCZJ$uDtKWY{=G%eIPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91GoS+i1ONa40RR91HUIzs00-^DC;$KiAxT6*RA>e5nOjH{Q5eTvqnnx* zNjK>V1yT88K}ZTkAwd@k3#=$Yii!x!3bL1wqCzZ+sOX|E5!gczL`Bd;m$&Mnq_@Z( zEGWc>Qb9db;;BVsP8I`dTn=Me4t>O2dMJ=FMAuQR?f%E<~Hq1^&xnG@{| zj`~DrhR#>Od(VPf>ZoG@x-I(uyAf0YK7&LH`UW7+x^qB&63&eQ#h?S|W>{9|Z0;jT zTBRqZ{R6CX&!mlj-~bo^V(}lF7Lt)BxyrzxB*k8UnZ6`~Xg1gk#`{9(kc52YU^Phk zZ9AmMg@s}Ud|SSeORy4ElfZj!o8Qb75;qqAN-za<64zxG7nqL%Ye5fCCi|yWV3;h< z<(B^i==Ok(iUhIf?H^)R<8{Qov+jRiG66P8CFZ zt2k0vgNhC0NMUU$ij%INsxaCOHnH+Xxj@BxIeK1N63f$guT4^Y+o8A3hRJKkZL=5XewXr+)MiXE{Woo5 zOuJo)L5RGC-UWu|hbeP<&JE<+>6XHk7|5H!Lbpjg#$Q3uG&f@%i-B0){EoRHPcv3N zW?XZ_;YbWZ;Jg^B4UEF z86>{!Bza0P2$9Q5E-%@(2Wfk#rN*v+lwufs94xmZ8KwSP2$G$LrCEw$;37tyu^c)2 z9dk%|F;!YqF$j@|qC6bK_Nn$DwN+aQG8IGcVc`6)11om_{W8gS5H!(@I!O$H^9Vd| z790|v>_D2$YDp49ki1hAngxf*ca%w&9f`pVVhE_Gcda2JoF}nI2K5>~?#IQ@F^jre z!&$p&Rk9;Ft@p&m;Jg9C@ySQ8Fm+r04lGw8F)=vmaBNb}EIP{0L_Vs-Ho*BbN^xAx zA_0k|R^Vt9r>x9LAcC^lnpl()5wk0UDuy-gsMhwoDZllNA|{io(yLXiw>7tK%~BEb z(KoteqL-TMHSJ8UPG=t1n*1}R{^q0Un|+1J@coPPv%aw#i5|+N#XcF~pHF)SkPmyY z+Z;!)Ho(9Spo9NjekYQUs}kROFdd|u>lYlaz$0?$zfW8_%gsP;268iyn}HC_z+aXn V!SIJ76oLQ%002ovPDHLkV1h4C3&#Ke literal 0 HcmV?d00001 diff --git a/Objective-C/TOCropViewController/Resources/arrow.trianglehead.up.and.down.righttriangle.up.righttriangle.down.fill@2x.png b/Objective-C/TOCropViewController/Resources/arrow.trianglehead.up.and.down.righttriangle.up.righttriangle.down.fill@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9bc7fe7fc281cfbe439dfecc9cde386e70462509 GIT binary patch literal 1154 zcmV-|1bzF7P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FQ5Ye1ONa40RR91G5`Po0B|Ig!~g&T>`6pHRA>dwnomd+Q5?rzHBGVz zDZzpu4;g_OQ5}MWItX%geQ`5HWp*g2wUdH}s9Up~xsD%WYbC`g>NF?&v={GtcdK->cpvov9L#JW{jS|!4#!+zCZFB75`s28Bqb4~P z;&>T+FEf)uS~h_i!grAHC1h2&L~GduVu)TuA}x`vfh06Y63EOWd^fdTgehEOvzmEC z>%B)`@J1yI+iYjP8NoRbu8Hz3o>^Z&yp~JO%(r+H*MQZsvmEufjrc*!;)Wq8&7WKiz_)k_W&jU3)I+b%!!|4N63YOdaHxYzpuL^Y%v!vZ zGJp{{N>k4T{gw^I0uk>jbv}jFoS%m=w?A9QK*dWW0~m$VG}l0EGfUTkwg^v3y_0MI z-Q5A!M_=v~(t6hWgzN(xLzwaXo!olYIRa-O=m*+eZE_J z#Jb&?nC>K&P>MkE?SmokYm?%Yn+z9b$kI=LWs3UNUF2O1Z8OyM>P`?}g!iMQh;hz0TC4O( zK#g7SEvdHG>6Cco$6GV;H7-iGxKNCmm2fMrwpTMzyi^jg3A7fKDDa1v*Gy2e5eC4^ zZl}yeROq7Pg;2i&u6w9&j*rQ^HsUwKrxb%2qfc+L`E7{Qe=Rr(Z($g^VT)A%2eV8J U*fZCDbN~PV07*qoM6N<$f}O(-fdBvi literal 0 HcmV?d00001 diff --git a/Objective-C/TOCropViewController/Resources/aspectratio.fill@2x.png b/Objective-C/TOCropViewController/Resources/aspectratio.fill@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..1c233eb6f818b26eb41c39476400fbea4258fb44 GIT binary patch literal 621 zcmeAS@N?(olHy`uVBq!ia0vp^hCr;&!3HFk2``uhq!^2X+?^QKos)S9PKZPZ!6K z2+p@N_j)lqinMMHbX1vpi^Wx^hUuu*FE*zuhtvhCqd$tWcpntsuxaz9Q}QBTRFBMW zNLr|LWd54_ibqn!O{zJ$jg7?5y*u+}@8sfV=hP<8TyY_>P^Ld1mciKJspEsLy-rW~ z<}@{Hblnp^q7ctA`vLEdgzXcqBy8_(%axz8+`?={_)?`s2B-fn(mlEQ5_67vSIo6N zi{`3Wdu`PcoSL$`d~27>l)a1Rs>Dp|2-&_gm>&TdUrQ5WM zQ>~1{he=m?nxp)O>k+nR8q*UtIxoM+8Mf^Bl7zdn*w)THw$@SdPqx6X3G#oltNwgz z{&mv$=dn*yAKD*y%bD``uT@ZhoSCkZk(K=GPmI@X=4VxzufO8I>qv=RS&~(z+M2Si z1#5g#%);+=J`_p5Y(7t0DM%{ks=^_&xSy5nK2OZIZJXz;mh*V({PGhnb;;SUEmM%C zrgrYqJ!1KOmy3;+`)RiqflBLg49``@dmWiqyk(Q{rT;(vPV4ymrqHi4Z{x{=;3uD0 z*SZ!w6WRMNrL5rQgxO^b+#D-6)*AR1a;|BJ^Y<^EaP!v5%dyN~r)Pd&^iY!3HGtR?Y^J42;D=?oJHr&dIz4a$Hg)JkxxA z8MJ_G4hF{dOa>N^5+IfWVg?501&j>LK$;OGwtxvPtG0j{&Q=8}JRB6&&A`C4)YHW= zB!ctp41bT%K!IcPrzLtxIk|dxuFgqq)mC0~Ys*A4U9Cl8+q94DU8u{wy)|jI8Qa$E zz#XeI#6=vpbSx0la;#VA`SwjH){d3&XPxU{` z7euY?Yh?W(>C;rU-#A%R`0#;svn6$}cs*RkUbA`K3GpSncb2I{rwXaQKmW~eHnaAT zSj)F}Gm#w%&ZoCwDUGP?y3U@joVQCUZCajV!t{`+zdgEXz-QI&Nz? z4mlt5nZb7pXb0c6X8nbe74}U&+M?dnIrD&rM4i&jXJ@CxIsb5ZapcJJNjwHuc=eh# zCwN~9n5$-S>wxuxwd|#C*Yyl5Wpvnh-glO-FEx=fv={u)y70x4!pkrA_RbH!e`m*1 zuhyM^AMk&;_B6$BS$!qXl|1#yYs`!`Gum{2uw8m@${Ekkk2xkz(N`6|=<(m9#rxi` zy}bpTk@7#}6#w}C4d7C&-R|kL?6)Bw8-9NU_6auqjukdX=$9tObmb9R4#w2t%`LEc%##iH-5$Oz+0wQ_TJxgnSp_6p{I*u zNCxZMS?9e)14WLfugOSEVAEY35R(z$$f~<&L1=(N*X(}`+0%EIJf5$R+r**7wa7s{ zP~{RoOY|1ul0@A}D_k!siBHu?va01f(|r2Jv&wa!pPf7Uu2H}E`MI6XYxY0CBPsvb zWn%j@m6MVi82&Zc@oT=)RD7To!ECyFeY~t|b?%(gUt0e3 zG(WcvoV+gbVZ~YVK-~#ate-m8rJtYihEe>0Kn4FMvj?vugnY6TOh}H;$*EU&$Zx1hrj+w>3h9B zNZvB1LAi3@)bq~kgkxBD)@$#)#&u?`d4%Q1MJxg;{Jpbm^E9O{G=Jr0YhS$ct;w8i zJeuz8lWiC9nrznK{8-oQ`e9-`bJ4!Jx{`eCYgiPY2zBQ-3%gC&)HeU5dDDu{T%jDP z>&d01oOP|O>l?WZ4+WMTw5ZHK(RnSTi9^d?=6#dxtpmPaq?P|KWczp~cjgKf!8bN5 zUP<2fdUubl=~-`f@vZ6?Q#P*XdU$frk~_)9B_E8gr*_+2;>tSD{UT%E#cACFUsoM- z-u-mdj`A#-rz_5_7foUNUM0g)c~|a4D_<+0`LhqZ{f4l+flmf zg5v8*mT>}BPj)=G_R^_+jrX&PgY~x;rXFuSw=DSi^|BY)X3dX3>))!0a=gqEbDd51 zpnsg>Y9G~(?{W4WW&WZYfBJp$J(P3#@TamO`wFI*ThWsu_deP3Z&C}(^A*!-`Y-dI u`tkpQ{G&Ri$tBNtk0&f-g23I|nf^0K%!!Y5YhK+7O0=G?elF{r5}E+4PFUyw literal 0 HcmV?d00001 diff --git a/Objective-C/TOCropViewController/Resources/rotate.right.fill@2x.png b/Objective-C/TOCropViewController/Resources/rotate.right.fill@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d67ca52f504fa2afa52955aa0e02af69ba76adee GIT binary patch literal 816 zcmeAS@N?(olHy`uVBq!ia0vp^ra)}Q!3HGLi!R;+QjEnx?oJHr&dIz4a$Hg)JkxxA z8MJ_G4hF{dOa>N^5+IfWVg?501&j>LK$;OGwtxvPYr23L&Nc%nTyV2gfq{W(mZytj zNCxZMSsSxO9R-fvKiaWCp_65c!Q6`>lSGvEgiLZun$_TRAn1gALaOR7uI>=ei6+~c zT2oI=Kc+O7Mc5>PNl~*!po7zci~H-J-Zuv~-g$ew_RYQ6jm+}Z+l{S1*KRKFSDhYq z`rsv#w+tIg3oa*S7CQ@WQtg^D*T61IFWNh;vHx7W(7=WxbARoa^2g| z%PxB_PU+ZT`hjQ5L5myAuHPn~{JD&C*3}tu1@->wCu1)1A65Ij{mYH0_lhPAWe#cg zckT#u>H4W3IYBC1LSOXgH7j<_+UauB1eLxu3l$&xu|sFwx5^W1wH6uW%;I>spX=Fy z4$XCC@+{jO)8^jdtW+>?==<|~^JB&Ix0+8%Ce%-P5^~}4jj%{_&L07ur=OoIB|l@5fb#w+`VDg~tlpsKnm5}*^p{E6UYEUBAFbXoHU8E{ zr%$SrKlwe?J$wJjdQTnpJFS0WT(3J{i7Z;Fx@ggIZNx&8XuRl z4bKz0#6O?dkag$*_gkTP<+ATFtZm1eE0q;i$GEP2jdhWg@@^}Sx?V1LmG}JaEr%5APuN^5+IfWVg?501&j>LK$;OGwtxvPtF(X_&Q=5|biH-E1*q(pr;B4q z1n1W2w*H3=1lqc1B(_bgZ)}+%pevo!!1^!hYU_GVMMEXSLoCLBxEy#46k4Av=}D;f z$v)3{`SxbspL>hr-l|MqwTkb8`6aXEn{70&e3-Y$+}CZ7lee(lyt$rl=Up;vKNzfF zXWA)T&hq5y%!1wNJZBO@7MgLfUAE)Uyu9Y8 zwUGvsF^5y;n#4^CHZ!VyoODqobh+3`FJ7SVFg?iOp5&eG?3Bx}JP` z;YpngYclJ?08WFq$NjQ7*wy}S6z delegate; /** - If true, when the user hits 'Done', a UIActivityController will appear + If YES, when the user hits 'Done', a UIActivityController will appear before the view controller ends. */ @property (nonatomic, assign) BOOL showActivitySheetOnDone; @@ -137,6 +161,18 @@ */ @property (nonatomic, assign) NSInteger angle; +/** + Use this and the ``angle`` property to flip (mirror) the image. + For horizontal flip set flipped to YES and angle to 0. + For vertical flip set flipped to YES and angle to 180. + For both horizontal and vertical flip set flipped to NO and angle to 180. + + This property can be set before the controller is presented to have + the image 'restored' to a previous cropping layout. + */ +@property (nonatomic, assign) BOOL flipped; +@property (nonatomic, assign) BOOL mirrored __attribute((unavailable("Use 'flipped' instead."))); + /** The toolbar view managed by this view controller. */ @@ -182,7 +218,7 @@ @property (nullable, nonatomic, copy) NSString *cancelButtonTitle; /** - If true, button icons are visible in portairt instead button text. + If YES, button icons are visible in portairt instead button text. Default is NO. */ @@ -207,8 +243,8 @@ @property (nonatomic, assign) BOOL showCancelConfirmationDialog; /** - If true, a custom aspect ratio is set, and the aspectRatioLockEnabled is set to YES, the crop box - will swap it's dimensions depending on portrait or landscape sized images. + If YES, a custom aspect ratio is set, and the aspectRatioLockEnabled is set to YES, the crop box + will swap its dimensions depending on portrait or landscape sized images. This value also controls whether the dimensions can swap when the image is rotated. Default is NO. @@ -216,7 +252,7 @@ @property (nonatomic, assign) BOOL aspectRatioLockDimensionSwapEnabled; /** - If true, while it can still be resized, the crop box will be locked to its current aspect ratio. + If YES, while it can still be resized, the crop box will be locked to its current aspect ratio. If this is set to YES, and `resetAspectRatioEnabled` is set to NO, then the aspect ratio button will automatically be hidden from the toolbar. @@ -226,7 +262,7 @@ @property (nonatomic, assign) BOOL aspectRatioLockEnabled; /** - If true, tapping the reset button will also reset the aspect ratio back to the image + If YES, tapping the reset button will also reset the aspect ratio back to the image default ratio. Otherwise, the reset will just zoom out to the current aspect ratio. If this is set to NO, and `aspectRatioLockEnabled` is set to YES, then the aspect ratio @@ -249,9 +285,23 @@ */ @property (nonatomic, assign) BOOL rotateClockwiseButtonHidden; +/** + When enabled, hides the flip horizontal button on the toolbar. + + Default is NO. + */ +@property (nonatomic, assign) BOOL flipHorizontalButtonHidden; + +/** + When enabled, hides the flip vertical button on the toolbar. + + Default is YES. + */ +@property (nonatomic, assign) BOOL flipVerticalButtonHidden; + /* If this controller is embedded in UINavigationController its navigation bar - is hidden by default. Set this property to false to show the navigation bar. + is hidden by default. Set this property to NO to show the navigation bar. This must be set before this controller is presented. */ @property (nonatomic, assign) BOOL hidesNavigationBar; @@ -296,26 +346,25 @@ Default is NO. */ -@property (nonatomic, assign) BOOL reverseContentLayout -; +@property (nonatomic, assign) BOOL reverseContentLayout; /** - If `showActivitySheetOnDone` is true, then these activity items will - be supplied to that UIActivityViewController in addition to the + If `showActivitySheetOnDone` is YES, then these activity items will + be supplied to that UIActivityViewController in addition to the `TOActivityCroppedImageProvider` object. */ @property (nullable, nonatomic, strong) NSArray *activityItems; /** - If `showActivitySheetOnDone` is true, then you may specify any - custom activities your app implements in this array. If your activity requires + If `showActivitySheetOnDone` is YES, then you may specify any + custom activities your app implements in this array. If your activity requires access to the cropping information, it can be accessed in the supplied `TOActivityCroppedImageProvider` object */ @property (nullable, nonatomic, strong) NSArray *applicationActivities; /** - If `showActivitySheetOnDone` is true, then you may expliclty + If `showActivitySheetOnDone` is YES, then you may expliclty set activities that won't appear in the share sheet here. */ @property (nullable, nonatomic, strong) NSArray *excludedActivityTypes; @@ -338,32 +387,35 @@ just the cropping rectangle. @param cropRect A rectangle indicating the crop region of the image the user chose - (In the original image's local co-ordinate space) + (In the original image's local coordinate space) @param angle The angle of the image when it was cropped + @param flipped Whether the image was flipped (mirrored) when it was cropped */ -@property (nullable, nonatomic, strong) void (^onDidCropImageToRect)(CGRect cropRect, NSInteger angle); +@property (nullable, nonatomic, strong) void (^onDidCropImageToRect)(CGRect cropRect, NSInteger angle, BOOL flipped); /** Called when the user has committed the crop action, and provides - both the cropped image with crop co-ordinates. + both the cropped image with crop coordinates. @param image The newly cropped image. @param cropRect A rectangle indicating the crop region of the image the user chose - (In the original image's local co-ordinate space) + (In the original image's local coordinate space) @param angle The angle of the image when it was cropped + @param flipped Whether the image was flipped (mirrored) when it was cropped */ -@property (nullable, nonatomic, strong) void (^onDidCropToRect)(UIImage* _Nonnull image, CGRect cropRect, NSInteger angle); +@property (nullable, nonatomic, strong) void (^onDidCropToRect)(UIImage* _Nonnull image, CGRect cropRect, NSInteger angle, BOOL flipped); /** If the cropping style is set to circular, this block will return a circle-cropped version of the selected - image, as well as it's cropping co-ordinates + image, as well as its cropping coordinates @param image The newly cropped image, clipped to a circle shape @param cropRect A rectangle indicating the crop region of the image the user chose - (In the original image's local co-ordinate space) + (In the original image's local coordinate space) @param angle The angle of the image when it was cropped + @param flipped Whether the image was flipped (mirrored) when it was cropped */ -@property (nullable, nonatomic, strong) void (^onDidCropToCircleImage)(UIImage* _Nonnull image, CGRect cropRect, NSInteger angle); +@property (nullable, nonatomic, strong) void (^onDidCropToCircleImage)(UIImage* _Nonnull image, CGRect cropRect, NSInteger angle, BOOL flipped); ///------------------------------------------------ @@ -432,6 +484,7 @@ @param fromView A view that's frame will be used as the origin for this animation. Optional if `fromFrame` has a value. @param fromFrame In the screen's coordinate space, the frame from which the image should animate from. @param angle The rotation angle in which the image was rotated when it was originally cropped. + @param flipped Whether the image was flipped (mirrored) when it was originally cropped. @param toFrame In the image's coordinate space, the previous crop frame that created the previous crop @param setup A block that is called just before the transition starts. Recommended for hiding any necessary image views. @param completion A block that is called once the transition animation is completed. @@ -441,9 +494,10 @@ fromView:(nullable UIView *)fromView fromFrame:(CGRect)fromFrame angle:(NSInteger)angle + flipped:(BOOL)flipped toImageFrame:(CGRect)toFrame setup:(nullable void (^)(void))setup - completion:(nullable void (^)(void))completion NS_SWIFT_NAME(presentAnimatedFrom(_:fromImage:fromView:fromFrame:angle:toFrame:setup:completion:)); + completion:(nullable void (^)(void))completion NS_SWIFT_NAME(presentAnimatedFrom(_:fromImage:fromView:fromFrame:angle:flipped:toFrame:setup:completion:)); /** Play a custom animation of the supplied cropped image zooming out from diff --git a/Objective-C/TOCropViewController/TOCropViewController.m b/Objective-C/TOCropViewController/TOCropViewController.m index df3665a2..6aa11ee5 100755 --- a/Objective-C/TOCropViewController/TOCropViewController.m +++ b/Objective-C/TOCropViewController/TOCropViewController.m @@ -85,7 +85,8 @@ - (instancetype)initWithCroppingStyle:(TOCropViewCroppingStyle)style image:(UIIm // Set up base view controller behaviour self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; self.modalPresentationStyle = UIModalPresentationFullScreen; - self.hidesNavigationBar = true; + self.hidesNavigationBar = YES; + self.flipVerticalButtonHidden = YES; // Controller object that handles the transition animation when presenting / dismissing this app _transitionController = [[TOCropViewControllerTransitioning alloc] init]; @@ -134,6 +135,8 @@ - (void)viewDidLoad self.toolbar.clampButtonTapped = ^{ [weakSelf showAspectRatioDialog]; }; self.toolbar.rotateCounterclockwiseButtonTapped = ^{ [weakSelf rotateCropViewCounterclockwise]; }; self.toolbar.rotateClockwiseButtonTapped = ^{ [weakSelf rotateCropViewClockwise]; }; + self.toolbar.flipHorizontalButtonTapped = ^{ [weakSelf flipCropViewHorizontally]; }; + self.toolbar.flipVerticalButtonTapped = ^{ [weakSelf flipCropViewVertically]; }; } - (void)viewWillAppear:(BOOL)animated @@ -705,6 +708,16 @@ - (void)rotateCropViewCounterclockwise [self.cropView rotateImageNinetyDegreesAnimated:YES clockwise:NO]; } +- (void)flipCropViewHorizontally +{ + [self.cropView flipImageAnimated:YES horizontal:YES]; +} + +- (void)flipCropViewVertically +{ + [self.cropView flipImageAnimated:YES horizontal:NO]; +} + #pragma mark - Crop View Delegates - - (void)cropViewDidBecomeResettable:(TOCropView *)cropView { @@ -724,7 +737,7 @@ - (void)presentAnimatedFromParentViewController:(UIViewController *)viewControll completion:(void (^)(void))completion { [self presentAnimatedFromParentViewController:viewController fromImage:nil fromView:fromView fromFrame:fromFrame - angle:0 toImageFrame:CGRectZero setup:setup completion:completion]; + angle:0 flipped:NO toImageFrame:CGRectZero setup:setup completion:completion]; } - (void)presentAnimatedFromParentViewController:(UIViewController *)viewController @@ -732,6 +745,7 @@ - (void)presentAnimatedFromParentViewController:(UIViewController *)viewControll fromView:(UIView *)fromView fromFrame:(CGRect)fromFrame angle:(NSInteger)angle + flipped:(BOOL)flipped toImageFrame:(CGRect)toFrame setup:(void (^)(void))setup completion:(void (^)(void))completion @@ -760,6 +774,8 @@ - (void)presentAnimatedFromParentViewController:(UIViewController *)viewControll if (!CGRectIsEmpty(fromFrame)) { [strongSelf.cropView setGridOverlayHidden:NO animated:YES]; } + + strongSelf.cropView.isFlippedHorizontally = flipped; }]; } @@ -916,11 +932,21 @@ - (void)doneButtonTapped { CGRect cropFrame = self.cropView.imageCropFrame; NSInteger angle = self.cropView.angle; + BOOL flipped = self.cropView.isFlippedHorizontally; + + //Convert vertical flip to a horizontal flip + 180 degree rotation + if (self.cropView.isFlippedVertically) { + angle += 180; + angle %= 360; + cropFrame.origin.x = self.cropView.image.size.width - cropFrame.size.width - cropFrame.origin.x; + cropFrame.origin.y = self.cropView.image.size.height - cropFrame.size.height - cropFrame.origin.y; + flipped ^= YES; //Flipped both ways equals a 180 rotation without flip + } //If desired, when the user taps done, show an activity sheet if (self.showActivitySheetOnDone) { - TOActivityCroppedImageProvider *imageItem = [[TOActivityCroppedImageProvider alloc] initWithImage:self.image cropFrame:cropFrame angle:angle circular:(self.croppingStyle == TOCropViewCroppingStyleCircular)]; - TOCroppedImageAttributes *attributes = [[TOCroppedImageAttributes alloc] initWithCroppedFrame:cropFrame angle:angle originalImageSize:self.image.size]; + TOActivityCroppedImageProvider *imageItem = [[TOActivityCroppedImageProvider alloc] initWithImage:self.image cropFrame:cropFrame angle:angle flipped:flipped circular:(self.croppingStyle == TOCropViewCroppingStyleCircular)]; + TOCroppedImageAttributes *attributes = [[TOCroppedImageAttributes alloc] initWithCroppedFrame:cropFrame angle:angle flipped:flipped originalImageSize:self.image.size]; NSMutableArray *activityItems = [@[imageItem, attributes] mutableCopy]; if (self.activityItems) { @@ -966,64 +992,76 @@ - (void)doneButtonTapped return; } else { - self.toolbar.doneTextButton.enabled = false; + self.toolbar.doneTextButton.enabled = NO; } BOOL isCallbackOrDelegateHandled = NO; //If the delegate/block that only supplies crop data is provided, call it + if ([self.delegate respondsToSelector:@selector(cropViewController:didCropImageToRect:angle:flipped:)]) { + [self.delegate cropViewController:self didCropImageToRect:cropFrame angle:angle flipped:flipped]; + isCallbackOrDelegateHandled = YES; + } + if ([self.delegate respondsToSelector:@selector(cropViewController:didCropImageToRect:angle:)]) { [self.delegate cropViewController:self didCropImageToRect:cropFrame angle:angle]; isCallbackOrDelegateHandled = YES; } if (self.onDidCropImageToRect != nil) { - self.onDidCropImageToRect(cropFrame, angle); + self.onDidCropImageToRect(cropFrame, angle, flipped); isCallbackOrDelegateHandled = YES; } // Check if the circular APIs were implemented - BOOL isCircularImageDelegateAvailable = [self.delegate respondsToSelector:@selector(cropViewController:didCropToCircularImage:withRect:angle:)]; + BOOL isCircularImageDelegateAvailable = [self.delegate respondsToSelector:@selector(cropViewController:didCropToCircularImage:withRect:angle:flipped:)]; + BOOL isCircularImageDelegateNoFlipAvailable = [self.delegate respondsToSelector:@selector(cropViewController:didCropToCircularImage:withRect:angle:)]; BOOL isCircularImageCallbackAvailable = self.onDidCropToCircleImage != nil; // Check if non-circular was implemented - BOOL isDidCropToImageDelegateAvailable = [self.delegate respondsToSelector:@selector(cropViewController:didCropToImage:withRect:angle:)]; + BOOL isDidCropToImageDelegateAvailable = [self.delegate respondsToSelector:@selector(cropViewController:didCropToImage:withRect:angle:flipped:)]; + BOOL isDidCropToImageDelegateNoFlipAvailable = [self.delegate respondsToSelector:@selector(cropViewController:didCropToImage:withRect:angle:)]; BOOL isDidCropToImageCallbackAvailable = self.onDidCropToRect != nil; //If cropping circular and the circular generation delegate/block is implemented, call it - if (self.croppingStyle == TOCropViewCroppingStyleCircular && (isCircularImageDelegateAvailable || isCircularImageCallbackAvailable)) { - UIImage *image = [self.image croppedImageWithFrame:cropFrame angle:angle circularClip:YES]; + if (self.croppingStyle == TOCropViewCroppingStyleCircular && (isCircularImageDelegateAvailable || isDidCropToImageDelegateNoFlipAvailable || isCircularImageCallbackAvailable)) { + UIImage *image = [self.image croppedImageWithFrame:cropFrame angle:angle flip:flipped circularClip:YES]; - //Dispatch on the next run-loop so the animation isn't interuppted by the crop operation + //Dispatch on the next run-loop so the animation isn't interrupted by the crop operation dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.03 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if (isCircularImageDelegateAvailable) { + [self.delegate cropViewController:self didCropToCircularImage:image withRect:cropFrame angle:angle flipped:flipped]; + } + if (isCircularImageDelegateNoFlipAvailable) { [self.delegate cropViewController:self didCropToCircularImage:image withRect:cropFrame angle:angle]; } if (isCircularImageCallbackAvailable) { - self.onDidCropToCircleImage(image, cropFrame, angle); + self.onDidCropToCircleImage(image, cropFrame, angle, flipped); } }); isCallbackOrDelegateHandled = YES; } //If the delegate/block that requires the specific cropped image is provided, call it - else if (isDidCropToImageDelegateAvailable || isDidCropToImageCallbackAvailable) { + else if (isDidCropToImageDelegateAvailable || isDidCropToImageDelegateNoFlipAvailable || isDidCropToImageCallbackAvailable) { UIImage *image = nil; - if (angle == 0 && CGRectEqualToRect(cropFrame, (CGRect){CGPointZero, self.image.size})) { + if (angle == 0 && !flipped && CGRectEqualToRect(cropFrame, (CGRect){CGPointZero, self.image.size})) { image = self.image; } else { - image = [self.image croppedImageWithFrame:cropFrame angle:angle circularClip:NO]; + image = [self.image croppedImageWithFrame:cropFrame angle:angle flip:flipped circularClip:NO]; } //Dispatch on the next run-loop so the animation isn't interuppted by the crop operation dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.03 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if (isDidCropToImageDelegateAvailable) { + [self.delegate cropViewController:self didCropToImage:image withRect:cropFrame angle:angle flipped:flipped]; + } + if (isDidCropToImageDelegateNoFlipAvailable) { [self.delegate cropViewController:self didCropToImage:image withRect:cropFrame angle:angle]; } - if (isDidCropToImageCallbackAvailable) { - self.onDidCropToRect(image, cropFrame, angle); + self.onDidCropToRect(image, cropFrame, angle, flipped); } }); @@ -1042,6 +1080,23 @@ - (void)commitCurrentCrop #pragma mark - Property Methods - +- (void)setDelegate:(id)delegate { + //Check for usage of outdated (pre-flip) delegate methods. + //When on a fresh build (less than 2 hours old) an exception is triggered telling you to update. + if ([delegate respondsToSelector:@selector(cropViewController:didCropImageToRect:angle:)] || + [delegate respondsToSelector:@selector(cropViewController:didCropToImage:withRect:angle:)] || + [delegate respondsToSelector:@selector(cropViewController:didCropToCircularImage:withRect:angle:)]) + { + NSString *bundleName = [[NSBundle mainBundle] infoDictionary][@"CFBundleExecutable"]; + NSString *infoPath = [[NSBundle mainBundle] pathForResource:bundleName ofType:nil]; + NSDate *date = [NSFileManager.defaultManager attributesOfItemAtPath:infoPath error:nil][NSFileCreationDate]; + if ([date timeIntervalSinceNow] > -7200) { + [NSException raise:@"TOCropViewController error" format:@"TOCropViewController has updated: add arg 'flipped:(BOOL)flipped' to your didCrop delegate method implementations."]; + } + } + _delegate = delegate; +} + - (void)setTitle:(NSString *)title { [super setTitle:title]; @@ -1148,6 +1203,26 @@ - (void)setRotateButtonsHidden:(BOOL)rotateButtonsHidden self.toolbar.rotateClockwiseButtonHidden = rotateButtonsHidden; } +- (BOOL)flipVerticalButtonHidden +{ + return self.toolbar.flipVerticalButtonHidden; +} + +- (void)setFlipVerticalButtonHidden:(BOOL)flipVerticalButtonHidden +{ + self.toolbar.flipVerticalButtonHidden = flipVerticalButtonHidden; +} + +- (BOOL)flipHorizontalButtonHidden +{ + return self.toolbar.flipHorizontalButtonHidden; +} + +- (void)setFlipHorizontalButtonHidden:(BOOL)flipHorizontalButtonHidden +{ + self.toolbar.flipHorizontalButtonHidden = flipHorizontalButtonHidden; +} + - (void)setResetButtonHidden:(BOOL)resetButtonHidden { self.toolbar.resetButtonHidden = resetButtonHidden; @@ -1237,6 +1312,16 @@ - (NSInteger)angle return self.cropView.angle; } +- (void)setFlipped:(BOOL)flipped +{ + self.cropView.isFlippedHorizontally = flipped; +} + +- (BOOL)flipped +{ + return self.cropView.isFlippedHorizontally ^ self.cropView.isFlippedVertically; +} + - (void)setImageCropFrame:(CGRect)imageCropFrame { self.cropView.imageCropFrame = imageCropFrame; diff --git a/Objective-C/TOCropViewController/Views/TOCropToolbar.h b/Objective-C/TOCropViewController/Views/TOCropToolbar.h index ff621729..5827afc4 100644 --- a/Objective-C/TOCropViewController/Views/TOCropToolbar.h +++ b/Objective-C/TOCropViewController/Views/TOCropToolbar.h @@ -60,6 +60,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong, readonly) UIButton *resetButton; @property (nonatomic, strong, readonly) UIButton *clampButton; @property (nullable, nonatomic, strong, readonly) UIButton *rotateClockwiseButton; +@property (nonatomic, strong, readonly) UIButton *flipHorizontalButton; +@property (nonatomic, strong, readonly) UIButton *flipVerticalButton; @property (nonatomic, readonly) UIButton *rotateButton; // Points to `rotateCounterClockwiseButton` @@ -70,6 +72,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, nonatomic, copy) void (^rotateClockwiseButtonTapped)(void); @property (nullable, nonatomic, copy) void (^clampButtonTapped)(void); @property (nullable, nonatomic, copy) void (^resetButtonTapped)(void); +@property (nullable, nonatomic, copy) void (^flipHorizontalButtonTapped)(void); +@property (nullable, nonatomic, copy) void (^flipVerticalButtonTapped)(void); /* State management for the 'clamp' button */ @property (nonatomic, assign) BOOL clampButtonGlowing; @@ -79,6 +83,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) BOOL clampButtonHidden; @property (nonatomic, assign) BOOL rotateCounterclockwiseButtonHidden; @property (nonatomic, assign) BOOL rotateClockwiseButtonHidden; +@property (nonatomic, assign) BOOL flipHorizontalButtonHidden; +@property (nonatomic, assign) BOOL flipVerticalButtonHidden; @property (nonatomic, assign) BOOL resetButtonHidden; @property (nonatomic, assign) BOOL doneButtonHidden; @property (nonatomic, assign) BOOL cancelButtonHidden; diff --git a/Objective-C/TOCropViewController/Views/TOCropToolbar.m b/Objective-C/TOCropViewController/Views/TOCropToolbar.m index 8782ac81..15aacc54 100644 --- a/Objective-C/TOCropViewController/Views/TOCropToolbar.m +++ b/Objective-C/TOCropViewController/Views/TOCropToolbar.m @@ -21,6 +21,7 @@ // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #import "TOCropToolbar.h" +#import "UIView+Pixels.h" #define TOCROPTOOLBAR_DEBUG_SHOWING_BUTTONS_CONTAINER_RECT 0 // convenience debug toggle @@ -133,6 +134,20 @@ - (void)setup { [_rotateClockwiseButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:_rotateClockwiseButton]; + _flipHorizontalButton = [UIButton buttonWithType:UIButtonTypeSystem]; + _flipHorizontalButton.contentMode = UIViewContentModeCenter; + _flipHorizontalButton.tintColor = [UIColor whiteColor]; + [_flipHorizontalButton setImage:[TOCropToolbar flipHImage] forState:UIControlStateNormal]; + [_flipHorizontalButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:_flipHorizontalButton]; + + _flipVerticalButton = [UIButton buttonWithType:UIButtonTypeSystem]; + _flipVerticalButton.contentMode = UIViewContentModeCenter; + _flipVerticalButton.tintColor = [UIColor whiteColor]; + [_flipVerticalButton setImage:[TOCropToolbar flipVImage] forState:UIControlStateNormal]; + [_flipVerticalButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:_flipVerticalButton]; + _resetButton = [UIButton buttonWithType:UIButtonTypeSystem]; _resetButton.contentMode = UIViewContentModeCenter; _resetButton.tintColor = [UIColor whiteColor]; @@ -153,10 +168,10 @@ - (void)layoutSubviews BOOL verticalLayout = (CGRectGetWidth(self.bounds) < CGRectGetHeight(self.bounds)); CGSize boundsSize = self.bounds.size; - self.cancelIconButton.hidden = self.cancelButtonHidden || (_showOnlyIcons ? false : !verticalLayout); - self.cancelTextButton.hidden = self.cancelButtonHidden || (_showOnlyIcons ? true : verticalLayout); - self.doneIconButton.hidden = self.doneButtonHidden || (_showOnlyIcons ? false : !verticalLayout); - self.doneTextButton.hidden = self.doneButtonHidden || (_showOnlyIcons ? true : verticalLayout); + self.cancelIconButton.hidden = self.cancelButtonHidden || (_showOnlyIcons ? NO : !verticalLayout); + self.cancelTextButton.hidden = self.cancelButtonHidden || (_showOnlyIcons ? YES : verticalLayout); + self.doneIconButton.hidden = self.doneButtonHidden || (_showOnlyIcons ? NO : !verticalLayout); + self.doneTextButton.hidden = self.doneButtonHidden || (_showOnlyIcons ? YES : verticalLayout); CGRect frame = self.bounds; frame.origin.x -= self.backgroundViewOutsets.left; @@ -215,8 +230,8 @@ - (void)layoutSubviews else { width = CGRectGetMinX((_showOnlyIcons ? self.cancelIconButton : self.cancelTextButton).frame) - CGRectGetMaxX((_showOnlyIcons ? self.doneIconButton : self.doneTextButton).frame); } - - CGRect containerRect = CGRectIntegral((CGRect){x,frame.origin.y,width,44.0}); + + CGRect containerRect = CGRectInset([self CGRectIntegralRetina:(CGRect){x,frame.origin.y,width,44.0}], _showOnlyIcons ? 0 : insetPadding, 0); #if TOCROPTOOLBAR_DEBUG_SHOWING_BUTTONS_CONTAINER_RECT containerView.frame = containerRect; @@ -225,21 +240,30 @@ - (void)layoutSubviews CGSize buttonSize = (CGSize){44.0,44.0}; NSMutableArray *buttonsInOrderHorizontally = [NSMutableArray new]; + + if (!self.clampButtonHidden) { + [buttonsInOrderHorizontally addObject:self.clampButton]; + } + if (!self.rotateCounterclockwiseButtonHidden) { [buttonsInOrderHorizontally addObject:self.rotateCounterclockwiseButton]; } - if (!self.resetButtonHidden) { - [buttonsInOrderHorizontally addObject:self.resetButton]; + if (!self.flipHorizontalButtonHidden) { + [buttonsInOrderHorizontally addObject:self.flipHorizontalButton]; } - - if (!self.clampButtonHidden) { - [buttonsInOrderHorizontally addObject:self.clampButton]; + + if (!self.flipVerticalButtonHidden) { + [buttonsInOrderHorizontally addObject:self.flipVerticalButton]; } - + if (!self.rotateClockwiseButtonHidden) { [buttonsInOrderHorizontally addObject:self.rotateClockwiseButton]; } + + if (!self.resetButtonHidden) { + [buttonsInOrderHorizontally addObject:self.resetButton]; + } [self layoutToolbarButtons:buttonsInOrderHorizontally withSameButtonSize:buttonSize inContainerRect:containerRect horizontally:YES]; } else { @@ -263,20 +287,29 @@ - (void)layoutSubviews CGSize buttonSize = (CGSize){44.0,44.0}; NSMutableArray *buttonsInOrderVertically = [NSMutableArray new]; + + if (!self.clampButtonHidden) { + [buttonsInOrderVertically addObject:self.clampButton]; + } + + if (!self.rotateClockwiseButtonHidden) { + [buttonsInOrderVertically addObject:self.rotateClockwiseButton]; + } + if (!self.rotateCounterclockwiseButtonHidden) { [buttonsInOrderVertically addObject:self.rotateCounterclockwiseButton]; } - if (!self.resetButtonHidden) { - [buttonsInOrderVertically addObject:self.resetButton]; + if (!self.flipHorizontalButtonHidden) { + [buttonsInOrderVertically addObject:self.flipHorizontalButton]; } - - if (!self.clampButtonHidden) { - [buttonsInOrderVertically addObject:self.clampButton]; + + if (!self.flipVerticalButtonHidden) { + [buttonsInOrderVertically addObject:self.flipVerticalButton]; } - if (!self.rotateClockwiseButtonHidden) { - [buttonsInOrderVertically addObject:self.rotateClockwiseButton]; + if (!self.resetButtonHidden) { + [buttonsInOrderVertically addObject:self.resetButton]; } [self layoutToolbarButtons:buttonsInOrderVertically withSameButtonSize:buttonSize inContainerRect:containerRect horizontally:NO]; @@ -334,6 +367,14 @@ - (void)buttonTapped:(id)button self.clampButtonTapped(); return; } + else if (button == self.flipHorizontalButton && self.flipHorizontalButtonTapped) { + self.flipHorizontalButtonTapped(); + return; + } + else if (button == self.flipVerticalButton && self.flipVerticalButtonTapped) { + self.flipVerticalButtonTapped(); + return; + } } - (CGRect)clampButtonFrame @@ -465,19 +506,7 @@ + (UIImage *)doneImage return [UIImage systemImageNamed:@"checkmark" withConfiguration:[UIImageSymbolConfiguration configurationWithWeight:UIImageSymbolWeightSemibold]]; } - - UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:(CGSize){17,14}]; - UIImage *doneImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *rendererContext) { - UIBezierPath* rectanglePath = UIBezierPath.bezierPath; - [rectanglePath moveToPoint: CGPointMake(1, 7)]; - [rectanglePath addLineToPoint: CGPointMake(6, 12)]; - [rectanglePath addLineToPoint: CGPointMake(16, 1)]; - [UIColor.whiteColor setStroke]; - rectanglePath.lineWidth = 2; - [rectanglePath stroke]; - }]; - - return doneImage; + return [UIImage imageNamed:@"checkmark"]; } + (UIImage *)cancelImage @@ -486,25 +515,7 @@ + (UIImage *)cancelImage return [UIImage systemImageNamed:@"xmark" withConfiguration:[UIImageSymbolConfiguration configurationWithWeight:UIImageSymbolWeightSemibold]]; } - - UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:(CGSize){16,16}]; - UIImage *cancelImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *rendererContext) { - UIBezierPath* bezierPath = UIBezierPath.bezierPath; - [bezierPath moveToPoint: CGPointMake(15, 15)]; - [bezierPath addLineToPoint: CGPointMake(1, 1)]; - [UIColor.whiteColor setStroke]; - bezierPath.lineWidth = 2; - [bezierPath stroke]; - - UIBezierPath* bezier2Path = UIBezierPath.bezierPath; - [bezier2Path moveToPoint: CGPointMake(1, 15)]; - [bezier2Path addLineToPoint: CGPointMake(15, 1)]; - [UIColor.whiteColor setStroke]; - bezier2Path.lineWidth = 2; - [bezier2Path stroke]; - }]; - - return cancelImage; + return [UIImage imageNamed:@"xmark"]; } + (UIImage *)rotateCCWImage @@ -514,31 +525,7 @@ + (UIImage *)rotateCCWImage withConfiguration:[UIImageSymbolConfiguration configurationWithWeight:UIImageSymbolWeightSemibold]] imageWithBaselineOffsetFromBottom:4]; } - - UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:(CGSize){18,21}]; - UIImage *rotateImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *rendererContext) { - UIBezierPath* rectangle2Path = [UIBezierPath bezierPathWithRect: CGRectMake(0, 9, 12, 12)]; - [UIColor.whiteColor setFill]; - [rectangle2Path fill]; - - UIBezierPath* rectangle3Path = UIBezierPath.bezierPath; - [rectangle3Path moveToPoint: CGPointMake(5, 3)]; - [rectangle3Path addLineToPoint: CGPointMake(10, 6)]; - [rectangle3Path addLineToPoint: CGPointMake(10, 0)]; - [rectangle3Path addLineToPoint: CGPointMake(5, 3)]; - [rectangle3Path closePath]; - [UIColor.whiteColor setFill]; - [rectangle3Path fill]; - - UIBezierPath* bezierPath = UIBezierPath.bezierPath; - [bezierPath moveToPoint: CGPointMake(10, 3)]; - [bezierPath addCurveToPoint: CGPointMake(17.5, 11) controlPoint1: CGPointMake(15, 3) controlPoint2: CGPointMake(17.5, 5.91)]; - [UIColor.whiteColor setStroke]; - bezierPath.lineWidth = 1; - [bezierPath stroke]; - }]; - - return rotateImage; + return [UIImage imageNamed:@"rotate.left.fill"]; } + (UIImage *)rotateCWImage @@ -548,17 +535,39 @@ + (UIImage *)rotateCWImage withConfiguration:[UIImageSymbolConfiguration configurationWithWeight:UIImageSymbolWeightSemibold]] imageWithBaselineOffsetFromBottom:4]; } + return [UIImage imageNamed:@"rotate.right.fill"]; +} - UIImage *rotateCCWImage = [self.class rotateCCWImage]; - UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:rotateCCWImage.size]; - UIImage *rotateCWImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *rendererContext) { - CGContextRef context = rendererContext.CGContext; - CGContextTranslateCTM(context, rotateCCWImage.size.width, rotateCCWImage.size.height); - CGContextRotateCTM(context, M_PI); - CGContextDrawImage(context,CGRectMake(0,0,rotateCCWImage.size.width,rotateCCWImage.size.height),rotateCCWImage.CGImage); - }]; ++ (UIImage *)flipHImage +{ + UIImage* image; + if (@available(iOS 14.0, *)) { + image = [UIImage systemImageNamed:@"arrow.left.and.right.righttriangle.left.righttriangle.right.fill" + withConfiguration:[UIImageSymbolConfiguration configurationWithWeight:UIImageSymbolWeightSemibold]]; + } else { + return [UIImage imageNamed:@"arrow.trianglehead.left.and.right.righttriangle.left.righttriangle.right.fill"]; + } + if (@available(iOS 13.0, *)) { + return [image imageWithBaselineOffsetFromBottom:4]; + } else { + return image; + } +} - return rotateCWImage; ++ (UIImage *)flipVImage +{ + UIImage* image; + if (@available(iOS 14.0, *)) { + image = [UIImage systemImageNamed:@"arrow.up.and.down.righttriangle.up.fill.righttriangle.down.fill" + withConfiguration:[UIImageSymbolConfiguration configurationWithWeight:UIImageSymbolWeightSemibold]]; + } else { + image = [UIImage imageNamed:@"arrow.trianglehead.up.and.down.righttriangle.up.righttriangle.down.fill"]; + } + if (@available(iOS 13.0, *)) { + return [image imageWithBaselineOffsetFromBottom:4]; + } else { + return image; + } } + (UIImage *)resetImage @@ -566,81 +575,19 @@ + (UIImage *)resetImage if (@available(iOS 13.0, *)) { return [[UIImage systemImageNamed:@"arrow.counterclockwise" withConfiguration:[UIImageSymbolConfiguration configurationWithWeight:UIImageSymbolWeightSemibold]] - imageWithBaselineOffsetFromBottom:0];; + imageWithBaselineOffsetFromBottom:1]; } - - UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:(CGSize){22,18}]; - UIImage *resetImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *rendererContext) { - UIBezierPath* bezier2Path = UIBezierPath.bezierPath; - [bezier2Path moveToPoint: CGPointMake(22, 9)]; - [bezier2Path addCurveToPoint: CGPointMake(13, 18) controlPoint1: CGPointMake(22, 13.97) controlPoint2: CGPointMake(17.97, 18)]; - [bezier2Path addCurveToPoint: CGPointMake(13, 16) controlPoint1: CGPointMake(13, 17.35) controlPoint2: CGPointMake(13, 16.68)]; - [bezier2Path addCurveToPoint: CGPointMake(20, 9) controlPoint1: CGPointMake(16.87, 16) controlPoint2: CGPointMake(20, 12.87)]; - [bezier2Path addCurveToPoint: CGPointMake(13, 2) controlPoint1: CGPointMake(20, 5.13) controlPoint2: CGPointMake(16.87, 2)]; - [bezier2Path addCurveToPoint: CGPointMake(6.55, 6.27) controlPoint1: CGPointMake(10.1, 2) controlPoint2: CGPointMake(7.62, 3.76)]; - [bezier2Path addCurveToPoint: CGPointMake(6, 9) controlPoint1: CGPointMake(6.2, 7.11) controlPoint2: CGPointMake(6, 8.03)]; - [bezier2Path addLineToPoint: CGPointMake(4, 9)]; - [bezier2Path addCurveToPoint: CGPointMake(4.65, 5.63) controlPoint1: CGPointMake(4, 7.81) controlPoint2: CGPointMake(4.23, 6.67)]; - [bezier2Path addCurveToPoint: CGPointMake(7.65, 1.76) controlPoint1: CGPointMake(5.28, 4.08) controlPoint2: CGPointMake(6.32, 2.74)]; - [bezier2Path addCurveToPoint: CGPointMake(13, 0) controlPoint1: CGPointMake(9.15, 0.65) controlPoint2: CGPointMake(11, 0)]; - [bezier2Path addCurveToPoint: CGPointMake(22, 9) controlPoint1: CGPointMake(17.97, 0) controlPoint2: CGPointMake(22, 4.03)]; - [bezier2Path closePath]; - [UIColor.whiteColor setFill]; - [bezier2Path fill]; - - UIBezierPath* polygonPath = UIBezierPath.bezierPath; - [polygonPath moveToPoint: CGPointMake(5, 15)]; - [polygonPath addLineToPoint: CGPointMake(10, 9)]; - [polygonPath addLineToPoint: CGPointMake(0, 9)]; - [polygonPath addLineToPoint: CGPointMake(5, 15)]; - [polygonPath closePath]; - [UIColor.whiteColor setFill]; - [polygonPath fill]; - }]; - - return resetImage; + return [UIImage imageNamed:@"arrow.counterclockwise"]; } + (UIImage *)clampImage { - if (@available(iOS 13.0, *)) { + if (@available(iOS 13.1, *)) { return [[UIImage systemImageNamed:@"aspectratio.fill" withConfiguration:[UIImageSymbolConfiguration configurationWithWeight:UIImageSymbolWeightSemibold]] imageWithBaselineOffsetFromBottom:0]; } - - UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:(CGSize){22,16}]; - UIImage *clampImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *rendererContext) { - //// Color Declarations - UIColor* outerBox = [UIColor colorWithRed: 1 green: 1 blue: 1 alpha: 0.553]; - UIColor* innerBox = [UIColor colorWithRed: 1 green: 1 blue: 1 alpha: 0.773]; - - //// Rectangle Drawing - UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRect: CGRectMake(0, 3, 13, 13)]; - [UIColor.whiteColor setFill]; - [rectanglePath fill]; - - //// Outer - { - //// Top Drawing - UIBezierPath* topPath = [UIBezierPath bezierPathWithRect: CGRectMake(0, 0, 22, 2)]; - [outerBox setFill]; - [topPath fill]; - - - //// Side Drawing - UIBezierPath* sidePath = [UIBezierPath bezierPathWithRect: CGRectMake(19, 2, 3, 14)]; - [outerBox setFill]; - [sidePath fill]; - } - - //// Rectangle 2 Drawing - UIBezierPath* rectangle2Path = [UIBezierPath bezierPathWithRect: CGRectMake(14, 3, 4, 13)]; - [innerBox setFill]; - [rectangle2Path fill]; - }]; - - return clampImage; + return [UIImage imageNamed:@"aspectratio.fill"]; } #pragma mark - Accessors - diff --git a/Objective-C/TOCropViewController/Views/TOCropView.h b/Objective-C/TOCropViewController/Views/TOCropView.h index be282964..c1ceee19 100755 --- a/Objective-C/TOCropViewController/Views/TOCropView.h +++ b/Objective-C/TOCropViewController/Views/TOCropView.h @@ -73,8 +73,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, nonatomic, weak) id delegate; /** - If false, the user cannot resize the crop box frame using a pan gesture from a corner. - Default vaue is YES. + If NO, the user cannot resize the crop box frame using a pan gesture from a corner. + Default value is YES. */ @property (nonatomic, assign) BOOL cropBoxResizeEnabled; @@ -120,8 +120,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) BOOL aspectRatioLockEnabled; /** - If true, a custom aspect ratio is set, and the aspectRatioLockEnabled is set to YES, - the crop box will swap it's dimensions depending on portrait or landscape sized images. + If YES, a custom aspect ratio is set, and the aspectRatioLockEnabled is set to YES, + the crop box will swap its dimensions depending on portrait or landscape sized images. This value also controls whether the dimensions can swap when the image is rotated. Default is NO. @@ -135,7 +135,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) BOOL resetAspectRatioEnabled; /** - True when the height of the crop box is bigger than the width + YES when the height of the crop box is bigger than the width */ @property (nonatomic, readonly) BOOL cropBoxAspectRatioIsPortrait; @@ -144,6 +144,16 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, assign) NSInteger angle; +/** + Whether the canvas is flipped horizontally + */ +@property (nonatomic, assign) BOOL isFlippedHorizontally; + +/** + Whether the canvas is flipped vertically + */ +@property (nonatomic, assign) BOOL isFlippedVertically; + /** Hide all of the crop elements for transition animations */ @@ -160,12 +170,12 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) BOOL gridOverlayHidden; ///** -// Paddings of the crop rectangle. Default to 14.0 +// Paddings of the crop rectangle. Defaults to 14.0 // */ @property (nonatomic) CGFloat cropViewPadding; /** - Delay before crop frame is adjusted according new crop area. Default to 0.8 + Delay before crop frame is adjusted according new crop area. Defaults to 0.8 */ @property (nonatomic) NSTimeInterval cropAdjustingDelay; @@ -177,7 +187,7 @@ The minimum croping aspect ratio. If set, user is prevented from setting croppin /** The maximum scale that user can apply to image by pinching to zoom. Small values - are only recomended with aspectRatioLockEnabled set to true. Default to 15.0 + are only recommended with aspectRatioLockEnabled set to YES. Defaults to 15.0 */ @property (nonatomic, assign) CGFloat maximumZoomScale; @@ -275,6 +285,14 @@ The minimum croping aspect ratio. If set, user is prevented from setting croppin */ - (void)rotateImageNinetyDegreesAnimated:(BOOL)animated clockwise:(BOOL)clockwise; +/** + Flips (mirrors) the entire canvas + + @param animated Whether the transition is animated + @param horizontal Whether to flip horizontally (YES) or vertically (NO) + */ +- (void)flipImageAnimated:(BOOL)animated horizontal:(BOOL)horizontal; + /** Animate the grid overlay graphic to be visible */ diff --git a/Objective-C/TOCropViewController/Views/TOCropView.m b/Objective-C/TOCropViewController/Views/TOCropView.m index d9f4d293..16a12d87 100755 --- a/Objective-C/TOCropViewController/Views/TOCropView.m +++ b/Objective-C/TOCropViewController/Views/TOCropView.m @@ -55,6 +55,7 @@ @interface TOCropView () @property (nonatomic, strong) UIImageView *backgroundImageView; /* The main image view, placed within the scroll view */ @property (nonatomic, strong) UIView *backgroundContainerView; /* A view which contains the background image view, to separate its transforms from the scroll view. */ @property (nonatomic, strong, readwrite) UIView *foregroundContainerView; +@property (nonatomic, strong) UIView *foregroundMaskView; @property (nonatomic, strong) UIImageView *foregroundImageView; /* A copy of the background image view, placed over the dimming views */ @property (nonatomic, strong) TOCropScrollView *scrollView; /* The scroll view in charge of panning/zooming the image. */ @property (nonatomic, strong) UIView *overlayView; /* A semi-transparent grey view, overlaid on top of the background image */ @@ -83,7 +84,7 @@ @interface TOCropView () /* View State information */ @property (nonatomic, readonly) CGRect contentBounds; /* Give the current screen real-estate, the frame that the scroll view is allowed to use */ @property (nonatomic, readonly) CGSize imageSize; /* Given the current rotation of the image, the size of the image */ -@property (nonatomic, readonly) BOOL hasAspectRatio; /* True if an aspect ratio was explicitly applied to this crop view */ +@property (nonatomic, readonly) BOOL hasAspectRatio; /* YES if an aspect ratio was explicitly applied to this crop view */ /* 90-degree rotation state data */ @property (nonatomic, assign) CGSize cropBoxLastEditedSize; /* When performing 90-degree rotations, remember what our last manual size was to use that as a base */ @@ -206,15 +207,20 @@ - (void)setup self.translucencyView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [self addSubview:self.translucencyView]; - // The forground container that holds the foreground image view + // The foreground container that holds the foreground image view self.foregroundContainerView = [[UIView alloc] initWithFrame:(CGRect){0,0,200,200}]; - self.foregroundContainerView.clipsToBounds = YES; self.foregroundContainerView.userInteractionEnabled = NO; [self addSubview:self.foregroundContainerView]; + self.foregroundMaskView = [[UIView alloc] initWithFrame:self.foregroundContainerView.bounds]; + self.foregroundMaskView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + self.foregroundMaskView.clipsToBounds = YES; + self.foregroundMaskView.userInteractionEnabled = NO; + [self.foregroundContainerView addSubview:self.foregroundMaskView]; + self.foregroundImageView = [[UIImageView alloc] initWithImage:self.image]; self.foregroundImageView.layer.minificationFilter = kCAFilterTrilinear; - [self.foregroundContainerView addSubview:self.foregroundImageView]; + [self.foregroundMaskView addSubview:self.foregroundImageView]; // Disable colour inversion for the image views if (@available(iOS 11.0, *)) { @@ -703,6 +709,10 @@ - (void)resetLayoutToDefaultAnimated:(BOOL)animated _aspectRatio = CGSizeZero; } + //Reset any flips + self.isFlippedVertically = NO; + self.isFlippedHorizontally = NO; + if (animated == NO || self.angle != 0) { //Reset all of the rotation transforms _angle = 0; @@ -1006,18 +1016,14 @@ - (void)setCropBoxFrame:(CGRect)cropBoxFrame _cropBoxFrame = cropBoxFrame; - CGRect pixelRoundedCropBoxFrame = CGRectMake([self roundToNearestPixel:cropBoxFrame.origin.x], - [self roundToNearestPixel:cropBoxFrame.origin.y], - [self roundToNearestPixel:cropBoxFrame.size.width], - [self roundToNearestPixel:cropBoxFrame.size.height]); - + CGRect pixelRoundedCropBoxFrame = [self CGRectIntegralRetina:cropBoxFrame]; self.foregroundContainerView.frame = pixelRoundedCropBoxFrame; //set the clipping view to match the new rect self.gridOverlayView.frame = pixelRoundedCropBoxFrame; //set the new overlay view to match the same region // If the mask layer is present, adjust its transform to fit the new container view size if (self.croppingStyle == TOCropViewCroppingStyleCircular) { - CGFloat halfWidth = self.foregroundContainerView.frame.size.width * 0.5; - self.foregroundContainerView.layer.cornerRadius = halfWidth; + CGFloat halfWidth = self.foregroundMaskView.frame.size.width * 0.5; + self.foregroundMaskView.layer.cornerRadius = halfWidth; } //reset the scroll view insets to match the region of the new crop rect @@ -1076,7 +1082,7 @@ - (CGRect)imageCropFrame frame.size.width = MIN(imageSize.width, frame.size.width); // Calculate normalized height - if ([self pixelCount:cropBoxFrame.size.width equals:cropBoxFrame.size.height]) { + if ([self pixelCountOf:cropBoxFrame.size.width equals:cropBoxFrame.size.height]) { frame.size.height = frame.size.width; } else { frame.size.height = cropBoxFrame.size.height * scale; @@ -1208,13 +1214,14 @@ - (void)setCanBeReset:(BOOL)canReset - (void)setAngle:(NSInteger)angle { + // Round to the nearest 90 degrees and sanitize + angle = round(angle/90.0) * 90; + angle %= 360; + //The initial layout would not have been performed yet. //Save the value and it will be applied when it has NSInteger newAngle = angle; - if (angle % 90 != 0) { - newAngle = 0; - } - + if (!self.initialSetupPerformed) { self.restoreAngle = newAngle; return; @@ -1529,6 +1536,9 @@ - (void)rotateImageNinetyDegreesAnimated:(BOOL)animated clockwise:(BOOL)clockwis [self captureStateForImageRotation]; } + //When canvas is flipped either way (but not both), change rotation direction to compensate + clockwise ^= self.isFlippedHorizontally ^ self.isFlippedVertically; + //Work out the new angle, and wrap around once we exceed 360s NSInteger newAngle = self.angle; newAngle = clockwise ? newAngle + 90 : newAngle - 90; @@ -1691,6 +1701,59 @@ - (void)rotateImageNinetyDegreesAnimated:(BOOL)animated clockwise:(BOOL)clockwis [self checkForCanReset]; } +- (void)flipImageAnimated:(BOOL)animated horizontal:(BOOL)horizontal +{ + if (self.rotateAnimationInProgress) + return; + + //Stop any inertia first + [self.scrollView setContentOffset:self.scrollView.contentOffset animated:NO]; + + [self.resetTimer invalidate]; + [self setEditing:NO resetCropBox:NO animated:NO]; + + //Avoid setter by setting ivar directly, setter is triggered later + _isFlippedHorizontally ^= horizontal; + _isFlippedVertically ^= !horizontal; + [self checkForCanReset]; + + if (!animated) { + self.isFlippedHorizontally = self.isFlippedHorizontally; //Setter triggers canvas transform + } else { + self.rotateAnimationInProgress = YES; + self.backgroundContainerView.hidden = YES; + self.translucencyView.hidden = YES; + self.gridOverlayView.hidden = YES; + self.overlayView.hidden = YES; + + //first animate the flip of the foregroundContainerView + self.foregroundContainerView.transform = CGAffineTransformScale(self.foregroundContainerView.transform, horizontal ? -1 : 1, horizontal ? 1 : -1); + [UIView transitionWithView: self.foregroundContainerView + duration: 0.45 + options: horizontal ? UIViewAnimationOptionTransitionFlipFromRight : UIViewAnimationOptionTransitionFlipFromBottom + animations: nil + completion:^(BOOL finished) { + + //once flipped unflip it and flip the entire canvas instead + self.foregroundContainerView.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1); + self.isFlippedHorizontally = self.isFlippedHorizontally; //Setter triggers canvas transform + + self.backgroundContainerView.hidden = NO; + self.translucencyView.hidden = self.translucencyAlwaysHidden; + self.gridOverlayView.hidden = NO; + self.overlayView.hidden = NO; + self.backgroundContainerView.alpha = 0.0; + self.gridOverlayView.alpha = 0.0; + [UIView animateWithDuration: 0.45 animations:^{ + self.backgroundContainerView.alpha = 1.0; + self.gridOverlayView.alpha = 1.0; + } completion:^(BOOL complete) { + self.rotateAnimationInProgress = NO; + }]; + }]; + } +} + - (void)captureStateForImageRotation { self.cropBoxLastEditedSize = self.cropBoxFrame.size; @@ -1709,13 +1772,17 @@ - (void)checkForCanReset else if (self.scrollView.zoomScale > self.scrollView.minimumZoomScale + DBL_EPSILON) { //image has been zoomed in canReset = YES; } - else if (![self pixelCount:self.cropBoxFrame.size.width equals:self.originalCropBoxSize.width] || - ![self pixelCount:self.cropBoxFrame.size.height equals:self.originalCropBoxSize.height]) + else if (![self pixelCountOf:self.cropBoxFrame.size.width equals:self.originalCropBoxSize.width] || + ![self pixelCountOf:self.cropBoxFrame.size.height equals:self.originalCropBoxSize.height]) { //crop box has been changed canReset = YES; } - else if (![self pixelCount:self.scrollView.contentOffset.x equals:self.originalContentOffset.x] || - ![self pixelCount:self.scrollView.contentOffset.y equals:self.originalContentOffset.y]) + else if (![self pixelCountOf:self.scrollView.contentOffset.x equals:self.originalContentOffset.x] || + ![self pixelCountOf:self.scrollView.contentOffset.y equals:self.originalContentOffset.y]) + { + canReset = YES; + } + else if (self.isFlippedHorizontally || self.isFlippedVertically) { canReset = YES; } @@ -1723,7 +1790,15 @@ - (void)checkForCanReset self.canBeReset = canReset; } -#pragma mark - Convienience Methods - +#pragma mark - Convenience Methods +- (void)setCropRegionInsets:(UIEdgeInsets)cropRegionInsets +{ + _cropRegionInsets = UIEdgeInsetsMake(self.isFlippedVertically ? cropRegionInsets.bottom : cropRegionInsets.top, + self.isFlippedHorizontally ? cropRegionInsets.right : cropRegionInsets.left, + self.isFlippedVertically ? cropRegionInsets.top : cropRegionInsets.bottom, + self.isFlippedHorizontally ? cropRegionInsets.left : cropRegionInsets.right); +} + - (CGRect)contentBounds { CGRect contentRect = CGRectZero; @@ -1747,4 +1822,14 @@ - (BOOL)hasAspectRatio return (self.aspectRatio.width > DBL_EPSILON && self.aspectRatio.height > DBL_EPSILON); } +- (void)setIsFlippedHorizontally:(BOOL)isFlippedHorizontally { + _isFlippedHorizontally = isFlippedHorizontally; + self.transform = CGAffineTransformScale(CGAffineTransformIdentity, self.isFlippedHorizontally ? -1 : 1, self.isFlippedVertically ? -1 : 1); +} + +- (void)setIsFlippedVertically:(BOOL)isFlippedVertically { + _isFlippedVertically = isFlippedVertically; + self.transform = CGAffineTransformScale(CGAffineTransformIdentity, self.isFlippedHorizontally ? -1 : 1, self.isFlippedVertically ? -1 : 1); +} + @end diff --git a/Objective-C/TOCropViewController/include/UIView+Pixels.h b/Objective-C/TOCropViewController/include/UIView+Pixels.h new file mode 120000 index 00000000..b8a91594 --- /dev/null +++ b/Objective-C/TOCropViewController/include/UIView+Pixels.h @@ -0,0 +1 @@ +/Users/jandevries/Projecten/TOCropViewControllerFork/Objective-C/TOCropViewController/Categories/UIView+Pixels.h \ No newline at end of file diff --git a/Objective-C/TOCropViewControllerExample/ViewController.m b/Objective-C/TOCropViewControllerExample/ViewController.m index bc539531..d349fcab 100644 --- a/Objective-C/TOCropViewControllerExample/ViewController.m +++ b/Objective-C/TOCropViewControllerExample/ViewController.m @@ -17,6 +17,7 @@ @interface ViewController () (Void))? { + public var onDidCropImageToRect: ((CGRect, NSInteger, Bool) -> (Void))? { set { toCropViewController.onDidCropImageToRect = newValue } get { return toCropViewController.onDidCropImageToRect } } /** Called when the user has committed the crop action, and provides - both the cropped image with crop co-ordinates. + both the cropped image with crop coordinates. @param image The newly cropped image. - @param cropRect A rectangle indicating the crop region of the image the user chose (In the original image's local co-ordinate space) + @param cropRect A rectangle indicating the crop region of the image the user chose (In the original image's local coordinate space) @param angle The angle of the image when it was cropped + @param flipped Whether the image was flipped (mirrored) when it was cropped */ - public var onDidCropToRect: ((UIImage, CGRect, NSInteger) -> (Void))? { + public var onDidCropToRect: ((UIImage, CGRect, NSInteger, Bool) -> (Void))? { set { toCropViewController.onDidCropToRect = newValue } get { return toCropViewController.onDidCropToRect } } /** If the cropping style is set to circular, this block will return a circle-cropped version of the selected - image, as well as it's cropping co-ordinates + image, as well as its cropping coordinates @param image The newly cropped image, clipped to a circle shape - @param cropRect A rectangle indicating the crop region of the image the user chose (In the original image's local co-ordinate space) + @param cropRect A rectangle indicating the crop region of the image the user chose (In the original image's local coordinate space) @param angle The angle of the image when it was cropped + @param flipped Whether the image was flipped (mirrored) when it was cropped */ - public var onDidCropToCircleImage: ((UIImage, CGRect, NSInteger) -> (Void))? { + public var onDidCropToCircleImage: ((UIImage, CGRect, NSInteger, Bool) -> (Void))? { set { toCropViewController.onDidCropToCircleImage = newValue } get { return toCropViewController.onDidCropToCircleImage } } @@ -611,16 +675,17 @@ open class CropViewController: UIViewController, TOCropViewControllerDelegate { @param fromView A view that's frame will be used as the origin for this animation. Optional if `fromFrame` has a value. @param fromFrame In the screen's coordinate space, the frame from which the image should animate from. @param angle The rotation angle in which the image was rotated when it was originally cropped. + @param flipped Whether the image was flipped (mirrored) when it was originally cropped. @param toFrame In the image's coordinate space, the previous crop frame that created the previous crop @param setup A block that is called just before the transition starts. Recommended for hiding any necessary image views. @param completion A block that is called once the transition animation is completed. */ public func presentAnimatedFrom(_ viewController: UIViewController, fromImage image: UIImage?, - fromView: UIView?, fromFrame: CGRect, angle: Int, toImageFrame toFrame: CGRect, + fromView: UIView?, fromFrame: CGRect, angle: Int, flipped: Bool, toImageFrame toFrame: CGRect, setup: (() -> (Void))?, completion:(() -> (Void))?) { toCropViewController.presentAnimatedFrom(viewController, fromImage: image, fromView: fromView, - fromFrame: fromFrame, angle: angle, toFrame: toFrame, + fromFrame: fromFrame, angle: angle, flipped: flipped, toFrame: toFrame, setup: setup, completion: completion) } @@ -678,27 +743,48 @@ extension CropViewController { onDidFinishCancelled = nil return } - + if delegate.responds(to: #selector((any CropViewControllerDelegate).cropViewController(_:didCropImageToRect:angle:))) { - self.onDidCropImageToRect = {[weak self] rect, angle in + crashWhenFresh() + self.onDidCropImageToRect = {[weak self] rect, angle, flipped in guard let strongSelf = self else { return } delegate.cropViewController!(strongSelf, didCropImageToRect: rect, angle: angle) } } + if delegate.responds(to: #selector((any CropViewControllerDelegate).cropViewController(_:didCropImageToRect:angle:flipped:))) { + self.onDidCropImageToRect = {[weak self] rect, angle, flipped in + guard let strongSelf = self else { return } + delegate.cropViewController!(strongSelf, didCropImageToRect: rect, angle: angle, flipped: flipped) + } + } if delegate.responds(to: #selector((any CropViewControllerDelegate).cropViewController(_:didCropToImage:withRect:angle:))) { - self.onDidCropToRect = {[weak self] image, rect, angle in + crashWhenFresh() + self.onDidCropToRect = {[weak self] image, rect, angle, flipped in guard let strongSelf = self else { return } delegate.cropViewController!(strongSelf, didCropToImage: image, withRect: rect, angle: angle) } } - + if delegate.responds(to: #selector((any CropViewControllerDelegate).cropViewController(_:didCropToImage:withRect:angle:flipped:))) { + self.onDidCropToRect = {[weak self] image, rect, angle, flipped in + guard let strongSelf = self else { return } + delegate.cropViewController!(strongSelf, didCropToImage: image, withRect: rect, angle: angle, flipped: flipped) + } + } + if delegate.responds(to: #selector((any CropViewControllerDelegate).cropViewController(_:didCropToCircularImage:withRect:angle:))) { - self.onDidCropToCircleImage = {[weak self] image, rect, angle in + crashWhenFresh() + self.onDidCropToCircleImage = {[weak self] image, rect, angle, flipped in guard let strongSelf = self else { return } delegate.cropViewController!(strongSelf, didCropToCircularImage: image, withRect: rect, angle: angle) } } + if delegate.responds(to: #selector((any CropViewControllerDelegate).cropViewController(_:didCropToCircularImage:withRect:angle:flipped:))) { + self.onDidCropToCircleImage = {[weak self] image, rect, angle, flipped in + guard let strongSelf = self else { return } + delegate.cropViewController!(strongSelf, didCropToCircularImage: image, withRect: rect, angle: angle, flipped: flipped) + } + } if delegate.responds(to: #selector((any CropViewControllerDelegate).cropViewController(_:didFinishCancelled:))) { self.onDidFinishCancelled = {[weak self] finished in @@ -707,5 +793,19 @@ extension CropViewController { } } } + + //Fresh builds that are less than 2 hours old require you to update your delegate method implementations with the 'flipped' arg. + //This should make sure end users won't experience crashes but you will be motivated to update the delegate methods. + private func crashWhenFresh() { + let bundleName = Bundle.main.infoDictionary!["CFBundleExecutable"] as? String ?? "Info.plist" + + if let infoPath = Bundle.main.path(forResource: bundleName, ofType: nil), + let infoAttr = try? FileManager.default.attributesOfItem(atPath: infoPath), + let infoDate = infoAttr[FileAttributeKey.creationDate] as? Date { + if infoDate.timeIntervalSinceNow > -7200 { + fatalError("Please add arg 'flipped: Bool' your didCrop delegate method implementations.") + } + } + } } diff --git a/Swift/CropViewControllerExample/ViewController.swift b/Swift/CropViewControllerExample/ViewController.swift index 6bcba7e2..473bb6d6 100644 --- a/Swift/CropViewControllerExample/ViewController.swift +++ b/Swift/CropViewControllerExample/ViewController.swift @@ -17,6 +17,7 @@ class ViewController: UIViewController, CropViewControllerDelegate, UIImagePicke private var croppedRect = CGRect.zero private var croppedAngle = 0 + private var croppedFlipped = false func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { guard let image = (info[UIImagePickerController.InfoKey.originalImage] as? UIImage) else { return } @@ -30,6 +31,7 @@ class ViewController: UIViewController, CropViewControllerDelegate, UIImagePicke // -- Uncomment these if you want to test out restoring to a previous crop setting -- //cropController.angle = 90 // The initial angle in which the image will be rotated + //cropController.flipped = true // Whether to initially flip (mirror) the image //cropController.imageCropFrame = CGRect(x: 0, y: 0, width: 2848, height: 4288) //The initial frame that the crop controller will have visible. // -- Uncomment the following lines of code to test out the aspect ratio features -- @@ -43,6 +45,9 @@ class ViewController: UIViewController, CropViewControllerDelegate, UIImagePicke //cropController.rotateButtonsHidden = true //cropController.rotateClockwiseButtonHidden = true + + //cropController.flipHorizontalButtonHidden = true + //cropController.flipVerticalButtonHidden = false //cropController.doneButtonTitle = "Title" //cropController.cancelButtonTitle = "Title" @@ -77,16 +82,18 @@ class ViewController: UIViewController, CropViewControllerDelegate, UIImagePicke }) } } - - public func cropViewController(_ cropViewController: CropViewController, didCropToImage image: UIImage, withRect cropRect: CGRect, angle: Int) { + + public func cropViewController(_ cropViewController: CropViewController, didCropToImage image: UIImage, withRect cropRect: CGRect, angle: Int, flipped: Bool) { self.croppedRect = cropRect self.croppedAngle = angle + self.croppedFlipped = flipped updateImageViewWithImage(image, fromCropViewController: cropViewController) } - public func cropViewController(_ cropViewController: CropViewController, didCropToCircularImage image: UIImage, withRect cropRect: CGRect, angle: Int) { + public func cropViewController(_ cropViewController: CropViewController, didCropToCircularImage image: UIImage, withRect cropRect: CGRect, angle: Int, flipped: Bool) { self.croppedRect = cropRect self.croppedAngle = angle + self.croppedFlipped = flipped updateImageViewWithImage(image, fromCropViewController: cropViewController) } @@ -182,6 +189,7 @@ class ViewController: UIViewController, CropViewControllerDelegate, UIImagePicke fromView: nil, fromFrame: viewFrame, angle: self.croppedAngle, + flipped: self.croppedFlipped, toImageFrame: self.croppedRect, setup: { self.imageView.isHidden = true }, completion: nil) diff --git a/TOCropViewControllerExample.xcodeproj/project.pbxproj b/TOCropViewControllerExample.xcodeproj/project.pbxproj index 2512d935..604b943d 100644 --- a/TOCropViewControllerExample.xcodeproj/project.pbxproj +++ b/TOCropViewControllerExample.xcodeproj/project.pbxproj @@ -98,13 +98,45 @@ 2F5062F31F53E31F00AA9F14 /* TOCropViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 22DB4D831B234CFA008B8466 /* TOCropViewController.m */; }; 2F5062F41F53E32800AA9F14 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 223424751ABBC0CD00BBC2B1 /* ViewController.m */; }; 2F5062F51F53E32D00AA9F14 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 223424771ABBC0CD00BBC2B1 /* Main.storyboard */; }; - 94189B982CC92BAA000C5263 /* UIView+Pixels.h in Headers */ = {isa = PBXBuildFile; fileRef = 94189B962CC92BAA000C5263 /* UIView+Pixels.h */; }; 94189B992CC92BAA000C5263 /* UIView+Pixels.m in Sources */ = {isa = PBXBuildFile; fileRef = 94189B972CC92BAA000C5263 /* UIView+Pixels.m */; }; 94189B9A2CC92BAA000C5263 /* UIView+Pixels.m in Sources */ = {isa = PBXBuildFile; fileRef = 94189B972CC92BAA000C5263 /* UIView+Pixels.m */; }; 94189B9B2CC92BAA000C5263 /* UIView+Pixels.m in Sources */ = {isa = PBXBuildFile; fileRef = 94189B972CC92BAA000C5263 /* UIView+Pixels.m */; }; - 94189B9C2CC92BAA000C5263 /* UIView+Pixels.h in Headers */ = {isa = PBXBuildFile; fileRef = 94189B962CC92BAA000C5263 /* UIView+Pixels.h */; }; 94189B9D2CC92BAA000C5263 /* UIView+Pixels.m in Sources */ = {isa = PBXBuildFile; fileRef = 94189B972CC92BAA000C5263 /* UIView+Pixels.m */; }; 94189B9E2CC92BAA000C5263 /* UIView+Pixels.m in Sources */ = {isa = PBXBuildFile; fileRef = 94189B972CC92BAA000C5263 /* UIView+Pixels.m */; }; + 94776A0A2CD4F185009566B1 /* arrow.trianglehead.left.and.right.righttriangle.left.righttriangle.right.fill@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A092CD4F185009566B1 /* arrow.trianglehead.left.and.right.righttriangle.left.righttriangle.right.fill@2x.png */; }; + 94776A0B2CD4F185009566B1 /* arrow.trianglehead.left.and.right.righttriangle.left.righttriangle.right.fill@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A092CD4F185009566B1 /* arrow.trianglehead.left.and.right.righttriangle.left.righttriangle.right.fill@2x.png */; }; + 94776A0C2CD4F185009566B1 /* arrow.trianglehead.left.and.right.righttriangle.left.righttriangle.right.fill@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A092CD4F185009566B1 /* arrow.trianglehead.left.and.right.righttriangle.left.righttriangle.right.fill@2x.png */; }; + 94776A0D2CD4F185009566B1 /* arrow.trianglehead.left.and.right.righttriangle.left.righttriangle.right.fill@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A092CD4F185009566B1 /* arrow.trianglehead.left.and.right.righttriangle.left.righttriangle.right.fill@2x.png */; }; + 94776A0F2CD4F28E009566B1 /* arrow.trianglehead.up.and.down.righttriangle.up.righttriangle.down.fill@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A0E2CD4F28E009566B1 /* arrow.trianglehead.up.and.down.righttriangle.up.righttriangle.down.fill@2x.png */; }; + 94776A102CD4F28E009566B1 /* arrow.trianglehead.up.and.down.righttriangle.up.righttriangle.down.fill@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A0E2CD4F28E009566B1 /* arrow.trianglehead.up.and.down.righttriangle.up.righttriangle.down.fill@2x.png */; }; + 94776A112CD4F28E009566B1 /* arrow.trianglehead.up.and.down.righttriangle.up.righttriangle.down.fill@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A0E2CD4F28E009566B1 /* arrow.trianglehead.up.and.down.righttriangle.up.righttriangle.down.fill@2x.png */; }; + 94776A122CD4F28E009566B1 /* arrow.trianglehead.up.and.down.righttriangle.up.righttriangle.down.fill@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A0E2CD4F28E009566B1 /* arrow.trianglehead.up.and.down.righttriangle.up.righttriangle.down.fill@2x.png */; }; + 94776A142CD4F3C6009566B1 /* rotate.right.fill@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A132CD4F3C6009566B1 /* rotate.right.fill@2x.png */; }; + 94776A152CD4F3C6009566B1 /* rotate.right.fill@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A132CD4F3C6009566B1 /* rotate.right.fill@2x.png */; }; + 94776A162CD4F3C6009566B1 /* rotate.right.fill@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A132CD4F3C6009566B1 /* rotate.right.fill@2x.png */; }; + 94776A172CD4F3C6009566B1 /* rotate.right.fill@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A132CD4F3C6009566B1 /* rotate.right.fill@2x.png */; }; + 94776A192CD4F492009566B1 /* rotate.left.fill@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A182CD4F492009566B1 /* rotate.left.fill@2x.png */; }; + 94776A1A2CD4F492009566B1 /* rotate.left.fill@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A182CD4F492009566B1 /* rotate.left.fill@2x.png */; }; + 94776A1B2CD4F492009566B1 /* rotate.left.fill@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A182CD4F492009566B1 /* rotate.left.fill@2x.png */; }; + 94776A1C2CD4F492009566B1 /* rotate.left.fill@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A182CD4F492009566B1 /* rotate.left.fill@2x.png */; }; + 94776A1E2CD5020D009566B1 /* arrow.counterclockwise@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A1D2CD5020D009566B1 /* arrow.counterclockwise@2x.png */; }; + 94776A1F2CD5020D009566B1 /* arrow.counterclockwise@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A1D2CD5020D009566B1 /* arrow.counterclockwise@2x.png */; }; + 94776A202CD5020D009566B1 /* arrow.counterclockwise@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A1D2CD5020D009566B1 /* arrow.counterclockwise@2x.png */; }; + 94776A212CD5020D009566B1 /* arrow.counterclockwise@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A1D2CD5020D009566B1 /* arrow.counterclockwise@2x.png */; }; + 94776A232CD5042B009566B1 /* aspectratio.fill@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A222CD5042B009566B1 /* aspectratio.fill@2x.png */; }; + 94776A242CD5042B009566B1 /* aspectratio.fill@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A222CD5042B009566B1 /* aspectratio.fill@2x.png */; }; + 94776A252CD5042B009566B1 /* aspectratio.fill@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A222CD5042B009566B1 /* aspectratio.fill@2x.png */; }; + 94776A262CD5042B009566B1 /* aspectratio.fill@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A222CD5042B009566B1 /* aspectratio.fill@2x.png */; }; + 94776A282CD508EE009566B1 /* checkmark@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A272CD508EE009566B1 /* checkmark@2x.png */; }; + 94776A292CD508EE009566B1 /* checkmark@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A272CD508EE009566B1 /* checkmark@2x.png */; }; + 94776A2A2CD508EE009566B1 /* checkmark@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A272CD508EE009566B1 /* checkmark@2x.png */; }; + 94776A2B2CD508EE009566B1 /* checkmark@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A272CD508EE009566B1 /* checkmark@2x.png */; }; + 94776A2D2CD509BB009566B1 /* xmark@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A2C2CD509BB009566B1 /* xmark@2x.png */; }; + 94776A2E2CD509BB009566B1 /* xmark@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A2C2CD509BB009566B1 /* xmark@2x.png */; }; + 94776A2F2CD509BB009566B1 /* xmark@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A2C2CD509BB009566B1 /* xmark@2x.png */; }; + 94776A302CD509BB009566B1 /* xmark@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94776A2C2CD509BB009566B1 /* xmark@2x.png */; }; + 947A472E2CE7A905005A9628 /* UIView+Pixels.h in Headers */ = {isa = PBXBuildFile; fileRef = 94189B962CC92BAA000C5263 /* UIView+Pixels.h */; }; + 947A472F2CE7A905005A9628 /* UIView+Pixels.h in Headers */ = {isa = PBXBuildFile; fileRef = 94189B962CC92BAA000C5263 /* UIView+Pixels.h */; }; FE3E0E3A21098448004DAE93 /* TOCropViewConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 220C8E9F21062DD300A9B25D /* TOCropViewConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ @@ -226,6 +258,14 @@ 88BBE90E22C206340073D22A /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/TOCropViewControllerLocalizable.strings; sourceTree = ""; }; 94189B962CC92BAA000C5263 /* UIView+Pixels.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+Pixels.h"; sourceTree = ""; }; 94189B972CC92BAA000C5263 /* UIView+Pixels.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+Pixels.m"; sourceTree = ""; }; + 94776A092CD4F185009566B1 /* arrow.trianglehead.left.and.right.righttriangle.left.righttriangle.right.fill@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "arrow.trianglehead.left.and.right.righttriangle.left.righttriangle.right.fill@2x.png"; sourceTree = ""; }; + 94776A0E2CD4F28E009566B1 /* arrow.trianglehead.up.and.down.righttriangle.up.righttriangle.down.fill@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "arrow.trianglehead.up.and.down.righttriangle.up.righttriangle.down.fill@2x.png"; sourceTree = ""; }; + 94776A132CD4F3C6009566B1 /* rotate.right.fill@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "rotate.right.fill@2x.png"; sourceTree = ""; }; + 94776A182CD4F492009566B1 /* rotate.left.fill@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "rotate.left.fill@2x.png"; sourceTree = ""; }; + 94776A1D2CD5020D009566B1 /* arrow.counterclockwise@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "arrow.counterclockwise@2x.png"; sourceTree = ""; }; + 94776A222CD5042B009566B1 /* aspectratio.fill@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "aspectratio.fill@2x.png"; sourceTree = ""; }; + 94776A272CD508EE009566B1 /* checkmark@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "checkmark@2x.png"; sourceTree = ""; }; + 94776A2C2CD509BB009566B1 /* xmark@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "xmark@2x.png"; sourceTree = ""; }; A93782CF214A55F900CAE7EE /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/TOCropViewControllerLocalizable.strings; sourceTree = ""; }; A93782D3214A81A200CAE7EE /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/TOCropViewControllerLocalizable.strings; sourceTree = ""; }; CDC8C5751C667B9100BB86A4 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Main.strings; sourceTree = ""; }; @@ -317,6 +357,14 @@ 220C8EA021062E6D00A9B25D /* Resources */ = { isa = PBXGroup; children = ( + 94776A0E2CD4F28E009566B1 /* arrow.trianglehead.up.and.down.righttriangle.up.righttriangle.down.fill@2x.png */, + 94776A092CD4F185009566B1 /* arrow.trianglehead.left.and.right.righttriangle.left.righttriangle.right.fill@2x.png */, + 94776A132CD4F3C6009566B1 /* rotate.right.fill@2x.png */, + 94776A182CD4F492009566B1 /* rotate.left.fill@2x.png */, + 94776A1D2CD5020D009566B1 /* arrow.counterclockwise@2x.png */, + 94776A222CD5042B009566B1 /* aspectratio.fill@2x.png */, + 94776A272CD508EE009566B1 /* checkmark@2x.png */, + 94776A2C2CD509BB009566B1 /* xmark@2x.png */, 22A105B22BC134A200DB3A80 /* PrivacyInfo.xcprivacy */, 22C3C5491AC8CA0D00E86280 /* TOCropViewControllerLocalizable.strings */, ); @@ -505,6 +553,7 @@ 2238CF451FC02AE00081B957 /* TOCropViewController.h in Headers */, 144B8CD71D22CD650085D774 /* TOCropToolbar.h in Headers */, 220C8EB22106344D00A9B25D /* UIImage+CropRotate.h in Headers */, + 947A472F2CE7A905005A9628 /* UIView+Pixels.h in Headers */, 144B8CD81D22CD650085D774 /* TOCropView.h in Headers */, 144B8CD11D22CD650085D774 /* TOCropViewControllerTransitioning.h in Headers */, 144B8CD21D22CD650085D774 /* TOActivityCroppedImageProvider.h in Headers */, @@ -512,7 +561,6 @@ 144B8CD31D22CD650085D774 /* TOCroppedImageAttributes.h in Headers */, 144B8CD61D22CD650085D774 /* TOCropScrollView.h in Headers */, 144B8CD51D22CD650085D774 /* TOCropOverlayView.h in Headers */, - 94189B9C2CC92BAA000C5263 /* UIView+Pixels.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -522,7 +570,7 @@ files = ( 04262DA520F6FD1000024177 /* CropViewController.h in Headers */, 04262DA220F6FC4600024177 /* TOCropToolbar.h in Headers */, - 94189B982CC92BAA000C5263 /* UIView+Pixels.h in Headers */, + 947A472E2CE7A905005A9628 /* UIView+Pixels.h in Headers */, 04262DA320F6FC4600024177 /* TOCropView.h in Headers */, 04262DA420F6FC4600024177 /* TOCropViewController.h in Headers */, 220C8EA82106341600A9B25D /* TOCropViewConstants.h in Headers */, @@ -743,8 +791,16 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 94776A262CD5042B009566B1 /* aspectratio.fill@2x.png in Resources */, + 94776A292CD508EE009566B1 /* checkmark@2x.png in Resources */, + 94776A162CD4F3C6009566B1 /* rotate.right.fill@2x.png in Resources */, + 94776A122CD4F28E009566B1 /* arrow.trianglehead.up.and.down.righttriangle.up.righttriangle.down.fill@2x.png in Resources */, 22A105B42BC134A200DB3A80 /* PrivacyInfo.xcprivacy in Resources */, + 94776A212CD5020D009566B1 /* arrow.counterclockwise@2x.png in Resources */, 04262D8620F6F1C600024177 /* TOCropViewControllerLocalizable.strings in Resources */, + 94776A0D2CD4F185009566B1 /* arrow.trianglehead.left.and.right.righttriangle.left.righttriangle.right.fill@2x.png in Resources */, + 94776A2E2CD509BB009566B1 /* xmark@2x.png in Resources */, + 94776A1C2CD4F492009566B1 /* rotate.left.fill@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -763,7 +819,15 @@ 2234247E1ABBC0CD00BBC2B1 /* LaunchScreen.xib in Resources */, 2234247B1ABBC0CD00BBC2B1 /* Images.xcassets in Resources */, 22A105B52BC134A200DB3A80 /* PrivacyInfo.xcprivacy in Resources */, + 94776A232CD5042B009566B1 /* aspectratio.fill@2x.png in Resources */, 22C3C5471AC8CA0D00E86280 /* TOCropViewControllerLocalizable.strings in Resources */, + 94776A202CD5020D009566B1 /* arrow.counterclockwise@2x.png in Resources */, + 94776A192CD4F492009566B1 /* rotate.left.fill@2x.png in Resources */, + 94776A142CD4F3C6009566B1 /* rotate.right.fill@2x.png in Resources */, + 94776A2F2CD509BB009566B1 /* xmark@2x.png in Resources */, + 94776A102CD4F28E009566B1 /* arrow.trianglehead.up.and.down.righttriangle.up.righttriangle.down.fill@2x.png in Resources */, + 94776A2A2CD508EE009566B1 /* checkmark@2x.png in Resources */, + 94776A0A2CD4F185009566B1 /* arrow.trianglehead.left.and.right.righttriangle.left.righttriangle.right.fill@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -775,7 +839,15 @@ 2238CF2A1FC0269C0081B957 /* Assets.xcassets in Resources */, 2238CF281FC0269C0081B957 /* Main.storyboard in Resources */, 22A105B62BC134A200DB3A80 /* PrivacyInfo.xcprivacy in Resources */, + 94776A252CD5042B009566B1 /* aspectratio.fill@2x.png in Resources */, 220C8EA121062EFC00A9B25D /* TOCropViewControllerLocalizable.strings in Resources */, + 94776A1F2CD5020D009566B1 /* arrow.counterclockwise@2x.png in Resources */, + 94776A1A2CD4F492009566B1 /* rotate.left.fill@2x.png in Resources */, + 94776A152CD4F3C6009566B1 /* rotate.right.fill@2x.png in Resources */, + 94776A302CD509BB009566B1 /* xmark@2x.png in Resources */, + 94776A112CD4F28E009566B1 /* arrow.trianglehead.up.and.down.righttriangle.up.righttriangle.down.fill@2x.png in Resources */, + 94776A2B2CD508EE009566B1 /* checkmark@2x.png in Resources */, + 94776A0C2CD4F185009566B1 /* arrow.trianglehead.left.and.right.righttriangle.left.righttriangle.right.fill@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -783,8 +855,16 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 94776A242CD5042B009566B1 /* aspectratio.fill@2x.png in Resources */, + 94776A282CD508EE009566B1 /* checkmark@2x.png in Resources */, + 94776A172CD4F3C6009566B1 /* rotate.right.fill@2x.png in Resources */, + 94776A0F2CD4F28E009566B1 /* arrow.trianglehead.up.and.down.righttriangle.up.righttriangle.down.fill@2x.png in Resources */, 22A105B32BC134A200DB3A80 /* PrivacyInfo.xcprivacy in Resources */, + 94776A1E2CD5020D009566B1 /* arrow.counterclockwise@2x.png in Resources */, 04262D8720F6F1D600024177 /* TOCropViewControllerLocalizable.strings in Resources */, + 94776A0B2CD4F185009566B1 /* arrow.trianglehead.left.and.right.righttriangle.left.righttriangle.right.fill@2x.png in Resources */, + 94776A2D2CD509BB009566B1 /* xmark@2x.png in Resources */, + 94776A1B2CD4F492009566B1 /* rotate.left.fill@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -869,13 +949,13 @@ buildActionMask = 2147483647; files = ( 220C8EAC2106344D00A9B25D /* UIImage+CropRotate.m in Sources */, + 94189B992CC92BAA000C5263 /* UIView+Pixels.m in Sources */, 22B68F9F1FFB3C0800601B1A /* TOCropViewControllerTransitioning.m in Sources */, 22B68FA01FFB3C0800601B1A /* TOActivityCroppedImageProvider.m in Sources */, 22B68FA11FFB3C0800601B1A /* TOCroppedImageAttributes.m in Sources */, 22B68FA31FFB3C0800601B1A /* TOCropOverlayView.m in Sources */, 22B68FA41FFB3C0800601B1A /* TOCropScrollView.m in Sources */, 22B68FA51FFB3C0800601B1A /* TOCropToolbar.m in Sources */, - 94189B992CC92BAA000C5263 /* UIView+Pixels.m in Sources */, 22B68FA61FFB3C0800601B1A /* TOCropView.m in Sources */, 22B68FA71FFB3C0800601B1A /* TOCropViewController.m in Sources */, 22DEA39F1FC1293A000FA1CB /* CropViewController.swift in Sources */, From da5b209f99518acdd9d47e37ef43a404e4bd9fa1 Mon Sep 17 00:00:00 2001 From: Jan de Vries Date: Tue, 19 Nov 2024 07:40:28 +0100 Subject: [PATCH 5/5] Fixed symlink --- Objective-C/TOCropViewController/include/UIView+Pixels.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objective-C/TOCropViewController/include/UIView+Pixels.h b/Objective-C/TOCropViewController/include/UIView+Pixels.h index b8a91594..be249e0b 120000 --- a/Objective-C/TOCropViewController/include/UIView+Pixels.h +++ b/Objective-C/TOCropViewController/include/UIView+Pixels.h @@ -1 +1 @@ -/Users/jandevries/Projecten/TOCropViewControllerFork/Objective-C/TOCropViewController/Categories/UIView+Pixels.h \ No newline at end of file +../Categories/UIView+Pixels.h \ No newline at end of file