Преглед изворни кода

better background picker,
that uses the phone screen size as aspect ratio for croping

fixes #1502
fixes #1688

Simon Laux пре 2 година
родитељ
комит
1751984b55
73 измењених фајлова са 9091 додато и 1906 уклоњено
  1. 1 0
      Podfile
  2. 6 2
      Podfile.lock
  3. 21 0
      Pods/CropViewController/LICENSE
  4. 39 0
      Pods/CropViewController/Objective-C/TOCropViewController/Categories/UIImage+CropRotate.h
  5. 80 0
      Pods/CropViewController/Objective-C/TOCropViewController/Categories/UIImage+CropRotate.m
  6. 79 0
      Pods/CropViewController/Objective-C/TOCropViewController/Constants/TOCropViewConstants.h
  7. 38 0
      Pods/CropViewController/Objective-C/TOCropViewController/Models/TOActivityCroppedImageProvider.h
  8. 76 0
      Pods/CropViewController/Objective-C/TOCropViewController/Models/TOActivityCroppedImageProvider.m
  9. 49 0
      Pods/CropViewController/Objective-C/TOCropViewController/Models/TOCropViewControllerTransitioning.h
  10. 120 0
      Pods/CropViewController/Objective-C/TOCropViewController/Models/TOCropViewControllerTransitioning.m
  11. 38 0
      Pods/CropViewController/Objective-C/TOCropViewController/Models/TOCroppedImageAttributes.h
  12. 46 0
      Pods/CropViewController/Objective-C/TOCropViewController/Models/TOCroppedImageAttributes.m
  13. 8 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/Base.lproj/TOCropViewControllerLocalizable.strings
  14. 9 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/ar.lproj/TOCropViewControllerLocalizable.strings
  15. 8 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/ca.lproj/TOCropViewControllerLocalizable.strings
  16. 8 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/cs.lproj/TOCropViewControllerLocalizable.strings
  17. 8 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/da-DK.lproj/TOCropViewControllerLocalizable.strings
  18. 8 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/de.lproj/TOCropViewControllerLocalizable.strings
  19. 8 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/en.lproj/TOCropViewControllerLocalizable.strings
  20. 8 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/es.lproj/TOCropViewControllerLocalizable.strings
  21. 8 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/fa-IR.lproj/TOCropViewControllerLocalizable.strings
  22. 8 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/fa.lproj/TOCropViewControllerLocalizable.strings
  23. 8 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/fi.lproj/TOCropViewControllerLocalizable.strings
  24. 8 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/fr.lproj/TOCropViewControllerLocalizable.strings
  25. 9 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/hu.lproj/TOCropViewControllerLocalizable.strings
  26. 8 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/id.lproj/TOCropViewControllerLocalizable.strings
  27. 8 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/it.lproj/TOCropViewControllerLocalizable.strings
  28. 8 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/ja.lproj/TOCropViewControllerLocalizable.strings
  29. 8 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/ko.lproj/TOCropViewControllerLocalizable.strings
  30. 8 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/ms.lproj/TOCropViewControllerLocalizable.strings
  31. 8 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/nl.lproj/TOCropViewControllerLocalizable.strings
  32. 8 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/pl.lproj/TOCropViewControllerLocalizable.strings
  33. 8 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/pt-BR.lproj/TOCropViewControllerLocalizable.strings
  34. 8 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/pt.lproj/TOCropViewControllerLocalizable.strings
  35. 8 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/ro.lproj/TOCropViewControllerLocalizable.strings
  36. 8 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/ru.lproj/TOCropViewControllerLocalizable.strings
  37. 8 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/sk.lproj/TOCropViewControllerLocalizable.strings
  38. 9 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/tr.lproj/TOCropViewControllerLocalizable.strings
  39. 9 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/vi.lproj/TOCropViewControllerLocalizable.strings
  40. 9 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/zh-Hans.lproj/TOCropViewControllerLocalizable.strings
  41. 9 0
      Pods/CropViewController/Objective-C/TOCropViewController/Resources/zh-Hant.lproj/TOCropViewControllerLocalizable.strings
  42. 471 0
      Pods/CropViewController/Objective-C/TOCropViewController/TOCropViewController.h
  43. 1330 0
      Pods/CropViewController/Objective-C/TOCropViewController/TOCropViewController.m
  44. 43 0
      Pods/CropViewController/Objective-C/TOCropViewController/Views/TOCropOverlayView.h
  45. 231 0
      Pods/CropViewController/Objective-C/TOCropViewController/Views/TOCropOverlayView.m
  46. 39 0
      Pods/CropViewController/Objective-C/TOCropViewController/Views/TOCropScrollView.h
  47. 51 0
      Pods/CropViewController/Objective-C/TOCropViewController/Views/TOCropScrollView.m
  48. 89 0
      Pods/CropViewController/Objective-C/TOCropViewController/Views/TOCropToolbar.h
  49. 723 0
      Pods/CropViewController/Objective-C/TOCropViewController/Views/TOCropToolbar.m
  50. 291 0
      Pods/CropViewController/Objective-C/TOCropViewController/Views/TOCropView.h
  51. 1747 0
      Pods/CropViewController/Objective-C/TOCropViewController/Views/TOCropView.m
  52. 256 0
      Pods/CropViewController/README.md
  53. 33 0
      Pods/CropViewController/Swift/CropViewController/CropViewController.h
  54. 673 0
      Pods/CropViewController/Swift/CropViewController/CropViewController.swift
  55. 6 2
      Pods/Manifest.lock
  56. 2020 1882
      Pods/Pods.xcodeproj/project.pbxproj
  57. 26 0
      Pods/Target Support Files/CropViewController/CropViewController-Info.plist
  58. 5 0
      Pods/Target Support Files/CropViewController/CropViewController-dummy.m
  59. 12 0
      Pods/Target Support Files/CropViewController/CropViewController-prefix.pch
  60. 27 0
      Pods/Target Support Files/CropViewController/CropViewController-umbrella.h
  61. 13 0
      Pods/Target Support Files/CropViewController/CropViewController.debug.xcconfig
  62. 6 0
      Pods/Target Support Files/CropViewController/CropViewController.modulemap
  63. 13 0
      Pods/Target Support Files/CropViewController/CropViewController.release.xcconfig
  64. 24 0
      Pods/Target Support Files/CropViewController/ResourceBundle-TOCropViewControllerBundle-CropViewController-Info.plist
  65. 25 0
      Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios-acknowledgements.markdown
  66. 31 0
      Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios-acknowledgements.plist
  67. 2 0
      Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios-frameworks.sh
  68. 4 4
      Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios.debug.xcconfig
  69. 4 4
      Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios.release.xcconfig
  70. 3 3
      Pods/Target Support Files/Pods-deltachat-iosTests/Pods-deltachat-iosTests.debug.xcconfig
  71. 3 3
      Pods/Target Support Files/Pods-deltachat-iosTests/Pods-deltachat-iosTests.release.xcconfig
  72. 2 0
      deltachat-ios.xcodeproj/project.pbxproj
  73. 57 6
      deltachat-ios/Controller/Settings/BackgroundOptionsViewController.swift

+ 1 - 0
Podfile

@@ -18,6 +18,7 @@ target 'deltachat-ios' do
   pod 'SDWebImageWebPCoder'
   pod 'SDWebImageSVGKitPlugin'
   pod 'SVGKit', :git => 'https://github.com/SVGKit/SVGKit.git', :branch => '3.x'
+  pod 'CropViewController'
   target 'deltachat-iosTests' do
     inherit! :search_paths
     # Pods for testing

+ 6 - 2
Podfile.lock

@@ -2,6 +2,7 @@ PODS:
   - CocoaLumberjack (3.7.2):
     - CocoaLumberjack/Core (= 3.7.2)
   - CocoaLumberjack/Core (3.7.2)
+  - CropViewController (2.6.1)
   - DBDebugToolkit (0.6.0)
   - libwebp (1.2.0):
     - libwebp/demux (= 1.2.0)
@@ -31,6 +32,7 @@ PODS:
   - SwiftyBeaver (1.9.4)
 
 DEPENDENCIES:
+  - CropViewController
   - DBDebugToolkit
   - ReachabilitySwift
   - SCSiriWaveformView
@@ -46,6 +48,7 @@ DEPENDENCIES:
 SPEC REPOS:
   trunk:
     - CocoaLumberjack
+    - CropViewController
     - DBDebugToolkit
     - libwebp
     - ReachabilitySwift
@@ -70,6 +73,7 @@ CHECKOUT OPTIONS:
 
 SPEC CHECKSUMS:
   CocoaLumberjack: b7e05132ff94f6ae4dfa9d5bce9141893a21d9da
+  CropViewController: 58fb440f30dac788b129d2a1f24cffdcb102669c
   DBDebugToolkit: 03eb3483da4daf03b853b83ade5a8da9c8b7aff4
   libwebp: e90b9c01d99205d03b6bb8f2c8c415e5a4ef66f0
   ReachabilitySwift: 4032e2f59586e11e3b0ebe15b167abdd587a388b
@@ -83,6 +87,6 @@ SPEC CHECKSUMS:
   SwiftLint: d41cc46a2ae58ac6d9f26954bc89f1d72e71fdef
   SwiftyBeaver: 576177b2c5c94b3aedd5993914e91271a0524e88
 
-PODFILE CHECKSUM: 6972a504404994ab0cbfd625f32d2cca6958bb1f
+PODFILE CHECKSUM: a958fb3d60b6f4c0d56fff6b2a3ec0be995f58ce
 
-COCOAPODS: 1.11.2
+COCOAPODS: 1.11.3

+ 21 - 0
Pods/CropViewController/LICENSE

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015-2022 Tim Oliver
+
+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.

+ 39 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Categories/UIImage+CropRotate.h

@@ -0,0 +1,39 @@
+//
+//  UIImage+CropRotate.h
+//
+//  Copyright 2015-2022 Timothy Oliver. 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 <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface UIImage (TOCropRotate)
+
+/// 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 circular Whether the resulting image is returned as a square or a circle
+- (nonnull UIImage *)croppedImageWithFrame:(CGRect)frame
+                                     angle:(NSInteger)angle
+                              circularClip:(BOOL)circular;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 80 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Categories/UIImage+CropRotate.m

@@ -0,0 +1,80 @@
+//
+//  UIImage+CropRotate.m
+//
+//  Copyright 2015-2022 Timothy Oliver. 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 "UIImage+CropRotate.h"
+
+@implementation UIImage (CropRotate)
+
+- (BOOL)hasAlpha
+{
+    CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(self.CGImage);
+    return (alphaInfo == kCGImageAlphaFirst || alphaInfo == kCGImageAlphaLast ||
+            alphaInfo == kCGImageAlphaPremultipliedFirst || alphaInfo == kCGImageAlphaPremultipliedLast);
+}
+
+- (UIImage *)croppedImageWithFrame:(CGRect)frame angle:(NSInteger)angle circularClip:(BOOL)circular
+{
+    UIImage *croppedImage = nil;
+    UIGraphicsBeginImageContextWithOptions(frame.size, !self.hasAlpha && !circular, self.scale);
+    {
+        CGContextRef context = UIGraphicsGetCurrentContext();
+
+        // If we're capturing a circular image, set the clip mask first
+        if (circular) {
+            CGContextAddEllipseInRect(context, (CGRect){CGPointZero, frame.size});
+            CGContextClip(context);
+        }
+
+        // Offset the origin (Which is the top left corner) to start where our cropping origin is
+        CGContextTranslateCTM(context, -frame.origin.x, -frame.origin.y);
+
+        // 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);
+
+            // Work out the new bounding size of the canvas after rotation
+            CGRect imageBounds = (CGRect){CGPointZero, self.size};
+            CGRect rotatedBounds = CGRectApplyAffineTransform(imageBounds,
+                                                              CGAffineTransformMakeRotation(rotation));
+            // As we're rotating from the top left corner, and not the center of the canvas, the frame
+            // will have rotated out of our visible canvas. Compensate for this.
+            CGContextTranslateCTM(context, -rotatedBounds.origin.x, -rotatedBounds.origin.y);
+
+            // Perform the rotation transformation
+            CGContextRotateCTM(context, rotation);
+        }
+
+        // Draw the image with all of the transformation parameters applied.
+        // We do not need to worry about specifying the size here since we're already
+        // constrained by the context image size
+        [self drawAtPoint:CGPointZero];
+        
+        croppedImage = UIGraphicsGetImageFromCurrentImageContext();
+    }
+    UIGraphicsEndImageContext();
+
+    // Re-apply the retina scale we originally had
+    return [UIImage imageWithCGImage:croppedImage.CGImage scale:self.scale orientation:UIImageOrientationUp];
+}
+
+@end

+ 79 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Constants/TOCropViewConstants.h

@@ -0,0 +1,79 @@
+//
+//  TOCropViewConstants.h
+//
+//  Copyright 2015-2022 Timothy Oliver. 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 <Foundation/Foundation.h>
+
+/**
+ The shape of the cropping region of this crop view controller
+ */
+typedef NS_ENUM(NSInteger, TOCropViewCroppingStyle) {
+    TOCropViewCroppingStyleDefault,     // The regular, rectangular crop box
+    TOCropViewCroppingStyleCircular     // A fixed, circular crop box
+};
+
+/**
+ Preset values of the most common aspect ratios that can be used to quickly configure
+ the crop view controller.
+ */
+typedef NS_ENUM(NSInteger, TOCropViewControllerAspectRatioPreset) {
+    TOCropViewControllerAspectRatioPresetOriginal,
+    TOCropViewControllerAspectRatioPresetSquare,
+    TOCropViewControllerAspectRatioPreset3x2,
+    TOCropViewControllerAspectRatioPreset5x3,
+    TOCropViewControllerAspectRatioPreset4x3,
+    TOCropViewControllerAspectRatioPreset5x4,
+    TOCropViewControllerAspectRatioPreset7x5,
+    TOCropViewControllerAspectRatioPreset16x9,
+    TOCropViewControllerAspectRatioPresetCustom
+};
+
+/**
+ Whether the control toolbar is placed at the bottom or the top
+ */
+typedef NS_ENUM(NSInteger, TOCropViewControllerToolbarPosition) {
+    TOCropViewControllerToolbarPositionBottom,  // Bar is placed along the bottom in portrait
+    TOCropViewControllerToolbarPositionTop     // Bar is placed along the top in portrait (Respects the status bar)
+};
+
+static inline NSBundle *TO_CROP_VIEW_RESOURCE_BUNDLE_FOR_OBJECT(NSObject *object) {
+#if SWIFT_PACKAGE
+	// SPM is supposed to support the keyword SWIFTPM_MODULE_BUNDLE
+	// but I can't figure out how to make it work, so doing it manually
+   	NSString *bundleName = @"TOCropViewController_TOCropViewController";
+#else
+	NSString *bundleName = @"TOCropViewControllerBundle";
+#endif
+    NSBundle *resourceBundle = nil;
+    NSBundle *classBundle = [NSBundle bundleForClass:object.class];
+    NSURL *resourceBundleURL = [classBundle URLForResource:bundleName withExtension:@"bundle"];
+    if (resourceBundleURL) {
+        resourceBundle = [[NSBundle alloc] initWithURL:resourceBundleURL];
+		#ifndef NDEBUG
+		if (resourceBundle == nil) {
+		    @throw [[NSException alloc] initWithName:@"BundleAccessor" reason:[NSString stringWithFormat:@"unable to find bundle named %@", bundleName] userInfo:nil];
+		}
+		#endif
+    } else {
+        resourceBundle = classBundle;
+    }
+    return resourceBundle;
+}

+ 38 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Models/TOActivityCroppedImageProvider.h

@@ -0,0 +1,38 @@
+//
+//  TOActivityCroppedImageProvider.h
+//
+//  Copyright 2015-2022 Timothy Oliver. 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 <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface TOActivityCroppedImageProvider : UIActivityItemProvider
+
+@property (nonnull, nonatomic, readonly) UIImage *image;
+@property (nonatomic, readonly) CGRect cropFrame;
+@property (nonatomic, readonly) NSInteger angle;
+@property (nonatomic, readonly) BOOL circular;
+
+- (nonnull instancetype)initWithImage:(nonnull UIImage *)image cropFrame:(CGRect)cropFrame angle:(NSInteger)angle circular:(BOOL)circular;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 76 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Models/TOActivityCroppedImageProvider.m

@@ -0,0 +1,76 @@
+//
+//  TOActivityCroppedImageProvider.m
+//
+//  Copyright 2015-2022 Timothy Oliver. 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 "TOActivityCroppedImageProvider.h"
+#import "UIImage+CropRotate.h"
+
+@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 circular;
+
+@property (atomic, strong) UIImage *croppedImage;
+
+@end
+
+@implementation TOActivityCroppedImageProvider
+
+- (instancetype)initWithImage:(UIImage *)image cropFrame:(CGRect)cropFrame angle:(NSInteger)angle circular:(BOOL)circular
+{
+    if (self = [super initWithPlaceholderItem:[UIImage new]]) {
+        _image = image;
+        _cropFrame = cropFrame;
+        _angle = angle;
+        _circular = circular;
+    }
+    
+    return self;
+}
+
+#pragma mark - UIActivity Protocols -
+- (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController
+{
+    return [[UIImage alloc] init];
+}
+
+- (id)activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(NSString *)activityType
+{
+    return self.croppedImage;
+}
+
+#pragma mark - Image Generation -
+- (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})) {
+        self.croppedImage = self.image;
+        return self.croppedImage;
+    }
+    
+    UIImage *image = [self.image croppedImageWithFrame:self.cropFrame angle:self.angle circularClip:self.circular];
+    self.croppedImage = image;
+    return self.croppedImage;
+}
+
+@end

+ 49 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Models/TOCropViewControllerTransitioning.h

@@ -0,0 +1,49 @@
+//
+//  TOCropViewControllerTransitioning.h
+//
+//  Copyright 2015-2022 Timothy Oliver. 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 <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface TOCropViewControllerTransitioning : NSObject <UIViewControllerAnimatedTransitioning>
+
+/* State Tracking */
+@property (nonatomic, assign) BOOL isDismissing; // Whether this animation is presenting or dismissing
+@property (nullable, nonatomic, strong) UIImage *image;    // The image that will be used in this animation
+
+/* Destination/Origin points */
+@property (nullable, nonatomic, strong) UIView *fromView;  // The origin view who's frame the image will be animated from
+@property (nullable, nonatomic, strong) UIView *toView;    // The destination view who's frame the image will animate to
+
+@property (nonatomic, assign) CGRect fromFrame;  // An origin frame that the image will be animated from
+@property (nonatomic, assign) CGRect toFrame;    // A destination frame the image will aniamte to
+
+/* A block called just before the transition to perform any last-second UI configuration */
+@property (nullable, nonatomic, copy) void (^prepareForTransitionHandler)(void);
+
+/* Empties all of the properties in this object */
+- (void)reset;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 120 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Models/TOCropViewControllerTransitioning.m

@@ -0,0 +1,120 @@
+//
+//  TOCropViewControllerTransitioning.m
+//
+//  Copyright 2015-2022 Timothy Oliver. 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 "TOCropViewControllerTransitioning.h"
+#import <QuartzCore/QuartzCore.h>
+
+@implementation TOCropViewControllerTransitioning
+
+- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
+{
+    return 0.45f;
+}
+
+- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
+{
+    // Get the master view where the animation takes place
+    UIView *containerView = [transitionContext containerView];
+    
+    // Get the origin/destination view controllers
+    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
+    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
+    
+    // Work out which one is the crop view controller
+    UIViewController *cropViewController = (self.isDismissing == NO) ? toViewController : fromViewController;
+    UIViewController *previousController = (self.isDismissing == NO) ? fromViewController : toViewController;
+    
+    // Just in case, match up the frame sizes
+    cropViewController.view.frame = containerView.bounds;
+    if (self.isDismissing) {
+        previousController.view.frame = containerView.bounds;
+    }
+    
+    // Add the view layers beforehand as this will trigger the initial sets of layouts
+    if (self.isDismissing == NO) {
+        [containerView addSubview:cropViewController.view];
+
+        //Force a relayout now that the view is in the view hierarchy (so things like the safe area insets are now valid)
+        [cropViewController viewDidLayoutSubviews];
+    }
+    else {
+        [containerView insertSubview:previousController.view belowSubview:cropViewController.view];
+    }
+    
+    // Perform any last UI updates now so we can potentially factor them into our calculations, but after
+    // the container views have been set up
+    if (self.prepareForTransitionHandler) {
+        self.prepareForTransitionHandler();
+    }
+        
+    // If origin/destination views were supplied, use them to supplant the
+    // frames
+    if (!self.isDismissing && self.fromView) {
+        self.fromFrame = [self.fromView.superview convertRect:self.fromView.frame toView:containerView];
+    }
+    else if (self.isDismissing && self.toView) {
+        self.toFrame = [self.toView.superview convertRect:self.toView.frame toView:containerView];
+    }
+        
+    UIImageView *imageView = nil;
+    if ((self.isDismissing && !CGRectIsEmpty(self.toFrame)) || (!self.isDismissing && !CGRectIsEmpty(self.fromFrame))) {
+        imageView = [[UIImageView alloc] initWithImage:self.image];
+        imageView.frame = self.fromFrame;
+        [containerView addSubview:imageView];
+        
+        if (@available(iOS 11.0, *)) {
+            imageView.accessibilityIgnoresInvertColors = YES;
+        }
+    }
+    
+    cropViewController.view.alpha = (self.isDismissing ? 1.0f : 0.0f);
+    if (imageView) {
+        [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0f usingSpringWithDamping:1.0f initialSpringVelocity:0.7f options:0 animations:^{
+            imageView.frame = self.toFrame;
+        } completion:^(BOOL complete) {
+            [UIView animateWithDuration:0.25f animations:^{
+                imageView.alpha = 0.0f;
+            }completion:^(BOOL complete) {
+                [imageView removeFromSuperview];
+            }];
+        }];
+    }
+    
+    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
+        cropViewController.view.alpha = (self.isDismissing ? 0.0f : 1.0f);
+    } completion:^(BOOL complete) {
+        [self reset];
+        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
+    }];
+}
+
+- (void)reset
+{
+    self.image = nil;
+    self.toView = nil;
+    self.fromView = nil;
+    self.fromFrame = CGRectZero;
+    self.toFrame = CGRectZero;
+    self.prepareForTransitionHandler = nil;
+}
+
+@end

+ 38 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Models/TOCroppedImageAttributes.h

@@ -0,0 +1,38 @@
+//
+//  TOCroppedImageAttributes.h
+//
+//  Copyright 2015-2022 Timothy Oliver. 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 <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface TOCroppedImageAttributes : NSObject
+
+@property (nonatomic, readonly) NSInteger angle;
+@property (nonatomic, readonly) CGRect croppedFrame;
+@property (nonatomic, readonly) CGSize originalImageSize;
+
+- (instancetype)initWithCroppedFrame:(CGRect)croppedFrame angle:(NSInteger)angle originalImageSize:(CGSize)originalSize;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 46 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Models/TOCroppedImageAttributes.m

@@ -0,0 +1,46 @@
+//
+//  TOCroppedImageAttributes.m
+//
+//  Copyright 2015-2022 Timothy Oliver. 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 "TOCroppedImageAttributes.h"
+
+@interface TOCroppedImageAttributes ()
+
+@property (nonatomic, assign, readwrite) NSInteger angle;
+@property (nonatomic, assign, readwrite) CGRect croppedFrame;
+@property (nonatomic, assign, readwrite) CGSize originalImageSize;
+
+@end
+
+@implementation TOCroppedImageAttributes
+
+- (instancetype)initWithCroppedFrame:(CGRect)croppedFrame angle:(NSInteger)angle originalImageSize:(CGSize)originalSize
+{
+    if (self = [super init]) {
+        _angle = angle;
+        _croppedFrame = croppedFrame;
+        _originalImageSize = originalSize;
+    }
+    
+    return self;
+}
+
+@end

+ 8 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/Base.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,8 @@
+"Done" = "Done";
+"Cancel" = "Cancel";
+"Reset" = "Reset";
+"Original" = "Original";
+"Square" = "Square";
+"Delete Changes" = "Delete Changes";
+"Yes" = "Yes";
+"No" = "No";

+ 9 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/ar.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,9 @@
+"Done" = "تم";
+"Cancel" = "إلغاء";
+"Reset" = "إعادة تعيين";
+"Original" = "أصلي";
+"Square" = "مربع";
+"Delete Changes" = "حذف التغييرات";
+"Yes" = "نعم";
+"No" = "لا";
+

+ 8 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/ca.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,8 @@
+"Done" = "Fet";
+"Cancel" = "Cancel·lar";
+"Reset" = "Restablir";
+"Original" = "Original";
+"Square" = "Quadrat";
+"Delete Changes" = "Esborrar Canvis";
+"Yes" = "Si";
+"No" = "No";

+ 8 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/cs.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,8 @@
+"Done" = "Hotovo";
+"Cancel" = "Zrušit";
+"Reset" = "Reset";
+"Original" = "Originál";
+"Square" = "Čtverec";
+"Delete Changes" = "Smazat změny";
+"Yes" = "Ano";
+"No" = "Ne";

+ 8 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/da-DK.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,8 @@
+"Done" = "OK";
+"Cancel" = "Annuller";
+"Reset" = "Nulstil";
+"Original" = "Original";
+"Square" = "Firkantet";
+"Delete Changes" = "Slet ændringer";
+"Yes" = "Ja";
+"No" = "Nej";

+ 8 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/de.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,8 @@
+"Done" = "Fertig";
+"Cancel" = "Abbrechen";
+"Reset" = "Zurücksetzen";
+"Original" = "Original";
+"Square" = "Quadrat";
+"Delete Changes" = "Änderungen löschen";
+"Yes" = "Ja";
+"No" = "Nein";

+ 8 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/en.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,8 @@
+"Done" = "Done";
+"Cancel" = "Cancel";
+"Reset" = "Reset";
+"Original" = "Original";
+"Square" = "Square";
+"Delete Changes" = "Delete Changes";
+"Yes" = "Yes";
+"No" = "No";

+ 8 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/es.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,8 @@
+"Done" = "Aceptar";
+"Cancel" = "Cancelar";
+"Reset" = "Cambiar";
+"Original" = "Original";
+"Square" = "Cuadrada";
+"Delete Changes" = "Eliminar cambios";
+"Yes" = "Sí";
+"No" = "No";

+ 8 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/fa-IR.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,8 @@
+"Done" = "انجام شد";
+"Cancel" = "انصراف";
+"Reset" = "بازنشانی";
+"Original" = "اصلی";
+"Square" = "مربع";
+"Delete Changes" = "حذف تغییرات";
+"Yes" = "آری";
+"No" = "نه";

+ 8 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/fa.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,8 @@
+"Done" = "انجام شد";
+"Cancel" = "انصراف";
+"Reset" = "بازنشانی";
+"Original" = "اصلی";
+"Square" = "مربع";
+"Delete Changes" = "حذف تغییرات";
+"Yes" = "آری";
+"No" = "نه";

+ 8 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/fi.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,8 @@
+"Done" = "Valmis";
+"Cancel" = "Kumoa";
+"Reset" = "Palauta";
+"Original" = "Alkuperäinen";
+"Square" = "Neliö";
+"Delete Changes" = "Peru muutokset";
+"Yes" = "Kyllä";
+"No" = "Ei";

+ 8 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/fr.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,8 @@
+"Done" = "OK";
+"Cancel" = "Annuler";
+"Reset" = "Réinitialiser";
+"Original" = "D’origine";
+"Square" = "Carré";
+"Delete Changes" = "Supprimer les modifications";
+"Yes" = "Oui";
+"No" = "Non";

+ 9 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/hu.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,9 @@
+"Done" = "Kész";
+"Cancel" = "Mégse";
+"Reset" = "Visszaállítás";
+"Original" = "Eredeti";
+"Square" = "Négyzet";
+"Delete Changes" = "Módosítások törlése";
+"Yes" = "Igen";
+"No" = "Nem";
+

+ 8 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/id.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,8 @@
+"Done" = "Selesai";
+"Cancel" = "Batalkan";
+"Reset" = "Atur Ulang";
+"Original" = "Asli";
+"Square" = "Persegi";
+"Delete Changes" = "Hapus Perubahan";
+"Yes" = "Ya";
+"No" = "Tidak";

+ 8 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/it.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,8 @@
+"Done" = "Fatto";
+"Cancel" = "Annulla";
+"Reset" = "Ripristina";
+"Original" = "Originale";
+"Square" = "Quadrato";
+"Delete Changes" = "Elimina modifiche";
+"Yes" = "Sì";
+"No" = "No";

+ 8 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/ja.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,8 @@
+"Done" = "完了";
+"Cancel" = "キャンセル";
+"Reset" = "リセット";
+"Original" = "オリジナル";
+"Square" = "スクエア";
+"Delete Changes" = "変更を削除";
+"Yes" = "はい";
+"No" = "いいえ";

+ 8 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/ko.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,8 @@
+"Done" = "완료";
+"Cancel" = "취소";
+"Reset" = "재설정";
+"Original" = "원본";
+"Square" = "정방형";
+"Delete Changes" = "변경사항 삭제";
+"Yes" = "예";
+"No" = "아니요";

+ 8 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/ms.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,8 @@
+"Done" = "Selesai";
+"Cancel" = "Batal";
+"Reset" = "Reset";
+"Original" = "Asal";
+"Square" = "Segi empat";
+"Delete Changes" = "Padam Perubahan";
+"Yes" = "Ya";
+"No" = "Tidak";

+ 8 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/nl.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,8 @@
+"Done" = "Gereed";
+"Cancel" = "Annuleer";
+"Reset" = "Herstel";
+"Original" = "Origineel";
+"Square" = "Vierkant";
+"Delete Changes" = "Wis wijzigingen";
+"Yes" = "Ja";
+"No" = "Nee";

+ 8 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/pl.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,8 @@
+"Done" = "Gotowe";
+"Cancel" = "Anuluj";
+"Reset" = "Wyzeruj";
+"Original" = "Orygin.";
+"Square" = "Kwadrat";
+"Delete Changes" = "Usuń zmiany";
+"Yes" = "Tak";
+"No" = "Nie";

+ 8 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/pt-BR.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,8 @@
+"Done" = "OK";
+"Cancel" = "Cancelar";
+"Reset" = "Redefinir";
+"Original" = "Original";
+"Square" = "Quadrada";
+"Delete Changes" = "Apagar Alterações";
+"Yes" = "Sim";
+"No" = "Não";

+ 8 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/pt.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,8 @@
+"Done" = "OK";
+"Cancel" = "Cancelar";
+"Reset" = "Redefinir";
+"Original" = "Original";
+"Square" = "Quadrada";
+"Delete Changes" = "Apagar Alterações";
+"Yes" = "Sim";
+"No" = "Não";

+ 8 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/ro.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,8 @@
+"Done" = "Gata";
+"Cancel" = "Anulare";
+"Reset" = "Resetare";
+"Original" = "Original";
+"Square" = "Patrat";
+"Delete Changes" = "Ștergeți modificările";
+"Yes" = "Da";
+"No" = "Nu";

+ 8 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/ru.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,8 @@
+"Done" = "Готово";
+"Cancel" = "Отменить";
+"Reset" = "Сбросить";
+"Original" = "Оригинал";
+"Square" = "Квадрат";
+"Delete Changes" = "Удалить изменения";
+"Yes" = "Да";
+"No" = "Нет";

+ 8 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/sk.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,8 @@
+"Done" = "Hotovo";
+"Cancel" = "Zrušiť";
+"Reset" = "Reset";
+"Original" = "Originál";
+"Square" = "Štvorec";
+"Delete Changes" = "Zmazať zmeny";
+"Yes" = "Áno";
+"No" = "Nie";

+ 9 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/tr.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,9 @@
+"Done" = "Tamam";
+"Cancel" = "Vazgeç";
+"Reset" = "Sıfırla";
+"Original" = "Orjinal";
+"Square" = "Kare";
+"Delete Changes" = "Değişiklikleri Sil";
+"Yes" = "Evet";
+"No" = "Hayır";
+

+ 9 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/vi.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,9 @@
+"Done" = "Xong";
+"Cancel" = "Huỷ";
+"Reset" = "Đặt lại";
+"Original" = "Gốc";
+"Square" = "Vuông";
+"Delete Changes" = "Xóa Thay đổi";
+"Yes" = "Có";
+"No" = "Không";
+

+ 9 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/zh-Hans.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,9 @@
+"Done" = "完成";
+"Cancel" = "取消";
+"Reset" = "重设";
+"Original" = "原有";
+"Square" = "正方形";
+"Delete Changes" = "删除更改";
+"Yes" = "是";
+"No" = "否";
+

+ 9 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Resources/zh-Hant.lproj/TOCropViewControllerLocalizable.strings

@@ -0,0 +1,9 @@
+"Done" = "完成";
+"Cancel" = "取消";
+"Reset" = "重置";
+"Original" = "原始檔";
+"Square" = "正方形";
+"Delete Changes" = "刪除更動";
+
+"Yes" = "是";
+"No" = "否";

+ 471 - 0
Pods/CropViewController/Objective-C/TOCropViewController/TOCropViewController.h

@@ -0,0 +1,471 @@
+//
+//  TOCropViewController.h
+//
+//  Copyright 2015-2022 Timothy Oliver. 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 <UIKit/UIKit.h>
+
+#import "TOCropViewConstants.h"
+#import "TOCropView.h"
+#import "TOCropToolbar.h"
+
+@class TOCropViewController;
+
+///------------------------------------------------
+/// @name Delegate
+///------------------------------------------------
+
+@protocol TOCropViewControllerDelegate <NSObject>
+@optional
+
+/**
+ Called when the user has committed the crop action, and provides 
+ 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)
+ @param angle The angle of the image when it was cropped
+ */
+- (void)cropViewController:(nonnull TOCropViewController *)cropViewController
+        didCropImageToRect:(CGRect)cropRect
+                     angle:(NSInteger)angle;
+
+/**
+ Called when the user has committed the crop action, and provides 
+ both the original image with crop co-ordinates.
+ 
+ @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 angle The angle of the image when it was cropped
+ */
+- (void)cropViewController:(nonnull TOCropViewController *)cropViewController
+            didCropToImage:(nonnull UIImage *)image withRect:(CGRect)cropRect
+                     angle:(NSInteger)angle;
+
+/**
+ If the cropping style is set to circular, implementing this delegate will return a circle-cropped version of the selected
+ image, as well as it's cropping co-ordinates
+ 
+ @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 angle The angle of the image when it was cropped
+ */
+- (void)cropViewController:(nonnull TOCropViewController *)cropViewController
+    didCropToCircularImage:(nonnull UIImage *)image withRect:(CGRect)cropRect
+                     angle:(NSInteger)angle;
+
+/**
+ If implemented, when the user hits cancel, or completes a 
+ UIActivityViewController operation, this delegate will be called,
+ giving you a chance to manually dismiss the view controller
+
+ @param cancelled Whether a cropping action was actually performed, or if the user explicitly hit 'Cancel'
+ 
+ */
+- (void)cropViewController:(nonnull TOCropViewController *)cropViewController
+        didFinishCancelled:(BOOL)cancelled;
+
+@end
+
+@interface TOCropViewController : UIViewController
+
+/**
+ The original, uncropped image that was passed to this controller.
+ */
+@property (nonnull, nonatomic, readonly) UIImage *image;
+
+/**
+ The minimum croping aspect ratio. If set, user is prevented from
+ setting cropping rectangle to lower aspect ratio than defined by the parameter.
+ */
+@property (nonatomic, assign) CGFloat minimumAspectRatio;
+
+/**
+ The view controller's delegate that will receive the resulting
+ cropped image, as well as crop information.
+ */
+@property (nullable, nonatomic, weak) id<TOCropViewControllerDelegate> delegate;
+
+/**
+ If true, when the user hits 'Done', a UIActivityController will appear
+ before the view controller ends.
+ */
+@property (nonatomic, assign) BOOL showActivitySheetOnDone;
+
+/**
+ The crop view managed by this view controller.
+ */
+@property (nonnull, nonatomic, strong, readonly) TOCropView *cropView;
+
+/** 
+ In the coordinate space of the image itself, the region that is currently
+ being highlighted by the crop box.
+ 
+ This property can be set before the controller is presented to have
+ the image 'restored' to a previous cropping layout.
+ */
+@property (nonatomic, assign) CGRect imageCropFrame;
+
+/**
+ The angle in which the image is rotated in the crop view.
+ This can only be in 90 degree increments (eg, 0, 90, 180, 270).
+ 
+ This property can be set before the controller is presented to have 
+ the image 'restored' to a previous cropping layout.
+ */
+@property (nonatomic, assign) NSInteger angle;
+
+/**
+ The toolbar view managed by this view controller.
+ */
+@property (nonnull, nonatomic, strong, readonly) TOCropToolbar *toolbar;
+
+/**
+ The cropping style of this particular crop view controller
+ */
+@property (nonatomic, readonly) TOCropViewCroppingStyle croppingStyle;
+
+/**
+ A choice from one of the pre-defined aspect ratio presets
+ */
+@property (nonatomic, assign) TOCropViewControllerAspectRatioPreset aspectRatioPreset;
+
+/**
+ A CGSize value representing a custom aspect ratio, not listed in the presets.
+ E.g. A ratio of 4:3 would be represented as (CGSize){4.0f, 3.0f}
+ */
+@property (nonatomic, assign) CGSize customAspectRatio;
+
+/**
+ If this is set alongside `customAspectRatio`, the custom aspect ratio
+ will be shown as a selectable choice in the list of aspect ratios. (Default is `nil`)
+ */
+@property (nullable, nonatomic, copy) NSString *customAspectRatioName;
+
+/**
+ Title label which can be used to show instruction on the top of the crop view controller
+ */
+@property (nullable, nonatomic, readonly) UILabel *titleLabel;
+
+/**
+ Title for the 'Done' button.
+ Setting this will override the Default which is a localized string for "Done".
+ */
+@property (nullable, nonatomic, copy) NSString *doneButtonTitle;
+
+/**
+ Title for the 'Cancel' button.
+ Setting this will override the Default which is a localized string for "Cancel".
+ */
+@property (nullable, nonatomic, copy) NSString *cancelButtonTitle;
+
+/**
+ If true, button icons are visible in portairt instead button text.
+
+ Default is NO.
+ */
+@property (nonatomic, assign) BOOL showOnlyIcons;
+
+/**
+ Color for the 'Done' button.
+ Setting this will override the default color.
+ */
+@property (null_resettable, nonatomic, copy) UIColor *doneButtonColor;
+
+/**
+ Color for the 'Cancel' button.
+ Setting this will override the default color.
+ */
+@property (nullable, nonatomic, copy) UIColor *cancelButtonColor;
+
+/**
+ Shows a confirmation dialog when the user hits 'Cancel' and there are pending changes.
+ (Default is NO)
+ */
+@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.
+ This value also controls whether the dimensions can swap when the image is rotated.
+ 
+ Default is NO.
+ */
+@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 this is set to YES, and `resetAspectRatioEnabled` is set to NO, then the aspect ratio
+ button will automatically be hidden from the toolbar.
+ 
+ Default is NO.
+ */
+@property (nonatomic, assign) BOOL aspectRatioLockEnabled;
+
+/** 
+ If true, 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
+ button will automatically be hidden from the toolbar.
+ 
+ Default is YES
+ */
+@property (nonatomic, assign) BOOL resetAspectRatioEnabled;
+
+/**
+ The position of the Toolbar the default value is `TOCropViewControllerToolbarPositionBottom`.
+ */
+@property (nonatomic, assign) TOCropViewControllerToolbarPosition toolbarPosition;
+
+/**
+ When disabled, an additional rotation button that rotates the canvas in 
+ 90-degree segments in a clockwise direction is shown in the toolbar.
+ 
+ Default is NO.
+ */
+@property (nonatomic, assign) BOOL rotateClockwiseButtonHidden;
+
+/*
+ If this controller is embedded in UINavigationController its navigation bar
+ is hidden by default. Set this property to false to show the navigation bar.
+ This must be set before this controller is presented.
+ */
+@property (nonatomic, assign) BOOL hidesNavigationBar;
+
+/**
+ When enabled, hides the rotation button, as well as the alternative rotation 
+ button visible when `showClockwiseRotationButton` is set to YES.
+ 
+ Default is NO.
+ */
+@property (nonatomic, assign) BOOL rotateButtonsHidden;
+
+/**
+ When enabled, hides the 'Reset' button on the toolbar.
+
+ Default is NO.
+ */
+@property (nonatomic, assign) BOOL resetButtonHidden;
+/**
+ When enabled, hides the 'Aspect Ratio Picker' button on the toolbar.
+ 
+ Default is NO.
+ */
+@property (nonatomic, assign) BOOL aspectRatioPickerButtonHidden;
+
+/**
+ When enabled, hides the 'Done' button on the toolbar.
+
+ Default is NO.
+ */
+@property (nonatomic, assign) BOOL doneButtonHidden;
+
+/**
+ When enabled, hides the 'Cancel' button on the toolbar.
+
+ Default is NO.
+ */
+@property (nonatomic, assign) BOOL cancelButtonHidden;
+
+/** 
+ If `showActivitySheetOnDone` is true, 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 
+ access to the cropping information, it can be accessed in the supplied 
+ `TOActivityCroppedImageProvider` object
+ */
+@property (nullable, nonatomic, strong) NSArray<UIActivity *> *applicationActivities;
+
+/**
+ If `showActivitySheetOnDone` is true, then you may expliclty 
+ set activities that won't appear in the share sheet here.
+ */
+@property (nullable, nonatomic, strong) NSArray<UIActivityType> *excludedActivityTypes;
+
+/**
+ An array of `TOCropViewControllerAspectRatioPreset` enum values denoting which
+ aspect ratios the crop view controller may display (Default is nil. All are shown)
+ */
+@property (nullable, nonatomic, strong) NSArray<NSNumber *> *allowedAspectRatios;
+
+/**
+ When the user hits cancel, or completes a
+ UIActivityViewController operation, this block will be called,
+ giving you a chance to manually dismiss the view controller
+ */
+@property (nullable, nonatomic, strong) void (^onDidFinishCancelled)(BOOL isFinished);
+
+/**
+ Called when the user has committed the crop action, and provides
+ 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)
+ @param angle The angle of the image when it was cropped
+ */
+@property (nullable, nonatomic, strong) void (^onDidCropImageToRect)(CGRect cropRect, NSInteger angle);
+
+/**
+ Called when the user has committed the crop action, and provides
+ both the cropped image with crop co-ordinates.
+ 
+ @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 angle The angle of the image when it was cropped
+ */
+@property (nullable, nonatomic, strong) void (^onDidCropToRect)(UIImage* _Nonnull image, CGRect cropRect, NSInteger angle);
+
+/**
+ 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
+ 
+ @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 angle The angle of the image when it was cropped
+ */
+@property (nullable, nonatomic, strong) void (^onDidCropToCircleImage)(UIImage* _Nonnull image, CGRect cropRect, NSInteger angle);
+
+
+///------------------------------------------------
+/// @name Object Creation
+///------------------------------------------------
+
+/**
+ Creates a new instance of a crop view controller with the supplied image
+ 
+ @param image The image that will be used to crop.
+ */
+- (nonnull instancetype)initWithImage:(nonnull UIImage *)image NS_SWIFT_NAME(init(image:));
+
+/** 
+ Creates a new instance of a crop view controller with the supplied image and cropping style
+ 
+ @param style The cropping style that will be used with this view controller (eg, rectangular, or circular)
+ @param image The image that will be cropped
+ */
+- (nonnull instancetype)initWithCroppingStyle:(TOCropViewCroppingStyle)style image:(nonnull UIImage *)image NS_SWIFT_NAME(init(croppingStyle:image:));
+
+/**
+ Commits the crop action as if user pressed done button in the bottom bar themself
+ */
+- (void)commitCurrentCrop;
+
+/**
+ Resets object of TOCropViewController class as if user pressed reset button in the bottom bar themself
+ */
+- (void)resetCropViewLayout;
+
+/** 
+ Set the aspect ratio to be one of the available preset options. These presets have specific behaviour
+ such as swapping their dimensions depending on portrait or landscape sized images.
+ 
+ @param aspectRatioPreset The aspect ratio preset
+ @param animated Whether the transition to the aspect ratio is animated
+ */
+- (void)setAspectRatioPreset:(TOCropViewControllerAspectRatioPreset)aspectRatioPreset animated:(BOOL)animated NS_SWIFT_NAME(setAspectRatioPreset(_:animated:));
+
+/**
+ Play a custom animation of the target image zooming to its position in
+ the crop controller while the background fades in. 
+ 
+ @param viewController The parent controller that this view controller would be presenting from.
+ @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. Optional if `fromView` has a value.
+ @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.
+ */
+- (void)presentAnimatedFromParentViewController:(nonnull UIViewController *)viewController
+                                       fromView:(nullable UIView *)fromView
+                                      fromFrame:(CGRect)fromFrame
+                                          setup:(nullable void (^)(void))setup
+                                     completion:(nullable void (^)(void))completion NS_SWIFT_NAME(presentAnimatedFrom(_:view:frame:setup:completion:));
+
+/**
+ Play a custom animation of the target image zooming to its position in
+ the crop controller while the background fades in. Additionally, if you're 
+ 'restoring' to a previous crop setup, this method lets you provide a previously
+ cropped copy of the image, and the previous crop settings to transition back to
+ where the user would have left off.
+ 
+ @param viewController The parent controller that this view controller would be presenting from.
+ @param image The previously cropped image that can be used in the transition animation.
+ @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 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.
+ */
+- (void)presentAnimatedFromParentViewController:(nonnull UIViewController *)viewController
+                                      fromImage:(nullable UIImage *)image
+                                       fromView:(nullable UIView *)fromView
+                                      fromFrame:(CGRect)fromFrame
+                                          angle:(NSInteger)angle
+                                   toImageFrame:(CGRect)toFrame
+                                          setup:(nullable void (^)(void))setup
+                                     completion:(nullable void (^)(void))completion NS_SWIFT_NAME(presentAnimatedFrom(_:fromImage:fromView:fromFrame:angle:toFrame:setup:completion:));
+
+/**
+ Play a custom animation of the supplied cropped image zooming out from
+ the cropped frame to the specified frame as the rest of the content fades out.
+ If any view configurations need to be done before the animation starts,
+ 
+ @param viewController The parent controller that this view controller would be presenting from.
+ @param toView A view who's frame will be used to establish the destination frame
+ @param frame The target frame that the image will animate to
+ @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.
+ */
+- (void)dismissAnimatedFromParentViewController:(nonnull UIViewController *)viewController
+                                         toView:(nullable UIView *)toView
+                                        toFrame:(CGRect)frame
+                                          setup:(nullable void (^)(void))setup
+                                     completion:(nullable void (^)(void))completion NS_SWIFT_NAME(dismissAnimatedFrom(_:toView:toFrame:setup:completion:));
+
+/**
+ Play a custom animation of the supplied cropped image zooming out from
+ the cropped frame to the specified frame as the rest of the content fades out.
+ If any view configurations need to be done before the animation starts,
+ 
+ @param viewController The parent controller that this view controller would be presenting from.
+ @param image The resulting 'cropped' image. If supplied, will animate out of the crop box zone. If nil, the default image will entirely zoom out
+ @param toView A view who's frame will be used to establish the destination frame
+ @param frame The target frame that the image will animate to
+ @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.
+ */
+- (void)dismissAnimatedFromParentViewController:(nonnull UIViewController *)viewController
+                               withCroppedImage:(nullable UIImage *)image
+                                         toView:(nullable UIView *)toView
+                                        toFrame:(CGRect)frame
+                                          setup:(nullable void (^)(void))setup
+                                     completion:(nullable void (^)(void))completion NS_SWIFT_NAME(dismissAnimatedFrom(_:croppedImage:toView:toFrame:setup:completion:));
+
+@end
+

+ 1330 - 0
Pods/CropViewController/Objective-C/TOCropViewController/TOCropViewController.m

@@ -0,0 +1,1330 @@
+//
+//  TOCropViewController.m
+//
+//  Copyright 2015-2022 Timothy Oliver. 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 "TOCropViewController.h"
+
+#import "TOCropViewControllerTransitioning.h"
+#import "TOActivityCroppedImageProvider.h"
+#import "UIImage+CropRotate.h"
+#import "TOCroppedImageAttributes.h"
+
+static const CGFloat kTOCropViewControllerTitleTopPadding = 14.0f;
+static const CGFloat kTOCropViewControllerToolbarHeight = 44.0f;
+
+@interface TOCropViewController () <UIActionSheetDelegate, UIViewControllerTransitioningDelegate, TOCropViewDelegate>
+
+/* The target image */
+@property (nonatomic, readwrite) UIImage *image;
+
+/* The cropping style of the crop view */
+@property (nonatomic, assign, readwrite) TOCropViewCroppingStyle croppingStyle;
+
+/* Views */
+@property (nonatomic, strong) TOCropToolbar *toolbar;
+@property (nonatomic, strong, readwrite) TOCropView *cropView;
+@property (nonatomic, strong) UIView *toolbarSnapshotView;
+@property (nonatomic, strong, readwrite) UILabel *titleLabel;
+
+/* Transition animation controller */
+@property (nonatomic, copy) void (^prepareForTransitionHandler)(void);
+@property (nonatomic, strong) TOCropViewControllerTransitioning *transitionController;
+@property (nonatomic, assign) BOOL inTransition;
+
+/* If pushed from a navigation controller, the visibility of that controller's bars. */
+@property (nonatomic, assign) BOOL navigationBarHidden;
+@property (nonatomic, assign) BOOL toolbarHidden;
+
+/* State for whether content is being laid out vertically or horizontally */
+@property (nonatomic, readonly) BOOL verticalLayout;
+
+/* Convenience method for managing status bar state */
+@property (nonatomic, readonly) BOOL overrideStatusBar; // Whether the view controller needs to touch the status bar
+@property (nonatomic, readonly) BOOL statusBarHidden;   // Whether it should be hidden or visible at this point
+@property (nonatomic, readonly) CGFloat statusBarHeight; // The height of the status bar when visible
+
+/* Convenience method for getting the vertical inset for both iPhone X and status bar */
+@property (nonatomic, readonly) UIEdgeInsets statusBarSafeInsets;
+
+/* Flag to perform initial setup on the first run */
+@property (nonatomic, assign) BOOL firstTime;
+
+@end
+
+@implementation TOCropViewController
+
+- (instancetype)initWithCroppingStyle:(TOCropViewCroppingStyle)style image:(UIImage *)image
+{
+    NSParameterAssert(image);
+
+    self = [super initWithNibName:nil bundle:nil];
+    if (self) {
+        // Init parameters
+        _image = image;
+        _croppingStyle = style;
+        
+        // Set up base view controller behaviour
+        self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
+        self.modalPresentationStyle = UIModalPresentationFullScreen;
+        self.automaticallyAdjustsScrollViewInsets = NO;
+        self.hidesNavigationBar = true;
+        
+        // Controller object that handles the transition animation when presenting / dismissing this app
+        _transitionController = [[TOCropViewControllerTransitioning alloc] init];
+
+        // Default initial behaviour
+        _aspectRatioPreset = TOCropViewControllerAspectRatioPresetOriginal;
+
+        #if TARGET_OS_MACCATALYST
+        _toolbarPosition = TOCropViewControllerToolbarPositionTop;
+        #else
+        _toolbarPosition = TOCropViewControllerToolbarPositionBottom;
+        #endif
+    }
+	
+    return self;
+}
+
+- (instancetype)initWithImage:(UIImage *)image
+{
+    return [self initWithCroppingStyle:TOCropViewCroppingStyleDefault image:image];
+}
+
+- (void)viewDidLoad
+{
+    [super viewDidLoad];
+
+    // Set up view controller properties
+    self.transitioningDelegate = self;
+    self.view.backgroundColor = self.cropView.backgroundColor;
+    
+    BOOL circularMode = (self.croppingStyle == TOCropViewCroppingStyleCircular);
+
+    // Layout the views initially
+    self.cropView.frame = [self frameForCropViewWithVerticalLayout:self.verticalLayout];
+    self.toolbar.frame = [self frameForToolbarWithVerticalLayout:self.verticalLayout];
+
+    // Set up toolbar default behaviour
+    self.toolbar.clampButtonHidden = self.aspectRatioPickerButtonHidden || circularMode;
+    self.toolbar.rotateClockwiseButtonHidden = self.rotateClockwiseButtonHidden;
+    
+    // Set up the toolbar button actions
+    __weak typeof(self) weakSelf = self;
+    self.toolbar.doneButtonTapped   = ^{ [weakSelf doneButtonTapped]; };
+    self.toolbar.cancelButtonTapped = ^{ [weakSelf cancelButtonTapped]; };
+    self.toolbar.resetButtonTapped = ^{ [weakSelf resetCropViewLayout]; };
+    self.toolbar.clampButtonTapped = ^{ [weakSelf showAspectRatioDialog]; };
+    self.toolbar.rotateCounterclockwiseButtonTapped = ^{ [weakSelf rotateCropViewCounterclockwise]; };
+    self.toolbar.rotateClockwiseButtonTapped        = ^{ [weakSelf rotateCropViewClockwise]; };
+}
+
+- (void)viewWillAppear:(BOOL)animated
+{
+    [super viewWillAppear:animated];
+    
+    // If we're animating onto the screen, set a flag
+    // so we can manually control the status bar fade out timing
+    if (animated) {
+        self.inTransition = YES;
+        [self setNeedsStatusBarAppearanceUpdate];
+    }
+    
+    // If this controller is pushed onto a navigation stack, set flags noting the
+    // state of the navigation controller bars before we present, and then hide them
+    if (self.navigationController) {
+        if (self.hidesNavigationBar) {
+            self.navigationBarHidden = self.navigationController.navigationBarHidden;
+            self.toolbarHidden = self.navigationController.toolbarHidden;
+            [self.navigationController setNavigationBarHidden:YES animated:animated];
+            [self.navigationController setToolbarHidden:YES animated:animated];
+        }
+
+        self.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
+    }
+    else {
+        // Hide the background content when transitioning for performance
+        [self.cropView setBackgroundImageViewHidden:YES animated:NO];
+        
+        // The title label will fade
+        self.titleLabel.alpha = animated ? 0.0f : 1.0f;
+    }
+
+    // If an initial aspect ratio was set before presentation, set it now once the rest of
+    // the setup will have been done
+    if (self.aspectRatioPreset != TOCropViewControllerAspectRatioPresetOriginal) {
+        [self setAspectRatioPreset:self.aspectRatioPreset animated:NO];
+    }
+}
+
+- (void)viewDidAppear:(BOOL)animated
+{
+    [super viewDidAppear:animated];
+    
+    // Disable the transition flag for the status bar
+    self.inTransition = NO;
+    
+    // Re-enable translucency now that the animation has completed
+    self.cropView.simpleRenderMode = NO;
+
+    // Now that the presentation animation will have finished, animate
+    // the status bar fading out, and if present, the title label fading in
+    void (^updateContentBlock)(void) = ^{
+        [self setNeedsStatusBarAppearanceUpdate];
+        self.titleLabel.alpha = 1.0f;
+    };
+
+    if (animated) {
+        [UIView animateWithDuration:0.3f animations:updateContentBlock];
+    }
+    else {
+        updateContentBlock();
+    }
+    
+    // Make the grid overlay view fade in
+    if (self.cropView.gridOverlayHidden) {
+        [self.cropView setGridOverlayHidden:NO animated:animated];
+    }
+    
+    // Fade in the background view content
+    if (self.navigationController == nil) {
+        [self.cropView setBackgroundImageViewHidden:NO animated:animated];
+    }
+}
+
+- (void)viewWillDisappear:(BOOL)animated
+{
+    [super viewWillDisappear:animated];
+    
+    // Set the transition flag again so we can defer the status bar
+    self.inTransition = YES;
+    [UIView animateWithDuration:0.5f animations:^{ [self setNeedsStatusBarAppearanceUpdate]; }];
+    
+    // Restore the navigation controller to its state before we were presented
+    if (self.navigationController && self.hidesNavigationBar) {
+        [self.navigationController setNavigationBarHidden:self.navigationBarHidden animated:animated];
+        [self.navigationController setToolbarHidden:self.toolbarHidden animated:animated];
+    }
+}
+
+- (void)viewDidDisappear:(BOOL)animated
+{
+    [super viewDidDisappear:animated];
+    
+    // Reset the state once the view has gone offscreen
+    self.inTransition = NO;
+    [self setNeedsStatusBarAppearanceUpdate];
+}
+
+#pragma mark - Status Bar -
+- (UIStatusBarStyle)preferredStatusBarStyle
+{
+    if (self.navigationController) {
+        return UIStatusBarStyleLightContent;
+    }
+
+    // Even though we are a dark theme, leave the status bar
+    // as black so it's not obvious that it's still visible during the transition
+    return UIStatusBarStyleDefault;
+}
+
+- (BOOL)prefersStatusBarHidden
+{
+    // Disregard the transition animation if we're not actively overriding it
+    if (!self.overrideStatusBar) {
+        return self.statusBarHidden;
+    }
+
+    // Work out whether the status bar needs to be visible
+    // during a transition animation or not
+    BOOL hidden = YES; // Default is yes
+    hidden = hidden && !(self.inTransition); // Not currently in a presentation animation (Where removing the status bar would break the layout)
+    hidden = hidden && !(self.view.superview == nil); // Not currently waiting to be added to a super view
+    return hidden;
+}
+
+- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures
+{
+    return UIRectEdgeAll;
+}
+
+- (CGRect)frameForToolbarWithVerticalLayout:(BOOL)verticalLayout
+{
+    UIEdgeInsets insets = self.statusBarSafeInsets;
+
+    CGRect frame = CGRectZero;
+    if (!verticalLayout) { // In landscape laying out toolbar to the left
+        frame.origin.x = insets.left;
+        frame.origin.y = 0.0f;
+        frame.size.width = kTOCropViewControllerToolbarHeight;
+        frame.size.height = CGRectGetHeight(self.view.frame);
+    }
+    else {
+        frame.origin.x = 0.0f;
+        frame.size.width = CGRectGetWidth(self.view.bounds);
+        frame.size.height = kTOCropViewControllerToolbarHeight;
+
+        if (self.toolbarPosition == TOCropViewControllerToolbarPositionBottom) {
+            frame.origin.y = CGRectGetHeight(self.view.bounds) - (frame.size.height + insets.bottom);
+        } else {
+            frame.origin.y = insets.top;
+        }
+    }
+    
+    return frame;
+}
+
+- (CGRect)frameForCropViewWithVerticalLayout:(BOOL)verticalLayout
+{
+    //On an iPad, if being presented in a modal view controller by a UINavigationController,
+    //at the time we need it, the size of our view will be incorrect.
+    //If this is the case, derive our view size from our parent view controller instead
+    UIView *view = nil;
+    if (self.parentViewController == nil) {
+        view = self.view;
+    }
+    else {
+        view = self.parentViewController.view;
+    }
+
+    UIEdgeInsets insets = self.statusBarSafeInsets;
+
+    CGRect bounds = view.bounds;
+    CGRect frame = CGRectZero;
+
+    // Horizontal layout (eg landscape)
+    if (!verticalLayout) {
+        frame.origin.x = kTOCropViewControllerToolbarHeight + insets.left;
+        frame.size.width = CGRectGetWidth(bounds) - frame.origin.x;
+		frame.size.height = CGRectGetHeight(bounds);
+    }
+    else { // Vertical layout
+        frame.size.height = CGRectGetHeight(bounds);
+        frame.size.width = CGRectGetWidth(bounds);
+
+        // Set Y and adjust for height
+        if (self.toolbarPosition == TOCropViewControllerToolbarPositionBottom) {
+            frame.size.height -= (insets.bottom + kTOCropViewControllerToolbarHeight);
+        } else if (self.toolbarPosition == TOCropViewControllerToolbarPositionTop) {
+			frame.origin.y = kTOCropViewControllerToolbarHeight + insets.top;
+            frame.size.height -= frame.origin.y;
+        }
+    }
+    
+    return frame;
+}
+
+- (CGRect)frameForTitleLabelWithSize:(CGSize)size verticalLayout:(BOOL)verticalLayout
+{
+    CGRect frame = (CGRect){CGPointZero, size};
+    CGFloat viewWidth = self.view.bounds.size.width;
+    CGFloat x = 0.0f; // Additional X offset in landscape mode
+
+    // Adjust for landscape layout
+    if (!verticalLayout) {
+        x = kTOCropViewControllerTitleTopPadding;
+        if (@available(iOS 11.0, *)) {
+            x += self.view.safeAreaInsets.left;
+        }
+
+        viewWidth -= x;
+    }
+
+    // Work out horizontal position
+    frame.origin.x = ceilf((viewWidth - frame.size.width) * 0.5f);
+    if (!verticalLayout) { frame.origin.x += x; }
+
+    // Work out vertical position
+    if (@available(iOS 11.0, *)) {
+        frame.origin.y = self.view.safeAreaInsets.top + kTOCropViewControllerTitleTopPadding;
+    }
+    else {
+        frame.origin.y = self.statusBarHeight + kTOCropViewControllerTitleTopPadding;
+    }
+
+    return frame;
+}
+
+- (void)adjustCropViewInsets
+{
+    UIEdgeInsets insets = self.statusBarSafeInsets;
+
+    // If there is no title text, inset the top of the content as high as possible
+    if (!self.titleLabel.text.length) {
+        if (self.verticalLayout) {
+          if (self.toolbarPosition == TOCropViewControllerToolbarPositionTop) {
+            self.cropView.cropRegionInsets = UIEdgeInsetsMake(0.0f, 0.0f, insets.bottom, 0.0f);
+          }
+          else { // Add padding to the top otherwise
+            self.cropView.cropRegionInsets = UIEdgeInsetsMake(insets.top, 0.0f, 0.0, 0.0f);
+          }
+        }
+        else {
+            self.cropView.cropRegionInsets = UIEdgeInsetsMake(0.0f, 0.0f, insets.bottom, 0.0f);
+        }
+
+        return;
+    }
+
+    // Work out the size of the title label based on the crop view size
+    CGRect frame = self.titleLabel.frame;
+    frame.size = [self.titleLabel sizeThatFits:self.cropView.frame.size];
+    self.titleLabel.frame = frame;
+
+    // Set out the appropriate inset for that
+    CGFloat verticalInset = self.statusBarHeight;
+    verticalInset += kTOCropViewControllerTitleTopPadding;
+    verticalInset += self.titleLabel.frame.size.height;
+    self.cropView.cropRegionInsets = UIEdgeInsetsMake(verticalInset, 0, insets.bottom, 0);
+}
+
+- (void)adjustToolbarInsets
+{
+    UIEdgeInsets insets = UIEdgeInsetsZero;
+
+    if (@available(iOS 11.0, *)) {
+        // Add padding to the left in landscape mode
+        if (!self.verticalLayout) {
+            insets.left = self.view.safeAreaInsets.left;
+        }
+        else {
+            // Add padding on top if in vertical and tool bar is at the top
+            if (self.toolbarPosition == TOCropViewControllerToolbarPositionTop) {
+                insets.top = self.view.safeAreaInsets.top;
+            }
+            else { // Add padding to the bottom otherwise
+                insets.bottom = self.view.safeAreaInsets.bottom;
+            }
+        }
+    }
+    else { // iOS <= 10
+        if (!self.statusBarHidden && self.toolbarPosition == TOCropViewControllerToolbarPositionTop) {
+            insets.top = self.statusBarHeight;
+        }
+    }
+
+    // Update the toolbar with these properties
+    self.toolbar.backgroundViewOutsets = insets;
+    self.toolbar.statusBarHeightInset = self.statusBarHeight;
+    [self.toolbar setNeedsLayout];
+}
+
+- (void)viewSafeAreaInsetsDidChange
+{
+    [super viewSafeAreaInsetsDidChange];
+    [self adjustCropViewInsets];
+    [self adjustToolbarInsets];
+}
+
+- (void)viewDidLayoutSubviews
+{
+    [super viewDidLayoutSubviews];
+
+    self.cropView.frame = [self frameForCropViewWithVerticalLayout:self.verticalLayout];
+    [self adjustCropViewInsets];
+    [self.cropView moveCroppedContentToCenterAnimated:NO];
+
+    if (self.firstTime == NO) {
+        [self.cropView performInitialSetup];
+        self.firstTime = YES;
+    }
+    
+    if (self.title.length) {
+        self.titleLabel.frame = [self frameForTitleLabelWithSize:self.titleLabel.frame.size verticalLayout:self.verticalLayout];
+        [self.cropView moveCroppedContentToCenterAnimated:NO];
+    }
+
+    [UIView performWithoutAnimation:^{
+        self.toolbar.frame = [self frameForToolbarWithVerticalLayout:self.verticalLayout];
+        [self adjustToolbarInsets];
+        [self.toolbar setNeedsLayout];
+    }];
+}
+
+#pragma mark - Rotation Handling -
+
+- (void)_willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
+{
+    self.toolbarSnapshotView = [self.toolbar snapshotViewAfterScreenUpdates:NO];
+    self.toolbarSnapshotView.frame = self.toolbar.frame;
+    
+    if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) {
+        self.toolbarSnapshotView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
+    }
+    else {
+        self.toolbarSnapshotView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleRightMargin;
+    }
+    [self.view addSubview:self.toolbarSnapshotView];
+
+    // Set up the toolbar frame to be just off t
+    CGRect frame = [self frameForToolbarWithVerticalLayout:UIInterfaceOrientationIsPortrait(toInterfaceOrientation)];
+    if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) {
+        frame.origin.x = -frame.size.width;
+    }
+    else {
+        frame.origin.y = self.view.bounds.size.height;
+    }
+    self.toolbar.frame = frame;
+
+    [self.toolbar layoutIfNeeded];
+    self.toolbar.alpha = 0.0f;
+    
+    [self.cropView prepareforRotation];
+    self.cropView.frame = [self frameForCropViewWithVerticalLayout:!UIInterfaceOrientationIsPortrait(toInterfaceOrientation)];
+    self.cropView.simpleRenderMode = YES;
+    self.cropView.internalLayoutDisabled = YES;
+}
+
+- (void)_willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
+{
+    //Remove all animations in the toolbar
+    self.toolbar.frame = [self frameForToolbarWithVerticalLayout:!UIInterfaceOrientationIsLandscape(toInterfaceOrientation)];
+    [self.toolbar.layer removeAllAnimations];
+    for (CALayer *sublayer in self.toolbar.layer.sublayers) {
+        [sublayer removeAllAnimations];
+    }
+
+    // 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
+                        options:UIViewAnimationOptionBeginFromCurrentState
+                     animations:
+    ^{
+        self.cropView.frame = [self frameForCropViewWithVerticalLayout:!UIInterfaceOrientationIsLandscape(toInterfaceOrientation)];
+        self.toolbar.frame = [self frameForToolbarWithVerticalLayout:UIInterfaceOrientationIsPortrait(toInterfaceOrientation)];
+        [self.cropView performRelayoutForRotation];
+    } completion:nil];
+
+    self.toolbarSnapshotView.alpha = 0.0f;
+    self.toolbar.alpha = 1.0f;
+}
+
+- (void)_didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
+{
+    [self.toolbarSnapshotView removeFromSuperview];
+    self.toolbarSnapshotView = nil;
+    
+    [self.cropView setSimpleRenderMode:NO animated:YES];
+    self.cropView.internalLayoutDisabled = NO;
+}
+
+- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
+{
+    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
+    
+    // If the size doesn't change (e.g, we did a 180 degree device rotation), don't bother doing a relayout
+    if (CGSizeEqualToSize(size, self.view.bounds.size)) { return; }
+    
+    UIInterfaceOrientation orientation = UIInterfaceOrientationPortrait;
+    CGSize currentSize = self.view.bounds.size;
+    if (currentSize.width < size.width) {
+        orientation = UIInterfaceOrientationLandscapeLeft;
+    }
+    
+    [self _willRotateToInterfaceOrientation:orientation duration:coordinator.transitionDuration];
+    [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
+        [self _willAnimateRotationToInterfaceOrientation:orientation duration:coordinator.transitionDuration];
+    } completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
+        [self _didRotateFromInterfaceOrientation:orientation];
+    }];
+}
+
+#pragma mark - Reset -
+- (void)resetCropViewLayout
+{
+    BOOL animated = (self.cropView.angle == 0);
+    
+    if (self.resetAspectRatioEnabled) {
+        self.aspectRatioLockEnabled = NO;
+    }
+    
+    [self.cropView resetLayoutToDefaultAnimated:animated];
+}
+
+#pragma mark - Aspect Ratio Handling -
+- (void)showAspectRatioDialog
+{
+    if (self.cropView.aspectRatioLockEnabled) {
+        self.cropView.aspectRatioLockEnabled = NO;
+        self.toolbar.clampButtonGlowing = NO;
+        return;
+    }
+    
+    //Depending on the shape of the image, work out if horizontal, or vertical options are required
+    BOOL verticalCropBox = self.cropView.cropBoxAspectRatioIsPortrait;
+    
+    // Get the resource bundle depending on the framework/dependency manager we're using
+	NSBundle *resourceBundle = TO_CROP_VIEW_RESOURCE_BUNDLE_FOR_OBJECT(self);
+    
+    //Prepare the localized options
+	NSString *cancelButtonTitle = NSLocalizedStringFromTableInBundle(@"Cancel", @"TOCropViewControllerLocalizable", resourceBundle, nil);
+	NSString *originalButtonTitle = NSLocalizedStringFromTableInBundle(@"Original", @"TOCropViewControllerLocalizable", resourceBundle, nil);
+	NSString *squareButtonTitle = NSLocalizedStringFromTableInBundle(@"Square", @"TOCropViewControllerLocalizable", resourceBundle, nil);
+    
+    //Prepare the list that will be fed to the alert view/controller
+    
+    // Ratio titles according to the order of enum TOCropViewControllerAspectRatioPreset
+    NSArray<NSString *> *portraitRatioTitles = @[originalButtonTitle, squareButtonTitle, @"2:3", @"3:5", @"3:4", @"4:5", @"5:7", @"9:16"];
+    NSArray<NSString *> *landscapeRatioTitles = @[originalButtonTitle, squareButtonTitle, @"3:2", @"5:3", @"4:3", @"5:4", @"7:5", @"16:9"];
+
+    NSMutableArray *ratioValues = [NSMutableArray array];
+    NSMutableArray *itemStrings = [NSMutableArray array];
+
+    if (self.allowedAspectRatios == nil) {
+        for (NSInteger i = 0; i < TOCropViewControllerAspectRatioPresetCustom; i++) {
+            NSString *itemTitle = verticalCropBox ? portraitRatioTitles[i] : landscapeRatioTitles[i];
+            [itemStrings addObject:itemTitle];
+            [ratioValues addObject:@(i)];
+        }
+    }
+    else {
+        for (NSNumber *allowedRatio in self.allowedAspectRatios) {
+            TOCropViewControllerAspectRatioPreset ratio = allowedRatio.integerValue;
+            NSString *itemTitle = verticalCropBox ? portraitRatioTitles[ratio] : landscapeRatioTitles[ratio];
+            [itemStrings addObject:itemTitle];
+            [ratioValues addObject:allowedRatio];
+        }
+    }
+    
+    // If a custom aspect ratio is provided, and a custom name has been given to it, add it as a visible choice
+    if (self.customAspectRatioName.length > 0 && !CGSizeEqualToSize(CGSizeZero, self.customAspectRatio)) {
+        [itemStrings addObject:self.customAspectRatioName];
+        [ratioValues addObject:@(TOCropViewControllerAspectRatioPresetCustom)];
+    }
+
+    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
+    [alertController addAction:[UIAlertAction actionWithTitle:cancelButtonTitle style:UIAlertActionStyleCancel handler:nil]];
+
+    //Add each item to the alert controller
+    for (NSInteger i = 0; i < itemStrings.count; i++) {
+        id handlerBlock = ^(UIAlertAction *action) {
+            [self setAspectRatioPreset:[ratioValues[i] integerValue] animated:YES];
+            self.aspectRatioLockEnabled = YES;
+        };
+        UIAlertAction *action = [UIAlertAction actionWithTitle:itemStrings[i] style:UIAlertActionStyleDefault handler:handlerBlock];
+        [alertController addAction:action];
+    }
+
+    alertController.modalPresentationStyle = UIModalPresentationPopover;
+    UIPopoverPresentationController *presentationController = [alertController popoverPresentationController];
+    presentationController.sourceView = self.toolbar;
+    presentationController.sourceRect = self.toolbar.clampButtonFrame;
+    [self presentViewController:alertController animated:YES completion:nil];
+}
+
+- (void)setAspectRatioPreset:(TOCropViewControllerAspectRatioPreset)aspectRatioPreset animated:(BOOL)animated
+{
+    CGSize aspectRatio = CGSizeZero;
+    
+    _aspectRatioPreset = aspectRatioPreset;
+    
+    switch (aspectRatioPreset) {
+        case TOCropViewControllerAspectRatioPresetOriginal:
+            aspectRatio = CGSizeZero;
+            break;
+        case TOCropViewControllerAspectRatioPresetSquare:
+            aspectRatio = CGSizeMake(1.0f, 1.0f);
+            break;
+        case TOCropViewControllerAspectRatioPreset3x2:
+            aspectRatio = CGSizeMake(3.0f, 2.0f);
+            break;
+        case TOCropViewControllerAspectRatioPreset5x3:
+            aspectRatio = CGSizeMake(5.0f, 3.0f);
+            break;
+        case TOCropViewControllerAspectRatioPreset4x3:
+            aspectRatio = CGSizeMake(4.0f, 3.0f);
+            break;
+        case TOCropViewControllerAspectRatioPreset5x4:
+            aspectRatio = CGSizeMake(5.0f, 4.0f);
+            break;
+        case TOCropViewControllerAspectRatioPreset7x5:
+            aspectRatio = CGSizeMake(7.0f, 5.0f);
+            break;
+        case TOCropViewControllerAspectRatioPreset16x9:
+            aspectRatio = CGSizeMake(16.0f, 9.0f);
+            break;
+        case TOCropViewControllerAspectRatioPresetCustom:
+            aspectRatio = self.customAspectRatio;
+            break;
+    }
+    
+    // If the aspect ratio lock is not enabled, allow a swap
+    // If the aspect ratio lock is on, allow a aspect ratio swap
+    // only if the allowDimensionSwap option is specified.
+    BOOL aspectRatioCanSwapDimensions = !self.aspectRatioLockEnabled ||
+                                (self.aspectRatioLockEnabled && self.aspectRatioLockDimensionSwapEnabled);
+    
+    //If the image is a portrait shape, flip the aspect ratio to match
+    if (self.cropView.cropBoxAspectRatioIsPortrait &&
+        aspectRatioCanSwapDimensions)
+    {
+        CGFloat width = aspectRatio.width;
+        aspectRatio.width = aspectRatio.height;
+        aspectRatio.height = width;
+    }
+    
+    [self.cropView setAspectRatio:aspectRatio animated:animated];
+}
+
+- (void)rotateCropViewClockwise
+{
+    [self.cropView rotateImageNinetyDegreesAnimated:YES clockwise:YES];
+}
+
+- (void)rotateCropViewCounterclockwise
+{
+    [self.cropView rotateImageNinetyDegreesAnimated:YES clockwise:NO];
+}
+
+#pragma mark - Crop View Delegates -
+- (void)cropViewDidBecomeResettable:(TOCropView *)cropView
+{
+    self.toolbar.resetButtonEnabled = YES;
+}
+
+- (void)cropViewDidBecomeNonResettable:(TOCropView *)cropView
+{
+    self.toolbar.resetButtonEnabled = NO;
+}
+
+#pragma mark - Presentation Handling -
+- (void)presentAnimatedFromParentViewController:(UIViewController *)viewController
+                                       fromView:(UIView *)fromView
+                                      fromFrame:(CGRect)fromFrame
+                                          setup:(void (^)(void))setup
+                                     completion:(void (^)(void))completion
+{
+    [self presentAnimatedFromParentViewController:viewController fromImage:nil fromView:fromView fromFrame:fromFrame
+                                            angle:0 toImageFrame:CGRectZero setup:setup completion:completion];
+}
+
+- (void)presentAnimatedFromParentViewController:(UIViewController *)viewController
+                                      fromImage:(UIImage *)image
+                                       fromView:(UIView *)fromView
+                                      fromFrame:(CGRect)fromFrame
+                                          angle:(NSInteger)angle
+                                   toImageFrame:(CGRect)toFrame
+                                          setup:(void (^)(void))setup
+                                     completion:(void (^)(void))completion
+{
+    self.transitionController.image     = image ? image : self.image;
+    self.transitionController.fromFrame = fromFrame;
+    self.transitionController.fromView  = fromView;
+    self.prepareForTransitionHandler    = setup;
+    
+    if (self.angle != 0 || !CGRectIsEmpty(toFrame)) {
+        self.angle = angle;
+        self.imageCropFrame = toFrame;
+    }
+    
+    __weak typeof (self) weakSelf = self;
+    [viewController presentViewController:self.parentViewController ? self.parentViewController : self
+                                 animated:YES
+                               completion:^
+    {
+        typeof (self) strongSelf = weakSelf;
+        if (completion) {
+            completion();
+        }
+        
+        [strongSelf.cropView setCroppingViewsHidden:NO animated:YES];
+        if (!CGRectIsEmpty(fromFrame)) {
+            [strongSelf.cropView setGridOverlayHidden:NO animated:YES];
+        }
+    }];
+}
+
+- (void)dismissAnimatedFromParentViewController:(UIViewController *)viewController
+                                         toView:(UIView *)toView
+                                        toFrame:(CGRect)frame
+                                          setup:(void (^)(void))setup
+                                     completion:(void (^)(void))completion
+{
+    [self dismissAnimatedFromParentViewController:viewController withCroppedImage:nil toView:toView toFrame:frame setup:setup completion:completion];
+}
+
+- (void)dismissAnimatedFromParentViewController:(UIViewController *)viewController
+                               withCroppedImage:(UIImage *)image
+                                         toView:(UIView *)toView
+                                        toFrame:(CGRect)frame
+                                          setup:(void (^)(void))setup
+                                     completion:(void (^)(void))completion
+{
+    // If a cropped image was supplied, use that, and only zoom out from the crop box
+    if (image) {
+        self.transitionController.image     = image ? image : self.image;
+        self.transitionController.fromFrame = [self.cropView convertRect:self.cropView.cropBoxFrame toView:self.view];
+    }
+    else { // else use the main image, and zoom out from its entirety
+        self.transitionController.image     = self.image;
+        self.transitionController.fromFrame = [self.cropView convertRect:self.cropView.imageViewFrame toView:self.view];
+    }
+    
+    self.transitionController.toView    = toView;
+    self.transitionController.toFrame   = frame;
+    self.prepareForTransitionHandler    = setup;
+
+    [viewController dismissViewControllerAnimated:YES completion:^ {
+        if (completion) { completion(); }
+    }];
+}
+
+- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
+{
+    if (self.navigationController || self.modalTransitionStyle == UIModalTransitionStyleCoverVertical) {
+        return nil;
+    }
+    
+    self.cropView.simpleRenderMode = YES;
+    
+    __weak typeof (self) weakSelf = self;
+    self.transitionController.prepareForTransitionHandler = ^{
+        typeof (self) strongSelf = weakSelf;
+        TOCropViewControllerTransitioning *transitioning = strongSelf.transitionController;
+
+        transitioning.toFrame = [strongSelf.cropView convertRect:strongSelf.cropView.cropBoxFrame toView:strongSelf.view];
+        if (!CGRectIsEmpty(transitioning.fromFrame) || transitioning.fromView) {
+            strongSelf.cropView.croppingViewsHidden = YES;
+        }
+
+        if (strongSelf.prepareForTransitionHandler) {
+            strongSelf.prepareForTransitionHandler();
+        }
+        
+        strongSelf.prepareForTransitionHandler = nil;
+    };
+    
+    self.transitionController.isDismissing = NO;
+    return self.transitionController;
+}
+
+- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
+{
+    if (self.navigationController || self.modalTransitionStyle == UIModalTransitionStyleCoverVertical) {
+        return nil;
+    }
+    
+    __weak typeof (self) weakSelf = self;
+    self.transitionController.prepareForTransitionHandler = ^{
+        typeof (self) strongSelf = weakSelf;
+        TOCropViewControllerTransitioning *transitioning = strongSelf.transitionController;
+        
+        if (!CGRectIsEmpty(transitioning.toFrame) || transitioning.toView) {
+            strongSelf.cropView.croppingViewsHidden = YES;
+        }
+        else {
+            strongSelf.cropView.simpleRenderMode = YES;
+        }
+        
+        if (strongSelf.prepareForTransitionHandler) {
+            strongSelf.prepareForTransitionHandler();
+        }
+    };
+    
+    self.transitionController.isDismissing = YES;
+    return self.transitionController;
+}
+
+#pragma mark - Button Feedback -
+- (void)cancelButtonTapped
+{
+    if (!self.showCancelConfirmationDialog) {
+        [self dismissCropViewController];
+        return;
+    }
+
+    // Get the resource bundle depending on the framework/dependency manager we're using
+    NSBundle *resourceBundle = TO_CROP_VIEW_RESOURCE_BUNDLE_FOR_OBJECT(self);
+
+    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil
+                                                                             message:nil
+                                                                      preferredStyle:UIAlertControllerStyleActionSheet];
+    alertController.popoverPresentationController.sourceView = self.toolbar.visibleCancelButton;
+
+    NSString *yesButtonTitle = NSLocalizedStringFromTableInBundle(@"Delete Changes", @"TOCropViewControllerLocalizable", resourceBundle, nil);
+    NSString *noButtonTitle = NSLocalizedStringFromTableInBundle(@"Cancel", @"TOCropViewControllerLocalizable", resourceBundle, nil);
+
+    __weak typeof (self) weakSelf = self;
+    UIAlertAction *yesAction = [UIAlertAction actionWithTitle:yesButtonTitle style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) {
+        [weakSelf dismissCropViewController];
+    }];
+    [alertController addAction:yesAction];
+
+    UIAlertAction *noAction = [UIAlertAction actionWithTitle:noButtonTitle style:UIAlertActionStyleCancel handler:nil];
+    [alertController addAction:noAction];
+
+    [weakSelf presentViewController:alertController animated:YES completion: nil];
+}
+
+- (void)dismissCropViewController
+{
+    bool isDelegateOrCallbackHandled = NO;
+
+    // Check if the delegate method was implemented and call if so
+    if ([self.delegate respondsToSelector:@selector(cropViewController:didFinishCancelled:)]) {
+        [self.delegate cropViewController:self didFinishCancelled:YES];
+        isDelegateOrCallbackHandled = YES;
+    }
+
+    // Check if the block version was implemented and call if so
+    if (self.onDidFinishCancelled != nil) {
+        self.onDidFinishCancelled(YES);
+        isDelegateOrCallbackHandled = YES;
+    }
+
+    // If neither callbacks were implemented, perform a default dismissing animation
+    if (!isDelegateOrCallbackHandled) {
+        if (self.navigationController && self.navigationController.viewControllers.count > 1) {
+            [self.navigationController popViewControllerAnimated:YES];
+        }
+        else {
+            [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
+        }
+    }
+}
+
+- (void)doneButtonTapped
+{
+    CGRect cropFrame = self.cropView.imageCropFrame;
+    NSInteger angle = self.cropView.angle;
+
+    //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];
+        
+        NSMutableArray *activityItems = [@[imageItem, attributes] mutableCopy];
+        if (self.activityItems) {
+            [activityItems addObjectsFromArray:self.activityItems];
+        }
+        
+        UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:self.applicationActivities];
+        activityController.excludedActivityTypes = self.excludedActivityTypes;
+
+        activityController.modalPresentationStyle = UIModalPresentationPopover;
+        activityController.popoverPresentationController.sourceView = self.toolbar;
+        activityController.popoverPresentationController.sourceRect = self.toolbar.doneButtonFrame;
+        [self presentViewController:activityController animated:YES completion:nil];
+
+        __weak typeof(activityController) blockController = activityController;
+
+        activityController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
+            if (!completed) {
+                return;
+            }
+            
+            bool isCallbackOrDelegateHandled = NO;
+            
+            if (self.onDidFinishCancelled != nil) {
+                self.onDidFinishCancelled(NO);
+                isCallbackOrDelegateHandled = YES;
+            }
+            if ([self.delegate respondsToSelector:@selector(cropViewController:didFinishCancelled:)]) {
+                [self.delegate cropViewController:self didFinishCancelled:NO];
+                isCallbackOrDelegateHandled = YES;
+            }
+            
+            if (!isCallbackOrDelegateHandled) {
+                if (self.navigationController != nil && self.navigationController.viewControllers.count > 1) {
+                    [self.navigationController popViewControllerAnimated:YES];
+                }
+                else {
+                    [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
+                    blockController.completionWithItemsHandler = nil;
+                }
+            }
+        };
+
+        return;
+    } else {
+      self.toolbar.doneTextButton.enabled = false;
+    }
+    
+    BOOL isCallbackOrDelegateHandled = NO;
+    
+    //If the delegate/block that only supplies crop data is provided, call it
+    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);
+        isCallbackOrDelegateHandled = YES;
+    }
+
+    // Check if the circular APIs were implemented
+    BOOL isCircularImageDelegateAvailable = [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 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];
+        
+        //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(), ^{
+            if (isCircularImageDelegateAvailable) {
+                [self.delegate cropViewController:self didCropToCircularImage:image withRect:cropFrame angle:angle];
+            }
+            if (isCircularImageCallbackAvailable) {
+                self.onDidCropToCircleImage(image, cropFrame, angle);
+            }
+        });
+        
+        isCallbackOrDelegateHandled = YES;
+    }
+    //If the delegate/block that requires the specific cropped image is provided, call it
+    else if (isDidCropToImageDelegateAvailable || isDidCropToImageCallbackAvailable) {
+        UIImage *image = nil;
+        if (angle == 0 && CGRectEqualToRect(cropFrame, (CGRect){CGPointZero, self.image.size})) {
+            image = self.image;
+        }
+        else {
+            image = [self.image croppedImageWithFrame:cropFrame angle:angle 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.03f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+            if (isDidCropToImageDelegateAvailable) {
+                [self.delegate cropViewController:self didCropToImage:image withRect:cropFrame angle:angle];
+            }
+
+            if (isDidCropToImageCallbackAvailable) {
+                self.onDidCropToRect(image, cropFrame, angle);
+            }
+        });
+        
+        isCallbackOrDelegateHandled = YES;
+    }
+    
+    if (!isCallbackOrDelegateHandled) {
+        [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
+    }
+}
+
+- (void)commitCurrentCrop
+{
+    [self doneButtonTapped];
+}
+
+#pragma mark - Property Methods -
+
+- (void)setTitle:(NSString *)title
+{
+    [super setTitle:title];
+
+    if (self.title.length == 0) {
+        [_titleLabel removeFromSuperview];
+        _cropView.cropRegionInsets = UIEdgeInsetsMake(0, 0, 0, 0);
+        _titleLabel = nil;
+        return;
+    }
+
+    self.titleLabel.text = self.title;
+    [self.titleLabel sizeToFit];
+    self.titleLabel.frame = [self frameForTitleLabelWithSize:self.titleLabel.frame.size verticalLayout:self.verticalLayout];
+}
+
+- (void)setDoneButtonTitle:(NSString *)title
+{
+    self.toolbar.doneTextButtonTitle = title;
+}
+
+- (void)setCancelButtonTitle:(NSString *)title
+{
+    self.toolbar.cancelTextButtonTitle = title;
+}
+
+- (void)setShowOnlyIcons:(BOOL)showOnlyIcons {
+    self.toolbar.showOnlyIcons = showOnlyIcons;
+}
+  
+- (void)setDoneButtonColor:(UIColor *)color {
+    self.toolbar.doneButtonColor = color;
+}
+
+- (void)setCancelButtonColor:(UIColor *)color {
+    self.toolbar.cancelButtonColor = color;
+}
+
+- (TOCropView *)cropView
+{
+    // Lazily create the crop view in case we try and access it before presentation, but
+    // don't add it until our parent view controller view has loaded at the right time
+    if (!_cropView) {
+        _cropView = [[TOCropView alloc] initWithCroppingStyle:self.croppingStyle image:self.image];
+        _cropView.delegate = self;
+        _cropView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+        [self.view addSubview:_cropView];
+    }
+    return _cropView;
+}
+
+- (TOCropToolbar *)toolbar
+{
+    if (!_toolbar) {
+        _toolbar = [[TOCropToolbar alloc] initWithFrame:CGRectZero];
+        [self.view addSubview:_toolbar];
+    }
+    return _toolbar;
+}
+
+- (UILabel *)titleLabel
+{
+    if (!self.title.length) { return nil; }
+    if (_titleLabel) { return _titleLabel; }
+
+    _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
+    _titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
+    _titleLabel.backgroundColor = [UIColor clearColor];
+    _titleLabel.textColor = [UIColor whiteColor];
+    _titleLabel.numberOfLines = 1;
+    _titleLabel.baselineAdjustment = UIBaselineAdjustmentAlignBaselines;
+    _titleLabel.clipsToBounds = YES;
+    _titleLabel.textAlignment = NSTextAlignmentCenter;
+    _titleLabel.text = self.title;
+
+    [self.view insertSubview:self.titleLabel aboveSubview:self.cropView];
+
+    return _titleLabel;
+}
+
+- (void)setAspectRatioLockEnabled:(BOOL)aspectRatioLockEnabled
+{
+    self.toolbar.clampButtonGlowing = aspectRatioLockEnabled;
+    self.cropView.aspectRatioLockEnabled = aspectRatioLockEnabled;
+    if (!self.aspectRatioPickerButtonHidden) {
+        self.aspectRatioPickerButtonHidden = (aspectRatioLockEnabled && self.resetAspectRatioEnabled == NO);
+    }
+}
+
+- (void)setAspectRatioLockDimensionSwapEnabled:(BOOL)aspectRatioLockDimensionSwapEnabled
+{
+    self.cropView.aspectRatioLockDimensionSwapEnabled = aspectRatioLockDimensionSwapEnabled;
+}
+
+- (BOOL)aspectRatioLockEnabled
+{
+    return self.cropView.aspectRatioLockEnabled;
+}
+
+- (void)setRotateButtonsHidden:(BOOL)rotateButtonsHidden
+{
+    self.toolbar.rotateCounterclockwiseButtonHidden = rotateButtonsHidden;
+    self.toolbar.rotateClockwiseButtonHidden = rotateButtonsHidden;
+}
+
+- (void)setResetButtonHidden:(BOOL)resetButtonHidden
+{
+    self.toolbar.resetButtonHidden = resetButtonHidden;
+}
+
+- (BOOL)rotateButtonsHidden
+{
+    return self.toolbar.rotateCounterclockwiseButtonHidden && self.toolbar.rotateClockwiseButtonHidden;
+}
+
+- (void)setRotateClockwiseButtonHidden:(BOOL)rotateClockwiseButtonHidden
+{
+    self.toolbar.rotateClockwiseButtonHidden = rotateClockwiseButtonHidden;
+}
+
+- (BOOL)rotateClockwiseButtonHidden
+{
+    return self.toolbar.rotateClockwiseButtonHidden;
+}
+
+- (void)setAspectRatioPickerButtonHidden:(BOOL)aspectRatioPickerButtonHidden
+{
+    self.toolbar.clampButtonHidden = aspectRatioPickerButtonHidden;
+}
+
+- (BOOL)aspectRatioPickerButtonHidden
+{
+    return self.toolbar.clampButtonHidden;
+}
+
+- (void)setDoneButtonHidden:(BOOL)doneButtonHidden
+{
+    self.toolbar.doneButtonHidden = doneButtonHidden;
+}
+
+- (BOOL)doneButtonHidden
+{
+    return self.toolbar.doneButtonHidden;
+}
+
+- (void)setCancelButtonHidden:(BOOL)cancelButtonHidden
+{
+    self.toolbar.cancelButtonHidden = cancelButtonHidden;
+}
+
+- (BOOL)cancelButtonHidden
+{
+    return self.toolbar.cancelButtonHidden;
+}
+
+- (void)setResetAspectRatioEnabled:(BOOL)resetAspectRatioEnabled
+{
+    self.cropView.resetAspectRatioEnabled = resetAspectRatioEnabled;
+    if (!self.aspectRatioPickerButtonHidden) {
+        self.aspectRatioPickerButtonHidden = (resetAspectRatioEnabled == NO && self.aspectRatioLockEnabled);
+    }
+}
+
+- (void)setCustomAspectRatio:(CGSize)customAspectRatio
+{
+    _customAspectRatio = customAspectRatio;
+    [self setAspectRatioPreset:TOCropViewControllerAspectRatioPresetCustom animated:NO];
+}
+
+- (BOOL)resetAspectRatioEnabled
+{
+    return self.cropView.resetAspectRatioEnabled;
+}
+
+- (void)setAngle:(NSInteger)angle
+{
+    self.cropView.angle = angle;
+}
+
+- (NSInteger)angle
+{
+    return self.cropView.angle;
+}
+
+- (void)setImageCropFrame:(CGRect)imageCropFrame
+{
+    self.cropView.imageCropFrame = imageCropFrame;
+}
+
+- (CGRect)imageCropFrame
+{
+    return self.cropView.imageCropFrame;
+}
+
+- (BOOL)verticalLayout
+{
+#if TARGET_OS_MACCATALYST
+    return YES;
+#endif
+
+    return CGRectGetWidth(self.view.bounds) < CGRectGetHeight(self.view.bounds);
+}
+
+- (BOOL)overrideStatusBar
+{
+    // If we're pushed from a navigation controller, we'll defer
+    // to its handling of the status bar
+    if (self.navigationController) {
+        return NO;
+    }
+    
+    // If the view controller presenting us already hid it, we don't need to
+    // do anything ourselves
+    if (self.presentingViewController.prefersStatusBarHidden) {
+        return NO;
+    }
+    
+    // We'll handle the status bar
+    return YES;
+}
+
+- (BOOL)statusBarHidden
+{
+    // Defer behaviour to the hosting navigation controller
+    if (self.navigationController) {
+        return self.navigationController.prefersStatusBarHidden;
+    }
+    
+    //If our presenting controller has already hidden the status bar,
+    //hide the status bar by default
+    if (self.presentingViewController.prefersStatusBarHidden) {
+        return YES;
+    }
+    
+    // Our default behaviour is to always hide the status bar
+    return YES;
+}
+
+- (CGFloat)statusBarHeight
+{
+    CGFloat statusBarHeight = 0.0f;
+    if (@available(iOS 11.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
+                                    && UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone;
+
+        // Always have insetting on Mac Catalyst
+        #if TARGET_OS_MACCATALYST
+        hardwareRelatedInset = YES;
+        #endif
+
+        // 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;
+        }
+    }
+    else {
+        if (self.statusBarHidden) {
+            statusBarHeight = 0.0f;
+        }
+        else {
+            statusBarHeight = self.topLayoutGuide.length;
+        }
+    }
+    
+    return statusBarHeight;
+}
+
+- (UIEdgeInsets)statusBarSafeInsets
+{
+    UIEdgeInsets insets = UIEdgeInsetsZero;
+    if (@available(iOS 11.0, *)) {
+        insets = self.view.safeAreaInsets;
+        insets.top = self.statusBarHeight;
+    }
+    else {
+        insets.top = self.statusBarHeight;
+    }
+
+    return insets;
+}
+
+- (void)setMinimumAspectRatio:(CGFloat)minimumAspectRatio
+{
+    self.cropView.minimumAspectRatio = minimumAspectRatio;
+}
+
+- (CGFloat)minimumAspectRatio
+{
+    return self.cropView.minimumAspectRatio;
+}
+
+@end

+ 43 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Views/TOCropOverlayView.h

@@ -0,0 +1,43 @@
+//
+//  TOCropOverlayView.h
+//
+//  Copyright 2015-2022 Timothy Oliver. 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 <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface TOCropOverlayView : UIView
+
+/** Hides the interior grid lines, sans animation. */
+@property (nonatomic, assign) BOOL gridHidden;
+
+/** Add/Remove the interior horizontal grid lines. */
+@property (nonatomic, assign) BOOL displayHorizontalGridLines;
+
+/** Add/Remove the interior vertical grid lines. */
+@property (nonatomic, assign) BOOL displayVerticalGridLines;
+
+/** Shows and hides the interior grid lines with an optional crossfade animation. */
+- (void)setGridHidden:(BOOL)hidden animated:(BOOL)animated;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 231 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Views/TOCropOverlayView.m

@@ -0,0 +1,231 @@
+//
+//  TOCropOverlayView.m
+//
+//  Copyright 2015-2022 Timothy Oliver. 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 "TOCropOverlayView.h"
+
+static const CGFloat kTOCropOverLayerCornerWidth = 20.0f;
+
+@interface TOCropOverlayView ()
+
+@property (nonatomic, strong) NSArray *horizontalGridLines;
+@property (nonatomic, strong) NSArray *verticalGridLines;
+
+@property (nonatomic, strong) NSArray *outerLineViews;   //top, right, bottom, left
+
+@property (nonatomic, strong) NSArray *topLeftLineViews; //vertical, horizontal
+@property (nonatomic, strong) NSArray *bottomLeftLineViews;
+@property (nonatomic, strong) NSArray *bottomRightLineViews;
+@property (nonatomic, strong) NSArray *topRightLineViews;
+
+@end
+
+@implementation TOCropOverlayView
+
+- (instancetype)initWithFrame:(CGRect)frame
+{
+    if (self = [super initWithFrame:frame]) {
+        self.clipsToBounds = NO;
+        [self setup];
+    }
+    
+    return self;
+}
+
+- (void)setup
+{
+    UIView *(^newLineView)(void) = ^UIView *(void){
+        return [self createNewLineView];
+    };
+
+    _outerLineViews     = @[newLineView(), newLineView(), newLineView(), newLineView()];
+    
+    _topLeftLineViews   = @[newLineView(), newLineView()];
+    _bottomLeftLineViews = @[newLineView(), newLineView()];
+    _topRightLineViews  = @[newLineView(), newLineView()];
+    _bottomRightLineViews = @[newLineView(), newLineView()];
+    
+    self.displayHorizontalGridLines = YES;
+    self.displayVerticalGridLines = YES;
+}
+
+- (void)setFrame:(CGRect)frame
+{
+    [super setFrame:frame];
+    if (_outerLineViews) {
+        [self layoutLines];
+    }
+}
+
+- (void)didMoveToSuperview
+{
+    [super didMoveToSuperview];
+    if (_outerLineViews) {
+        [self layoutLines];
+    }
+}
+
+- (void)layoutLines
+{
+    CGSize boundsSize = self.bounds.size;
+    
+    //border lines
+    for (NSInteger i = 0; i < 4; i++) {
+        UIView *lineView = self.outerLineViews[i];
+        
+        CGRect frame = CGRectZero;
+        switch (i) {
+            case 0: frame = (CGRect){0,-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
+        }
+        
+        lineView.frame = frame;
+    }
+    
+    //corner liness
+    NSArray *cornerLines = @[self.topLeftLineViews, self.topRightLineViews, self.bottomRightLineViews, self.bottomLeftLineViews];
+    for (NSInteger i = 0; i < 4; i++) {
+        NSArray *cornerLine = cornerLines[i];
+        
+        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};
+                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};
+                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};
+                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};
+                break;
+        }
+        
+        [cornerLine[0] setFrame:verticalFrame];
+        [cornerLine[1] setFrame:horizontalFrame];
+    }
+    
+    //grid lines - horizontal
+    CGFloat thickness = 1.0f / [[UIScreen mainScreen] scale];
+    NSInteger numberOfLines = self.horizontalGridLines.count;
+    CGFloat padding = (CGRectGetHeight(self.bounds) - (thickness*numberOfLines)) / (numberOfLines + 1);
+    for (NSInteger i = 0; i < numberOfLines; i++) {
+        UIView *lineView = self.horizontalGridLines[i];
+        CGRect frame = CGRectZero;
+        frame.size.height = thickness;
+        frame.size.width = CGRectGetWidth(self.bounds);
+        frame.origin.y = (padding * (i+1)) + (thickness * i);
+        lineView.frame = frame;
+    }
+    
+    //grid lines - vertical
+    numberOfLines = self.verticalGridLines.count;
+    padding = (CGRectGetWidth(self.bounds) - (thickness*numberOfLines)) / (numberOfLines + 1);
+    for (NSInteger i = 0; i < numberOfLines; i++) {
+        UIView *lineView = self.verticalGridLines[i];
+        CGRect frame = CGRectZero;
+        frame.size.width = thickness;
+        frame.size.height = CGRectGetHeight(self.bounds);
+        frame.origin.x = (padding * (i+1)) + (thickness * i);
+        lineView.frame = frame;
+    }
+}
+
+- (void)setGridHidden:(BOOL)hidden animated:(BOOL)animated
+{
+    _gridHidden = hidden;
+    
+    if (animated == NO) {
+        for (UIView *lineView in self.horizontalGridLines) {
+            lineView.alpha = hidden ? 0.0f : 1.0f;
+        }
+        
+        for (UIView *lineView in self.verticalGridLines) {
+            lineView.alpha = hidden ? 0.0f : 1.0f;
+        }
+    
+        return;
+    }
+    
+    [UIView animateWithDuration:hidden?0.35f:0.2f animations:^{
+        for (UIView *lineView in self.horizontalGridLines)
+            lineView.alpha = hidden ? 0.0f : 1.0f;
+        
+        for (UIView *lineView in self.verticalGridLines)
+            lineView.alpha = hidden ? 0.0f : 1.0f;
+    }];
+}
+
+#pragma mark - Property methods
+
+- (void)setDisplayHorizontalGridLines:(BOOL)displayHorizontalGridLines {
+    _displayHorizontalGridLines = displayHorizontalGridLines;
+    
+    [self.horizontalGridLines enumerateObjectsUsingBlock:^(UIView *__nonnull lineView, NSUInteger idx, BOOL * __nonnull stop) {
+        [lineView removeFromSuperview];
+    }];
+    
+    if (_displayHorizontalGridLines) {
+        self.horizontalGridLines = @[[self createNewLineView], [self createNewLineView]];
+    } else {
+        self.horizontalGridLines = @[];
+    }
+    [self setNeedsDisplay];
+}
+
+- (void)setDisplayVerticalGridLines:(BOOL)displayVerticalGridLines {
+    _displayVerticalGridLines = displayVerticalGridLines;
+    
+    [self.verticalGridLines enumerateObjectsUsingBlock:^(UIView *__nonnull lineView, NSUInteger idx, BOOL * __nonnull stop) {
+        [lineView removeFromSuperview];
+    }];
+    
+    if (_displayVerticalGridLines) {
+        self.verticalGridLines = @[[self createNewLineView], [self createNewLineView]];
+    } else {
+        self.verticalGridLines = @[];
+    }
+    [self setNeedsDisplay];
+}
+
+- (void)setGridHidden:(BOOL)gridHidden
+{
+    [self setGridHidden:gridHidden animated:NO];
+}
+
+#pragma mark - Private methods
+
+- (nonnull UIView *)createNewLineView {
+    UIView *newLine = [[UIView alloc] initWithFrame:CGRectZero];
+    newLine.backgroundColor = [UIColor whiteColor];
+    [self addSubview:newLine];
+    return newLine;
+}
+
+@end

+ 39 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Views/TOCropScrollView.h

@@ -0,0 +1,39 @@
+//
+//  TOCropScrollView
+//
+//  Copyright 2015-2022 Timothy Oliver. 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 <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/*
+ Subclassing UIScrollView was necessary in order to directly capture
+ touch events that weren't otherwise accessible via UIGestureRecognizer objects.
+ */
+@interface TOCropScrollView : UIScrollView
+
+@property (nullable, nonatomic, copy) void (^touchesBegan)(void);
+@property (nullable, nonatomic, copy) void (^touchesCancelled)(void);
+@property (nullable, nonatomic, copy) void (^touchesEnded)(void);
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 51 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Views/TOCropScrollView.m

@@ -0,0 +1,51 @@
+//
+//  TOCropScrollView
+//
+//  Copyright 2015-2022 Timothy Oliver. 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 "TOCropScrollView.h"
+
+@implementation TOCropScrollView
+
+- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
+{
+    if (self.touchesBegan)
+        self.touchesBegan();
+        
+    [super touchesBegan:touches withEvent:event];
+}
+
+- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
+{
+    if (self.touchesEnded)
+        self.touchesEnded();
+    
+    [super touchesEnded:touches withEvent:event];
+}
+
+- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
+{
+    if (self.touchesCancelled)
+        self.touchesCancelled();
+    
+    [super touchesCancelled:touches withEvent:event];
+}
+
+@end

+ 89 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Views/TOCropToolbar.h

@@ -0,0 +1,89 @@
+//
+//  TOCropToolbar.h
+//
+//  Copyright 2015-2022 Timothy Oliver. 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 <UIKit/UIKit.h>
+#import "TOCropViewConstants.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface TOCropToolbar : UIView
+
+/* In horizontal mode, offsets all of the buttons vertically by height of status bar. */
+@property (nonatomic, assign) CGFloat statusBarHeightInset;
+
+/* Set an inset that will expand the background view beyond the bounds. */
+@property (nonatomic, assign) UIEdgeInsets backgroundViewOutsets;
+
+/* The 'Done' buttons to commit the crop. The text button is displayed
+ in portrait mode and the icon one, in landscape. */
+@property (nonatomic, strong, readonly) UIButton *doneTextButton;
+@property (nonatomic, strong, readonly) UIButton *doneIconButton;
+@property (nonatomic, copy) NSString *doneTextButtonTitle;
+@property (null_resettable, nonatomic, copy) UIColor *doneButtonColor;
+
+/* The 'Cancel' buttons to cancel the crop. The text button is displayed
+ in portrait mode and the icon one, in landscape. */
+@property (nonatomic, strong, readonly) UIButton *cancelTextButton;
+@property (nonatomic, strong, readonly) UIButton *cancelIconButton;
+@property (nonatomic, readonly) UIView *visibleCancelButton;
+@property (nonatomic, copy) NSString *cancelTextButtonTitle;
+@property (nullable, nonatomic, copy) UIColor *cancelButtonColor;
+
+@property (nonatomic, assign) BOOL showOnlyIcons;
+
+/* The cropper control buttons */
+@property (nonatomic, strong, readonly)  UIButton *rotateCounterclockwiseButton;
+@property (nonatomic, strong, readonly)  UIButton *resetButton;
+@property (nonatomic, strong, readonly)  UIButton *clampButton;
+@property (nullable, nonatomic, strong, readonly) UIButton *rotateClockwiseButton;
+
+@property (nonatomic, readonly) UIButton *rotateButton; // Points to `rotateCounterClockwiseButton`
+
+/* Button feedback handler blocks */
+@property (nullable, nonatomic, copy) void (^cancelButtonTapped)(void);
+@property (nullable, nonatomic, copy) void (^doneButtonTapped)(void);
+@property (nullable, nonatomic, copy) void (^rotateCounterclockwiseButtonTapped)(void);
+@property (nullable, nonatomic, copy) void (^rotateClockwiseButtonTapped)(void);
+@property (nullable, nonatomic, copy) void (^clampButtonTapped)(void);
+@property (nullable, nonatomic, copy) void (^resetButtonTapped)(void);
+
+/* State management for the 'clamp' button */
+@property (nonatomic, assign) BOOL clampButtonGlowing;
+@property (nonatomic, readonly) CGRect clampButtonFrame;
+
+/* Aspect ratio button visibility settings */
+@property (nonatomic, assign) BOOL clampButtonHidden;
+@property (nonatomic, assign) BOOL rotateCounterclockwiseButtonHidden;
+@property (nonatomic, assign) BOOL rotateClockwiseButtonHidden;
+@property (nonatomic, assign) BOOL resetButtonHidden;
+@property (nonatomic, assign) BOOL doneButtonHidden;
+@property (nonatomic, assign) BOOL cancelButtonHidden;
+
+/* Enable the reset button */
+@property (nonatomic, assign) BOOL resetButtonEnabled;
+
+/* Done button frame for popover controllers */
+@property (nonatomic, readonly) CGRect doneButtonFrame;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 723 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Views/TOCropToolbar.m

@@ -0,0 +1,723 @@
+//
+//  TOCropToolbar.h
+//
+//  Copyright 2015-2022 Timothy Oliver. 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 "TOCropToolbar.h"
+
+#define TOCROPTOOLBAR_DEBUG_SHOWING_BUTTONS_CONTAINER_RECT 0   // convenience debug toggle
+
+@interface TOCropToolbar()
+
+@property (nonatomic, strong) UIView *backgroundView;
+
+@property (nonatomic, strong, readwrite) UIButton *doneTextButton;
+@property (nonatomic, strong, readwrite) UIButton *doneIconButton;
+
+@property (nonatomic, strong, readwrite) UIButton *cancelTextButton;
+@property (nonatomic, strong, readwrite) UIButton *cancelIconButton;
+
+@property (nonatomic, strong) UIButton *resetButton;
+@property (nonatomic, strong) UIButton *clampButton;
+
+@property (nonatomic, strong) UIButton *rotateButton; // defaults to counterclockwise button for legacy compatibility
+
+@property (nonatomic, assign) BOOL reverseContentLayout; // For languages like Arabic where they natively present content flipped from English
+
+@end
+
+@implementation TOCropToolbar
+
+- (instancetype)initWithFrame:(CGRect)frame
+{
+    if (self = [super initWithFrame:frame]) {
+        [self setup];
+    }
+    
+    return self;
+}
+
+- (void)setup {
+    self.backgroundView = [[UIView alloc] initWithFrame:self.bounds];
+    self.backgroundView.backgroundColor = [UIColor colorWithWhite:0.12f alpha:1.0f];
+    [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
+    if (@available(iOS 9.0, *)) {
+        self.reverseContentLayout = ([UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft);
+    }
+    else {
+        self.reverseContentLayout = [[[NSLocale preferredLanguages] objectAtIndex:0] hasPrefix:@"ar"];
+    }
+    
+    // Get the resource bundle depending on the framework/dependency manager we're using
+    NSBundle *resourceBundle = TO_CROP_VIEW_RESOURCE_BUNDLE_FOR_OBJECT(self);
+    
+    _doneTextButton = [UIButton buttonWithType:UIButtonTypeSystem];
+    [_doneTextButton setTitle: _doneTextButtonTitle ?
+        _doneTextButtonTitle : NSLocalizedStringFromTableInBundle(@"Done",
+																  @"TOCropViewControllerLocalizable",
+																  resourceBundle,
+                                                                  nil)
+                     forState:UIControlStateNormal];
+    [_doneTextButton setTitleColor:[UIColor colorWithRed:1.0f green:0.8f blue:0.0f alpha:1.0f] forState:UIControlStateNormal];
+    if (@available(iOS 13.0, *)) {
+        [_doneTextButton.titleLabel setFont:[UIFont systemFontOfSize:17.0f weight:UIFontWeightMedium]];
+    } else {
+        [_doneTextButton.titleLabel setFont:[UIFont systemFontOfSize:17.0f]];
+    }
+    [_doneTextButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
+    [_doneTextButton sizeToFit];
+    [self addSubview:_doneTextButton];
+    
+    _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 addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
+    [self addSubview:_doneIconButton];
+
+    // Set the default color for the done buttons
+    self.doneButtonColor = nil;
+
+    _cancelTextButton = [UIButton buttonWithType:UIButtonTypeSystem];
+    
+    [_cancelTextButton setTitle: _cancelTextButtonTitle ?
+        _cancelTextButtonTitle : NSLocalizedStringFromTableInBundle(@"Cancel",
+																	@"TOCropViewControllerLocalizable",
+																	resourceBundle,
+                                                                    nil)
+                       forState:UIControlStateNormal];
+    [_cancelTextButton.titleLabel setFont:[UIFont systemFontOfSize:17.0f]];
+    [_cancelTextButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
+    [_cancelTextButton sizeToFit];
+    [self addSubview:_cancelTextButton];
+    
+    _cancelIconButton = [UIButton buttonWithType:UIButtonTypeSystem];
+    [_cancelIconButton setImage:[TOCropToolbar cancelImage] forState:UIControlStateNormal];
+    [_cancelIconButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
+    [self addSubview:_cancelIconButton];
+    
+    _clampButton = [UIButton buttonWithType:UIButtonTypeSystem];
+    _clampButton.contentMode = UIViewContentModeCenter;
+    _clampButton.tintColor = [UIColor whiteColor];
+    [_clampButton setImage:[TOCropToolbar clampImage] forState:UIControlStateNormal];
+    [_clampButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
+    [self addSubview:_clampButton];
+    
+    _rotateCounterclockwiseButton = [UIButton buttonWithType:UIButtonTypeSystem];
+    _rotateCounterclockwiseButton.contentMode = UIViewContentModeCenter;
+    _rotateCounterclockwiseButton.tintColor = [UIColor whiteColor];
+    [_rotateCounterclockwiseButton setImage:[TOCropToolbar rotateCCWImage] forState:UIControlStateNormal];
+    [_rotateCounterclockwiseButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
+    [self addSubview:_rotateCounterclockwiseButton];
+    
+    _rotateClockwiseButton = [UIButton buttonWithType:UIButtonTypeSystem];
+    _rotateClockwiseButton.contentMode = UIViewContentModeCenter;
+    _rotateClockwiseButton.tintColor = [UIColor whiteColor];
+    [_rotateClockwiseButton setImage:[TOCropToolbar rotateCWImage] forState:UIControlStateNormal];
+    [_rotateClockwiseButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
+    [self addSubview:_rotateClockwiseButton];
+    
+    _resetButton = [UIButton buttonWithType:UIButtonTypeSystem];
+    _resetButton.contentMode = UIViewContentModeCenter;
+    _resetButton.tintColor = [UIColor whiteColor];
+    _resetButton.enabled = NO;
+    [_resetButton setImage:[TOCropToolbar resetImage] forState:UIControlStateNormal];
+    [_resetButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
+    _resetButton.accessibilityLabel = NSLocalizedStringFromTableInBundle(@"Reset",
+                                                                         @"TOCropViewControllerLocalizable",
+                                                                         resourceBundle,
+                                                                         nil);
+    [self addSubview:_resetButton];
+}
+
+- (void)layoutSubviews
+{
+    [super 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);
+
+    CGRect frame = self.bounds;
+    frame.origin.x -= self.backgroundViewOutsets.left;
+    frame.size.width += self.backgroundViewOutsets.left;
+    frame.size.width += self.backgroundViewOutsets.right;
+    frame.origin.y -= self.backgroundViewOutsets.top;
+    frame.size.height += self.backgroundViewOutsets.top;
+    frame.size.height += self.backgroundViewOutsets.bottom;
+    self.backgroundView.frame = frame;
+    
+#if TOCROPTOOLBAR_DEBUG_SHOWING_BUTTONS_CONTAINER_RECT
+    static UIView *containerView = nil;
+    if (!containerView) {
+        containerView = [[UIView alloc] initWithFrame:CGRectZero];
+        containerView.backgroundColor = [UIColor redColor];
+        containerView.alpha = 0.1;
+        [self addSubview:containerView];
+    }
+#endif
+    
+    if (verticalLayout == NO) {
+        CGFloat insetPadding = 10.0f;
+        
+        // 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);
+
+        //If normal layout, place on the left side, else place on the right
+        if (self.reverseContentLayout == NO) {
+            frame.origin.x = insetPadding;
+        }
+        else {
+            frame.origin.x = boundsSize.width - (frame.size.width + insetPadding);
+        }
+        (_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);
+        
+        if (self.reverseContentLayout == NO) {
+            frame.origin.x = boundsSize.width - (frame.size.width + insetPadding);
+        }
+        else {
+            frame.origin.x = insetPadding;
+        }
+        (_showOnlyIcons ? self.doneIconButton : self.doneTextButton).frame = frame;
+        
+        // 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;
+        
+        if (self.reverseContentLayout == NO) {
+            width = CGRectGetMinX((_showOnlyIcons ? self.doneIconButton : self.doneTextButton).frame) - CGRectGetMaxX((_showOnlyIcons ? self.cancelIconButton : self.cancelTextButton).frame);
+        }
+        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.0f});
+
+#if TOCROPTOOLBAR_DEBUG_SHOWING_BUTTONS_CONTAINER_RECT
+        containerView.frame = containerRect;
+#endif
+        
+        CGSize buttonSize = (CGSize){44.0f,44.0f};
+        
+        NSMutableArray *buttonsInOrderHorizontally = [NSMutableArray new];
+        if (!self.rotateCounterclockwiseButtonHidden) {
+            [buttonsInOrderHorizontally addObject:self.rotateCounterclockwiseButton];
+        }
+        
+        if (!self.resetButtonHidden) {
+            [buttonsInOrderHorizontally addObject:self.resetButton];
+        }
+        
+        if (!self.clampButtonHidden) {
+            [buttonsInOrderHorizontally addObject:self.clampButton];
+        }
+        
+        if (!self.rotateClockwiseButtonHidden) {
+            [buttonsInOrderHorizontally addObject:self.rotateClockwiseButton];
+        }
+        [self layoutToolbarButtons:buttonsInOrderHorizontally withSameButtonSize:buttonSize inContainerRect:containerRect horizontally:YES];
+    }
+    else {
+        CGRect frame = CGRectZero;
+        frame.size.height = 44.0f;
+        frame.size.width = 44.0f;
+        frame.origin.y = CGRectGetHeight(self.bounds) - 44.0f;
+        self.cancelIconButton.frame = frame;
+        
+        frame.origin.y = self.statusBarHeightInset;
+        frame.size.width = 44.0f;
+        frame.size.height = 44.0f;
+        self.doneIconButton.frame = frame;
+        
+        CGRect containerRect = (CGRect){0,CGRectGetMaxY(self.doneIconButton.frame),44.0f,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};
+        
+        NSMutableArray *buttonsInOrderVertically = [NSMutableArray new];
+        if (!self.rotateCounterclockwiseButtonHidden) {
+            [buttonsInOrderVertically addObject:self.rotateCounterclockwiseButton];
+        }
+        
+        if (!self.resetButtonHidden) {
+            [buttonsInOrderVertically addObject:self.resetButton];
+        }
+        
+        if (!self.clampButtonHidden) {
+            [buttonsInOrderVertically addObject:self.clampButton];
+        }
+        
+        if (!self.rotateClockwiseButtonHidden) {
+            [buttonsInOrderVertically addObject:self.rotateClockwiseButton];
+        }
+        
+        [self layoutToolbarButtons:buttonsInOrderVertically withSameButtonSize:buttonSize inContainerRect:containerRect horizontally:NO];
+    }
+}
+
+// The convenience method for calculating button's frame inside of the container rect
+- (void)layoutToolbarButtons:(NSArray *)buttons withSameButtonSize:(CGSize)size inContainerRect:(CGRect)containerRect horizontally:(BOOL)horizontally
+{
+    if (buttons.count > 0){
+        NSInteger count = buttons.count;
+        CGFloat fixedSize = horizontally ? size.width : size.height;
+        CGFloat maxLength = horizontally ? CGRectGetWidth(containerRect) : CGRectGetHeight(containerRect);
+        CGFloat padding = (maxLength - fixedSize * count) / (count + 1);
+        
+        for (NSInteger i = 0; i < count; i++) {
+            UIButton *button = buttons[i];
+            CGFloat sameOffset = horizontally ? fabs(CGRectGetHeight(containerRect)-CGRectGetHeight(button.bounds)) : fabs(CGRectGetWidth(containerRect)-CGRectGetWidth(button.bounds));
+            CGFloat diffOffset = padding + i * (fixedSize + padding);
+            CGPoint origin = horizontally ? CGPointMake(diffOffset, sameOffset) : CGPointMake(sameOffset, diffOffset);
+            if (horizontally) {
+                origin.x += CGRectGetMinX(containerRect);
+                if (@available(iOS 13.0, *)) {
+                    UIImage *image = button.imageView.image;
+                    button.imageEdgeInsets = UIEdgeInsetsMake(0, 0, image.baselineOffsetFromBottom, 0);
+                }
+            } else {
+                origin.y += CGRectGetMinY(containerRect);
+            }
+            button.frame = (CGRect){origin, size};
+        }
+    }
+}
+
+- (void)buttonTapped:(id)button
+{
+    if (button == self.cancelTextButton || button == self.cancelIconButton) {
+        if (self.cancelButtonTapped)
+            self.cancelButtonTapped();
+    }
+    else if (button == self.doneTextButton || button == self.doneIconButton) {
+        if (self.doneButtonTapped)
+            self.doneButtonTapped();
+    }
+    else if (button == self.resetButton && self.resetButtonTapped) {
+        self.resetButtonTapped();
+    }
+    else if (button == self.rotateCounterclockwiseButton && self.rotateCounterclockwiseButtonTapped) {
+        self.rotateCounterclockwiseButtonTapped();
+    }
+    else if (button == self.rotateClockwiseButton && self.rotateClockwiseButtonTapped) {
+        self.rotateClockwiseButtonTapped();
+    }
+    else if (button == self.clampButton && self.clampButtonTapped) {
+        self.clampButtonTapped();
+        return;
+    }
+}
+
+- (CGRect)clampButtonFrame
+{
+    return self.clampButton.frame;
+}
+
+- (void)setClampButtonHidden:(BOOL)clampButtonHidden {
+    if (_clampButtonHidden == clampButtonHidden)
+        return;
+    
+    _clampButtonHidden = clampButtonHidden;
+    [self setNeedsLayout];
+}
+
+- (void)setClampButtonGlowing:(BOOL)clampButtonGlowing
+{
+    if (_clampButtonGlowing == clampButtonGlowing)
+        return;
+    
+    _clampButtonGlowing = clampButtonGlowing;
+    
+    if (_clampButtonGlowing)
+        self.clampButton.tintColor = nil;
+    else
+        self.clampButton.tintColor = [UIColor whiteColor];
+}
+
+- (void)setRotateCounterClockwiseButtonHidden:(BOOL)rotateButtonHidden
+{
+    if (_rotateCounterclockwiseButtonHidden == rotateButtonHidden)
+        return;
+    
+    _rotateCounterclockwiseButtonHidden = rotateButtonHidden;
+    [self setNeedsLayout];
+}
+
+- (BOOL)resetButtonEnabled
+{
+    return self.resetButton.enabled;
+}
+
+- (void)setResetButtonEnabled:(BOOL)resetButtonEnabled
+{
+    self.resetButton.enabled = resetButtonEnabled;
+}
+
+- (void)setDoneButtonHidden:(BOOL)doneButtonHidden {
+    if (_doneButtonHidden == doneButtonHidden)
+        return;
+    
+    _doneButtonHidden = doneButtonHidden;
+    [self setNeedsLayout];
+}
+
+- (void)setCancelButtonHidden:(BOOL)cancelButtonHidden {
+    if (_cancelButtonHidden == cancelButtonHidden)
+        return;
+    
+    _cancelButtonHidden = cancelButtonHidden;
+    [self setNeedsLayout];
+}
+
+- (CGRect)doneButtonFrame
+{
+    if (self.doneIconButton.hidden == NO)
+        return self.doneIconButton.frame;
+    
+    return self.doneTextButton.frame;
+}
+
+- (void)setShowOnlyIcons:(BOOL)showOnlyIcons {
+    if (_showOnlyIcons == showOnlyIcons)
+        return;
+
+    _showOnlyIcons = showOnlyIcons;
+    [_doneIconButton sizeToFit];
+    [_cancelIconButton sizeToFit];
+    [self setNeedsLayout];
+}
+
+- (void)setCancelTextButtonTitle:(NSString *)cancelTextButtonTitle {
+    _cancelTextButtonTitle = cancelTextButtonTitle;
+    [_cancelTextButton setTitle:_cancelTextButtonTitle forState:UIControlStateNormal];
+    [_cancelTextButton sizeToFit];
+}
+
+- (void)setDoneTextButtonTitle:(NSString *)doneTextButtonTitle {
+    _doneTextButtonTitle = doneTextButtonTitle;
+    [_doneTextButton setTitle:_doneTextButtonTitle forState:UIControlStateNormal];
+    [_doneTextButton sizeToFit];
+}
+
+- (void)setCancelButtonColor:(UIColor *)cancelButtonColor {
+    // Default color is app tint color
+    if (cancelButtonColor == _cancelButtonColor) { return; }
+    _cancelButtonColor = cancelButtonColor;
+    [_cancelTextButton setTitleColor:_cancelButtonColor forState:UIControlStateNormal];
+    [_cancelIconButton setTintColor:_cancelButtonColor];
+    [_cancelTextButton sizeToFit];
+}
+
+- (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];
+    }
+
+    if (doneButtonColor == _doneButtonColor) { return; }
+
+    _doneButtonColor = doneButtonColor;
+    [_doneTextButton setTitleColor:_doneButtonColor forState:UIControlStateNormal];
+    [_doneIconButton setTintColor:_doneButtonColor];
+    [_doneTextButton sizeToFit];
+}
+
+#pragma mark - Image Generation -
++ (UIImage *)doneImage
+{
+    if (@available(iOS 13.0, *)) {
+        return [UIImage systemImageNamed:@"checkmark"
+                       withConfiguration:[UIImageSymbolConfiguration configurationWithWeight:UIImageSymbolWeightSemibold]];
+    }
+
+    UIImage *doneImage = nil;
+    
+    UIGraphicsBeginImageContextWithOptions((CGSize){17,14}, NO, 0.0f);
+    {
+        //// Rectangle Drawing
+        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];
+        
+        
+        doneImage = UIGraphicsGetImageFromCurrentImageContext();
+    }
+    UIGraphicsEndImageContext();
+    
+    return doneImage;
+}
+
++ (UIImage *)cancelImage
+{
+    if (@available(iOS 13.0, *)) {
+        return [UIImage systemImageNamed:@"xmark"
+                       withConfiguration:[UIImageSymbolConfiguration configurationWithWeight:UIImageSymbolWeightSemibold]];
+    }
+
+    UIImage *cancelImage = nil;
+    
+    UIGraphicsBeginImageContextWithOptions((CGSize){16,16}, NO, 0.0f);
+    {
+        UIBezierPath* bezierPath = UIBezierPath.bezierPath;
+        [bezierPath moveToPoint: CGPointMake(15, 15)];
+        [bezierPath addLineToPoint: CGPointMake(1, 1)];
+        [UIColor.whiteColor setStroke];
+        bezierPath.lineWidth = 2;
+        [bezierPath stroke];
+        
+        
+        //// Bezier 2 Drawing
+        UIBezierPath* bezier2Path = UIBezierPath.bezierPath;
+        [bezier2Path moveToPoint: CGPointMake(1, 15)];
+        [bezier2Path addLineToPoint: CGPointMake(15, 1)];
+        [UIColor.whiteColor setStroke];
+        bezier2Path.lineWidth = 2;
+        [bezier2Path stroke];
+        
+        cancelImage = UIGraphicsGetImageFromCurrentImageContext();
+    }
+    UIGraphicsEndImageContext();
+    
+    return cancelImage;
+}
+
++ (UIImage *)rotateCCWImage
+{
+    if (@available(iOS 13.0, *)) {
+        return [[UIImage systemImageNamed:@"rotate.left.fill"
+                        withConfiguration:[UIImageSymbolConfiguration configurationWithWeight:UIImageSymbolWeightSemibold]]
+                imageWithBaselineOffsetFromBottom:4];
+    }
+
+    UIImage *rotateImage = nil;
+    
+    UIGraphicsBeginImageContextWithOptions((CGSize){18,21}, NO, 0.0f);
+    {
+        //// Rectangle 2 Drawing
+        UIBezierPath* rectangle2Path = [UIBezierPath bezierPathWithRect: CGRectMake(0, 9, 12, 12)];
+        [UIColor.whiteColor setFill];
+        [rectangle2Path fill];
+        
+        
+        //// Rectangle 3 Drawing
+        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];
+        
+        
+        //// Bezier Drawing
+        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];
+        rotateImage = UIGraphicsGetImageFromCurrentImageContext();
+    }
+    UIGraphicsEndImageContext();
+    
+    return rotateImage;
+}
+
++ (UIImage *)rotateCWImage
+{
+    if (@available(iOS 13.0, *)) {
+        return [[UIImage systemImageNamed:@"rotate.right.fill"
+                        withConfiguration:[UIImageSymbolConfiguration configurationWithWeight:UIImageSymbolWeightSemibold]]
+                imageWithBaselineOffsetFromBottom:4];
+    }
+
+    UIImage *rotateCCWImage = [self.class rotateCCWImage];
+    UIGraphicsBeginImageContextWithOptions(rotateCCWImage.size, NO, rotateCCWImage.scale);
+    CGContextRef context = UIGraphicsGetCurrentContext();
+    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 *rotateCWImage = UIGraphicsGetImageFromCurrentImageContext();
+    UIGraphicsEndImageContext();
+    return rotateCWImage;
+}
+
++ (UIImage *)resetImage
+{
+    if (@available(iOS 13.0, *)) {
+        return [[UIImage systemImageNamed:@"arrow.counterclockwise"
+                       withConfiguration:[UIImageSymbolConfiguration configurationWithWeight:UIImageSymbolWeightSemibold]]
+                imageWithBaselineOffsetFromBottom:0];;
+    }
+
+    UIImage *resetImage = nil;
+    
+    UIGraphicsBeginImageContextWithOptions((CGSize){22,18}, NO, 0.0f);
+    {
+        
+        //// Bezier 2 Drawing
+        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];
+        
+        
+        //// Polygon Drawing
+        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];
+
+
+        resetImage = UIGraphicsGetImageFromCurrentImageContext();
+    }
+    UIGraphicsEndImageContext();
+    
+    return resetImage;
+}
+
++ (UIImage *)clampImage
+{
+    if (@available(iOS 13.0, *)) {
+        return [[UIImage systemImageNamed:@"aspectratio.fill"
+                       withConfiguration:[UIImageSymbolConfiguration configurationWithWeight:UIImageSymbolWeightSemibold]]
+                imageWithBaselineOffsetFromBottom:0];
+    }
+
+    UIImage *clampImage = nil;
+    
+    UIGraphicsBeginImageContextWithOptions((CGSize){22,16}, NO, 0.0f);
+    {
+        //// 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];
+        
+        
+        clampImage = UIGraphicsGetImageFromCurrentImageContext();
+    }
+    UIGraphicsEndImageContext();
+    
+    return clampImage;
+}
+
+#pragma mark - Accessors -
+
+- (void)setRotateClockwiseButtonHidden:(BOOL)rotateClockwiseButtonHidden
+{
+    if (_rotateClockwiseButtonHidden == rotateClockwiseButtonHidden) {
+        return;
+    }
+    
+    _rotateClockwiseButtonHidden = rotateClockwiseButtonHidden;
+    
+    [self setNeedsLayout];
+}
+
+- (void)setResetButtonHidden:(BOOL)resetButtonHidden
+{
+    if (_resetButtonHidden == resetButtonHidden) {
+        return;
+    }
+    
+    _resetButtonHidden = resetButtonHidden;
+    
+    [self setNeedsLayout];
+}
+- (UIButton *)rotateButton
+{
+    return self.rotateCounterclockwiseButton;
+}
+
+- (void)setStatusBarHeightInset:(CGFloat)statusBarHeightInset
+{
+    _statusBarHeightInset = statusBarHeightInset;
+    [self setNeedsLayout];
+}
+
+- (UIView *)visibleCancelButton
+{
+    if (self.cancelIconButton.hidden == NO) {
+        return self.cancelIconButton;
+    }
+
+    return self.cancelTextButton;
+}
+
+@end

+ 291 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Views/TOCropView.h

@@ -0,0 +1,291 @@
+//
+//  TOCropView.h
+//
+//  Copyright 2015-2022 Timothy Oliver. 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 <UIKit/UIKit.h>
+#import "TOCropViewConstants.h"
+
+@class TOCropOverlayView;
+@class TOCropView;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol TOCropViewDelegate<NSObject>
+
+- (void)cropViewDidBecomeResettable:(nonnull TOCropView *)cropView;
+- (void)cropViewDidBecomeNonResettable:(nonnull TOCropView *)cropView;
+
+@end
+
+@interface TOCropView : UIView
+
+/**
+ The image that the crop view is displaying. This cannot be changed once the crop view is instantiated.
+ */
+@property (nonnull, nonatomic, strong, readonly) UIImage *image;
+
+/**
+ The cropping style of the crop view (eg, rectangular or circular)
+ */
+@property (nonatomic, assign, readonly) TOCropViewCroppingStyle croppingStyle;
+
+/**
+ A grid view overlaid on top of the foreground image view's container.
+ */
+@property (nonnull, nonatomic, strong, readonly) TOCropOverlayView *gridOverlayView;
+
+/**
+ A container view that clips the a copy of the image so it appears over the dimming view
+ */
+@property (nonnull, nonatomic, readonly) UIView *foregroundContainerView;
+
+/**
+ A delegate object that receives notifications from the crop view
+ */
+@property (nullable, nonatomic, weak) id<TOCropViewDelegate> delegate;
+
+/**
+ If false, the user cannot resize the crop box frame using a pan gesture from a corner.
+ Default vaue is YES.
+ */
+@property (nonatomic, assign) BOOL cropBoxResizeEnabled;
+
+/**
+ Whether the user has manipulated the crop view to the point where it can be reset
+ */
+@property (nonatomic, readonly) BOOL canBeReset;
+
+/** 
+ The frame of the cropping box in the coordinate space of the crop view
+ */
+@property (nonatomic, readonly) CGRect cropBoxFrame;
+
+/**
+ The frame of the entire image in the backing scroll view
+ */
+@property (nonatomic, readonly) CGRect imageViewFrame;
+
+/**
+ Inset the workable region of the crop view in case in order to make space for accessory views
+ */
+@property (nonatomic, assign) UIEdgeInsets cropRegionInsets;
+
+/**
+ Disable the dynamic translucency in order to smoothly relayout the view
+ */
+@property (nonatomic, assign) BOOL simpleRenderMode;
+
+/**
+ When performing manual content layout (such as during screen rotation), disable any internal layout
+ */
+@property (nonatomic, assign) BOOL internalLayoutDisabled;
+
+/**
+ A width x height ratio that the crop box will be rescaled to (eg 4:3 is {4.0f, 3.0f})
+ Setting it to CGSizeZero will reset the aspect ratio to the image's own ratio.
+ */
+@property (nonatomic, assign) CGSize aspectRatio;
+
+/**
+ When the cropping box is locked to its current aspect ratio (But can still be resized)
+ */
+@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.
+ This value also controls whether the dimensions can swap when the image is rotated.
+ 
+ Default is NO.
+ */
+@property (nonatomic, assign) BOOL aspectRatioLockDimensionSwapEnabled;
+
+/**
+ When the user taps 'reset', whether the aspect ratio will also be reset as well
+ Default is YES
+ */
+@property (nonatomic, assign) BOOL resetAspectRatioEnabled;
+
+/**
+ True when the height of the crop box is bigger than the width
+ */
+@property (nonatomic, readonly) BOOL cropBoxAspectRatioIsPortrait;
+
+/**
+ The rotation angle of the crop view (Will always be negative as it rotates in a counter-clockwise direction)
+ */
+@property (nonatomic, assign) NSInteger angle;
+
+/**
+ Hide all of the crop elements for transition animations 
+ */
+@property (nonatomic, assign) BOOL croppingViewsHidden;
+
+/**
+ In relation to the coordinate space of the image, the frame that the crop view is focusing on
+ */
+@property (nonatomic, assign) CGRect imageCropFrame;
+
+/**
+ Set the grid overlay graphic to be hidden
+ */
+@property (nonatomic, assign) BOOL gridOverlayHidden;
+
+///**
+// Paddings of the crop rectangle. Default to 14.0
+// */
+@property (nonatomic) CGFloat cropViewPadding;
+
+/**
+ Delay before crop frame is adjusted according new crop area. Default to 0.8
+ */
+@property (nonatomic) NSTimeInterval cropAdjustingDelay;
+
+/**
+The minimum croping aspect ratio. If set, user is prevented from setting cropping
+ rectangle to lower aspect ratio than defined by the parameter.
+*/
+@property (nonatomic, assign) CGFloat minimumAspectRatio;
+
+/**
+ 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
+ */
+@property (nonatomic, assign) CGFloat maximumZoomScale;
+
+/**
+ Always show the cropping grid lines, even when the user isn't interacting.
+ This also disables the fading animation.
+ (Default is NO)
+ */
+@property (nonatomic, assign) BOOL alwaysShowCroppingGrid;
+
+/**
+ Permanently hides the translucency effect covering the outside bounds of the
+ crop box. (Default is NO)
+ */
+@property (nonatomic, assign) BOOL translucencyAlwaysHidden;
+
+///*
+// if YES it will always show grid
+// if NO it will never show grid
+// NOTE : Do not use this method if you want to keep grid hide/show animation
+// */
+//- (void)setAlwaysShowGrid:(BOOL)showGrid;
+//
+///*
+// if YES it will disable translucency effect
+// */
+//- (void)setTranslucencyOff:(BOOL)disableTranslucency;
+
+
+/**
+ Create a default instance of the crop view with the supplied image
+ */
+- (nonnull instancetype)initWithImage:(nonnull UIImage *)image;
+
+/**
+ Create a new instance of the crop view with the specified image and cropping
+ */
+- (nonnull instancetype)initWithCroppingStyle:(TOCropViewCroppingStyle)style image:(nonnull UIImage *)image;
+
+/**
+ Performs the initial set up, including laying out the image and applying any restore properties.
+ This should be called once the crop view has been added to a parent that is in its final layout frame.
+ */
+- (void)performInitialSetup;
+
+/**
+ When performing large size transitions (eg, orientation rotation),
+ set simple mode to YES to temporarily graphically heavy effects like translucency.
+ 
+ @param simpleMode Whether simple mode is enabled or not
+ 
+ */
+- (void)setSimpleRenderMode:(BOOL)simpleMode animated:(BOOL)animated;
+
+/**
+ When performing a screen rotation that will change the size of the scroll view, this takes 
+ a snapshot of all of the scroll view data before it gets manipulated by iOS.
+ Please call this in your view controller, before the rotation animation block is committed.
+ */
+- (void)prepareforRotation;
+
+/**
+ Performs the realignment of the crop view while the screen is rotating.
+ Please call this inside your view controller's screen rotation animation block.
+ */
+- (void)performRelayoutForRotation;
+
+/**
+ Reset the crop box and zoom scale back to the initial layout
+ 
+ @param animated The reset is animated
+ */
+- (void)resetLayoutToDefaultAnimated:(BOOL)animated;
+
+/**
+ Changes the aspect ratio of the crop box to match the one specified
+ 
+ @param aspectRatio The aspect ratio (For example 16:9 is 16.0f/9.0f). 'CGSizeZero' will reset it to the image's own ratio
+ @param animated Whether the locking effect is animated
+ */
+- (void)setAspectRatio:(CGSize)aspectRatio animated:(BOOL)animated;
+
+/**
+ Rotates the entire canvas to a 90-degree angle. The default rotation is counterclockwise.
+ 
+ @param animated Whether the transition is animated
+ */
+- (void)rotateImageNinetyDegreesAnimated:(BOOL)animated;
+
+/**
+ Rotates the entire canvas to a 90-degree angle
+ 
+ @param animated Whether the transition is animated
+ @param clockwise Whether the rotation is clockwise. Passing 'NO' means counterclockwise
+ */
+- (void)rotateImageNinetyDegreesAnimated:(BOOL)animated clockwise:(BOOL)clockwise;
+
+/**
+ Animate the grid overlay graphic to be visible
+ */
+- (void)setGridOverlayHidden:(BOOL)gridOverlayHidden animated:(BOOL)animated;
+
+/**
+ Animate the cropping component views to become visible
+ */
+- (void)setCroppingViewsHidden:(BOOL)hidden animated:(BOOL)animated;
+
+/**
+ Animate the background image view to become visible
+ */
+- (void)setBackgroundImageViewHidden:(BOOL)hidden animated:(BOOL)animated;
+
+/**
+ When triggered, the crop view will perform a relayout to ensure the crop box
+ fills the entire crop view region
+ */
+- (void)moveCroppedContentToCenterAnimated:(BOOL)animated;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 1747 - 0
Pods/CropViewController/Objective-C/TOCropViewController/Views/TOCropView.m

@@ -0,0 +1,1747 @@
+//
+//  TOCropView.m
+//
+//  Copyright 2015-2022 Timothy Oliver. 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 "TOCropView.h"
+#import "TOCropOverlayView.h"
+#import "TOCropScrollView.h"
+
+#define TOCROPVIEW_BACKGROUND_COLOR [UIColor colorWithWhite:0.12f alpha:1.0f]
+
+static const CGFloat kTOCropViewPadding = 14.0f;
+static const NSTimeInterval kTOCropTimerDuration = 0.8f;
+static const CGFloat kTOCropViewMinimumBoxSize = 42.0f;
+static const CGFloat kTOMaximumZoomScale = 15.0f;
+
+/* When the user taps down to resize the box, this state is used
+ to determine where they tapped and how to manipulate the box */
+typedef NS_ENUM(NSInteger, TOCropViewOverlayEdge) {
+    TOCropViewOverlayEdgeNone,
+    TOCropViewOverlayEdgeTopLeft,
+    TOCropViewOverlayEdgeTop,
+    TOCropViewOverlayEdgeTopRight,
+    TOCropViewOverlayEdgeRight,
+    TOCropViewOverlayEdgeBottomRight,
+    TOCropViewOverlayEdgeBottom,
+    TOCropViewOverlayEdgeBottomLeft,
+    TOCropViewOverlayEdgeLeft
+};
+
+@interface TOCropView () <UIScrollViewDelegate, UIGestureRecognizerDelegate>
+
+@property (nonatomic, strong, readwrite) UIImage *image;
+@property (nonatomic, assign, readwrite) TOCropViewCroppingStyle croppingStyle;
+
+/* Views */
+@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) 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 */
+@property (nonatomic, strong) UIView *translucencyView;             /* A blur view that is made visible when the user isn't interacting with the crop view */
+@property (nonatomic, strong) id translucencyEffect;                /* The dark blur visual effect applied to the visual effect view. */
+@property (nonatomic, strong, readwrite) TOCropOverlayView *gridOverlayView;   /* A grid view overlaid on top of the foreground image view's container. */
+
+/* Gesture Recognizers */
+@property (nonatomic, strong) UIPanGestureRecognizer *gridPanGestureRecognizer; /* The gesture recognizer in charge of controlling the resizing of the crop view */
+
+/* Crop box handling */
+@property (nonatomic, assign) BOOL applyInitialCroppedImageFrame; /* No by default, when setting initialCroppedImageFrame this will be set to YES, and set back to NO after first application - so it's only done once */
+@property (nonatomic, assign) TOCropViewOverlayEdge tappedEdge; /* The edge region that the user tapped on, to resize the cropping region */
+@property (nonatomic, assign) CGRect cropOriginFrame;     /* When resizing, this is the original frame of the crop box. */
+@property (nonatomic, assign) CGPoint panOriginPoint;     /* The initial touch point of the pan gesture recognizer */
+@property (nonatomic, assign, readwrite) CGRect cropBoxFrame;  /* The frame, in relation to to this view where the grid, and crop container view are aligned */
+@property (nonatomic, strong) NSTimer *resetTimer;  /* The timer used to reset the view after the user stops interacting with it */
+@property (nonatomic, assign) BOOL editing;         /* Used to denote the active state of the user manipulating the content */
+@property (nonatomic, assign) BOOL disableForgroundMatching; /* At times during animation, disable matching the forground image view to the background */
+
+/* Pre-screen-rotation state information */
+@property (nonatomic, assign) CGPoint rotationContentOffset;
+@property (nonatomic, assign) CGSize  rotationContentSize;
+@property (nonatomic, assign) CGRect  rotationBoundFrame;
+
+/* 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 */
+
+/* 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 */
+@property (nonatomic, assign) NSInteger cropBoxLastEditedAngle; /* Remember which angle we were at when we saved the editing size */
+@property (nonatomic, assign) CGFloat cropBoxLastEditedZoomScale; /* Remember the zoom size when we last edited */
+@property (nonatomic, assign) CGFloat cropBoxLastEditedMinZoomScale; /* Remember the minimum size when we last edited. */
+@property (nonatomic, assign) BOOL rotateAnimationInProgress;   /* Disallow any input while the rotation animation is playing */
+
+/* Reset state data */
+@property (nonatomic, assign) CGSize originalCropBoxSize; /* Save the original crop box size so we can tell when the content has been edited */
+@property (nonatomic, assign) CGPoint originalContentOffset; /* Save the original content offset so we can tell if it's been scrolled. */
+@property (nonatomic, assign, readwrite) BOOL canBeReset;
+
+/* In iOS 9, a new dynamic blur effect became available. */
+@property (nonatomic, assign) BOOL dynamicBlurEffect;
+
+/* If restoring to a previous crop setting, these properties hang onto the
+ values until the view is configured for the first time. */
+@property (nonatomic, assign) NSInteger restoreAngle;
+@property (nonatomic, assign) CGRect    restoreImageCropFrame;
+
+/* Set to YES once `performInitialLayout` is called. This lets pending properties get queued until the view
+ has been properly set up in its parent. */
+@property (nonatomic, assign) BOOL initialSetupPerformed;
+
+@end
+
+@implementation TOCropView
+
+- (instancetype)initWithImage:(UIImage *)image
+{
+    return [self initWithCroppingStyle:TOCropViewCroppingStyleDefault image:image];
+}
+
+- (instancetype)initWithCroppingStyle:(TOCropViewCroppingStyle)style image:(UIImage *)image
+{
+    if (self = [super init]) {
+        _image = image;
+        _croppingStyle = style;
+        [self setup];
+    }
+    
+    return self;
+}
+
+- (void)setup
+{
+    __weak typeof(self) weakSelf = self;
+    
+    BOOL circularMode = (self.croppingStyle == TOCropViewCroppingStyleCircular);
+    
+    //View properties
+    self.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
+    self.backgroundColor = TOCROPVIEW_BACKGROUND_COLOR;
+    self.cropBoxFrame = CGRectZero;
+    self.applyInitialCroppedImageFrame = NO;
+    self.editing = NO;
+    self.cropBoxResizeEnabled = !circularMode;
+    self.aspectRatio = circularMode ? (CGSize){1.0f, 1.0f} : CGSizeZero;
+    self.resetAspectRatioEnabled = !circularMode;
+    self.restoreImageCropFrame = CGRectZero;
+    self.restoreAngle = 0;
+    self.cropAdjustingDelay = kTOCropTimerDuration;
+    self.cropViewPadding = kTOCropViewPadding;
+    self.maximumZoomScale = kTOMaximumZoomScale;
+    
+    /* Dynamic animation blurring is only possible on iOS 9, however since the API was available on iOS 8,
+     we'll need to manually check the system version to ensure that it's available. */
+    self.dynamicBlurEffect = ([[[UIDevice currentDevice] systemVersion] compare:@"9.0" options:NSNumericSearch] != NSOrderedAscending);
+    
+    //Scroll View properties
+    self.scrollView = [[TOCropScrollView alloc] initWithFrame:self.bounds];
+    self.scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+    self.scrollView.alwaysBounceHorizontal = YES;
+    self.scrollView.alwaysBounceVertical = YES;
+    self.scrollView.showsHorizontalScrollIndicator = NO;
+    self.scrollView.showsVerticalScrollIndicator = NO;
+    self.scrollView.delegate = self;
+    [self addSubview:self.scrollView];
+
+    // Disable smart inset behavior in iOS 11
+    if (@available(iOS 11.0, *)) {
+        self.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
+    }
+
+    self.scrollView.touchesBegan = ^{ [weakSelf startEditing]; };
+    self.scrollView.touchesEnded = ^{ [weakSelf startResetTimer]; };
+    
+    //Background Image View
+    self.backgroundImageView = [[UIImageView alloc] initWithImage:self.image];
+    self.backgroundImageView.layer.minificationFilter = kCAFilterTrilinear;
+    
+    //Background container view
+    self.backgroundContainerView = [[UIView alloc] initWithFrame:self.backgroundImageView.frame];
+    [self.backgroundContainerView addSubview:self.backgroundImageView];
+    [self.scrollView addSubview:self.backgroundContainerView];
+    
+    //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.hidden = NO;
+    self.overlayView.userInteractionEnabled = NO;
+    [self addSubview:self.overlayView];
+    
+    //Translucency View
+    if (NSClassFromString(@"UIVisualEffectView")) {
+        self.translucencyEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
+        self.translucencyView = [[UIVisualEffectView alloc] initWithEffect:self.translucencyEffect];
+        self.translucencyView.frame = self.bounds;
+    }
+    else {
+        UIToolbar *toolbar = [[UIToolbar alloc] init];
+        toolbar.barStyle = UIBarStyleBlack;
+        self.translucencyView = toolbar;
+        self.translucencyView.frame = CGRectInset(self.bounds, -1.0f, -1.0f);
+    }
+    self.translucencyView.hidden = self.translucencyAlwaysHidden;
+    self.translucencyView.userInteractionEnabled = NO;
+    self.translucencyView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+    [self addSubview:self.translucencyView];
+    
+    // The forground 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.foregroundImageView = [[UIImageView alloc] initWithImage:self.image];
+    self.foregroundImageView.layer.minificationFilter = kCAFilterTrilinear;
+    [self.foregroundContainerView addSubview:self.foregroundImageView];
+    
+    // Disable colour inversion for the image views
+    if (@available(iOS 11.0, *)) {
+        self.foregroundImageView.accessibilityIgnoresInvertColors = YES;
+        self.backgroundImageView.accessibilityIgnoresInvertColors = YES;
+    }
+    
+    // The following setup isn't needed during circular cropping
+    if (circularMode) { return; }
+    
+    // The white grid overlay view
+    self.gridOverlayView = [[TOCropOverlayView alloc] initWithFrame:self.foregroundContainerView.frame];
+    self.gridOverlayView.userInteractionEnabled = NO;
+    self.gridOverlayView.gridHidden = YES;
+    [self addSubview:self.gridOverlayView];
+    
+    // The pan controller to recognize gestures meant to resize the grid view
+    self.gridPanGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(gridPanGestureRecognized:)];
+    self.gridPanGestureRecognizer.delegate = self;
+    [self.scrollView.panGestureRecognizer requireGestureRecognizerToFail:self.gridPanGestureRecognizer];
+    [self addGestureRecognizer:self.gridPanGestureRecognizer];
+}
+
+#pragma mark - View Layout -
+- (void)performInitialSetup
+{
+    // Calling this more than once is potentially destructive
+    if (self.initialSetupPerformed) {
+        return;
+    }
+    
+    // Disable from calling again
+    self.initialSetupPerformed = YES;
+    
+    //Perform the initial layout of the image
+    [self layoutInitialImage];
+    
+    // -- State Restoration --
+    
+    //If the angle value was previously set before this point, apply it now
+    if (self.restoreAngle != 0) {
+        self.angle = self.restoreAngle;
+        self.restoreAngle = 0;
+        self.cropBoxLastEditedAngle = self.angle;
+    }
+    
+    //If an image crop frame was also specified before creation, apply it now
+    if (!CGRectIsEmpty(self.restoreImageCropFrame)) {
+        self.imageCropFrame = self.restoreImageCropFrame;
+        self.restoreImageCropFrame = CGRectZero;
+    }
+
+    // Save the current layout state for later
+    [self captureStateForImageRotation];
+    
+    //Check if we performed any resetabble modifications
+    [self checkForCanReset];
+}
+
+- (void)layoutInitialImage
+{
+    CGSize imageSize = self.imageSize;
+    self.scrollView.contentSize = imageSize;
+    
+    CGRect bounds = self.contentBounds;
+    CGSize boundsSize = bounds.size;
+
+    //work out the minimum scale of the object
+    CGFloat scale = 0.0f;
+    
+    // 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)};
+    
+    // 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;
+    if (self.hasAspectRatio) {
+        CGFloat ratioScale = (self.aspectRatio.width / self.aspectRatio.height); //Work out the size of the width in relation to height
+        CGSize fullSizeRatio = (CGSize){boundsSize.height * ratioScale, boundsSize.height};
+        CGFloat fitScale = MIN(boundsSize.width/fullSizeRatio.width, boundsSize.height/fullSizeRatio.height);
+        cropBoxSize = (CGSize){fullSizeRatio.width * fitScale, fullSizeRatio.height * fitScale};
+        
+        scale = MAX(cropBoxSize.width/imageSize.width, cropBoxSize.height/imageSize.height);
+    }
+
+    //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)};
+    
+    // Configure the scroll view
+    self.scrollView.minimumZoomScale = scale;
+    self.scrollView.maximumZoomScale = scale * self.maximumZoomScale;
+
+    //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));
+    self.cropBoxFrame = frame;
+    
+    //set the fully zoomed out state initially
+    self.scrollView.zoomScale = self.scrollView.minimumZoomScale;
+    self.scrollView.contentSize = scaledSize;
+    
+    // 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) {
+        CGPoint offset = CGPointZero;
+        offset.x = -floorf(CGRectGetMidX(bounds) - (scaledSize.width * 0.5f));
+        offset.y = -floorf(CGRectGetMidY(bounds) - (scaledSize.height * 0.5f));
+        self.scrollView.contentOffset = offset;
+    }
+
+    //save the current state for use with 90-degree rotations
+    self.cropBoxLastEditedAngle = 0;
+    [self captureStateForImageRotation];
+    
+    //save the size for checking if we're in a resettable state
+    self.originalCropBoxSize = self.resetAspectRatioEnabled ? scaledImageSize : self.cropBoxFrame.size;
+    self.originalContentOffset = self.scrollView.contentOffset;
+    
+    [self checkForCanReset];
+    [self matchForegroundToBackground];
+}
+
+- (void)prepareforRotation
+{
+    self.rotationContentOffset = self.scrollView.contentOffset;
+    self.rotationContentSize   = self.scrollView.contentSize;
+    self.rotationBoundFrame     = self.contentBounds;
+}
+
+- (void)performRelayoutForRotation
+{
+    CGRect cropFrame = self.cropBoxFrame;
+    CGRect contentFrame = self.contentBounds;
+ 
+    CGFloat scale = MIN(contentFrame.size.width / cropFrame.size.width, contentFrame.size.height / cropFrame.size.height);
+    self.scrollView.minimumZoomScale *= scale;
+    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));
+    self.cropBoxFrame = cropFrame;
+    
+    [self captureStateForImageRotation];
+    
+    //Work out the center point of the content before we rotated
+    CGPoint oldMidPoint = (CGPoint){CGRectGetMidX(self.rotationBoundFrame), CGRectGetMidY(self.rotationBoundFrame)};
+    CGPoint contentCenter = (CGPoint){self.rotationContentOffset.x + oldMidPoint.x, self.rotationContentOffset.y + oldMidPoint.y};
+    
+    //Normalize it to a percentage we can apply to different sizes
+    CGPoint normalizedCenter = CGPointZero;
+    normalizedCenter.x = contentCenter.x / self.rotationContentSize.width;
+    normalizedCenter.y = contentCenter.y / self.rotationContentSize.height;
+    
+    //Work out the new content offset by applying the normalized values to the new layout
+    CGPoint newMidPoint = (CGPoint){CGRectGetMidX(self.contentBounds),CGRectGetMidY(self.contentBounds)};
+
+    CGPoint translatedContentOffset = CGPointZero;
+    translatedContentOffset.x = self.scrollView.contentSize.width * normalizedCenter.x;
+    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);
+    
+    //Make sure it doesn't overshoot the top left corner of the crop box
+    offset.x = MAX(-self.scrollView.contentInset.left, offset.x);
+    offset.y = MAX(-self.scrollView.contentInset.top, offset.y);
+
+    //Nor undershoot the bottom right corner
+    CGPoint maximumOffset = CGPointZero;
+    maximumOffset.x = (self.bounds.size.width - self.scrollView.contentInset.right) + self.scrollView.contentSize.width;
+    maximumOffset.y = (self.bounds.size.height - self.scrollView.contentInset.bottom) + self.scrollView.contentSize.height;
+    offset.x = MIN(offset.x, maximumOffset.x);
+    offset.y = MIN(offset.y, maximumOffset.y);
+    self.scrollView.contentOffset = offset;
+    
+    //Line up the background instance of the image
+    [self matchForegroundToBackground];
+}
+
+- (void)matchForegroundToBackground
+{
+    if (self.disableForgroundMatching)
+        return;
+    
+    //We can't simply match the frames since if the images are rotated, the frame property becomes unusable
+    self.foregroundImageView.frame = [self.backgroundContainerView.superview
+                                      convertRect:self.backgroundContainerView.frame
+                                      toView:self.foregroundContainerView];
+}
+
+- (void)updateCropBoxFrameWithGesturePoint:(CGPoint)point
+{
+    CGRect frame = self.cropBoxFrame;
+    CGRect originFrame = self.cropOriginFrame;
+    CGRect contentFrame = self.contentBounds;
+
+    point.x = MAX(contentFrame.origin.x - self.cropViewPadding, point.x);
+    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);
+
+    //Current aspect ratio of the crop box in case we need to clamp it
+    CGFloat aspectRatio = (originFrame.size.width / originFrame.size.height);
+
+    //Note whether we're being aspect transformed horizontally or vertically
+    BOOL aspectHorizontal = NO, aspectVertical = NO;
+    
+    //Depending on which corner we drag from, set the appropriate min flag to
+    //ensure we can properly clamp the XY value of the box if it overruns the minimum size
+    //(Otherwise the image itself will slide with the drag gesture)
+    BOOL clampMinFromTop = NO, clampMinFromLeft = NO;
+
+    switch (self.tappedEdge) {
+        case TOCropViewOverlayEdgeLeft:
+            if (self.aspectRatioLockEnabled) {
+                aspectHorizontal = YES;
+                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);
+            }
+            CGFloat newWidth = originFrame.size.width - xDelta;
+            CGFloat newHeight = originFrame.size.height;
+            if (MIN(newHeight, newWidth) / MAX(newHeight, newWidth) >= (double)_minimumAspectRatio) {
+                frame.origin.x   = originFrame.origin.x + xDelta;
+                frame.size.width = originFrame.size.width - xDelta;
+            }
+            
+            clampMinFromLeft = YES;
+            
+            break;
+        case TOCropViewOverlayEdgeRight:
+            if (self.aspectRatioLockEnabled) {
+                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.size.width = originFrame.size.width + xDelta;
+                frame.size.width = MIN(frame.size.width, contentFrame.size.height * aspectRatio);
+            }
+            else {
+                CGFloat newWidth = originFrame.size.width + xDelta;
+                CGFloat newHeight = originFrame.size.height;
+                if (MIN(newHeight, newWidth) / MAX(newHeight, newWidth) >= (double)_minimumAspectRatio) {
+                    frame.size.width = originFrame.size.width + xDelta;
+                }
+            }
+            
+            break;
+        case TOCropViewOverlayEdgeBottom:
+            if (self.aspectRatioLockEnabled) {
+                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.size.height = originFrame.size.height + yDelta;
+                frame.size.height = MIN(frame.size.height, contentFrame.size.width / aspectRatio);
+            }
+            else {
+                CGFloat newWidth = originFrame.size.width;
+                CGFloat newHeight = originFrame.size.height + yDelta;
+                
+                if (MIN(newHeight, newWidth) / MAX(newHeight, newWidth) >= (double)_minimumAspectRatio) {
+                    frame.size.height = originFrame.size.height + yDelta;
+                }
+            }
+            break;
+        case TOCropViewOverlayEdgeTop:
+            if (self.aspectRatioLockEnabled) {
+                aspectVertical = YES;
+                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.y    = originFrame.origin.y + yDelta;
+                frame.size.height = originFrame.size.height - yDelta;
+            }
+            else {
+                CGFloat newWidth = originFrame.size.width;
+                CGFloat newHeight = originFrame.size.height - yDelta;
+                
+                if (MIN(newHeight, newWidth) / MAX(newHeight, newWidth) >= (double)_minimumAspectRatio) {
+                    frame.origin.y    = originFrame.origin.y + yDelta;
+                    frame.size.height = originFrame.size.height - yDelta;
+                }
+            }
+            
+            clampMinFromTop = YES;
+            
+            break;
+        case TOCropViewOverlayEdgeTopLeft:
+            if (self.aspectRatioLockEnabled) {
+                xDelta = MAX(xDelta, 0);
+                yDelta = MAX(yDelta, 0);
+                
+                CGPoint distance;
+                distance.x = 1.0f - (xDelta / CGRectGetWidth(originFrame));
+                distance.y = 1.0f - (yDelta / CGRectGetHeight(originFrame));
+                
+                CGFloat scale = (distance.x + distance.y) * 0.5f;
+                
+                frame.size.width = ceilf(CGRectGetWidth(originFrame) * scale);
+                frame.size.height = ceilf(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);
+                
+                aspectVertical = YES;
+                aspectHorizontal = YES;
+            }
+            else {
+                CGFloat newWidth = originFrame.size.width - xDelta;
+                CGFloat newHeight = originFrame.size.height - yDelta;
+                
+                if (MIN(newHeight, newWidth) / MAX(newHeight, newWidth) >= (double)_minimumAspectRatio) {
+                    frame.origin.x   = originFrame.origin.x + xDelta;
+                    frame.size.width = originFrame.size.width - xDelta;
+                    frame.origin.y   = originFrame.origin.y + yDelta;
+                    frame.size.height = originFrame.size.height - yDelta;
+                }
+            }
+            
+            clampMinFromTop = YES;
+            clampMinFromLeft = YES;
+            
+            break;
+        case TOCropViewOverlayEdgeTopRight:
+            if (self.aspectRatioLockEnabled) {
+                xDelta = MIN(xDelta, 0);
+                yDelta = MAX(yDelta, 0);
+                
+                CGPoint distance;
+                distance.x = 1.0f - ((-xDelta) / CGRectGetWidth(originFrame));
+                distance.y = 1.0f - ((yDelta) / CGRectGetHeight(originFrame));
+                
+                CGFloat scale = (distance.x + distance.y) * 0.5f;
+                
+                frame.size.width = ceilf(CGRectGetWidth(originFrame) * scale);
+                frame.size.height = ceilf(CGRectGetHeight(originFrame) * scale);
+                frame.origin.y = originFrame.origin.y + (CGRectGetHeight(originFrame) - frame.size.height);
+                
+                aspectVertical = YES;
+                aspectHorizontal = YES;
+            }
+            else {
+                CGFloat newWidth = originFrame.size.width + xDelta;
+                CGFloat newHeight = originFrame.size.height - yDelta;
+                
+                if (MIN(newHeight, newWidth) / MAX(newHeight, newWidth) >= (double)_minimumAspectRatio) {
+                    frame.size.width  = originFrame.size.width + xDelta;
+                    frame.origin.y    = originFrame.origin.y + yDelta;
+                    frame.size.height = originFrame.size.height - yDelta;
+                }
+            }
+            
+            clampMinFromTop = YES;
+            
+            break;
+        case TOCropViewOverlayEdgeBottomLeft:
+            if (self.aspectRatioLockEnabled) {
+                CGPoint distance;
+                distance.x = 1.0f - (xDelta / CGRectGetWidth(originFrame));
+                distance.y = 1.0f - (-yDelta / CGRectGetHeight(originFrame));
+                
+                CGFloat scale = (distance.x + distance.y) * 0.5f;
+                
+                frame.size.width = ceilf(CGRectGetWidth(originFrame) * scale);
+                frame.size.height = ceilf(CGRectGetHeight(originFrame) * scale);
+                frame.origin.x = CGRectGetMaxX(originFrame) - frame.size.width;
+                
+                aspectVertical = YES;
+                aspectHorizontal = YES;
+            }
+            else {
+                CGFloat newWidth = originFrame.size.width - xDelta;
+                CGFloat newHeight = originFrame.size.height + yDelta;
+                
+                if (MIN(newHeight, newWidth) / MAX(newHeight, newWidth) >= (double)_minimumAspectRatio) {
+                    frame.size.height = originFrame.size.height + yDelta;
+                    frame.origin.x    = originFrame.origin.x + xDelta;
+                    frame.size.width  = originFrame.size.width - xDelta;
+                }
+            }
+            
+            clampMinFromLeft = YES;
+            
+            break;
+        case TOCropViewOverlayEdgeBottomRight:
+            if (self.aspectRatioLockEnabled) {
+                
+                CGPoint distance;
+                distance.x = 1.0f - ((-1 * xDelta) / CGRectGetWidth(originFrame));
+                distance.y = 1.0f - ((-1 * yDelta) / CGRectGetHeight(originFrame));
+                
+                CGFloat scale = (distance.x + distance.y) * 0.5f;
+                
+                frame.size.width = ceilf(CGRectGetWidth(originFrame) * scale);
+                frame.size.height = ceilf(CGRectGetHeight(originFrame) * scale);
+                
+                aspectVertical = YES;
+                aspectHorizontal = YES;
+            }
+            else {
+                CGFloat newWidth = originFrame.size.width + xDelta;
+                CGFloat newHeight = originFrame.size.height + yDelta;
+                
+                if (MIN(newHeight, newWidth) / MAX(newHeight, newWidth) >= (double)_minimumAspectRatio) {
+                    frame.size.height = originFrame.size.height + yDelta;
+                    frame.size.width = originFrame.size.width + xDelta;
+                }
+            }
+            break;
+        case TOCropViewOverlayEdgeNone: break;
+    }
+    
+    //The absolute max/min size the box may be in the bounds of the crop view
+    CGSize minSize = (CGSize){kTOCropViewMinimumBoxSize, kTOCropViewMinimumBoxSize};
+    CGSize maxSize = (CGSize){CGRectGetWidth(contentFrame), CGRectGetHeight(contentFrame)};
+    
+    //clamp the box to ensure it doesn't go beyond the bounds we've set
+    if (self.aspectRatioLockEnabled && aspectHorizontal) {
+        maxSize.height = contentFrame.size.width / aspectRatio;
+        minSize.width = kTOCropViewMinimumBoxSize * aspectRatio;
+    }
+        
+    if (self.aspectRatioLockEnabled && aspectVertical) {
+        maxSize.width = contentFrame.size.height * aspectRatio;
+        minSize.height = kTOCropViewMinimumBoxSize / aspectRatio;
+    }
+
+    // Clamp the width if it goes over
+    if (clampMinFromLeft) {
+        CGFloat maxWidth = CGRectGetMaxX(self.cropOriginFrame) - contentFrame.origin.x;
+        frame.size.width = MIN(frame.size.width, maxWidth);
+    }
+
+    if (clampMinFromTop) {
+        CGFloat maxHeight = CGRectGetMaxY(self.cropOriginFrame) - contentFrame.origin.y;
+        frame.size.height = MIN(frame.size.height, maxHeight);
+    }
+
+    //Clamp the minimum size
+    frame.size.width  = MAX(frame.size.width, minSize.width);
+    frame.size.height = MAX(frame.size.height, minSize.height);
+    
+    //Clamp the maximum size
+    frame.size.width  = MIN(frame.size.width, maxSize.width);
+    frame.size.height = MIN(frame.size.height, maxSize.height);
+
+    //Clamp the X position of the box to the interior of the cropping bounds
+    frame.origin.x = MAX(frame.origin.x, CGRectGetMinX(contentFrame));
+    frame.origin.x = MIN(frame.origin.x, CGRectGetMaxX(contentFrame) - minSize.width);
+
+    //Clamp the Y postion of the box to the interior of the cropping bounds
+    frame.origin.y = MAX(frame.origin.y, CGRectGetMinY(contentFrame));
+    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) {
+        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) {
+        frame.origin.y = CGRectGetMaxY(originFrame) - minSize.height;
+    }
+    
+    self.cropBoxFrame = frame;
+    
+    [self checkForCanReset];
+}
+
+- (void)resetLayoutToDefaultAnimated:(BOOL)animated
+{
+    // If resetting the crop view includes resetting the aspect ratio,
+    // reset it to zero here. But set the ivar directly since there's no point
+    // in performing the relayout calculations right before a reset.
+    if (self.hasAspectRatio && self.resetAspectRatioEnabled) {
+        _aspectRatio = CGSizeZero;
+    }
+    
+    if (animated == NO || self.angle != 0) {
+        //Reset all of the rotation transforms
+        _angle = 0;
+
+        //Set the scroll to 1.0f to reset the transform scale
+        self.scrollView.zoomScale = 1.0f;
+        
+        CGRect imageRect = (CGRect){CGPointZero, self.image.size};
+        
+        //Reset everything about the background container and image views
+        self.backgroundImageView.transform = CGAffineTransformIdentity;
+        self.backgroundContainerView.transform = CGAffineTransformIdentity;
+        self.backgroundImageView.frame = imageRect;
+        self.backgroundContainerView.frame = imageRect;
+
+        //Reset the transform ans size of just the foreground image
+        self.foregroundImageView.transform = CGAffineTransformIdentity;
+        self.foregroundImageView.frame = imageRect;
+        
+        //Reset the layout
+        [self layoutInitialImage];
+        
+        //Enable / Disable the reset button
+        [self checkForCanReset];
+        
+        return;
+    }
+
+    //If we were in the middle of a reset timer, cancel it as we'll
+    //manually perform a restoration animation here
+    if (self.resetTimer) {
+        [self cancelResetTimer];
+        [self setEditing:NO resetCropBox:NO animated:NO];
+    }
+   
+    [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:^{
+            [self layoutInitialImage];
+        } completion:^(BOOL complete) {
+            [self setSimpleRenderMode:NO animated:YES];
+        }];
+    });
+}
+
+- (void)toggleTranslucencyViewVisible:(BOOL)visible
+{
+    if (self.dynamicBlurEffect == NO) {
+        self.translucencyView.alpha = visible ? 1.0f : 0.0f;
+    }
+    else {
+        [(UIVisualEffectView *)self.translucencyView setEffect:visible ? self.translucencyEffect : nil];
+    }
+}
+
+- (void)updateToImageCropFrame:(CGRect)imageCropframe
+{
+    //Convert the image crop frame's size from image space to the screen space
+    CGFloat minimumSize = self.scrollView.minimumZoomScale;
+    CGPoint scaledOffset = (CGPoint){imageCropframe.origin.x * minimumSize, imageCropframe.origin.y * minimumSize};
+    CGSize scaledCropSize = (CGSize){imageCropframe.size.width * minimumSize, imageCropframe.size.height * minimumSize};
+    
+    // Work out the scale necessary to upscale the crop size to fit the content bounds of the crop bound
+    CGRect bounds = self.contentBounds;
+    CGFloat scale = MIN(bounds.size.width / scaledCropSize.width, bounds.size.height / scaledCropSize.height);
+    
+    // Zoom into the scroll view to the appropriate size
+    self.scrollView.zoomScale = self.scrollView.minimumZoomScale * scale;
+
+    CGSize contentSize = self.scrollView.contentSize;
+    self.scrollView.contentSize = CGSizeMake(floorf(contentSize.width), floorf(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)};
+    
+    //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));
+    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);
+    self.scrollView.contentOffset = frame.origin;
+}
+
+#pragma mark - Gesture Recognizer -
+- (void)gridPanGestureRecognized:(UIPanGestureRecognizer *)recognizer
+{
+    CGPoint point = [recognizer locationInView:self];
+    
+    if (recognizer.state == UIGestureRecognizerStateBegan) {
+        [self startEditing];
+        self.panOriginPoint = point;
+        self.cropOriginFrame = self.cropBoxFrame;
+        self.tappedEdge = [self cropEdgeForPoint:self.panOriginPoint];
+    }
+    
+    if (recognizer.state == UIGestureRecognizerStateEnded) {
+        [self startResetTimer];
+    }
+    
+    [self updateCropBoxFrameWithGesturePoint:point];
+}
+
+- (void)longPressGestureRecognized:(UILongPressGestureRecognizer *)recognizer
+{
+    if (recognizer.state == UIGestureRecognizerStateBegan)
+        [self.gridOverlayView setGridHidden:NO animated:YES];
+    
+    if (recognizer.state == UIGestureRecognizerStateEnded)
+        [self.gridOverlayView setGridHidden:YES animated:YES];
+}
+
+- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
+{
+    if (gestureRecognizer != self.gridPanGestureRecognizer)
+        return YES;
+    
+    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);
+    
+    if (CGRectContainsPoint(innerFrame, tapPoint) || !CGRectContainsPoint(outerFrame, tapPoint))
+        return NO;
+    
+    return YES;
+}
+
+- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
+{
+    if (self.gridPanGestureRecognizer.state == UIGestureRecognizerStateChanged) {
+        return NO;
+    }
+    return YES;
+}
+
+#pragma mark - Timer -
+- (void)startResetTimer
+{
+    if (self.resetTimer)
+        return;
+    
+    self.resetTimer = [NSTimer scheduledTimerWithTimeInterval:self.cropAdjustingDelay target:self selector:@selector(timerTriggered) userInfo:nil repeats:NO];
+}
+
+- (void)timerTriggered
+{
+    [self setEditing:NO resetCropBox:YES animated:YES];
+    [self.resetTimer invalidate];
+    self.resetTimer = nil;
+}
+
+- (void)cancelResetTimer
+{
+    [self.resetTimer invalidate];
+    self.resetTimer = nil;
+}
+
+- (TOCropViewOverlayEdge)cropEdgeForPoint:(CGPoint)point
+{
+    CGRect frame = self.cropBoxFrame;
+    
+    //account for padding around the box
+    frame = CGRectInset(frame, -32.0f, -32.0f);
+    
+    //Make sure the corners take priority
+    CGRect topLeftRect = (CGRect){frame.origin, {64,64}};
+    if (CGRectContainsPoint(topLeftRect, point))
+        return TOCropViewOverlayEdgeTopLeft;
+    
+    CGRect topRightRect = topLeftRect;
+    topRightRect.origin.x = CGRectGetMaxX(frame) - 64.0f;
+    if (CGRectContainsPoint(topRightRect, point))
+        return TOCropViewOverlayEdgeTopRight;
+    
+    CGRect bottomLeftRect = topLeftRect;
+    bottomLeftRect.origin.y = CGRectGetMaxY(frame) - 64.0f;
+    if (CGRectContainsPoint(bottomLeftRect, point))
+        return TOCropViewOverlayEdgeBottomLeft;
+    
+    CGRect bottomRightRect = topRightRect;
+    bottomRightRect.origin.y = bottomLeftRect.origin.y;
+    if (CGRectContainsPoint(bottomRightRect, point))
+        return TOCropViewOverlayEdgeBottomRight;
+    
+    //Check for edges
+    CGRect topRect = (CGRect){frame.origin, {CGRectGetWidth(frame), 64.0f}};
+    if (CGRectContainsPoint(topRect, point))
+        return TOCropViewOverlayEdgeTop;
+    
+    CGRect bottomRect = topRect;
+    bottomRect.origin.y = CGRectGetMaxY(frame) - 64.0f;
+    if (CGRectContainsPoint(bottomRect, point))
+        return TOCropViewOverlayEdgeBottom;
+    
+    CGRect leftRect = (CGRect){frame.origin, {64.0f, CGRectGetHeight(frame)}};
+    if (CGRectContainsPoint(leftRect, point))
+        return TOCropViewOverlayEdgeLeft;
+    
+    CGRect rightRect = leftRect;
+    rightRect.origin.x = CGRectGetMaxX(frame) - 64.0f;
+    if (CGRectContainsPoint(rightRect, point))
+        return TOCropViewOverlayEdgeRight;
+    
+    return TOCropViewOverlayEdgeNone;
+}
+
+#pragma mark - Scroll View Delegate -
+
+- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { return self.backgroundContainerView; }
+- (void)scrollViewDidScroll:(UIScrollView *)scrollView            { [self matchForegroundToBackground]; }
+
+- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
+{
+    [self startEditing];
+    self.canBeReset = YES;
+}
+
+- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
+{
+    [self startEditing];
+    self.canBeReset = YES;
+}
+
+- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
+{
+    [self startResetTimer];
+    [self checkForCanReset];
+}
+
+- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
+    [self startResetTimer];
+    [self checkForCanReset];
+}
+
+- (void)scrollViewDidZoom:(UIScrollView *)scrollView
+{
+    if (scrollView.isTracking) {
+        self.cropBoxLastEditedZoomScale = scrollView.zoomScale;
+        self.cropBoxLastEditedMinZoomScale = scrollView.minimumZoomScale;
+    }
+    
+    [self matchForegroundToBackground];
+}
+
+- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
+{
+    if (!decelerate)
+        [self startResetTimer];
+}
+
+#pragma mark - Accessors -
+
+- (void)setCropBoxResizeEnabled:(BOOL)panResizeEnabled {
+    _cropBoxResizeEnabled = panResizeEnabled;
+    self.gridPanGestureRecognizer.enabled = _cropBoxResizeEnabled;
+}
+
+- (void)setCropBoxFrame:(CGRect)cropBoxFrame
+{
+    if (CGRectEqualToRect(cropBoxFrame, _cropBoxFrame)) {
+        return;
+    }
+    
+    // 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 (isnan(frameSize.width) || isnan(frameSize.height)) { return; }
+
+    //clamp the cropping region to the inset boundaries of the screen
+    CGRect contentFrame = self.contentBounds;
+    CGFloat xOrigin = ceilf(contentFrame.origin.x);
+    CGFloat xDelta = cropBoxFrame.origin.x - xOrigin;
+    cropBoxFrame.origin.x = floorf(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 yDelta = cropBoxFrame.origin.y - yOrigin;
+    cropBoxFrame.origin.y = floorf(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));
+    
+    CGFloat maxHeight = (contentFrame.size.height + contentFrame.origin.y) - cropBoxFrame.origin.y;
+    cropBoxFrame.size.height = floorf(MIN(cropBoxFrame.size.height, maxHeight));
+    
+    //Make sure we can't make the crop box too small
+    cropBoxFrame.size.width  = MAX(cropBoxFrame.size.width, kTOCropViewMinimumBoxSize);
+    cropBoxFrame.size.height = MAX(cropBoxFrame.size.height, kTOCropViewMinimumBoxSize);
+    
+    _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
+    
+    // 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;
+        self.foregroundContainerView.layer.cornerRadius = halfWidth;
+    }
+    
+    //reset the scroll view insets to match the region of the new crop rect
+    self.scrollView.contentInset = (UIEdgeInsets){CGRectGetMinY(_cropBoxFrame),
+                                                    CGRectGetMinX(_cropBoxFrame),
+                                                    CGRectGetMaxY(self.bounds) - CGRectGetMaxY(_cropBoxFrame),
+                                                    CGRectGetMaxX(self.bounds) - CGRectGetMaxX(_cropBoxFrame)};
+
+    //if necessary, work out the new minimum size of the scroll view so it fills the crop box
+    CGSize imageSize = self.backgroundContainerView.bounds.size;
+    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;
+    
+    [self matchForegroundToBackground]; //re-align the background content to match
+}
+
+- (void)setEditing:(BOOL)editing
+{
+    [self setEditing:editing resetCropBox:NO animated:NO];
+}
+
+- (void)setSimpleRenderMode:(BOOL)simpleMode
+{
+    [self setSimpleRenderMode:simpleMode animated:NO];
+}
+
+- (BOOL)cropBoxAspectRatioIsPortrait
+{
+    CGRect cropFrame = self.cropBoxFrame;
+    return CGRectGetWidth(cropFrame) < CGRectGetHeight(cropFrame);
+}
+
+- (CGRect)imageCropFrame
+{
+    CGSize imageSize = self.imageSize;
+    CGSize contentSize = self.scrollView.contentSize;
+    CGRect cropBoxFrame = self.cropBoxFrame;
+    CGPoint contentOffset = self.scrollView.contentOffset;
+    UIEdgeInsets edgeInsets = self.scrollView.contentInset;
+    CGFloat scale = MIN(imageSize.width / contentSize.width, imageSize.height / contentSize.height);
+    
+    CGRect frame = CGRectZero;
+    
+    // Calculate the normalized origin
+    frame.origin.x = floorf((floorf(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 = MAX(0, frame.origin.y);
+    
+    // Calculate the normalized width
+    frame.size.width = ceilf(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)) {
+        frame.size.height = frame.size.width;
+    } else {
+        frame.size.height = ceilf(cropBoxFrame.size.height * scale);
+    }
+    frame.size.height = MIN(imageSize.height, frame.size.height);
+
+    return frame;
+}
+
+- (void)setImageCropFrame:(CGRect)imageCropFrame
+{
+    if (!self.initialSetupPerformed) {
+        self.restoreImageCropFrame = imageCropFrame;
+        return;
+    }
+    
+    [self updateToImageCropFrame:imageCropFrame];
+}
+
+- (void)setCroppingViewsHidden:(BOOL)hidden
+{
+    [self setCroppingViewsHidden:hidden animated:NO];
+}
+
+- (void)setCroppingViewsHidden:(BOOL)hidden animated:(BOOL)animated
+{
+    if (_croppingViewsHidden == hidden)
+        return;
+        
+    _croppingViewsHidden = hidden;
+    
+    CGFloat alpha = hidden ? 0.0f : 1.0f;
+    
+    if (animated == NO) {
+        self.backgroundImageView.alpha = alpha;
+        self.foregroundContainerView.alpha = alpha;
+        self.gridOverlayView.alpha = alpha;
+
+        [self toggleTranslucencyViewVisible:!hidden];
+        
+        return;
+    }
+    
+    self.foregroundContainerView.alpha = alpha;
+    self.backgroundImageView.alpha = alpha;
+    
+    [UIView animateWithDuration:0.4f animations:^{
+        [self toggleTranslucencyViewVisible:!hidden];
+        self.gridOverlayView.alpha = alpha;
+    }];
+}
+
+- (void)setBackgroundImageViewHidden:(BOOL)hidden animated:(BOOL)animated
+{
+    if (animated == NO) {
+        self.backgroundImageView.hidden = hidden;
+        return;
+    }
+    
+    CGFloat beforeAlpha = hidden ? 1.0f : 0.0f;
+    CGFloat toAlpha = hidden ? 0.0f : 1.0f;
+    
+    self.backgroundImageView.hidden = NO;
+    self.backgroundImageView.alpha = beforeAlpha;
+    [UIView animateWithDuration:0.5f animations:^{
+        self.backgroundImageView.alpha = toAlpha;
+    }completion:^(BOOL complete) {
+        if (hidden) {
+            self.backgroundImageView.hidden = YES;
+        }
+    }];
+}
+
+-(void)setAlwaysShowCroppingGrid:(BOOL)alwaysShowCroppingGrid
+{
+    if (alwaysShowCroppingGrid == _alwaysShowCroppingGrid) { return; }
+    _alwaysShowCroppingGrid = alwaysShowCroppingGrid;
+    [self.gridOverlayView setGridHidden:!_alwaysShowCroppingGrid animated:YES];
+}
+
+-(void)setTranslucencyAlwaysHidden:(BOOL)translucencyAlwaysHidden
+{
+    if (_translucencyAlwaysHidden == translucencyAlwaysHidden) { return; }
+    _translucencyAlwaysHidden = translucencyAlwaysHidden;
+    self.translucencyView.hidden = _translucencyAlwaysHidden;
+}
+
+- (void)setGridOverlayHidden:(BOOL)gridOverlayHidden
+{
+    [self setGridOverlayHidden:_gridOverlayHidden animated:NO];
+}
+
+- (void)setGridOverlayHidden:(BOOL)gridOverlayHidden animated:(BOOL)animated
+{
+    _gridOverlayHidden = gridOverlayHidden;
+    self.gridOverlayView.alpha = gridOverlayHidden ? 1.0f : 0.0f;
+    
+    [UIView animateWithDuration:0.4f animations:^{
+        self.gridOverlayView.alpha = gridOverlayHidden ? 0.0f : 1.0f;
+    }];
+}
+
+- (CGRect)imageViewFrame
+{
+    CGRect frame = CGRectZero;
+    frame.origin.x = -self.scrollView.contentOffset.x;
+    frame.origin.y = -self.scrollView.contentOffset.y;
+    frame.size = self.scrollView.contentSize;
+    return frame;
+}
+
+- (void)setCanBeReset:(BOOL)canReset
+{
+    if (canReset == _canBeReset) {
+        return;
+    }
+    
+    _canBeReset = canReset;
+    
+    if (canReset) {
+        if ([self.delegate respondsToSelector:@selector(cropViewDidBecomeResettable:)])
+            [self.delegate cropViewDidBecomeResettable:self];
+    }
+    else  {
+        if ([self.delegate respondsToSelector:@selector(cropViewDidBecomeNonResettable:)])
+            [self.delegate cropViewDidBecomeNonResettable:self];
+    }
+}
+
+- (void)setAngle:(NSInteger)angle
+{
+    //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;
+    }
+    
+    // Negative values are allowed, so rotate clockwise or counter clockwise depending
+    // on direction
+    if (newAngle >= 0) {
+        while (labs(self.angle) != labs(newAngle)) {
+            [self rotateImageNinetyDegreesAnimated:NO clockwise:YES];
+        }
+    }
+    else {
+        while (-labs(self.angle) != -labs(newAngle)) {
+            [self rotateImageNinetyDegreesAnimated:NO clockwise:NO];
+        }
+    }
+}
+
+#pragma mark - Editing Mode -
+- (void)startEditing
+{
+    [self cancelResetTimer];
+    [self setEditing:YES resetCropBox:NO animated:YES];
+}
+
+- (void)setEditing:(BOOL)editing resetCropBox:(BOOL)resetCropbox animated:(BOOL)animated
+{
+    if (editing == _editing)
+        return;
+    
+    _editing = editing;
+
+    // Toggle the visiblity of the gridlines when not editing
+    BOOL hidden = !_editing;
+    if (self.alwaysShowCroppingGrid) { hidden = NO; } // Override this if the user requires
+    [self.gridOverlayView setGridHidden:hidden animated:animated];
+    
+    if (resetCropbox) {
+        [self moveCroppedContentToCenterAnimated:animated];
+        [self captureStateForImageRotation];
+        self.cropBoxLastEditedAngle = self.angle;
+    }
+    
+    if (animated == NO) {
+        [self toggleTranslucencyViewVisible:!editing];
+        return;
+    }
+    
+    CGFloat duration = editing ? 0.05f : 0.35f;
+    CGFloat delay = editing? 0.0f : 0.35f;
+    
+    if (self.croppingStyle == TOCropViewCroppingStyleCircular) {
+        delay = 0.0f;
+    }
+    
+    [UIView animateKeyframesWithDuration:duration delay:delay options:0 animations:^{
+        [self toggleTranslucencyViewVisible:!editing];
+    } completion:nil];
+}
+
+- (void)moveCroppedContentToCenterAnimated:(BOOL)animated
+{
+    if (self.internalLayoutDisabled)
+        return;
+    
+    CGRect contentRect = self.contentBounds;
+    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) {
+        return;
+    }
+    
+    //The scale we need to scale up the crop box to fit full screen
+    CGFloat scale = MIN(CGRectGetWidth(contentRect)/CGRectGetWidth(cropFrame), CGRectGetHeight(contentRect)/CGRectGetHeight(cropFrame));
+    
+    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);
+    
+    //Work out the point on the scroll content that the focusPoint is aiming at
+    CGPoint contentTargetPoint = CGPointZero;
+    contentTargetPoint.x = ((focusPoint.x + self.scrollView.contentOffset.x) * scale);
+    contentTargetPoint.y = ((focusPoint.y + self.scrollView.contentOffset.y) * scale);
+    
+    //Work out where the crop box is focusing, so we can re-align to center that point
+    __block CGPoint offset = CGPointZero;
+    offset.x = -midPoint.x + contentTargetPoint.x;
+    offset.y = -midPoint.y + contentTargetPoint.y;
+    
+    //clamp the content so it doesn't create any seams around the grid
+    offset.x = MAX(-cropFrame.origin.x, offset.x);
+    offset.y = MAX(-cropFrame.origin.y, offset.y);
+    
+    __weak typeof(self) weakSelf = self;
+    void (^translateBlock)(void) = ^{
+        typeof(self) strongSelf = weakSelf;
+        
+        // Setting these scroll view properties will trigger
+        // the foreground matching method via their delegates,
+        // multiple times inside the same animation block, resulting
+        // in glitchy animations.
+        //
+        // Disable matching for now, and explicitly update at the end.
+        strongSelf.disableForgroundMatching = YES;
+        {
+            // Slight hack. This method needs to be called during `[UIViewController viewDidLayoutSubviews]`
+            // 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) {
+                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
+            // the content offset
+            if (strongSelf.scrollView.zoomScale < strongSelf.scrollView.maximumZoomScale - FLT_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;
+            }
+            
+            strongSelf.cropBoxFrame = cropFrame;
+        }
+        strongSelf.disableForgroundMatching = NO;
+        
+        //Explicitly update the matching at the end of the calculations
+        [strongSelf matchForegroundToBackground];
+    };
+    
+    if (!animated) {
+        translateBlock();
+        return;
+    }
+
+    [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
+                            options:UIViewAnimationOptionBeginFromCurrentState
+                         animations:translateBlock
+                         completion:nil];
+    });
+}
+
+- (void)setSimpleRenderMode:(BOOL)simpleMode animated:(BOOL)animated
+{
+    if (simpleMode == _simpleRenderMode)
+        return;
+    
+    _simpleRenderMode = simpleMode;
+    
+    self.editing = NO;
+    
+    if (animated == NO) {
+        [self toggleTranslucencyViewVisible:!simpleMode];
+        
+        return;
+    }
+    
+    [UIView animateWithDuration:0.25f animations:^{
+        [self toggleTranslucencyViewVisible:!simpleMode];
+    }];
+}
+
+- (void)setAspectRatio:(CGSize)aspectRatio
+{
+    [self setAspectRatio:aspectRatio animated:NO];
+}
+
+- (void)setAspectRatio:(CGSize)aspectRatio animated:(BOOL)animated
+{
+    _aspectRatio = aspectRatio;
+    
+    // Will be executed automatically when added to a super view
+    if (!self.initialSetupPerformed) {
+        return;
+    }
+    
+    // Passing in an empty size will revert back to the image aspect ratio
+    if (aspectRatio.width < FLT_EPSILON && aspectRatio.height < FLT_EPSILON) {
+        aspectRatio = (CGSize){self.imageSize.width, self.imageSize.height};
+    }
+
+    CGRect boundsFrame = self.contentBounds;
+    CGRect cropBoxFrame = self.cropBoxFrame;
+    CGPoint offset = self.scrollView.contentOffset;
+    
+    BOOL cropBoxIsPortrait = NO;
+    if ((NSInteger)aspectRatio.width == 1 && (NSInteger)aspectRatio.height == 1)
+        cropBoxIsPortrait = self.image.size.width > self.image.size.height;
+    else
+        cropBoxIsPortrait = aspectRatio.width < aspectRatio.height;
+
+    BOOL zoomOut = NO;
+    if (cropBoxIsPortrait) {
+        CGFloat newWidth = floorf(cropBoxFrame.size.height * (aspectRatio.width/aspectRatio.height));
+        CGFloat delta = cropBoxFrame.size.width - newWidth;
+        cropBoxFrame.size.width = newWidth;
+        offset.x += (delta * 0.5f);
+
+        if (delta < FLT_EPSILON) {
+            cropBoxFrame.origin.x = self.contentBounds.origin.x; //set to 0 to avoid accidental clamping by the crop frame sanitizer
+        }
+
+        // If the aspect ratio causes the new width to extend
+        // beyond the content width, we'll need to zoom the image out
+        CGFloat boundsWidth = CGRectGetWidth(boundsFrame);
+        if (newWidth > boundsWidth) {
+            CGFloat scale = boundsWidth / newWidth;
+
+            // Scale the new height
+            CGFloat newHeight = cropBoxFrame.size.height * scale;
+            delta = cropBoxFrame.size.height - newHeight;
+            cropBoxFrame.size.height = newHeight;
+
+            // Offset the Y position so it stays in the middle
+            offset.y += (delta * 0.5f);
+
+            // Clamp the width to the bounds width
+            cropBoxFrame.size.width = boundsWidth;
+            zoomOut = YES;
+        }
+    }
+    else {
+        CGFloat newHeight = floorf(cropBoxFrame.size.width * (aspectRatio.height/aspectRatio.width));
+        CGFloat delta = cropBoxFrame.size.height - newHeight;
+        cropBoxFrame.size.height = newHeight;
+        offset.y += (delta * 0.5f);
+
+        if (delta < FLT_EPSILON) {
+            cropBoxFrame.origin.y = self.contentBounds.origin.y;
+        }
+
+        // If the aspect ratio causes the new height to extend
+        // beyond the content width, we'll need to zoom the image out
+        CGFloat boundsHeight = CGRectGetHeight(boundsFrame);
+        if (newHeight > boundsHeight) {
+            CGFloat scale = boundsHeight / newHeight;
+
+            // Scale the new width
+            CGFloat newWidth = cropBoxFrame.size.width * scale;
+            delta = cropBoxFrame.size.width - newWidth;
+            cropBoxFrame.size.width = newWidth;
+
+            // Offset the Y position so it stays in the middle
+            offset.x += (delta * 0.5f);
+
+            // Clamp the width to the bounds height
+            cropBoxFrame.size.height = boundsHeight;
+            zoomOut = YES;
+        }
+    }
+    
+    self.cropBoxLastEditedSize = cropBoxFrame.size;
+    self.cropBoxLastEditedAngle = self.angle;
+    
+    void (^translateBlock)(void) = ^{
+        self.scrollView.contentOffset = offset;
+        self.cropBoxFrame = cropBoxFrame;
+        
+        if (zoomOut) {
+            self.scrollView.zoomScale = self.scrollView.minimumZoomScale;
+        }
+            
+        [self moveCroppedContentToCenterAnimated:NO];
+        [self checkForCanReset];
+    };
+    
+    if (animated == NO) {
+        translateBlock();
+        return;
+    }
+    
+    [UIView animateWithDuration:0.5f
+                          delay:0.0
+         usingSpringWithDamping:1.0f
+          initialSpringVelocity:0.7f
+                        options:UIViewAnimationOptionBeginFromCurrentState
+                     animations:translateBlock
+                     completion:nil];
+}
+
+- (void)rotateImageNinetyDegreesAnimated:(BOOL)animated
+{
+    [self rotateImageNinetyDegreesAnimated:animated clockwise:NO];
+}
+
+- (void)rotateImageNinetyDegreesAnimated:(BOOL)animated clockwise:(BOOL)clockwise
+{
+    //Only allow one rotation animation at a time
+    if (self.rotateAnimationInProgress)
+        return;
+    
+    //Cancel any pending resizing timers
+    if (self.resetTimer) {
+        [self cancelResetTimer];
+        [self setEditing:NO resetCropBox:YES animated:NO];
+        
+        self.cropBoxLastEditedAngle = self.angle;
+        [self captureStateForImageRotation];
+    }
+    
+    //Work out the new angle, and wrap around once we exceed 360s
+    NSInteger newAngle = self.angle;
+    newAngle = clockwise ? newAngle + 90 : newAngle - 90;
+    if (newAngle <= -360 || newAngle >= 360) {
+        newAngle = 0;
+    }
+
+    _angle = newAngle;
+    
+    //Convert the new angle to radians
+    CGFloat angleInRadians = 0.0f;
+    switch (newAngle) {
+        case 90:    angleInRadians = M_PI_2;            break;
+        case -90:   angleInRadians = -M_PI_2;           break;
+        case 180:   angleInRadians = M_PI;              break;
+        case -180:  angleInRadians = -M_PI;             break;
+        case 270:   angleInRadians = (M_PI + M_PI_2);   break;
+        case -270:  angleInRadians = -(M_PI + M_PI_2);  break;
+        default:                                        break;
+    }
+    
+    // Set up the transformation matrix for the rotation
+    CGAffineTransform rotation = CGAffineTransformRotate(CGAffineTransformIdentity, angleInRadians);
+    
+    //Work out how much we'll need to scale everything to fit to the new rotation
+    CGRect contentBounds = self.contentBounds;
+    CGRect cropBoxFrame = self.cropBoxFrame;
+    CGFloat scale = MIN(contentBounds.size.width / cropBoxFrame.size.height, contentBounds.size.height / cropBoxFrame.size.width);
+    
+    //Work out which section of the image we're currently focusing at
+    CGPoint cropMidPoint = (CGPoint){CGRectGetMidX(cropBoxFrame), CGRectGetMidY(cropBoxFrame)};
+    CGPoint cropTargetPoint = (CGPoint){cropMidPoint.x + self.scrollView.contentOffset.x, cropMidPoint.y + self.scrollView.contentOffset.y};
+    
+    //Work out the dimensions of the crop box when rotated
+    CGRect newCropFrame = CGRectZero;
+    if (labs(self.angle) == labs(self.cropBoxLastEditedAngle) || (labs(self.angle)*-1) == ((labs(self.cropBoxLastEditedAngle) - 180) % 360)) {
+        newCropFrame.size = self.cropBoxLastEditedSize;
+        
+        self.scrollView.minimumZoomScale = self.cropBoxLastEditedMinZoomScale;
+        self.scrollView.zoomScale = self.cropBoxLastEditedZoomScale;
+    }
+    else {
+        newCropFrame.size = (CGSize){floorf(self.cropBoxFrame.size.height * scale), floorf(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));
+    
+    //If we're animated, generate a snapshot view that we'll animate in place of the real view
+    UIView *snapshotView = nil;
+    if (animated) {
+        snapshotView = [self.foregroundContainerView snapshotViewAfterScreenUpdates:NO];
+        self.rotateAnimationInProgress = YES;
+    }
+    
+    //Rotate the background image view, inside its container view
+    self.backgroundImageView.transform = rotation;
+    
+    //Flip the width/height of the container view so it matches the rotated image view's size
+    CGSize containerSize = self.backgroundContainerView.frame.size;
+    self.backgroundContainerView.frame = (CGRect){CGPointZero, {containerSize.height, containerSize.width}};
+    self.backgroundImageView.frame = (CGRect){CGPointZero, self.backgroundImageView.frame.size};
+
+    //Rotate the foreground image view to match
+    self.foregroundContainerView.transform = CGAffineTransformIdentity;
+    self.foregroundImageView.transform = rotation;
+    
+    //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;
+    
+    //swap the target dimensions to match a 90 degree rotation (clockwise or counterclockwise)
+    CGFloat swap = cropTargetPoint.x;
+    if (clockwise) {
+        cropTargetPoint.x = self.scrollView.contentSize.width - cropTargetPoint.y;
+        cropTargetPoint.y = swap;
+    } else {
+        cropTargetPoint.x = cropTargetPoint.y;
+        cropTargetPoint.y = self.scrollView.contentSize.height - swap;
+    }
+    
+    //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 = 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);
+    offset.y = MIN(self.scrollView.contentSize.height - (newCropFrame.size.height - self.scrollView.contentInset.bottom), offset.y);
+    
+    //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) {
+        [self matchForegroundToBackground];
+    }
+    self.scrollView.contentOffset = offset;
+    
+    //If we're animated, play an animation of the snapshot view rotating,
+    //then fade it out over the live content
+    if (animated) {
+        snapshotView.center = (CGPoint){CGRectGetMidX(contentBounds), CGRectGetMidY(contentBounds)};
+        [self addSubview:snapshotView];
+        
+        self.backgroundContainerView.hidden = YES;
+        self.foregroundContainerView.hidden = YES;
+        self.translucencyView.hidden = YES;
+        self.gridOverlayView.hidden = YES;
+        
+        [UIView animateWithDuration:0.45f delay:0.0f usingSpringWithDamping:1.0f initialSpringVelocity:0.8f options:UIViewAnimationOptionBeginFromCurrentState animations:^{
+            CGAffineTransform transform = CGAffineTransformRotate(CGAffineTransformIdentity, clockwise ? M_PI_2 : -M_PI_2);
+            transform = CGAffineTransformScale(transform, scale, scale);
+            snapshotView.transform = transform;
+        } completion:^(BOOL complete) {
+            self.backgroundContainerView.hidden = NO;
+            self.foregroundContainerView.hidden = NO;
+            self.translucencyView.hidden = self.translucencyAlwaysHidden;
+            self.gridOverlayView.hidden = NO;
+            
+            self.backgroundContainerView.alpha = 0.0f;
+            self.gridOverlayView.alpha = 0.0f;
+            
+            self.translucencyView.alpha = 1.0f;
+            
+            [UIView animateWithDuration:0.45f animations:^{
+                snapshotView.alpha = 0.0f;
+                self.backgroundContainerView.alpha = 1.0f;
+                self.gridOverlayView.alpha = 1.0f;
+            } completion:^(BOOL complete) {
+                self.rotateAnimationInProgress = NO;
+                [snapshotView removeFromSuperview];
+                
+                // If the aspect ratio lock is not enabled, allow a swap
+                // If the aspect ratio lock is on, allow a aspect ratio swap
+                // only if the allowDimensionSwap option is specified.
+                BOOL aspectRatioCanSwapDimensions = !self.aspectRatioLockEnabled ||
+                (self.aspectRatioLockEnabled && self.aspectRatioLockDimensionSwapEnabled);
+                
+                if (!aspectRatioCanSwapDimensions) {
+                    //This will animate the aspect ratio back to the desired locked ratio after the image is rotated.
+                    [self setAspectRatio:self.aspectRatio animated:animated];
+                }
+            }];
+        }];
+    }
+    
+    [self checkForCanReset];
+}
+
+- (void)captureStateForImageRotation
+{
+    self.cropBoxLastEditedSize = self.cropBoxFrame.size;
+    self.cropBoxLastEditedZoomScale = self.scrollView.zoomScale;
+    self.cropBoxLastEditedMinZoomScale = self.scrollView.minimumZoomScale;
+}
+
+#pragma mark - Resettable State -
+- (void)checkForCanReset
+{
+    BOOL canReset = NO;
+    
+    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
+        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))
+    { //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))
+    {
+        canReset = YES;
+    }
+
+    self.canBeReset = canReset;
+}
+
+#pragma mark - Convienience Methods -
+- (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);
+    return contentRect;
+}
+
+- (CGSize)imageSize
+{
+    if (self.angle == -90 || self.angle == -270 || self.angle == 90 || self.angle == 270)
+        return (CGSize){self.image.size.height, self.image.size.width};
+
+    return (CGSize){self.image.size.width, self.image.size.height};
+}
+
+- (BOOL)hasAspectRatio
+{
+    return (self.aspectRatio.width > FLT_EPSILON && self.aspectRatio.height > FLT_EPSILON);
+}
+
+@end

+ 256 - 0
Pods/CropViewController/README.md

@@ -0,0 +1,256 @@
+# TOCropViewController
+
+<p align="center">
+<img src="https://github.com/TimOliver/TOCropViewController/raw/main/Images/screenshot.png" width="840" style="margin:0 auto" />
+</p>
+
+[![CI](https://github.com/TimOliver/TOCropViewController/workflows/CI/badge.svg)](https://github.com/TimOliver/TOCropViewController/actions?query=workflow%3ACI)
+[![Version](https://img.shields.io/cocoapods/v/TOCropViewController.svg?style=flat)](http://cocoadocs.org/docsets/TOCropViewController)
+[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
+[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/TimOliver/TOCropViewController/master/LICENSE)
+[![Platform](https://img.shields.io/cocoapods/p/TOCropViewController.svg?style=flat)](http://cocoadocs.org/docsets/TOCropViewController)
+
+`TOCropViewController` is an open-source `UIViewController` subclass to crop out sections of `UIImage` objects, as well as perform basic rotations. It is excellent for things like editing profile pictures, or sharing parts of a photo online. It has been designed with the iOS Photos app editor in mind, and as such, behaves in a way that should already feel familiar to users of iOS.
+
+For Swift developers, `CropViewController` is a Swift wrapper that completely encapsulates `TOCropViewController` and provides a much more native, Swiftier interface.
+
+#### Proudly powering apps by
+
+<p align="center">
+<img src="https://github.com/TimOliver/TOCropViewController/raw/main/Images/users.png" width="840" style="margin:0 auto" />
+</p>
+
+_Looking for something more? If `TOCropViewController` doesn't meet your exact requirements, please consider [IMG.LY](https://img.ly/?via=tim) with video editing and photo filter capabilities instead! (Disclaimer: Affiliate Link)_
+
+## Features
+* Crop images by dragging the edges of a grid overlay.
+* Optionally, crop circular copies of images.
+* Rotate images in 90-degree segments.
+* Clamp the crop box to a specific aspect ratio.
+* A reset button to completely undo all changes.
+* iOS 7/8 translucency to make it easier to view the cropped region.
+* The choice of having the controller return the cropped image to a delegate, or immediately pass it to a `UIActivityViewController`.
+* A custom animation and layout when the device is rotated to landscape mode.
+* Custom 'opening' and 'dismissal' animations.
+* Localized in 28 languages.
+
+## System Requirements
+iOS 8.0 or above
+
+## Installation
+
+<details>
+  <summary><strong>CocoaPods</strong></summary>
+  
+  <h4>Objective-C</h4>
+
+Add the following to your Podfile:
+``` ruby
+pod 'TOCropViewController'
+```
+
+<h4>Swift</h4>
+
+Add the following to your Podfile:
+``` ruby
+pod 'CropViewController'
+```
+</details>
+
+<details>
+  <summary><strong>Swift Package Manager</strong></summary>
+
+Add the following to your `Package.swift`:
+``` swift
+dependencies: [
+  // ...
+  .package(url: "https://github.com/TimOliver/TOCropViewController.git"),
+],
+```
+</details>
+
+<details>
+  <summary><strong>Carthage</strong></summary>
+
+1. Add the following to your Cartfile:
+``` 
+github "TimOliver/TOCropViewController"
+```
+
+2. Run `carthage update`
+
+3. From the `Carthage/Build` folder, import one of the two frameworks into your Xcode project. For Objective-C projects, import just `TOCropViewController.framework`  and for Swift, import `CropViewController.framework` instead. Each framework is separate; you do not need to import both.
+
+4. Follow the remaining steps on [Getting Started with Carthage](https://github.com/Carthage/Carthage#getting-started) to finish integrating the framework.
+
+</details>
+
+<details>
+<summary><strong>Manual Installation</strong></summary>
+
+All of the necessary source and resource files for `TOCropViewController` are in `Objective-C/TOCropViewController`, and all of the necessary Swift files are in `Swift/CropViewController`.
+
+For Objective-C projects, copy just the `TOCropViewController` directory to your Xcode project. For Swift projects, copy both `TOCropViewController` and `CropViewController` to your project.
+</details>
+
+## Examples
+Using `TOCropViewController` is very straightforward. Simply create a new instance passing the `UIImage` object you wish to crop, and then present it modally on the screen.
+
+While `TOCropViewController` prefers to be presented modally, it can also be pushed to a `UINavigationController` stack.
+
+For a complete working example, check out the sample apps included in this repo.
+
+<details>
+<summary><strong>Basic Implementation</strong></summary>
+
+#### Swift
+```swift
+func presentCropViewController() {
+  let image: UIImage = ... //Load an image
+  
+  let cropViewController = CropViewController(image: image)
+  cropViewController.delegate = self
+  present(cropViewController, animated: true, completion: nil)
+}
+
+func cropViewController(_ cropViewController: CropViewController, didCropToImage image: UIImage, withRect cropRect: CGRect, angle: Int) {
+        // 'image' is the newly cropped version of the original image
+    }
+```
+
+#### Objective-C
+```objc
+- (void)presentCropViewController
+{
+  UIImage *image = ...; // Load an image
+  
+  TOCropViewController *cropViewController = [[TOCropViewController alloc] initWithImage:image];
+  cropViewController.delegate = self;
+  [self presentViewController:cropViewController animated:YES completion:nil];
+}
+
+- (void)cropViewController:(TOCropViewController *)cropViewController didCropToImage:(UIImage *)image withRect:(CGRect)cropRect angle:(NSInteger)angle
+{
+  // 'image' is the newly cropped version of the original image
+}
+```
+
+Similar to many `UIKit` `UIViewController` subclasses, like `MFMailComposeViewController`, the class responsible for presenting view controller should also take care of dismissing it upon cancellation. To dismiss `TOCropViewController`, implement the `cropViewController:didFinishCancelled:` delegate method, and call `dismissViewController:animated:` from there.
+</details>
+
+<details>
+<summary><strong>Making a Circular Cropped Image</strong></summary>
+
+#### Swift
+```swift
+func presentCropViewController() {
+    var image: UIImage? // Load an image
+    let cropViewController = CropViewController(croppingStyle: .circular, image: image)
+    cropViewController.delegate = self
+    self.present(cropViewController, animated: true, completion: nil)
+}
+
+func cropViewController(_ cropViewController: TOCropViewController?, didCropToCircularImage image: UIImage?, with cropRect: CGRect, angle: Int) {
+    // 'image' is the newly cropped, circular version of the original image
+}
+```
+
+
+#### Objective-C
+```objc
+- (void)presentCropViewController
+{
+UIImage *image = ...; // Load an image
+
+TOCropViewController *cropViewController = [[TOCropViewController alloc] initWithCroppingStyle:TOCropViewCroppingStyleCircular image:image];
+cropViewController.delegate = self;
+[self presentViewController:cropViewController animated:YES completion:nil];
+}
+
+- (void)cropViewController:(TOCropViewController *)cropViewController didCropToCircularImage:(UIImage *)image withRect:(CGRect)cropRect angle:(NSInteger)angle
+{
+// 'image' is the newly cropped, circular version of the original image
+}
+```
+</details>
+
+<details>
+<summary><strong>Sharing Cropped Images Via a Share Sheet</strong></summary>
+
+#### Swift
+```swift
+func presentCropViewController() {
+    var image: UIImage? // Load an image
+    let cropViewController = CropViewController(image: image)
+    cropViewController.showActivitySheetOnDone = true
+    self.present(cropViewController, animated: true, completion: nil)
+}
+```
+
+#### Objective-C
+```objc
+- (void)presentCropViewController
+{
+  UIImage *image = ...; // Load an image
+  
+  TOCropViewController *cropViewController = [[TOCropViewController alloc] initWithImage:image];
+  cropViewController.showActivitySheetOnDone = YES;
+  [self presentViewController:cropViewController animated:YES completion:nil];
+}
+```
+</details>
+
+<details>
+<summary><strong>Presenting With a Custom Animation</strong></summary>
+
+Optionally, `TOCropViewController` also supports a custom presentation animation where an already-visible copy of the image will zoom in to fill the screen.
+
+#### Swift
+```swift
+
+func presentCropViewController() {
+    var image: UIImage? // Load an image
+    var imageView = UIImageView(image: image)
+    var frame: CGRect = view.convert(imageView.frame, to: view)
+    
+    let cropViewController = CropViewController(image: image)
+    cropViewController.delegate = self
+    self.present(cropViewController, animated: true, completion: nil)
+    cropViewController.presentAnimated(fromParentViewController: self, fromFrame: frame, completion: nil)
+}
+```
+
+#### Objective-C
+```objc
+- (void)presentCropViewController
+{
+  UIImage *image = ...;
+  UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
+  CGRect frame = [self.view convertRect:imageView.frame toView:self.view];
+  
+  TOCropViewController *cropViewController = [[TOCropViewController alloc] initWithImage:image];
+  cropViewController.delegate = self;
+  [self presentViewController:cropViewController animated:YES completion:nil];
+  [cropViewController presentAnimatedFromParentViewController:self fromFrame:frame completion:nil];
+}
+```
+</details>
+
+## Architecture of `TOCropViewController`
+While traditional cropping UI implementations will usually just have a dimming view with a square hole cut out of the middle, `TOCropViewController` goes about its implementation a little differently.
+
+<p align="center">
+<img src="https://raw.githubusercontent.com/TimOliver/TOCropViewController/master/breakdown.jpg" width="702" style="margin:0 auto" />
+</p>
+
+Since there are two views that are overlaid over the image (A dimming view and a translucency view), trying to cut a hole open in both of them would be rather complex. Instead, an image view is placed in a scroll view in the background, and a copy of the image view is placed on top, inside a container view that is clipped to the designated cropping size. The size and position of the foreground image is then made to match the background view, creating the illusion that there is a hole in the dimming views, and minimising the number of views onscreen.
+
+## Credits
+`TOCropViewController` was originally created by [Tim Oliver](http://twitter.com/TimOliverAU) as a component for [iComics](http://icomics.co), a comic reader app for iOS.
+
+Thanks also goes to `TOCropViewController`'s growing list of [contributors](https://github.com/TimOliver/TOCropViewController/graphs/contributors)!
+
+iOS Device mockups used in the screenshot created by [Pixeden](http://www.pixeden.com).
+
+## License
+TOCropViewController is licensed under the MIT License, please see the [LICENSE](LICENSE) file.

+ 33 - 0
Pods/CropViewController/Swift/CropViewController/CropViewController.h

@@ -0,0 +1,33 @@
+//
+//  CropViewController.h
+//
+//  Copyright 2017-2022 Timothy Oliver. 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 <UIKit/UIKit.h>
+
+#import "TOCropViewController.h"
+#import "TOCropView.h"
+#import "TOCropToolbar.h"
+#import "TOCropViewConstants.h"
+#import "UIImage+CropRotate.h"
+
+FOUNDATION_EXPORT double CropViewControllerVersionNumber;
+FOUNDATION_EXPORT const unsigned char CropViewControllerVersionString[];
+

+ 673 - 0
Pods/CropViewController/Swift/CropViewController/CropViewController.swift

@@ -0,0 +1,673 @@
+//
+//  CropViewController.swift
+//
+//  Copyright 2017-2022 Timothy Oliver. 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.
+
+#if canImport(TOCropViewController)
+import TOCropViewController
+#endif
+
+/**
+ An enum containing all of the aspect ratio presets that this view controller supports
+ */
+public typealias CropViewControllerAspectRatioPreset = TOCropViewControllerAspectRatioPreset
+
+/**
+ An enum denoting whether the control tool bar is drawn at the top, or the bottom of the screen in portrait mode
+ */
+public typealias CropViewControllerToolbarPosition = TOCropViewControllerToolbarPosition
+
+/**
+ The type of cropping style for this view controller (ie a square or a circle cropping region)
+ */
+public typealias CropViewCroppingStyle = TOCropViewCroppingStyle
+
+// ------------------------------------------------
+/// @name Delegate
+// ------------------------------------------------
+
+@objc public protocol CropViewControllerDelegate: NSObjectProtocol {
+    /**
+     Called when the user has committed the crop action, and provides
+     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)
+     @param angle The angle of the image when it was cropped
+     */
+    @objc optional func cropViewController(_ cropViewController: CropViewController, didCropImageToRect cropRect: CGRect, angle: Int)
+    
+    /**
+     Called when the user has committed the crop action, and provides
+     both the original image with crop co-ordinates.
+     
+     @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 angle The angle of the image when it was cropped
+     */
+    @objc optional func cropViewController(_ cropViewController: CropViewController, didCropToImage image: UIImage, withRect cropRect: CGRect, angle: Int)
+    
+    /**
+     If the cropping style is set to circular, implementing this delegate will return a circle-cropped version of the selected
+     image, as well as it's cropping co-ordinates
+     
+     @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 angle The angle of the image when it was cropped
+     */
+    @objc optional func cropViewController(_ cropViewController: CropViewController, didCropToCircularImage image: UIImage, withRect cropRect: CGRect, angle: Int)
+    
+    /**
+     If implemented, when the user hits cancel, or completes a
+     UIActivityViewController operation, this delegate will be called,
+     giving you a chance to manually dismiss the view controller
+     
+     @param cancelled Whether a cropping action was actually performed, or if the user explicitly hit 'Cancel'
+     
+     */
+    @objc optional func cropViewController(_ cropViewController: CropViewController, didFinishCancelled cancelled: Bool)
+}
+
+// ------------------------------------------------
+/// @name Class
+// ------------------------------------------------
+
+open class CropViewController: UIViewController, TOCropViewControllerDelegate {
+    
+    /**
+     The original, uncropped image that was passed to this controller.
+     */
+    public var image: UIImage { return self.toCropViewController.image }
+    
+    /**
+     The view controller's delegate that will receive the resulting
+     cropped image, as well as crop information.
+    */
+    public weak var delegate: CropViewControllerDelegate? {
+        didSet { self.setUpDelegateHandlers() }
+    }
+    
+    /**
+     Set the title text that appears at the top of the view controller
+    */
+    override open var title: String? {
+        set { toCropViewController.title = newValue }
+        get { return toCropViewController.title }
+    }
+    
+    /**
+     If true, when the user hits 'Done', a UIActivityController will appear
+     before the view controller ends.
+     */
+    public var showActivitySheetOnDone: Bool {
+        set { toCropViewController.showActivitySheetOnDone = newValue }
+        get { return toCropViewController.showActivitySheetOnDone }
+    }
+    
+    /**
+     In the coordinate space of the image itself, the region that is currently
+     being highlighted by the crop box.
+     
+     This property can be set before the controller is presented to have
+     the image 'restored' to a previous cropping layout.
+     */
+    public var imageCropFrame: CGRect {
+        set { toCropViewController.imageCropFrame = newValue }
+        get { return toCropViewController.imageCropFrame }
+    }
+    
+    /**
+     The angle in which the image is rotated in the crop view.
+     This can only be in 90 degree increments (eg, 0, 90, 180, 270).
+     
+     This property can be set before the controller is presented to have
+     the image 'restored' to a previous cropping layout.
+     */
+    public var angle: Int {
+        set { toCropViewController.angle = newValue }
+        get { return toCropViewController.angle }
+    }
+    
+    /**
+     The cropping style of this particular crop view controller
+     */
+    public var croppingStyle: CropViewCroppingStyle {
+        return toCropViewController.croppingStyle
+    }
+    
+    /**
+      A choice from one of the pre-defined aspect ratio presets
+    */
+    public var aspectRatioPreset: CropViewControllerAspectRatioPreset {
+        set { toCropViewController.aspectRatioPreset = newValue }
+        get { return toCropViewController.aspectRatioPreset }
+    }
+    
+    /**
+     A CGSize value representing a custom aspect ratio, not listed in the presets.
+     E.g. A ratio of 4:3 would be represented as (CGSize){4.0f, 3.0f}
+     */
+    public var customAspectRatio: CGSize {
+        set { toCropViewController.customAspectRatio = newValue }
+        get { return toCropViewController.customAspectRatio }
+    }
+    
+    /**
+     Title label which can be used to show instruction on the top of the crop view controller
+     */
+    public var titleLabel: UILabel? {
+        return toCropViewController.titleLabel
+    }
+    
+    /**
+     If true, 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.
+     
+     Default is false.
+     */
+    public var aspectRatioLockEnabled: Bool {
+        set { toCropViewController.aspectRatioLockEnabled = newValue }
+        get { return toCropViewController.aspectRatioLockEnabled }
+    }
+    
+    /**
+     If true, a custom aspect ratio is set, and the aspectRatioLockEnabled is set to true, the crop box will swap it's dimensions depending on portrait or landscape sized images.  This value also controls whether the dimensions can swap when the image is rotated.
+     
+     Default is false.
+     */
+    public var aspectRatioLockDimensionSwapEnabled: Bool {
+        set { toCropViewController.aspectRatioLockDimensionSwapEnabled = newValue }
+        get { return toCropViewController.aspectRatioLockDimensionSwapEnabled }
+    }
+    
+    /**
+     If true, 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 false, and `aspectRatioLockEnabled` is set to YES, then the aspect ratio
+     button will automatically be hidden from the toolbar.
+     
+     Default is true
+     */
+    public var resetAspectRatioEnabled: Bool {
+        set { toCropViewController.resetAspectRatioEnabled = newValue }
+        get { return toCropViewController.resetAspectRatioEnabled }
+    }
+    
+    /**
+     The position of the Toolbar the default value is `TOCropViewControllerToolbarPositionBottom`.
+     */
+    public var toolbarPosition: CropViewControllerToolbarPosition {
+        set { toCropViewController.toolbarPosition = newValue }
+        get { return toCropViewController.toolbarPosition }
+    }
+    
+    /**
+     When disabled, an additional rotation button that rotates the canvas in
+     90-degree segments in a clockwise direction is shown in the toolbar.
+     
+     Default is false.
+     */
+    public var rotateClockwiseButtonHidden: Bool {
+        set { toCropViewController.rotateClockwiseButtonHidden = newValue }
+        get { return toCropViewController.rotateClockwiseButtonHidden }
+    }
+    
+    /**
+     When enabled, hides the rotation button, as well as the alternative rotation
+     button visible when `showClockwiseRotationButton` is set to true.
+     
+     Default is false.
+     */
+    public var rotateButtonsHidden: Bool {
+        set { toCropViewController.rotateButtonsHidden = newValue }
+        get { return toCropViewController.rotateButtonsHidden }
+    }
+    /**
+     When enabled, hides the 'Reset' button on the toolbar.
+
+     Default is false.
+     */
+    public var resetButtonHidden: Bool {
+        set { toCropViewController.resetButtonHidden = newValue }
+        get { return toCropViewController.resetButtonHidden }
+    }
+    
+    /**
+     When enabled, hides the 'Aspect Ratio Picker' button on the toolbar.
+     
+     Default is false.
+     */
+    public var aspectRatioPickerButtonHidden: Bool {
+        set { toCropViewController.aspectRatioPickerButtonHidden = newValue }
+        get { return toCropViewController.aspectRatioPickerButtonHidden }
+    }
+    
+    /**
+     When enabled, hides the 'Done' button on the toolbar.
+
+     Default is false.
+     */
+    public var doneButtonHidden: Bool {
+        set { toCropViewController.doneButtonHidden = newValue }
+        get { return toCropViewController.doneButtonHidden }
+    }
+    
+    /**
+     When enabled, hides the 'Cancel' button on the toolbar.
+
+     Default is false.
+     */
+    public var cancelButtonHidden: Bool {
+        set { toCropViewController.cancelButtonHidden = newValue }
+        get { return toCropViewController.cancelButtonHidden }
+    }
+
+    /**
+     If `showActivitySheetOnDone` is true, then these activity items will
+     be supplied to that UIActivityViewController in addition to the
+     `TOActivityCroppedImageProvider` object.
+     */
+    public var activityItems: [Any]? {
+        set { toCropViewController.activityItems = newValue }
+        get { return toCropViewController.activityItems }
+    }
+    
+    /**
+     If `showActivitySheetOnDone` is true, 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
+     */
+    public var applicationActivities: [UIActivity]? {
+        set { toCropViewController.applicationActivities = newValue }
+        get { return toCropViewController.applicationActivities }
+    }
+    
+    /**
+     If `showActivitySheetOnDone` is true, then you may expliclty
+     set activities that won't appear in the share sheet here.
+     */
+    public var excludedActivityTypes: [UIActivity.ActivityType]? {
+        set { toCropViewController.excludedActivityTypes = newValue }
+        get { return toCropViewController.excludedActivityTypes }
+    }
+    
+    /**
+     An array of `TOCropViewControllerAspectRatioPreset` enum values denoting which
+     aspect ratios the crop view controller may display (Default is nil. All are shown)
+     */
+    public var allowedAspectRatios: [CropViewControllerAspectRatioPreset]? {
+        set { toCropViewController.allowedAspectRatios = newValue?.map { NSNumber(value: $0.rawValue) } }
+        get { return toCropViewController.allowedAspectRatios?.compactMap { CropViewControllerAspectRatioPreset(rawValue: $0.intValue) } }
+    }
+    
+    /**
+     When the user hits cancel, or completes a
+     UIActivityViewController operation, this block will be called,
+     giving you a chance to manually dismiss the view controller
+     */
+    public var onDidFinishCancelled: ((Bool) -> (Void))? {
+        set { toCropViewController.onDidFinishCancelled = newValue }
+        get { return toCropViewController.onDidFinishCancelled }
+    }
+    
+    /**
+     Called when the user has committed the crop action, and provides
+     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)
+     @param angle The angle of the image when it was cropped
+     */
+    public var onDidCropImageToRect: ((CGRect, Int) -> (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.
+     
+     @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 angle The angle of the image when it was cropped
+     */
+    public var onDidCropToRect: ((UIImage, CGRect, NSInteger) -> (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
+     
+     @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 angle The angle of the image when it was cropped
+     */
+    public var onDidCropToCircleImage: ((UIImage, CGRect, NSInteger) -> (Void))? {
+        set { toCropViewController.onDidCropToCircleImage = newValue }
+        get { return toCropViewController.onDidCropToCircleImage }
+    }
+
+    /**
+     The crop view managed by this view controller.
+     */
+    public var cropView: TOCropView {
+        return toCropViewController.cropView
+    }
+    
+    /**
+     The toolbar managed by this view controller.
+     */
+    public var toolbar: TOCropToolbar {
+        return toCropViewController.toolbar
+    }
+
+    /*
+     If this controller is embedded in UINavigationController its navigation bar is hidden by default. Set this property to false to show the navigation bar. This must be set before this controller is presented.
+     */
+    public var hidesNavigationBar: Bool {
+        set { toCropViewController.hidesNavigationBar = newValue }
+        get { return toCropViewController.hidesNavigationBar }
+    }
+    
+    /**
+     Title for the 'Done' button.
+     Setting this will override the Default which is a localized string for "Done".
+     */
+    public var doneButtonTitle: String! {
+        set { toCropViewController.doneButtonTitle = newValue }
+        get { return toCropViewController.doneButtonTitle }
+    }
+    
+    /**
+     Title for the 'Cancel' button.
+     Setting this will override the Default which is a localized string for "Cancel".
+     */
+    public var cancelButtonTitle: String! {
+        set { toCropViewController.cancelButtonTitle = newValue }
+        get { return toCropViewController.cancelButtonTitle }
+    }
+
+    /**
+    If true, button icons are visible in portairt instead button text.
+
+    Default is NO.
+    */
+    public var showOnlyIcons: Bool {
+        set { toCropViewController.showOnlyIcons = newValue }
+        get { return toCropViewController.showOnlyIcons }
+    }
+
+    /**
+    Color for the 'Done' button.
+    Setting this will override the default color.
+    */
+    public var doneButtonColor: UIColor? {
+        set { toCropViewController.doneButtonColor = newValue }
+        get { return toCropViewController.doneButtonColor }
+    }
+    
+    /**
+    Color for the 'Cancel' button.
+    Setting this will override the default color.
+    */
+    public var cancelButtonColor: UIColor? {
+        set { toCropViewController.cancelButtonColor = newValue }
+        get { return toCropViewController.cancelButtonColor }
+    }
+    
+    /**
+     This class internally manages and abstracts access to a `TOCropViewController` instance
+     :nodoc:
+     */
+    internal let toCropViewController: TOCropViewController!
+    
+    /**
+     Forward status bar status style changes to the crop view controller
+     :nodoc:
+     */
+    open override var childForStatusBarStyle: UIViewController? {
+        return toCropViewController
+    }
+    
+    /**
+     Forward status bar status visibility changes to the crop view controller
+     :nodoc:
+     */
+    open override var childForStatusBarHidden: UIViewController? {
+        return toCropViewController
+    }
+    
+    open override var prefersStatusBarHidden: Bool {
+        return false
+    }
+    
+    open override var preferredStatusBarStyle: UIStatusBarStyle {
+        return toCropViewController.preferredStatusBarStyle
+    }
+    
+    open override var preferredScreenEdgesDeferringSystemGestures: UIRectEdge {
+        if #available(iOS 11.0, *) {
+            return toCropViewController.preferredScreenEdgesDeferringSystemGestures
+        }
+        
+        return UIRectEdge.all
+    }
+    
+    // ------------------------------------------------
+    /// @name Object Creation
+    // ------------------------------------------------
+    
+    /**
+     Creates a new instance of a crop view controller with the supplied image
+     
+     @param image The image that will be used to crop.
+     */
+    public init(image: UIImage) {
+        self.toCropViewController = TOCropViewController(image: image)
+        super.init(nibName: nil, bundle: nil)
+        setUpCropController()
+    }
+    
+    /**
+     Creates a new instance of a crop view controller with the supplied image and cropping style
+     
+     @param style The cropping style that will be used with this view controller (eg, rectangular, or circular)
+     @param image The image that will be cropped
+     */
+    public init(croppingStyle: CropViewCroppingStyle, image: UIImage) {
+        self.toCropViewController = TOCropViewController(croppingStyle: croppingStyle, image: image)
+        super.init(nibName: nil, bundle: nil)
+        setUpCropController()
+    }
+    
+    required public init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    open override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+        
+        // Defer adding the view until we're about to be presented
+        if toCropViewController.view.superview == nil {
+            view.addSubview(toCropViewController.view)
+        }
+    }
+    
+    open override func viewDidLayoutSubviews() {
+        super.viewDidLayoutSubviews()
+        toCropViewController.view.frame = view.bounds
+        toCropViewController.viewDidLayoutSubviews()
+    }
+
+    /**
+     Commits the crop action as if user pressed done button in the bottom bar themself
+     */
+    public func commitCurrentCrop() {
+        toCropViewController.commitCurrentCrop()
+    }
+    
+    /**
+    Resets object of TOCropViewController class as if user pressed reset button in the bottom bar themself
+    */
+    public func resetCropViewLayout() {
+        toCropViewController.resetCropViewLayout()
+    }
+
+    /**
+    Set the aspect ratio to be one of the available preset options. These presets have specific behaviour
+    such as swapping their dimensions depending on portrait or landscape sized images.
+
+    @param aspectRatioPreset The aspect ratio preset
+    @param animated Whether the transition to the aspect ratio is animated
+    */
+    public func setAspectRatioPreset(_ aspectRatio: CropViewControllerAspectRatioPreset, animated: Bool) {
+        toCropViewController.setAspectRatioPreset(aspectRatio, animated: animated)
+    }
+    
+    /**
+    Play a custom animation of the target image zooming to its position in
+    the crop controller while the background fades in.
+
+    @param viewController The parent controller that this view controller would be presenting from.
+    @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. Optional if `fromView` has a value.
+    @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, fromView view: UIView?, fromFrame frame: CGRect,
+                                    setup: (() -> (Void))?, completion: (() -> (Void))?)
+    {
+        toCropViewController.presentAnimatedFrom(viewController, view: view, frame: frame, setup: setup, completion: completion)
+    }
+    
+    /**
+     Play a custom animation of the target image zooming to its position in
+     the crop controller while the background fades in. Additionally, if you're
+     'restoring' to a previous crop setup, this method lets you provide a previously
+     cropped copy of the image, and the previous crop settings to transition back to
+     where the user would have left off.
+
+     @param viewController The parent controller that this view controller would be presenting from.
+     @param image The previously cropped image that can be used in the transition animation.
+     @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 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,
+                                    setup: (() -> (Void))?, completion:(() -> (Void))?)
+    {
+        toCropViewController.presentAnimatedFrom(viewController, fromImage: image, fromView: fromView,
+                                                 fromFrame: fromFrame, angle: angle, toFrame: toFrame,
+                                                 setup: setup, completion: completion)
+    }
+    
+    /**
+     Play a custom animation of the supplied cropped image zooming out from
+     the cropped frame to the specified frame as the rest of the content fades out.
+     If any view configurations need to be done before the animation starts,
+
+     @param viewController The parent controller that this view controller would be presenting from.
+     @param toView A view who's frame will be used to establish the destination frame
+     @param frame The target frame that the image will animate to
+     @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 dismissAnimatedFrom(_ viewController: UIViewController, toView: UIView?, toFrame: CGRect,
+                                    setup: (() -> (Void))?, completion:(() -> (Void))?)
+    {
+        toCropViewController.dismissAnimatedFrom(viewController, toView: toView, toFrame: toFrame, setup: setup, completion: completion)
+    }
+    
+    /**
+     Play a custom animation of the supplied cropped image zooming out from
+     the cropped frame to the specified frame as the rest of the content fades out.
+     If any view configurations need to be done before the animation starts,
+
+     @param viewController The parent controller that this view controller would be presenting from.
+     @param image The resulting 'cropped' image. If supplied, will animate out of the crop box zone. If nil, the default image will entirely zoom out
+     @param toView A view who's frame will be used to establish the destination frame
+     @param frame The target frame that the image will animate to
+     @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 dismissAnimatedFrom(_ viewController: UIViewController, withCroppedImage croppedImage: UIImage?, toView: UIView?,
+                                    toFrame: CGRect, setup: (() -> (Void))?, completion:(() -> (Void))?)
+    {
+        toCropViewController.dismissAnimatedFrom(viewController, croppedImage: croppedImage, toView: toView,
+                                                 toFrame: toFrame, setup: setup, completion: completion)
+    }
+}
+
+extension CropViewController {
+    fileprivate func setUpCropController() {
+        modalPresentationStyle = .fullScreen
+        addChild(toCropViewController)
+        transitioningDelegate = (toCropViewController as! UIViewControllerTransitioningDelegate)
+        toCropViewController.delegate = self
+        toCropViewController.didMove(toParent: self)
+    }
+    
+    fileprivate func setUpDelegateHandlers() {
+        guard let delegate = self.delegate else {
+            onDidCropToRect = nil
+            onDidCropImageToRect = nil
+            onDidCropToCircleImage = nil
+            onDidFinishCancelled = nil
+            return
+        }
+        
+        if delegate.responds(to: #selector(CropViewControllerDelegate.cropViewController(_:didCropImageToRect:angle:))) {
+            self.onDidCropImageToRect = {[weak self] rect, angle in
+                guard let strongSelf = self else { return }
+                delegate.cropViewController!(strongSelf, didCropImageToRect: rect, angle: angle)
+            }
+        }
+        
+        if delegate.responds(to: #selector(CropViewControllerDelegate.cropViewController(_:didCropToImage:withRect:angle:))) {
+            self.onDidCropToRect = {[weak self] image, rect, angle in
+                guard let strongSelf = self else { return }
+                delegate.cropViewController!(strongSelf, didCropToImage: image, withRect: rect, angle: angle)
+            }
+        }
+        
+        if delegate.responds(to: #selector(CropViewControllerDelegate.cropViewController(_:didCropToCircularImage:withRect:angle:))) {
+            self.onDidCropToCircleImage = {[weak self] image, rect, angle in
+                guard let strongSelf = self else { return }
+                delegate.cropViewController!(strongSelf, didCropToCircularImage: image, withRect: rect, angle: angle)
+            }
+        }
+        
+        if delegate.responds(to: #selector(CropViewControllerDelegate.cropViewController(_:didFinishCancelled:))) {
+            self.onDidFinishCancelled = {[weak self] finished in
+                guard let strongSelf = self else { return }
+                delegate.cropViewController!(strongSelf, didFinishCancelled: finished)
+            }
+        }
+    }
+}
+

+ 6 - 2
Pods/Manifest.lock

@@ -2,6 +2,7 @@ PODS:
   - CocoaLumberjack (3.7.2):
     - CocoaLumberjack/Core (= 3.7.2)
   - CocoaLumberjack/Core (3.7.2)
+  - CropViewController (2.6.1)
   - DBDebugToolkit (0.6.0)
   - libwebp (1.2.0):
     - libwebp/demux (= 1.2.0)
@@ -31,6 +32,7 @@ PODS:
   - SwiftyBeaver (1.9.4)
 
 DEPENDENCIES:
+  - CropViewController
   - DBDebugToolkit
   - ReachabilitySwift
   - SCSiriWaveformView
@@ -46,6 +48,7 @@ DEPENDENCIES:
 SPEC REPOS:
   trunk:
     - CocoaLumberjack
+    - CropViewController
     - DBDebugToolkit
     - libwebp
     - ReachabilitySwift
@@ -70,6 +73,7 @@ CHECKOUT OPTIONS:
 
 SPEC CHECKSUMS:
   CocoaLumberjack: b7e05132ff94f6ae4dfa9d5bce9141893a21d9da
+  CropViewController: 58fb440f30dac788b129d2a1f24cffdcb102669c
   DBDebugToolkit: 03eb3483da4daf03b853b83ade5a8da9c8b7aff4
   libwebp: e90b9c01d99205d03b6bb8f2c8c415e5a4ef66f0
   ReachabilitySwift: 4032e2f59586e11e3b0ebe15b167abdd587a388b
@@ -83,6 +87,6 @@ SPEC CHECKSUMS:
   SwiftLint: d41cc46a2ae58ac6d9f26954bc89f1d72e71fdef
   SwiftyBeaver: 576177b2c5c94b3aedd5993914e91271a0524e88
 
-PODFILE CHECKSUM: 6972a504404994ab0cbfd625f32d2cca6958bb1f
+PODFILE CHECKSUM: a958fb3d60b6f4c0d56fff6b2a3ec0be995f58ce
 
-COCOAPODS: 1.11.2
+COCOAPODS: 1.11.3

Разлика између датотеке није приказан због своје велике величине
+ 2020 - 1882
Pods/Pods.xcodeproj/project.pbxproj


+ 26 - 0
Pods/Target Support Files/CropViewController/CropViewController-Info.plist

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>CFBundleDevelopmentRegion</key>
+  <string>en</string>
+  <key>CFBundleExecutable</key>
+  <string>${EXECUTABLE_NAME}</string>
+  <key>CFBundleIdentifier</key>
+  <string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
+  <key>CFBundleInfoDictionaryVersion</key>
+  <string>6.0</string>
+  <key>CFBundleName</key>
+  <string>${PRODUCT_NAME}</string>
+  <key>CFBundlePackageType</key>
+  <string>FMWK</string>
+  <key>CFBundleShortVersionString</key>
+  <string>2.6.1</string>
+  <key>CFBundleSignature</key>
+  <string>????</string>
+  <key>CFBundleVersion</key>
+  <string>${CURRENT_PROJECT_VERSION}</string>
+  <key>NSPrincipalClass</key>
+  <string></string>
+</dict>
+</plist>

+ 5 - 0
Pods/Target Support Files/CropViewController/CropViewController-dummy.m

@@ -0,0 +1,5 @@
+#import <Foundation/Foundation.h>
+@interface PodsDummy_CropViewController : NSObject
+@end
+@implementation PodsDummy_CropViewController
+@end

+ 12 - 0
Pods/Target Support Files/CropViewController/CropViewController-prefix.pch

@@ -0,0 +1,12 @@
+#ifdef __OBJC__
+#import <UIKit/UIKit.h>
+#else
+#ifndef FOUNDATION_EXPORT
+#if defined(__cplusplus)
+#define FOUNDATION_EXPORT extern "C"
+#else
+#define FOUNDATION_EXPORT extern
+#endif
+#endif
+#endif
+

+ 27 - 0
Pods/Target Support Files/CropViewController/CropViewController-umbrella.h

@@ -0,0 +1,27 @@
+#ifdef __OBJC__
+#import <UIKit/UIKit.h>
+#else
+#ifndef FOUNDATION_EXPORT
+#if defined(__cplusplus)
+#define FOUNDATION_EXPORT extern "C"
+#else
+#define FOUNDATION_EXPORT extern
+#endif
+#endif
+#endif
+
+#import "CropViewController.h"
+#import "UIImage+CropRotate.h"
+#import "TOCropViewConstants.h"
+#import "TOActivityCroppedImageProvider.h"
+#import "TOCroppedImageAttributes.h"
+#import "TOCropViewControllerTransitioning.h"
+#import "TOCropViewController.h"
+#import "TOCropOverlayView.h"
+#import "TOCropScrollView.h"
+#import "TOCropToolbar.h"
+#import "TOCropView.h"
+
+FOUNDATION_EXPORT double CropViewControllerVersionNumber;
+FOUNDATION_EXPORT const unsigned char CropViewControllerVersionString[];
+

+ 13 - 0
Pods/Target Support Files/CropViewController/CropViewController.debug.xcconfig

@@ -0,0 +1,13 @@
+CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
+CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/CropViewController
+GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
+LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
+OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -suppress-warnings
+PODS_BUILD_DIR = ${BUILD_DIR}
+PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
+PODS_ROOT = ${SRCROOT}
+PODS_TARGET_SRCROOT = ${PODS_ROOT}/CropViewController
+PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
+PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
+SKIP_INSTALL = YES
+USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES

+ 6 - 0
Pods/Target Support Files/CropViewController/CropViewController.modulemap

@@ -0,0 +1,6 @@
+framework module CropViewController {
+  umbrella header "CropViewController-umbrella.h"
+
+  export *
+  module * { export * }
+}

+ 13 - 0
Pods/Target Support Files/CropViewController/CropViewController.release.xcconfig

@@ -0,0 +1,13 @@
+CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
+CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/CropViewController
+GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
+LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
+OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -suppress-warnings
+PODS_BUILD_DIR = ${BUILD_DIR}
+PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
+PODS_ROOT = ${SRCROOT}
+PODS_TARGET_SRCROOT = ${PODS_ROOT}/CropViewController
+PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
+PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
+SKIP_INSTALL = YES
+USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES

+ 24 - 0
Pods/Target Support Files/CropViewController/ResourceBundle-TOCropViewControllerBundle-CropViewController-Info.plist

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>CFBundleDevelopmentRegion</key>
+  <string>en</string>
+  <key>CFBundleIdentifier</key>
+  <string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
+  <key>CFBundleInfoDictionaryVersion</key>
+  <string>6.0</string>
+  <key>CFBundleName</key>
+  <string>${PRODUCT_NAME}</string>
+  <key>CFBundlePackageType</key>
+  <string>BNDL</string>
+  <key>CFBundleShortVersionString</key>
+  <string>2.6.1</string>
+  <key>CFBundleSignature</key>
+  <string>????</string>
+  <key>CFBundleVersion</key>
+  <string>1</string>
+  <key>NSPrincipalClass</key>
+  <string></string>
+</dict>
+</plist>

+ 25 - 0
Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios-acknowledgements.markdown

@@ -19,6 +19,31 @@ Redistribution and use in source and binary forms, with or without modification,
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
+## CropViewController
+
+The MIT License (MIT)
+
+Copyright (c) 2015-2022 Tim Oliver
+
+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.
+
+
 ## DBDebugToolkit
 
 Copyright (c) 2016 Dariusz Bukowski <dariusz.m.bukowski@gmail.com>

+ 31 - 0
Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios-acknowledgements.plist

@@ -36,6 +36,37 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 			<key>Type</key>
 			<string>PSGroupSpecifier</string>
 		</dict>
+		<dict>
+			<key>FooterText</key>
+			<string>The MIT License (MIT)
+
+Copyright (c) 2015-2022 Tim Oliver
+
+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.
+</string>
+			<key>License</key>
+			<string>MIT</string>
+			<key>Title</key>
+			<string>CropViewController</string>
+			<key>Type</key>
+			<string>PSGroupSpecifier</string>
+		</dict>
 		<dict>
 			<key>FooterText</key>
 			<string>Copyright (c) 2016 Dariusz Bukowski &lt;dariusz.m.bukowski@gmail.com&gt;

+ 2 - 0
Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios-frameworks.sh

@@ -177,6 +177,7 @@ code_sign_if_enabled() {
 
 if [[ "$CONFIGURATION" == "Debug" ]]; then
   install_framework "${BUILT_PRODUCTS_DIR}/CocoaLumberjack/CocoaLumberjack.framework"
+  install_framework "${BUILT_PRODUCTS_DIR}/CropViewController/CropViewController.framework"
   install_framework "${BUILT_PRODUCTS_DIR}/DBDebugToolkit/DBDebugToolkit.framework"
   install_framework "${BUILT_PRODUCTS_DIR}/ReachabilitySwift/Reachability.framework"
   install_framework "${BUILT_PRODUCTS_DIR}/SCSiriWaveformView/SCSiriWaveformView.framework"
@@ -190,6 +191,7 @@ if [[ "$CONFIGURATION" == "Debug" ]]; then
 fi
 if [[ "$CONFIGURATION" == "Release" ]]; then
   install_framework "${BUILT_PRODUCTS_DIR}/CocoaLumberjack/CocoaLumberjack.framework"
+  install_framework "${BUILT_PRODUCTS_DIR}/CropViewController/CropViewController.framework"
   install_framework "${BUILT_PRODUCTS_DIR}/DBDebugToolkit/DBDebugToolkit.framework"
   install_framework "${BUILT_PRODUCTS_DIR}/ReachabilitySwift/Reachability.framework"
   install_framework "${BUILT_PRODUCTS_DIR}/SCSiriWaveformView/SCSiriWaveformView.framework"

+ 4 - 4
Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios.debug.xcconfig

@@ -1,12 +1,12 @@
 ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
 CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
-FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder" "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit" "${PODS_CONFIGURATION_BUILD_DIR}/Swifter" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" "${PODS_CONFIGURATION_BUILD_DIR}/libwebp"
+FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack" "${PODS_CONFIGURATION_BUILD_DIR}/CropViewController" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder" "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit" "${PODS_CONFIGURATION_BUILD_DIR}/Swifter" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" "${PODS_CONFIGURATION_BUILD_DIR}/libwebp"
 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 $(inherited) SD_WEBP=1
-HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack/CocoaLumberjack.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView/SCSiriWaveformView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin/SDWebImageSVGKitPlugin.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder/SDWebImageWebPCoder.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit/SVGKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Swifter/Swifter.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/libwebp/libwebp.framework/Headers"
+HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack/CocoaLumberjack.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/CropViewController/CropViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView/SCSiriWaveformView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin/SDWebImageSVGKitPlugin.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder/SDWebImageWebPCoder.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit/SVGKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Swifter/Swifter.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/libwebp/libwebp.framework/Headers"
 LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks'
 LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
-OTHER_CFLAGS = $(inherited) -isystem "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack/CocoaLumberjack.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView/SCSiriWaveformView.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin/SDWebImageSVGKitPlugin.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder/SDWebImageWebPCoder.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit/SVGKit.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/Swifter/Swifter.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/libwebp/libwebp.framework/Headers" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/Swifter" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/libwebp"
-OTHER_LDFLAGS = $(inherited) -l"xml2" -framework "AVFoundation" -framework "CocoaLumberjack" -framework "CoreGraphics" -framework "CoreTelephony" -framework "CoreText" -framework "DBDebugToolkit" -framework "Foundation" -framework "ImageIO" -framework "QuartzCore" -framework "Reachability" -framework "SCSiriWaveformView" -framework "SDWebImage" -framework "SDWebImageSVGKitPlugin" -framework "SDWebImageWebPCoder" -framework "SVGKit" -framework "Swifter" -framework "SwiftyBeaver" -framework "SystemConfiguration" -framework "UIKit" -framework "libwebp"
+OTHER_CFLAGS = $(inherited) -isystem "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack/CocoaLumberjack.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/CropViewController/CropViewController.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView/SCSiriWaveformView.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin/SDWebImageSVGKitPlugin.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder/SDWebImageWebPCoder.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit/SVGKit.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/Swifter/Swifter.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/libwebp/libwebp.framework/Headers" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/CropViewController" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/Swifter" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/libwebp"
+OTHER_LDFLAGS = $(inherited) -l"xml2" -framework "AVFoundation" -framework "CocoaLumberjack" -framework "CoreGraphics" -framework "CoreTelephony" -framework "CoreText" -framework "CropViewController" -framework "DBDebugToolkit" -framework "Foundation" -framework "ImageIO" -framework "QuartzCore" -framework "Reachability" -framework "SCSiriWaveformView" -framework "SDWebImage" -framework "SDWebImageSVGKitPlugin" -framework "SDWebImageWebPCoder" -framework "SVGKit" -framework "Swifter" -framework "SwiftyBeaver" -framework "SystemConfiguration" -framework "UIKit" -framework "libwebp"
 OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
 PODS_BUILD_DIR = ${BUILD_DIR}
 PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)

+ 4 - 4
Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios.release.xcconfig

@@ -1,12 +1,12 @@
 ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
 CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
-FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder" "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit" "${PODS_CONFIGURATION_BUILD_DIR}/Swifter" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" "${PODS_CONFIGURATION_BUILD_DIR}/libwebp"
+FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack" "${PODS_CONFIGURATION_BUILD_DIR}/CropViewController" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder" "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit" "${PODS_CONFIGURATION_BUILD_DIR}/Swifter" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" "${PODS_CONFIGURATION_BUILD_DIR}/libwebp"
 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 $(inherited) SD_WEBP=1
-HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack/CocoaLumberjack.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView/SCSiriWaveformView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin/SDWebImageSVGKitPlugin.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder/SDWebImageWebPCoder.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit/SVGKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Swifter/Swifter.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/libwebp/libwebp.framework/Headers"
+HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack/CocoaLumberjack.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/CropViewController/CropViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView/SCSiriWaveformView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin/SDWebImageSVGKitPlugin.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder/SDWebImageWebPCoder.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit/SVGKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Swifter/Swifter.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/libwebp/libwebp.framework/Headers"
 LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks'
 LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
-OTHER_CFLAGS = $(inherited) -isystem "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack/CocoaLumberjack.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView/SCSiriWaveformView.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin/SDWebImageSVGKitPlugin.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder/SDWebImageWebPCoder.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit/SVGKit.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/Swifter/Swifter.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/libwebp/libwebp.framework/Headers" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/Swifter" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/libwebp"
-OTHER_LDFLAGS = $(inherited) -l"xml2" -framework "AVFoundation" -framework "CocoaLumberjack" -framework "CoreGraphics" -framework "CoreTelephony" -framework "CoreText" -framework "DBDebugToolkit" -framework "Foundation" -framework "ImageIO" -framework "QuartzCore" -framework "Reachability" -framework "SCSiriWaveformView" -framework "SDWebImage" -framework "SDWebImageSVGKitPlugin" -framework "SDWebImageWebPCoder" -framework "SVGKit" -framework "Swifter" -framework "SwiftyBeaver" -framework "SystemConfiguration" -framework "UIKit" -framework "libwebp"
+OTHER_CFLAGS = $(inherited) -isystem "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack/CocoaLumberjack.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/CropViewController/CropViewController.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView/SCSiriWaveformView.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin/SDWebImageSVGKitPlugin.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder/SDWebImageWebPCoder.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit/SVGKit.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/Swifter/Swifter.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/libwebp/libwebp.framework/Headers" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/CropViewController" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/Swifter" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/libwebp"
+OTHER_LDFLAGS = $(inherited) -l"xml2" -framework "AVFoundation" -framework "CocoaLumberjack" -framework "CoreGraphics" -framework "CoreTelephony" -framework "CoreText" -framework "CropViewController" -framework "DBDebugToolkit" -framework "Foundation" -framework "ImageIO" -framework "QuartzCore" -framework "Reachability" -framework "SCSiriWaveformView" -framework "SDWebImage" -framework "SDWebImageSVGKitPlugin" -framework "SDWebImageWebPCoder" -framework "SVGKit" -framework "Swifter" -framework "SwiftyBeaver" -framework "SystemConfiguration" -framework "UIKit" -framework "libwebp"
 OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
 PODS_BUILD_DIR = ${BUILD_DIR}
 PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)

+ 3 - 3
Pods/Target Support Files/Pods-deltachat-iosTests/Pods-deltachat-iosTests.debug.xcconfig

@@ -1,8 +1,8 @@
 CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
-FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder" "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit" "${PODS_CONFIGURATION_BUILD_DIR}/Swifter" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" "${PODS_CONFIGURATION_BUILD_DIR}/libwebp"
+FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack" "${PODS_CONFIGURATION_BUILD_DIR}/CropViewController" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder" "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit" "${PODS_CONFIGURATION_BUILD_DIR}/Swifter" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" "${PODS_CONFIGURATION_BUILD_DIR}/libwebp"
 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 $(inherited) SD_WEBP=1
-HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack/CocoaLumberjack.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView/SCSiriWaveformView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin/SDWebImageSVGKitPlugin.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder/SDWebImageWebPCoder.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit/SVGKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Swifter/Swifter.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/libwebp/libwebp.framework/Headers"
-OTHER_LDFLAGS = $(inherited) -l"xml2" -framework "AVFoundation" -framework "CocoaLumberjack" -framework "CoreGraphics" -framework "CoreTelephony" -framework "CoreText" -framework "DBDebugToolkit" -framework "Foundation" -framework "ImageIO" -framework "QuartzCore" -framework "Reachability" -framework "SCSiriWaveformView" -framework "SDWebImage" -framework "SDWebImageSVGKitPlugin" -framework "SDWebImageWebPCoder" -framework "SVGKit" -framework "Swifter" -framework "SwiftyBeaver" -framework "SystemConfiguration" -framework "UIKit" -framework "libwebp"
+HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack/CocoaLumberjack.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/CropViewController/CropViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView/SCSiriWaveformView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin/SDWebImageSVGKitPlugin.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder/SDWebImageWebPCoder.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit/SVGKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Swifter/Swifter.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/libwebp/libwebp.framework/Headers"
+OTHER_LDFLAGS = $(inherited) -l"xml2" -framework "AVFoundation" -framework "CocoaLumberjack" -framework "CoreGraphics" -framework "CoreTelephony" -framework "CoreText" -framework "CropViewController" -framework "DBDebugToolkit" -framework "Foundation" -framework "ImageIO" -framework "QuartzCore" -framework "Reachability" -framework "SCSiriWaveformView" -framework "SDWebImage" -framework "SDWebImageSVGKitPlugin" -framework "SDWebImageWebPCoder" -framework "SVGKit" -framework "Swifter" -framework "SwiftyBeaver" -framework "SystemConfiguration" -framework "UIKit" -framework "libwebp"
 PODS_BUILD_DIR = ${BUILD_DIR}
 PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
 PODS_PODFILE_DIR_PATH = ${SRCROOT}/.

+ 3 - 3
Pods/Target Support Files/Pods-deltachat-iosTests/Pods-deltachat-iosTests.release.xcconfig

@@ -1,8 +1,8 @@
 CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
-FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder" "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit" "${PODS_CONFIGURATION_BUILD_DIR}/Swifter" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" "${PODS_CONFIGURATION_BUILD_DIR}/libwebp"
+FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack" "${PODS_CONFIGURATION_BUILD_DIR}/CropViewController" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder" "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit" "${PODS_CONFIGURATION_BUILD_DIR}/Swifter" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" "${PODS_CONFIGURATION_BUILD_DIR}/libwebp"
 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 $(inherited) SD_WEBP=1
-HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack/CocoaLumberjack.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView/SCSiriWaveformView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin/SDWebImageSVGKitPlugin.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder/SDWebImageWebPCoder.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit/SVGKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Swifter/Swifter.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/libwebp/libwebp.framework/Headers"
-OTHER_LDFLAGS = $(inherited) -l"xml2" -framework "AVFoundation" -framework "CocoaLumberjack" -framework "CoreGraphics" -framework "CoreTelephony" -framework "CoreText" -framework "DBDebugToolkit" -framework "Foundation" -framework "ImageIO" -framework "QuartzCore" -framework "Reachability" -framework "SCSiriWaveformView" -framework "SDWebImage" -framework "SDWebImageSVGKitPlugin" -framework "SDWebImageWebPCoder" -framework "SVGKit" -framework "Swifter" -framework "SwiftyBeaver" -framework "SystemConfiguration" -framework "UIKit" -framework "libwebp"
+HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack/CocoaLumberjack.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/CropViewController/CropViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SCSiriWaveformView/SCSiriWaveformView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin/SDWebImageSVGKitPlugin.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageWebPCoder/SDWebImageWebPCoder.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SVGKit/SVGKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Swifter/Swifter.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/libwebp/libwebp.framework/Headers"
+OTHER_LDFLAGS = $(inherited) -l"xml2" -framework "AVFoundation" -framework "CocoaLumberjack" -framework "CoreGraphics" -framework "CoreTelephony" -framework "CoreText" -framework "CropViewController" -framework "DBDebugToolkit" -framework "Foundation" -framework "ImageIO" -framework "QuartzCore" -framework "Reachability" -framework "SCSiriWaveformView" -framework "SDWebImage" -framework "SDWebImageSVGKitPlugin" -framework "SDWebImageWebPCoder" -framework "SVGKit" -framework "Swifter" -framework "SwiftyBeaver" -framework "SystemConfiguration" -framework "UIKit" -framework "libwebp"
 PODS_BUILD_DIR = ${BUILD_DIR}
 PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
 PODS_PODFILE_DIR_PATH = ${SRCROOT}/.

+ 2 - 0
deltachat-ios.xcodeproj/project.pbxproj

@@ -1263,6 +1263,7 @@
 			inputPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios-frameworks.sh",
 				"${BUILT_PRODUCTS_DIR}/CocoaLumberjack/CocoaLumberjack.framework",
+				"${BUILT_PRODUCTS_DIR}/CropViewController/CropViewController.framework",
 				"${BUILT_PRODUCTS_DIR}/DBDebugToolkit/DBDebugToolkit.framework",
 				"${BUILT_PRODUCTS_DIR}/ReachabilitySwift/Reachability.framework",
 				"${BUILT_PRODUCTS_DIR}/SCSiriWaveformView/SCSiriWaveformView.framework",
@@ -1277,6 +1278,7 @@
 			name = "[CP] Embed Pods Frameworks";
 			outputPaths = (
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaLumberjack.framework",
+				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CropViewController.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DBDebugToolkit.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SCSiriWaveformView.framework",

+ 57 - 6
deltachat-ios/Controller/Settings/BackgroundOptionsViewController.swift

@@ -1,8 +1,11 @@
 import Foundation
 import UIKit
 import DcCore
+import CropViewController
+import MobileCoreServices
+import Photos
 
-class BackgroundOptionsViewController: UIViewController, MediaPickerDelegate {
+class BackgroundOptionsViewController: UIViewController, BgPickerDelegate {
 
     private let dcContext: DcContext
 
@@ -80,10 +83,10 @@ class BackgroundOptionsViewController: UIViewController, MediaPickerDelegate {
         return view
     }()
 
-    private lazy var mediaPicker: MediaPicker = {
-        let mediaPicker = MediaPicker(navigationController: navigationController)
-        mediaPicker.delegate = self
-        return mediaPicker
+    private lazy var bgPicker: BgPicker = {
+        let picker = BgPicker(navigationController: navigationController)
+        picker.delegate = self
+        return picker
     }()
 
     init(dcContext: DcContext) {
@@ -135,7 +138,7 @@ class BackgroundOptionsViewController: UIViewController, MediaPickerDelegate {
     }
 
     @objc private func onSelectBackgroundImage() {
-        mediaPicker.showPhotoGallery()
+        bgPicker.showBackgroundPicker()
     }
 
     @objc private func onDefaultSelected() {
@@ -154,6 +157,21 @@ class BackgroundOptionsViewController: UIViewController, MediaPickerDelegate {
 
     // MARK: MediaPickerDelegate
     func onImageSelected(image: UIImage) {
+        let screenSize: CGRect = UIScreen.main.bounds
+        let cropViewController = CropViewController(image: image)
+        cropViewController.delegate = self
+        cropViewController.aspectRatioLockEnabled = true
+        cropViewController.resetAspectRatioEnabled = false
+        cropViewController.aspectRatioPreset = .presetCustom
+        cropViewController.customAspectRatio = screenSize.size
+        present(cropViewController, animated: true, completion: nil)
+    }
+
+}
+
+extension BackgroundOptionsViewController: CropViewControllerDelegate {
+    func cropViewController(_ cropViewController: CropViewController, didCropToImage image: UIImage, withRect cropRect: CGRect, angle: Int) {
+        // 'image' is the newly cropped version of the original image
         if let path = ImageFormat.saveImage(image: image, name: Constants.backgroundImageName) {
             UserDefaults.standard.set(URL(fileURLWithPath: path).lastPathComponent, forKey: Constants.Keys.backgroundImageName)
             UserDefaults.standard.synchronize()
@@ -161,5 +179,38 @@ class BackgroundOptionsViewController: UIViewController, MediaPickerDelegate {
         } else {
             logger.error("failed to save background image")
         }
+        cropViewController.dismiss(animated: true)
+    }
+}
+
+protocol BgPickerDelegate: class {
+    func onImageSelected(image: UIImage)
+}
+
+class BgPicker: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
+    private weak var navigationController: UINavigationController?
+    weak var delegate: BgPickerDelegate?
+    
+    init(navigationController: UINavigationController?) {
+        // it does not make sense to give nil here, but it makes construction easier
+        self.navigationController = navigationController
+    }
+    
+    func showBackgroundPicker() {
+        if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
+            let imagePickerController = UIImagePickerController()
+            imagePickerController.delegate = self
+            imagePickerController.sourceType = .photoLibrary
+            imagePickerController.mediaTypes = [kUTTypeImage as String]
+            imagePickerController.allowsEditing = false
+            navigationController?.present(imagePickerController, animated: true, completion: nil)
+        }
+    }
+    
+    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
+        if let image = info[.originalImage] as? UIImage {
+            picker.dismiss(animated: true, completion: nil)
+            self.delegate?.onImageSelected(image: image)
+        }
     }
 }

Неке датотеке нису приказане због велике количине промена