xiongzhu před 7 roky
rodič
revize
e0be645990
95 změnil soubory, kde provedl 7079 přidání a 737 odebrání
  1. 1 0
      Podfile
  2. 5 1
      Podfile.lock
  3. 1 0
      Pods/Headers/Private/TOCropViewController/TOActivityCroppedImageProvider.h
  4. 1 0
      Pods/Headers/Private/TOCropViewController/TOCropOverlayView.h
  5. 1 0
      Pods/Headers/Private/TOCropViewController/TOCropScrollView.h
  6. 1 0
      Pods/Headers/Private/TOCropViewController/TOCropToolbar.h
  7. 1 0
      Pods/Headers/Private/TOCropViewController/TOCropView.h
  8. 1 0
      Pods/Headers/Private/TOCropViewController/TOCropViewConstants.h
  9. 1 0
      Pods/Headers/Private/TOCropViewController/TOCropViewController.h
  10. 1 0
      Pods/Headers/Private/TOCropViewController/TOCropViewControllerTransitioning.h
  11. 1 0
      Pods/Headers/Private/TOCropViewController/TOCroppedImageAttributes.h
  12. 1 0
      Pods/Headers/Private/TOCropViewController/UIImage+CropRotate.h
  13. 1 0
      Pods/Headers/Public/TOCropViewController/TOActivityCroppedImageProvider.h
  14. 1 0
      Pods/Headers/Public/TOCropViewController/TOCropOverlayView.h
  15. 1 0
      Pods/Headers/Public/TOCropViewController/TOCropScrollView.h
  16. 1 0
      Pods/Headers/Public/TOCropViewController/TOCropToolbar.h
  17. 1 0
      Pods/Headers/Public/TOCropViewController/TOCropView.h
  18. 1 0
      Pods/Headers/Public/TOCropViewController/TOCropViewConstants.h
  19. 1 0
      Pods/Headers/Public/TOCropViewController/TOCropViewController.h
  20. 1 0
      Pods/Headers/Public/TOCropViewController/TOCropViewControllerTransitioning.h
  21. 1 0
      Pods/Headers/Public/TOCropViewController/TOCroppedImageAttributes.h
  22. 1 0
      Pods/Headers/Public/TOCropViewController/UIImage+CropRotate.h
  23. 5 1
      Pods/Manifest.lock
  24. 657 558
      Pods/Pods.xcodeproj/project.pbxproj
  25. 21 0
      Pods/TOCropViewController/LICENSE
  26. 31 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Categories/UIImage+CropRotate.h
  27. 72 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Categories/UIImage+CropRotate.m
  28. 70 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Constants/TOCropViewConstants.h
  29. 38 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Models/TOActivityCroppedImageProvider.h
  30. 76 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Models/TOActivityCroppedImageProvider.m
  31. 49 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Models/TOCropViewControllerTransitioning.h
  32. 120 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Models/TOCropViewControllerTransitioning.m
  33. 38 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Models/TOCroppedImageAttributes.h
  34. 46 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Models/TOCroppedImageAttributes.m
  35. 5 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Resources/Base.lproj/TOCropViewControllerLocalizable.strings
  36. 5 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Resources/ar.lproj/TOCropViewControllerLocalizable.strings
  37. 5 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Resources/da-DK.lproj/TOCropViewControllerLocalizable.strings
  38. 5 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Resources/de.lproj/TOCropViewControllerLocalizable.strings
  39. 5 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Resources/en.lproj/TOCropViewControllerLocalizable.strings
  40. 5 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Resources/es.lproj/TOCropViewControllerLocalizable.strings
  41. 5 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Resources/fr.lproj/TOCropViewControllerLocalizable.strings
  42. 5 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Resources/id.lproj/TOCropViewControllerLocalizable.strings
  43. 5 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Resources/it.lproj/TOCropViewControllerLocalizable.strings
  44. 5 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Resources/ja.lproj/TOCropViewControllerLocalizable.strings
  45. 5 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Resources/ko.lproj/TOCropViewControllerLocalizable.strings
  46. 5 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Resources/ms.lproj/TOCropViewControllerLocalizable.strings
  47. 5 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Resources/nl.lproj/TOCropViewControllerLocalizable.strings
  48. 5 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Resources/pl.lproj/TOCropViewControllerLocalizable.strings
  49. 5 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Resources/pt.lproj/TOCropViewControllerLocalizable.strings
  50. 5 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Resources/ru.lproj/TOCropViewControllerLocalizable.strings
  51. 5 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Resources/tr.lproj/TOCropViewControllerLocalizable.strings
  52. 5 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Resources/vi.lproj/TOCropViewControllerLocalizable.strings
  53. 5 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Resources/zh-Hans.lproj/TOCropViewControllerLocalizable.strings
  54. 5 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Resources/zh-Hant.lproj/TOCropViewControllerLocalizable.strings
  55. 394 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/TOCropViewController.h
  56. 1285 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/TOCropViewController.m
  57. 43 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Views/TOCropOverlayView.h
  58. 231 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Views/TOCropOverlayView.m
  59. 39 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Views/TOCropScrollView.h
  60. 51 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Views/TOCropScrollView.m
  61. 82 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Views/TOCropToolbar.h
  62. 601 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Views/TOCropToolbar.m
  63. 261 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Views/TOCropView.h
  64. 1710 0
      Pods/TOCropViewController/Objective-C/TOCropViewController/Views/TOCropView.m
  65. 179 0
      Pods/TOCropViewController/README.md
  66. 25 0
      Pods/Target Support Files/Pods-model/Pods-model-acknowledgements.markdown
  67. 31 0
      Pods/Target Support Files/Pods-model/Pods-model-acknowledgements.plist
  68. 2 0
      Pods/Target Support Files/Pods-model/Pods-model-resources.sh
  69. 4 4
      Pods/Target Support Files/Pods-model/Pods-model.debug.xcconfig
  70. 4 4
      Pods/Target Support Files/Pods-model/Pods-model.release.xcconfig
  71. 24 0
      Pods/Target Support Files/TOCropViewController/ResourceBundle-TOCropViewControllerBundle-Info.plist
  72. 5 0
      Pods/Target Support Files/TOCropViewController/TOCropViewController-dummy.m
  73. 12 0
      Pods/Target Support Files/TOCropViewController/TOCropViewController-prefix.pch
  74. 9 0
      Pods/Target Support Files/TOCropViewController/TOCropViewController.xcconfig
  75. 24 4
      model.xcodeproj/project.pbxproj
  76. 23 0
      model/Assets.xcassets/icon_bofang.imageset/Contents.json
  77. binární
      model/Assets.xcassets/icon_bofang.imageset/icon_bofang.png
  78. binární
      model/Assets.xcassets/icon_bofang.imageset/icon_bofang@2x.png
  79. binární
      model/Assets.xcassets/icon_bofang.imageset/icon_bofang@3x.png
  80. 2 2
      model/Classes/Application/AppDelegate.m
  81. 1 9
      model/Classes/Application/ModelHeader.h
  82. 6 2
      model/Classes/Controllers/ActivityVC/ActivityDetailViewController.m
  83. 12 3
      model/Classes/Controllers/ActivityVC/PlatformActivityController.m
  84. 1 2
      model/Classes/Controllers/ActivityVC/SignUpViewController.h
  85. 56 64
      model/Classes/Controllers/ActivityVC/SignUpViewController.m
  86. 0 81
      model/Classes/Controllers/ActivityVC/SignUpViewController.xib
  87. 18 0
      model/Classes/Controllers/ActivityVC/UploadVideoViewController.h
  88. 208 0
      model/Classes/Controllers/ActivityVC/UploadVideoViewController.m
  89. 206 0
      model/Classes/Controllers/ActivityVC/UploadVideoViewController.xib
  90. 1 1
      model/Classes/Controllers/SettingsVC/SettingsViewController.m
  91. 13 0
      model/Classes/Public/WeakScriptMessageDelegate.h
  92. 24 0
      model/Classes/Public/WeakScriptMessageDelegate.m
  93. 19 0
      model/Classes/Public/WebViewController.h
  94. 123 0
      model/Classes/Public/WebViewController.m
  95. 1 1
      model/Info.plist

+ 1 - 0
Podfile

@@ -18,5 +18,6 @@ pod 'UMCSecurityPlugins'
 pod 'WechatOpenSDK'
 pod 'WZLBadge'
 pod 'UICollectionViewLeftAlignedLayout'
+pod 'TOCropViewController'
 end
 

+ 5 - 1
Podfile.lock

@@ -25,6 +25,7 @@ PODS:
     - SDWebImage/Core (= 4.3.3)
   - SDWebImage/Core (4.3.3)
   - Toast (4.0.0)
+  - TOCropViewController (2.3.8)
   - TTGTagCollectionView (1.9.0)
   - UICollectionViewLeftAlignedLayout (1.0.2)
   - UMCCommon (1.5.2)
@@ -45,6 +46,7 @@ DEPENDENCIES:
   - NIMSDK
   - SDWebImage
   - Toast
+  - TOCropViewController
   - TTGTagCollectionView
   - UICollectionViewLeftAlignedLayout
   - UMCCommon
@@ -65,6 +67,7 @@ SPEC REPOS:
     - NIMSDK
     - SDWebImage
     - Toast
+    - TOCropViewController
     - TTGTagCollectionView
     - UICollectionViewLeftAlignedLayout
     - UMCCommon
@@ -84,6 +87,7 @@ SPEC CHECKSUMS:
   NIMSDK: 5dd9182d3c3a2c50504db47465b7da4d1f556fd5
   SDWebImage: de4d90b5bff3571eae7bd16202b1f43135409fa5
   Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
+  TOCropViewController: 0a075f02c253e88095143bbac7b013fc6fba5090
   TTGTagCollectionView: 958f20b946dee92a8ba20dc37d10a06737641ae9
   UICollectionViewLeftAlignedLayout: 830bf6fa5bab9f9b464f62e3384f9d2e00b3c0f6
   UMCCommon: 4392868ac47ff1b101663106bb114ba616fabbad
@@ -92,6 +96,6 @@ SPEC CHECKSUMS:
   WechatOpenSDK: 3117412f8aafde4758cac1e8d20b93c67cafcfef
   WZLBadge: 9ec779dcfd94c825518b395e8315fccaabff1bfa
 
-PODFILE CHECKSUM: 6ebc3ae8070ce9785966a1870b1ac59dcf6107d9
+PODFILE CHECKSUM: d6eee6df8c44b563cf2cee8995dc49267ed1c58a
 
 COCOAPODS: 1.5.3

+ 1 - 0
Pods/Headers/Private/TOCropViewController/TOActivityCroppedImageProvider.h

@@ -0,0 +1 @@
+../../../TOCropViewController/Objective-C/TOCropViewController/Models/TOActivityCroppedImageProvider.h

+ 1 - 0
Pods/Headers/Private/TOCropViewController/TOCropOverlayView.h

@@ -0,0 +1 @@
+../../../TOCropViewController/Objective-C/TOCropViewController/Views/TOCropOverlayView.h

+ 1 - 0
Pods/Headers/Private/TOCropViewController/TOCropScrollView.h

@@ -0,0 +1 @@
+../../../TOCropViewController/Objective-C/TOCropViewController/Views/TOCropScrollView.h

+ 1 - 0
Pods/Headers/Private/TOCropViewController/TOCropToolbar.h

@@ -0,0 +1 @@
+../../../TOCropViewController/Objective-C/TOCropViewController/Views/TOCropToolbar.h

+ 1 - 0
Pods/Headers/Private/TOCropViewController/TOCropView.h

@@ -0,0 +1 @@
+../../../TOCropViewController/Objective-C/TOCropViewController/Views/TOCropView.h

+ 1 - 0
Pods/Headers/Private/TOCropViewController/TOCropViewConstants.h

@@ -0,0 +1 @@
+../../../TOCropViewController/Objective-C/TOCropViewController/Constants/TOCropViewConstants.h

+ 1 - 0
Pods/Headers/Private/TOCropViewController/TOCropViewController.h

@@ -0,0 +1 @@
+../../../TOCropViewController/Objective-C/TOCropViewController/TOCropViewController.h

+ 1 - 0
Pods/Headers/Private/TOCropViewController/TOCropViewControllerTransitioning.h

@@ -0,0 +1 @@
+../../../TOCropViewController/Objective-C/TOCropViewController/Models/TOCropViewControllerTransitioning.h

+ 1 - 0
Pods/Headers/Private/TOCropViewController/TOCroppedImageAttributes.h

@@ -0,0 +1 @@
+../../../TOCropViewController/Objective-C/TOCropViewController/Models/TOCroppedImageAttributes.h

+ 1 - 0
Pods/Headers/Private/TOCropViewController/UIImage+CropRotate.h

@@ -0,0 +1 @@
+../../../TOCropViewController/Objective-C/TOCropViewController/Categories/UIImage+CropRotate.h

+ 1 - 0
Pods/Headers/Public/TOCropViewController/TOActivityCroppedImageProvider.h

@@ -0,0 +1 @@
+../../../TOCropViewController/Objective-C/TOCropViewController/Models/TOActivityCroppedImageProvider.h

+ 1 - 0
Pods/Headers/Public/TOCropViewController/TOCropOverlayView.h

@@ -0,0 +1 @@
+../../../TOCropViewController/Objective-C/TOCropViewController/Views/TOCropOverlayView.h

+ 1 - 0
Pods/Headers/Public/TOCropViewController/TOCropScrollView.h

@@ -0,0 +1 @@
+../../../TOCropViewController/Objective-C/TOCropViewController/Views/TOCropScrollView.h

+ 1 - 0
Pods/Headers/Public/TOCropViewController/TOCropToolbar.h

@@ -0,0 +1 @@
+../../../TOCropViewController/Objective-C/TOCropViewController/Views/TOCropToolbar.h

+ 1 - 0
Pods/Headers/Public/TOCropViewController/TOCropView.h

@@ -0,0 +1 @@
+../../../TOCropViewController/Objective-C/TOCropViewController/Views/TOCropView.h

+ 1 - 0
Pods/Headers/Public/TOCropViewController/TOCropViewConstants.h

@@ -0,0 +1 @@
+../../../TOCropViewController/Objective-C/TOCropViewController/Constants/TOCropViewConstants.h

+ 1 - 0
Pods/Headers/Public/TOCropViewController/TOCropViewController.h

@@ -0,0 +1 @@
+../../../TOCropViewController/Objective-C/TOCropViewController/TOCropViewController.h

+ 1 - 0
Pods/Headers/Public/TOCropViewController/TOCropViewControllerTransitioning.h

@@ -0,0 +1 @@
+../../../TOCropViewController/Objective-C/TOCropViewController/Models/TOCropViewControllerTransitioning.h

+ 1 - 0
Pods/Headers/Public/TOCropViewController/TOCroppedImageAttributes.h

@@ -0,0 +1 @@
+../../../TOCropViewController/Objective-C/TOCropViewController/Models/TOCroppedImageAttributes.h

+ 1 - 0
Pods/Headers/Public/TOCropViewController/UIImage+CropRotate.h

@@ -0,0 +1 @@
+../../../TOCropViewController/Objective-C/TOCropViewController/Categories/UIImage+CropRotate.h

+ 5 - 1
Pods/Manifest.lock

@@ -25,6 +25,7 @@ PODS:
     - SDWebImage/Core (= 4.3.3)
   - SDWebImage/Core (4.3.3)
   - Toast (4.0.0)
+  - TOCropViewController (2.3.8)
   - TTGTagCollectionView (1.9.0)
   - UICollectionViewLeftAlignedLayout (1.0.2)
   - UMCCommon (1.5.2)
@@ -45,6 +46,7 @@ DEPENDENCIES:
   - NIMSDK
   - SDWebImage
   - Toast
+  - TOCropViewController
   - TTGTagCollectionView
   - UICollectionViewLeftAlignedLayout
   - UMCCommon
@@ -65,6 +67,7 @@ SPEC REPOS:
     - NIMSDK
     - SDWebImage
     - Toast
+    - TOCropViewController
     - TTGTagCollectionView
     - UICollectionViewLeftAlignedLayout
     - UMCCommon
@@ -84,6 +87,7 @@ SPEC CHECKSUMS:
   NIMSDK: 5dd9182d3c3a2c50504db47465b7da4d1f556fd5
   SDWebImage: de4d90b5bff3571eae7bd16202b1f43135409fa5
   Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
+  TOCropViewController: 0a075f02c253e88095143bbac7b013fc6fba5090
   TTGTagCollectionView: 958f20b946dee92a8ba20dc37d10a06737641ae9
   UICollectionViewLeftAlignedLayout: 830bf6fa5bab9f9b464f62e3384f9d2e00b3c0f6
   UMCCommon: 4392868ac47ff1b101663106bb114ba616fabbad
@@ -92,6 +96,6 @@ SPEC CHECKSUMS:
   WechatOpenSDK: 3117412f8aafde4758cac1e8d20b93c67cafcfef
   WZLBadge: 9ec779dcfd94c825518b395e8315fccaabff1bfa
 
-PODFILE CHECKSUM: 6ebc3ae8070ce9785966a1870b1ac59dcf6107d9
+PODFILE CHECKSUM: d6eee6df8c44b563cf2cee8995dc49267ed1c58a
 
 COCOAPODS: 1.5.3

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 657 - 558
Pods/Pods.xcodeproj/project.pbxproj


+ 21 - 0
Pods/TOCropViewController/LICENSE

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

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

@@ -0,0 +1,31 @@
+//
+//  UIImage+CropRotate.h
+//
+//  Copyright 2015-2018 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)
+- (nonnull UIImage *)croppedImageWithFrame:(CGRect)frame angle:(NSInteger)angle circularClip:(BOOL)circular;
+@end
+
+NS_ASSUME_NONNULL_END

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

@@ -0,0 +1,72 @@
+//
+//  UIImage+CropRotate.m
+//
+//  Copyright 2015-2018 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 (circular) {
+            CGContextAddEllipseInRect(context, (CGRect){CGPointZero, frame.size});
+            CGContextClip(context);
+        }
+        
+        //To conserve memory in not needing to completely re-render the image re-rotated,
+        //map the image to a view and then use Core Animation to manipulate its rotation
+        if (angle != 0) {
+            UIImageView *imageView = [[UIImageView alloc] initWithImage:self];
+            imageView.layer.minificationFilter = kCAFilterNearest;
+            imageView.layer.magnificationFilter = kCAFilterNearest;
+            imageView.transform = CGAffineTransformRotate(CGAffineTransformIdentity, angle * (M_PI/180.0f));
+            CGRect rotatedRect = CGRectApplyAffineTransform(imageView.bounds, imageView.transform);
+            UIView *containerView = [[UIView alloc] initWithFrame:(CGRect){CGPointZero, rotatedRect.size}];
+            [containerView addSubview:imageView];
+            imageView.center = containerView.center;
+            CGContextTranslateCTM(context, -frame.origin.x, -frame.origin.y);
+            [containerView.layer renderInContext:context];
+        }
+        else {
+            CGContextTranslateCTM(context, -frame.origin.x, -frame.origin.y);
+            [self drawAtPoint:CGPointZero];
+        }
+        
+        croppedImage = UIGraphicsGetImageFromCurrentImageContext();
+    }
+    UIGraphicsEndImageContext();
+    
+    return [UIImage imageWithCGImage:croppedImage.CGImage scale: self.scale orientation:UIImageOrientationUp];
+}
+
+@end

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

@@ -0,0 +1,70 @@
+//
+//  TOCropViewConstants.h
+//
+//  Copyright 2015-2018 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) {
+    NSBundle *resourceBundle = nil;
+    
+    NSBundle *classBundle = [NSBundle bundleForClass:object.class];
+    NSURL *resourceBundleURL = [classBundle URLForResource:@"TOCropViewControllerBundle" withExtension:@"bundle"];
+    if (resourceBundleURL) {
+        resourceBundle = [[NSBundle alloc] initWithURL:resourceBundleURL];
+    }
+    else {
+        resourceBundle = classBundle;
+    }
+    
+    return resourceBundle;
+}

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

@@ -0,0 +1,38 @@
+//
+//  TOActivityCroppedImageProvider.h
+//
+//  Copyright 2015-2018 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/TOCropViewController/Objective-C/TOCropViewController/Models/TOActivityCroppedImageProvider.m

@@ -0,0 +1,76 @@
+//
+//  TOActivityCroppedImageProvider.m
+//
+//  Copyright 2015-2018 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/TOCropViewController/Objective-C/TOCropViewController/Models/TOCropViewControllerTransitioning.h

@@ -0,0 +1,49 @@
+//
+//  TOCropViewControllerTransitioning.h
+//
+//  Copyright 2015-2018 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/TOCropViewController/Objective-C/TOCropViewController/Models/TOCropViewControllerTransitioning.m

@@ -0,0 +1,120 @@
+//
+//  TOCropViewControllerTransitioning.m
+//
+//  Copyright 2015-2018 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/TOCropViewController/Objective-C/TOCropViewController/Models/TOCroppedImageAttributes.h

@@ -0,0 +1,38 @@
+//
+//  TOCroppedImageAttributes.h
+//
+//  Copyright 2015-2018 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/TOCropViewController/Objective-C/TOCropViewController/Models/TOCroppedImageAttributes.m

@@ -0,0 +1,46 @@
+//
+//  TOCroppedImageAttributes.m
+//
+//  Copyright 2015-2018 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

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

@@ -0,0 +1,5 @@
+"Done" = "Done";
+"Cancel" = "Cancel";
+"Reset" = "Reset";
+"Original" = "Original";
+"Square" = "Square";

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

@@ -0,0 +1,5 @@
+"Done" = "تم";
+"Cancel" = "إلغاء";
+"Reset" = "إعادة تعيين";
+"Original" = "أصلي";
+"Square" = "مربع";

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

@@ -0,0 +1,5 @@
+"Done" = "OK";
+"Cancel" = "Annuller";
+"Reset" = "Nulstil";
+"Original" = "Original";
+"Square" = "Firkantet";

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

@@ -0,0 +1,5 @@
+"Done" = "Fertig";
+"Cancel" = "Abbrechen";
+"Reset" = "Zurücksetzen";
+"Original" = "Original";
+"Square" = "Quadrat";

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

@@ -0,0 +1,5 @@
+"Done" = "Done";
+"Cancel" = "Cancel";
+"Reset" = "Reset";
+"Original" = "Original";
+"Square" = "Square";

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

@@ -0,0 +1,5 @@
+"Done" = "Aceptar";
+"Cancel" = "Cancelar";
+"Reset" = "Cambiar";
+"Original" = "Original";
+"Square" = "Cuadrada";

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

@@ -0,0 +1,5 @@
+"Done" = "OK";
+"Cancel" = "Annuler";
+"Reset" = "Réinitialiser";
+"Original" = "D’origine";
+"Square" = "Carré";

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

@@ -0,0 +1,5 @@
+"Done" = "Selesai";
+"Cancel" = "Batalkan";
+"Reset" = "Atur Ulang";
+"Original" = "Asli";
+"Square" = "Persegi";

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

@@ -0,0 +1,5 @@
+"Done" = "Fatto";
+"Cancel" = "Annulla";
+"Reset" = "Ripristina";
+"Original" = "Originale";
+"Square" = "Quadrato";

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

@@ -0,0 +1,5 @@
+"Done" = "完了";
+"Cancel" = "戻る";
+"Reset" = "リセット";
+"Original" = "オリジナル";
+"Square" = "スクエア";

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

@@ -0,0 +1,5 @@
+"Done" = "완료";
+"Cancel" = "취소";
+"Reset" = "재설정";
+"Original" = "원본";
+"Square" = "정방형";

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

@@ -0,0 +1,5 @@
+"Done" = "Selesai";
+"Cancel" = "Batal";
+"Reset" = "Reset";
+"Original" = "Asal";
+"Square" = "Segi empat";

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

@@ -0,0 +1,5 @@
+"Done" = "Gereed";
+"Cancel" = "Annuleer";
+"Reset" = "Herstel";
+"Original" = "Origineel";
+"Square" = "Vierkant";

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

@@ -0,0 +1,5 @@
+"Done" = "Gotowe";
+"Cancel" = "Anuluj";
+"Reset" = "Wyzeruj";
+"Original" = "Orygin.";
+"Square" = "Kwadrat";

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

@@ -0,0 +1,5 @@
+"Done" = "OK";
+"Cancel" = "Cancelar";
+"Reset" = "Redefinir";
+"Original" = "Original";
+"Square" = "Quadrada";

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

@@ -0,0 +1,5 @@
+"Done" = "Готово";
+"Cancel" = "Отменить";
+"Reset" = "Сбросить";
+"Original" = "Оригинал";
+"Square" = "Квадрат";

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

@@ -0,0 +1,5 @@
+"Done" = "Tamam";
+"Cancel" = "Vazgeç";
+"Reset" = "Sıfırla";
+"Original" = "Orjinal";
+"Square" = "Kare";

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

@@ -0,0 +1,5 @@
+"Done" = "Xong";
+"Cancel" = "Huỷ";
+"Reset" = "Đặt lại";
+"Original" = "Gốc";
+"Square" = "Vuông";

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

@@ -0,0 +1,5 @@
+"Done" = "完成";
+"Cancel" = "取消";
+"Reset" = "重设";
+"Original" = "原有";
+"Square" = "正方形";

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

@@ -0,0 +1,5 @@
+"Done" = "完成";
+"Cancel" = "取消";
+"Reset" = "重置";
+"Original" = "原始檔";
+"Square" = "正方形";

+ 394 - 0
Pods/TOCropViewController/Objective-C/TOCropViewController/TOCropViewController.h

@@ -0,0 +1,394 @@
+//
+//  TOCropViewController.h
+//
+//  Copyright 2015-2018 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 NS_SWIFT_NAME(cropViewController(_:didCropImageToRect: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 NS_SWIFT_NAME(cropViewController(_:didCropToImage:rect: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 NS_SWIFT_NAME(cropViewController(_:didCropToCircleImage:rect: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 NS_SWIFT_NAME(cropViewController(_:didFinishCancelled:));
+
+@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;
+
+/**
+ 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, 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 'Aspect Ratio Picker' button on the toolbar.
+ 
+ Default is NO.
+ */
+@property (nonatomic, assign) BOOL aspectRatioPickerButtonHidden;
+
+/** 
+ 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;
+
+/**
+ 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:));
+
+/**
+ 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(setAspectRatioPresent(_: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
+

+ 1285 - 0
Pods/TOCropViewController/Objective-C/TOCropViewController/TOCropViewController.m

@@ -0,0 +1,1285 @@
+//
+//  TOCropViewController.m
+//
+//  Copyright 2015-2018 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;
+
+/* On iOS 7, the popover view controller that appears when tapping 'Done' */
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+@property (nonatomic, strong) UIPopoverController *activityPopoverController;
+#pragma clang diagnostic pop
+
+@end
+
+@implementation TOCropViewController
+
+- (instancetype)initWithCroppingStyle:(TOCropViewCroppingStyle)style image:(UIImage *)image
+{
+    NSParameterAssert(image);
+
+    self = [super init];
+    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;
+        _toolbarPosition = TOCropViewControllerToolbarPositionBottom;
+    }
+	
+    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 (!self.titleLabel.text.length) {
+        if (self.verticalLayout) {
+            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 -
+
+//TODO: Deprecate iOS 7 properly at the right time
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+#pragma clang diagnostic ignored "-Wdeprecated-implementations"
+- (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 clang diagnostic pop
+
+#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
+    NSMutableArray *items = [NSMutableArray array];
+    [items addObject:originalButtonTitle];
+    [items addObject:squareButtonTitle];
+    if (verticalCropBox) {
+        [items addObjectsFromArray:@[@"2:3", @"3:5", @"3:4", @"4:5", @"5:7", @"9:16"]];
+    }
+    else {
+        [items addObjectsFromArray:@[@"3:2", @"5:3", @"4:3", @"5:4", @"7:5", @"16:9"]];
+    }
+    
+    //Present via a UIAlertController if >= iOS 8
+    if (NSClassFromString(@"UIAlertController")) {
+        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
+        NSInteger i = 0;
+        for (NSString *item in items) {
+            UIAlertAction *action = [UIAlertAction actionWithTitle:item style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
+                [self setAspectRatioPreset:(TOCropViewControllerAspectRatioPreset)i animated:YES];
+                self.aspectRatioLockEnabled = YES;
+            }];
+            [alertController addAction:action];
+            
+            i++;
+        }
+        
+        alertController.modalPresentationStyle = UIModalPresentationPopover;
+        UIPopoverPresentationController *presentationController = [alertController popoverPresentationController];
+        presentationController.sourceView = self.toolbar;
+        presentationController.sourceRect = self.toolbar.clampButtonFrame;
+        [self presentViewController:alertController animated:YES completion:nil];
+    }
+    else {
+        
+#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
+    //TODO: Completely overhaul this once iOS 7 support is dropped
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+        UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:nil
+                                                                 delegate:self
+                                                        cancelButtonTitle:cancelButtonTitle
+                                                   destructiveButtonTitle:nil
+                                                        otherButtonTitles:nil];
+        
+        for (NSString *item in items) {
+            [actionSheet addButtonWithTitle:item];
+        }
+        
+        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
+            [actionSheet showFromRect:self.toolbar.clampButtonFrame inView:self.toolbar animated:YES];
+        else
+            [actionSheet showInView:self.view];
+#pragma clang diagnostic pop
+#endif
+    }
+}
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
+- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
+{
+    [self setAspectRatioPreset:(TOCropViewControllerAspectRatioPreset)buttonIndex animated:YES];
+    self.aspectRatioLockEnabled = YES;
+}
+#endif
+
+- (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:nil];
+}
+
+- (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
+{
+    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 popViewControllerAnimated:YES];
+        }
+        else {
+            self.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
+            [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;
+        
+        if (NSClassFromString(@"UIPopoverPresentationController")) {
+            activityController.modalPresentationStyle = UIModalPresentationPopover;
+            activityController.popoverPresentationController.sourceView = self.toolbar;
+            activityController.popoverPresentationController.sourceRect = self.toolbar.doneButtonFrame;
+            [self presentViewController:activityController animated:YES completion:nil];
+        }
+        else {
+            if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
+                [self presentViewController:activityController animated:YES completion:nil];
+            }
+            else {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+                [self.activityPopoverController dismissPopoverAnimated:NO];
+                self.activityPopoverController = [[UIPopoverController alloc] initWithContentViewController:activityController];
+                [self.activityPopoverController presentPopoverFromRect:self.toolbar.doneButtonFrame inView:self.toolbar permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
+#pragma clang diagnostic pop
+            }
+        }
+        __weak typeof(activityController) blockController = activityController;
+#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0
+        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) {
+                [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
+                blockController.completionWithItemsHandler = nil;
+            }
+        };
+#else
+        activityController.completionHandler = ^(NSString *activityType, BOOL completed) {
+            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) {
+                [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
+                blockController.completionHandler = nil;
+            }
+        };
+#endif
+
+        return;
+    }
+    
+    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];
+    }
+}
+
+#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;
+}
+
+- (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;
+}
+
+- (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)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
+{
+    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 behavioir 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
+{
+    if (self.statusBarHidden) {
+        return 0.0f;
+    }
+
+    CGFloat statusBarHeight = 0.0f;
+    if (@available(iOS 11.0, *)) {
+        statusBarHeight = self.view.safeAreaInsets.top;
+    }
+    else {
+        statusBarHeight = self.topLayoutGuide.length;
+    }
+    
+    return statusBarHeight;
+}
+
+- (UIEdgeInsets)statusBarSafeInsets
+{
+    UIEdgeInsets insets = UIEdgeInsetsZero;
+    if (@available(iOS 11.0, *)) {
+        insets = self.view.safeAreaInsets;
+
+        // Since iPhone X insets are always 44, check if this is merely
+        // accounting for a non-X status bar and cancel it
+        if (insets.top <= 20.0f + FLT_EPSILON) {
+            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/TOCropViewController/Objective-C/TOCropViewController/Views/TOCropOverlayView.h

@@ -0,0 +1,43 @@
+//
+//  TOCropOverlayView.h
+//
+//  Copyright 2015-2018 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/TOCropViewController/Objective-C/TOCropViewController/Views/TOCropOverlayView.m

@@ -0,0 +1,231 @@
+//
+//  TOCropOverlayView.m
+//
+//  Copyright 2015-2018 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/TOCropViewController/Objective-C/TOCropViewController/Views/TOCropScrollView.h

@@ -0,0 +1,39 @@
+//
+//  TOCropScrollView
+//
+//  Copyright 2015-2018 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/TOCropViewController/Objective-C/TOCropViewController/Views/TOCropScrollView.m

@@ -0,0 +1,51 @@
+//
+//  TOCropScrollView
+//
+//  Copyright 2015-2018 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

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

@@ -0,0 +1,82 @@
+//
+//  TOCropToolbar.h
+//
+//  Copyright 2015-2018 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 (nonnull, nonatomic, strong, readonly) UIButton *doneTextButton;
+@property (nonnull, nonatomic, strong, readonly) UIButton *doneIconButton;
+@property (nonnull, nonatomic, copy) NSString *doneTextButtonTitle;
+
+
+/* The 'Cancel' buttons to cancel the crop. The text button is displayed
+ in portrait mode and the icon one, in landscape. */
+@property (nonnull, nonatomic, strong, readonly) UIButton *cancelTextButton;
+@property (nonnull, nonatomic, strong, readonly) UIButton *cancelIconButton;
+@property (nonnull, nonatomic, copy) NSString *cancelTextButtonTitle;
+
+/* The cropper control buttons */
+@property (nonnull, nonatomic, strong, readonly)  UIButton *rotateCounterclockwiseButton;
+@property (nonnull, nonatomic, strong, readonly)  UIButton *resetButton;
+@property (nonnull, nonatomic, strong, readonly)  UIButton *clampButton;
+@property (nullable, nonatomic, strong, readonly) UIButton *rotateClockwiseButton;
+
+@property (nonnull, 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;
+
+/* 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

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

@@ -0,0 +1,601 @@
+//
+//  TOCropToolbar.h
+//
+//  Copyright 2015-2018 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];
+    [_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];
+    
+    _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];
+    [self addSubview:_resetButton];
+}
+
+- (void)layoutSubviews
+{
+    [super layoutSubviews];
+    
+    BOOL verticalLayout = (CGRectGetWidth(self.bounds) < CGRectGetHeight(self.bounds));
+    CGSize boundsSize = self.bounds.size;
+    
+    self.cancelIconButton.hidden = (!verticalLayout);
+    self.cancelTextButton.hidden = (verticalLayout);
+    self.doneIconButton.hidden   = (!verticalLayout);
+    self.doneTextButton.hidden   = (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 = 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);
+        }
+        self.cancelTextButton.frame = frame;
+        
+        // Work out the Done button frame
+        frame.size.width = 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;
+        }
+        self.doneTextButton.frame = frame;
+        
+        // Work out the frame between the two buttons where we can layout our action buttons
+        CGFloat x = self.reverseContentLayout ? CGRectGetMaxX(self.doneTextButton.frame) : CGRectGetMaxX(self.cancelTextButton.frame);
+        CGFloat width = 0.0f;
+        
+        if (self.reverseContentLayout == NO) {
+            width = CGRectGetMinX(self.doneTextButton.frame) - CGRectGetMaxX(self.cancelTextButton.frame);
+        }
+        else {
+            width = CGRectGetMinX(self.cancelTextButton.frame) - CGRectGetMaxX(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];
+        }
+        
+        [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];
+        }
+        
+        [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
+{
+    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++) {
+        UIView *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);
+        } 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;
+}
+
+- (CGRect)doneButtonFrame
+{
+    if (self.doneIconButton.hidden == NO)
+        return self.doneIconButton.frame;
+    
+    return self.doneTextButton.frame;
+}
+
+- (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];
+}
+
+#pragma mark - Image Generation -
++ (UIImage *)doneImage
+{
+    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
+{
+    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
+{
+    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
+{
+    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
+{
+    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
+{
+    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];
+}
+
+- (UIButton *)rotateButton
+{
+    return self.rotateCounterclockwiseButton;
+}
+
+- (void)setStatusBarHeightInset:(CGFloat)statusBarHeightInset
+{
+    _statusBarHeightInset = statusBarHeightInset;
+    [self setNeedsLayout];
+}
+
+@end

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

@@ -0,0 +1,261 @@
+//
+//  TOCropView.h
+//
+//  Copyright 2015-2018 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;
+
+/**
+ 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

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

@@ -0,0 +1,1710 @@
+//
+//  TOCropView.m
+//
+//  Copyright 2015-2018 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.
+
+#include <math.h>
+
+#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 kTOCropViewCircularPathRadius = 300.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. */
+@property (nonatomic, strong) CAShapeLayer *circularMaskLayer;      /* Managing the clipping of the foreground container into a circle */
+
+/* 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 = NO;
+    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) {
+        UIBezierPath *circlePath = [UIBezierPath bezierPathWithOvalInRect:(CGRect){0,0,kTOCropViewCircularPathRadius, kTOCropViewCircularPathRadius}];
+        self.circularMaskLayer = [[CAShapeLayer alloc] init];
+        self.circularMaskLayer.path = circlePath.CGPath;
+        self.foregroundContainerView.layer.mask = self.circularMaskLayer;
+        
+        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;
+        [self captureStateForImageRotation];
+    }
+    
+    //If an image crop frame was also specified before creation, apply it now
+    if (!CGRectIsEmpty(self.restoreImageCropFrame)) {
+        self.imageCropFrame = self.restoreImageCropFrame;
+        self.restoreImageCropFrame = CGRectZero;
+    }
+    
+    //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 = bounds.origin.x + floorf((CGRectGetWidth(bounds) - frame.size.width) * 0.5f);
+    frame.origin.y = 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 : cropBoxSize;
+    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;
+    
+    // Work out the size and offset of the upscaled crop box
+    CGRect frame = CGRectZero;
+    frame.size = (CGSize){scaledCropSize.width * scale, scaledCropSize.height * scale};
+    
+    //set the crop box
+    CGRect cropBoxFrame = CGRectZero;
+    cropBoxFrame.size = frame.size;
+    cropBoxFrame.origin.x = CGRectGetMidX(bounds) - (frame.size.width * 0.5f);
+    cropBoxFrame.origin.y = CGRectGetMidY(bounds) - (frame.size.height * 0.5f);
+    self.cropBoxFrame = cropBoxFrame;
+    
+    frame.origin.x = (scaledOffset.x * scale) - self.scrollView.contentInset.left;
+    frame.origin.y = (scaledOffset.y * scale) - self.scrollView.contentInset.top;
+    self.scrollView.contentOffset = frame.origin;
+}
+
+#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.circularMaskLayer) {
+        CGFloat scale = _cropBoxFrame.size.width / kTOCropViewCircularPathRadius;
+        self.circularMaskLayer.transform = CATransform3DScale(CATransform3DIdentity, scale, scale, 1.0f);
+    }
+    
+    //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);
+    }
+    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)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;
+    
+    [self.gridOverlayView setGridHidden:!editing 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
+        
+        CGFloat boundsWidth = CGRectGetWidth(boundsFrame);
+        if (newWidth > boundsWidth) {
+            CGFloat scale = boundsWidth / newWidth;
+            cropBoxFrame.size.height *= scale;
+            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.x = self.contentBounds.origin.y;
+        
+        CGFloat boundsHeight = CGRectGetHeight(boundsFrame);
+        if (newHeight > boundsHeight) {
+            CGFloat scale = boundsHeight / newHeight;
+            cropBoxFrame.size.width *= scale;
+            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 = NO;
+            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

+ 179 - 0
Pods/TOCropViewController/README.md

@@ -0,0 +1,179 @@
+# TOCropViewController
+
+<p align="center">
+<img src="https://github.com/TimOliver/TOCropViewController/raw/master/Images/screenshot.jpg" width="890" style="margin:0 auto" />
+</p>
+
+[![CI Status](http://img.shields.io/travis/TimOliver/TOCropViewController.svg?style=flat)](http://api.travis-ci.org/TimOliver/TOCropViewController.svg)
+[![CocoaPods](https://img.shields.io/cocoapods/dt/TOCropViewController.svg?maxAge=3600)](https://cocoapods.org/pods/TOCropViewController)
+[![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)
+[![Beerpay](https://beerpay.io/TimOliver/TOCropViewController/badge.svg?style=flat)](https://beerpay.io/TimOliver/TOCropViewController)
+[![PayPal](https://img.shields.io/badge/paypal-donate-blue.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=M4RKULAVKV7K8)
+[![Twitch](https://img.shields.io/badge/twitch-timXD-6441a5.svg)](http://twitch.tv/timXD)
+
+
+`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/master/Images/users.png" width="900" style="margin:0 auto" />
+</p>
+
+## 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 18 languages.
+
+## System Requirements
+iOS 8.0 or above
+
+## Installation
+
+#### As a CocoaPods Dependency
+
+##### Objective-C
+
+Add the following to your Podfile:
+``` ruby
+pod 'TOCropViewController'
+```
+
+##### Swift
+
+Add the following to your Podfile:
+``` ruby
+pod 'CropViewController'
+```
+
+
+#### As a Carthage Dependency
+
+Add the following to your Cartfile:
+``` 
+github "TimOliver/TOCropViewController"
+```
+
+#### Manual Installation
+
+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.
+
+## 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.
+
+### Basic Implementation
+
+#### 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
+}
+```
+
+### Making a Circular Cropped Image
+```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
+}
+```
+
+### Sharing Cropped Images Via a Share Sheet
+```objc
+- (void)presentCropViewController
+{
+  UIImage *image = ...; //Load an image
+  
+  TOCropViewController *cropViewController = [[TOCropViewController alloc] initWithImage:image];
+  cropViewController.showActivitySheetOnDone = YES;
+  [self presentViewController:cropViewController animated:YES completion:nil];
+}
+```
+
+### Presenting With a Custom Animation
+Optionally, `TOCropViewController` also supports a custom presentation animation where an already-visible copy of the image will zoom in to fill the screen.
+
+```objc
+- (void)presentViewController
+{
+  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];
+}
+```
+
+## 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. ![analytics](https://ga-beacon.appspot.com/UA-5643664-16/TOCropViewController/README.md?pixel)

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

@@ -373,6 +373,31 @@ THE SOFTWARE.
 
 
 
+## TOCropViewController
+
+The MIT License (MIT)
+
+Copyright (c) 2015-2017 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.
+
+
 ## TTGTagCollectionView
 
 Copyright (c) 2015 zekunyan <zekunyan@163.com>

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

@@ -438,6 +438,37 @@ THE SOFTWARE.
 			<key>Type</key>
 			<string>PSGroupSpecifier</string>
 		</dict>
+		<dict>
+			<key>FooterText</key>
+			<string>The MIT License (MIT)
+
+Copyright (c) 2015-2017 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>TOCropViewController</string>
+			<key>Type</key>
+			<string>PSGroupSpecifier</string>
+		</dict>
 		<dict>
 			<key>FooterText</key>
 			<string>Copyright (c) 2015 zekunyan &lt;zekunyan@163.com&gt;

+ 2 - 0
Pods/Target Support Files/Pods-model/Pods-model-resources.sh

@@ -96,12 +96,14 @@ if [[ "$CONFIGURATION" == "Debug" ]]; then
   install_resource "${PODS_ROOT}/IQKeyboardManager/IQKeyboardManager/Resources/IQKeyboardManager.bundle"
   install_resource "${PODS_ROOT}/MJRefresh/MJRefresh/MJRefresh.bundle"
   install_resource "${PODS_ROOT}/NIMSDK/NIMAVChat/Resources/NMCVideoFilter.bundle"
+  install_resource "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle"
 fi
 if [[ "$CONFIGURATION" == "Release" ]]; then
   install_resource "${PODS_ROOT}/BRPickerView/BRPickerView/AddressPickerView/BRPickerView.bundle"
   install_resource "${PODS_ROOT}/IQKeyboardManager/IQKeyboardManager/Resources/IQKeyboardManager.bundle"
   install_resource "${PODS_ROOT}/MJRefresh/MJRefresh/MJRefresh.bundle"
   install_resource "${PODS_ROOT}/NIMSDK/NIMAVChat/Resources/NMCVideoFilter.bundle"
+  install_resource "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle"
 fi
 
 mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"

+ 4 - 4
Pods/Target Support Files/Pods-model/Pods-model.debug.xcconfig

@@ -1,9 +1,9 @@
 FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/NIMSDK/NIMSDK" "${PODS_ROOT}/NIMSDK/NIMAVChat" "${PODS_ROOT}/UMCCommon" "${PODS_ROOT}/UMCPush" "${PODS_ROOT}/UMCSecurityPlugins/thirdparties"
 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
-HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/AFNetworking" "${PODS_ROOT}/Headers/Public/BRPickerView" "${PODS_ROOT}/Headers/Public/IQKeyboardManager" "${PODS_ROOT}/Headers/Public/M80AttributedLabel" "${PODS_ROOT}/Headers/Public/MBProgressHUD" "${PODS_ROOT}/Headers/Public/MJExtension" "${PODS_ROOT}/Headers/Public/MJRefresh" "${PODS_ROOT}/Headers/Public/NIMSDK" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/TTGTagCollectionView" "${PODS_ROOT}/Headers/Public/Toast" "${PODS_ROOT}/Headers/Public/UICollectionViewLeftAlignedLayout" "${PODS_ROOT}/Headers/Public/UMCCommon" "${PODS_ROOT}/Headers/Public/UMCPush" "${PODS_ROOT}/Headers/Public/UMCSecurityPlugins" "${PODS_ROOT}/Headers/Public/WZLBadge" "${PODS_ROOT}/Headers/Public/WechatOpenSDK"
-LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/BRPickerView" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManager" "${PODS_CONFIGURATION_BUILD_DIR}/M80AttributedLabel" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJExtension" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/TTGTagCollectionView" "${PODS_CONFIGURATION_BUILD_DIR}/Toast" "${PODS_CONFIGURATION_BUILD_DIR}/UICollectionViewLeftAlignedLayout" "${PODS_CONFIGURATION_BUILD_DIR}/WZLBadge" "${PODS_ROOT}/NIMSDK/NIMAVChat/Libs" "${PODS_ROOT}/NIMSDK/NIMSDK/Libs" "${PODS_ROOT}/WechatOpenSDK/WeChatSDK1.8.3"
-OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/AFNetworking" -isystem "${PODS_ROOT}/Headers/Public/BRPickerView" -isystem "${PODS_ROOT}/Headers/Public/IQKeyboardManager" -isystem "${PODS_ROOT}/Headers/Public/M80AttributedLabel" -isystem "${PODS_ROOT}/Headers/Public/MBProgressHUD" -isystem "${PODS_ROOT}/Headers/Public/MJExtension" -isystem "${PODS_ROOT}/Headers/Public/MJRefresh" -isystem "${PODS_ROOT}/Headers/Public/NIMSDK" -isystem "${PODS_ROOT}/Headers/Public/SDWebImage" -isystem "${PODS_ROOT}/Headers/Public/TTGTagCollectionView" -isystem "${PODS_ROOT}/Headers/Public/Toast" -isystem "${PODS_ROOT}/Headers/Public/UICollectionViewLeftAlignedLayout" -isystem "${PODS_ROOT}/Headers/Public/UMCCommon" -isystem "${PODS_ROOT}/Headers/Public/UMCPush" -isystem "${PODS_ROOT}/Headers/Public/UMCSecurityPlugins" -isystem "${PODS_ROOT}/Headers/Public/WZLBadge" -isystem "${PODS_ROOT}/Headers/Public/WechatOpenSDK"
-OTHER_LDFLAGS = $(inherited) -ObjC -l"AFNetworking" -l"BRPickerView" -l"GPUImage" -l"IQKeyboardManager" -l"M80AttributedLabel" -l"MBProgressHUD" -l"MJExtension" -l"MJRefresh" -l"NMCAudioModule" -l"NMCVideoModule" -l"SDWebImage" -l"TTGTagCollectionView" -l"Toast" -l"UICollectionViewLeftAlignedLayout" -l"WZLBadge" -l"WeChatSDK" -l"aacplus" -l"c++" -l"crypto" -l"event" -l"nio" -l"nts" -l"nvs" -l"openh264" -l"opus" -l"sqlite3" -l"sqlite3.0" -l"ssl" -l"z" -framework "AVFoundation" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreGraphics" -framework "CoreMedia" -framework "CoreTelephony" -framework "CoreText" -framework "Foundation" -framework "ImageIO" -framework "MobileCoreServices" -framework "NIMAVChat" -framework "NIMSDK" -framework "QuartzCore" -framework "Security" -framework "SecurityEnvSDK" -framework "SystemConfiguration" -framework "UIKit" -framework "UMCommon" -framework "UMPush" -framework "UTDID" -framework "UserNotifications" -framework "VideoToolbox"
+HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/AFNetworking" "${PODS_ROOT}/Headers/Public/BRPickerView" "${PODS_ROOT}/Headers/Public/IQKeyboardManager" "${PODS_ROOT}/Headers/Public/M80AttributedLabel" "${PODS_ROOT}/Headers/Public/MBProgressHUD" "${PODS_ROOT}/Headers/Public/MJExtension" "${PODS_ROOT}/Headers/Public/MJRefresh" "${PODS_ROOT}/Headers/Public/NIMSDK" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/TOCropViewController" "${PODS_ROOT}/Headers/Public/TTGTagCollectionView" "${PODS_ROOT}/Headers/Public/Toast" "${PODS_ROOT}/Headers/Public/UICollectionViewLeftAlignedLayout" "${PODS_ROOT}/Headers/Public/UMCCommon" "${PODS_ROOT}/Headers/Public/UMCPush" "${PODS_ROOT}/Headers/Public/UMCSecurityPlugins" "${PODS_ROOT}/Headers/Public/WZLBadge" "${PODS_ROOT}/Headers/Public/WechatOpenSDK"
+LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/BRPickerView" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManager" "${PODS_CONFIGURATION_BUILD_DIR}/M80AttributedLabel" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJExtension" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController" "${PODS_CONFIGURATION_BUILD_DIR}/TTGTagCollectionView" "${PODS_CONFIGURATION_BUILD_DIR}/Toast" "${PODS_CONFIGURATION_BUILD_DIR}/UICollectionViewLeftAlignedLayout" "${PODS_CONFIGURATION_BUILD_DIR}/WZLBadge" "${PODS_ROOT}/NIMSDK/NIMAVChat/Libs" "${PODS_ROOT}/NIMSDK/NIMSDK/Libs" "${PODS_ROOT}/WechatOpenSDK/WeChatSDK1.8.3"
+OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/AFNetworking" -isystem "${PODS_ROOT}/Headers/Public/BRPickerView" -isystem "${PODS_ROOT}/Headers/Public/IQKeyboardManager" -isystem "${PODS_ROOT}/Headers/Public/M80AttributedLabel" -isystem "${PODS_ROOT}/Headers/Public/MBProgressHUD" -isystem "${PODS_ROOT}/Headers/Public/MJExtension" -isystem "${PODS_ROOT}/Headers/Public/MJRefresh" -isystem "${PODS_ROOT}/Headers/Public/NIMSDK" -isystem "${PODS_ROOT}/Headers/Public/SDWebImage" -isystem "${PODS_ROOT}/Headers/Public/TOCropViewController" -isystem "${PODS_ROOT}/Headers/Public/TTGTagCollectionView" -isystem "${PODS_ROOT}/Headers/Public/Toast" -isystem "${PODS_ROOT}/Headers/Public/UICollectionViewLeftAlignedLayout" -isystem "${PODS_ROOT}/Headers/Public/UMCCommon" -isystem "${PODS_ROOT}/Headers/Public/UMCPush" -isystem "${PODS_ROOT}/Headers/Public/UMCSecurityPlugins" -isystem "${PODS_ROOT}/Headers/Public/WZLBadge" -isystem "${PODS_ROOT}/Headers/Public/WechatOpenSDK"
+OTHER_LDFLAGS = $(inherited) -ObjC -l"AFNetworking" -l"BRPickerView" -l"GPUImage" -l"IQKeyboardManager" -l"M80AttributedLabel" -l"MBProgressHUD" -l"MJExtension" -l"MJRefresh" -l"NMCAudioModule" -l"NMCVideoModule" -l"SDWebImage" -l"TOCropViewController" -l"TTGTagCollectionView" -l"Toast" -l"UICollectionViewLeftAlignedLayout" -l"WZLBadge" -l"WeChatSDK" -l"aacplus" -l"c++" -l"crypto" -l"event" -l"nio" -l"nts" -l"nvs" -l"openh264" -l"opus" -l"sqlite3" -l"sqlite3.0" -l"ssl" -l"z" -framework "AVFoundation" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreGraphics" -framework "CoreMedia" -framework "CoreTelephony" -framework "CoreText" -framework "Foundation" -framework "ImageIO" -framework "MobileCoreServices" -framework "NIMAVChat" -framework "NIMSDK" -framework "QuartzCore" -framework "Security" -framework "SecurityEnvSDK" -framework "SystemConfiguration" -framework "UIKit" -framework "UMCommon" -framework "UMPush" -framework "UTDID" -framework "UserNotifications" -framework "VideoToolbox"
 PODS_BUILD_DIR = ${BUILD_DIR}
 PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
 PODS_PODFILE_DIR_PATH = ${SRCROOT}/.

+ 4 - 4
Pods/Target Support Files/Pods-model/Pods-model.release.xcconfig

@@ -1,9 +1,9 @@
 FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/NIMSDK/NIMSDK" "${PODS_ROOT}/NIMSDK/NIMAVChat" "${PODS_ROOT}/UMCCommon" "${PODS_ROOT}/UMCPush" "${PODS_ROOT}/UMCSecurityPlugins/thirdparties"
 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
-HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/AFNetworking" "${PODS_ROOT}/Headers/Public/BRPickerView" "${PODS_ROOT}/Headers/Public/IQKeyboardManager" "${PODS_ROOT}/Headers/Public/M80AttributedLabel" "${PODS_ROOT}/Headers/Public/MBProgressHUD" "${PODS_ROOT}/Headers/Public/MJExtension" "${PODS_ROOT}/Headers/Public/MJRefresh" "${PODS_ROOT}/Headers/Public/NIMSDK" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/TTGTagCollectionView" "${PODS_ROOT}/Headers/Public/Toast" "${PODS_ROOT}/Headers/Public/UICollectionViewLeftAlignedLayout" "${PODS_ROOT}/Headers/Public/UMCCommon" "${PODS_ROOT}/Headers/Public/UMCPush" "${PODS_ROOT}/Headers/Public/UMCSecurityPlugins" "${PODS_ROOT}/Headers/Public/WZLBadge" "${PODS_ROOT}/Headers/Public/WechatOpenSDK"
-LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/BRPickerView" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManager" "${PODS_CONFIGURATION_BUILD_DIR}/M80AttributedLabel" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJExtension" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/TTGTagCollectionView" "${PODS_CONFIGURATION_BUILD_DIR}/Toast" "${PODS_CONFIGURATION_BUILD_DIR}/UICollectionViewLeftAlignedLayout" "${PODS_CONFIGURATION_BUILD_DIR}/WZLBadge" "${PODS_ROOT}/NIMSDK/NIMAVChat/Libs" "${PODS_ROOT}/NIMSDK/NIMSDK/Libs" "${PODS_ROOT}/WechatOpenSDK/WeChatSDK1.8.3"
-OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/AFNetworking" -isystem "${PODS_ROOT}/Headers/Public/BRPickerView" -isystem "${PODS_ROOT}/Headers/Public/IQKeyboardManager" -isystem "${PODS_ROOT}/Headers/Public/M80AttributedLabel" -isystem "${PODS_ROOT}/Headers/Public/MBProgressHUD" -isystem "${PODS_ROOT}/Headers/Public/MJExtension" -isystem "${PODS_ROOT}/Headers/Public/MJRefresh" -isystem "${PODS_ROOT}/Headers/Public/NIMSDK" -isystem "${PODS_ROOT}/Headers/Public/SDWebImage" -isystem "${PODS_ROOT}/Headers/Public/TTGTagCollectionView" -isystem "${PODS_ROOT}/Headers/Public/Toast" -isystem "${PODS_ROOT}/Headers/Public/UICollectionViewLeftAlignedLayout" -isystem "${PODS_ROOT}/Headers/Public/UMCCommon" -isystem "${PODS_ROOT}/Headers/Public/UMCPush" -isystem "${PODS_ROOT}/Headers/Public/UMCSecurityPlugins" -isystem "${PODS_ROOT}/Headers/Public/WZLBadge" -isystem "${PODS_ROOT}/Headers/Public/WechatOpenSDK"
-OTHER_LDFLAGS = $(inherited) -ObjC -l"AFNetworking" -l"BRPickerView" -l"GPUImage" -l"IQKeyboardManager" -l"M80AttributedLabel" -l"MBProgressHUD" -l"MJExtension" -l"MJRefresh" -l"NMCAudioModule" -l"NMCVideoModule" -l"SDWebImage" -l"TTGTagCollectionView" -l"Toast" -l"UICollectionViewLeftAlignedLayout" -l"WZLBadge" -l"WeChatSDK" -l"aacplus" -l"c++" -l"crypto" -l"event" -l"nio" -l"nts" -l"nvs" -l"openh264" -l"opus" -l"sqlite3" -l"sqlite3.0" -l"ssl" -l"z" -framework "AVFoundation" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreGraphics" -framework "CoreMedia" -framework "CoreTelephony" -framework "CoreText" -framework "Foundation" -framework "ImageIO" -framework "MobileCoreServices" -framework "NIMAVChat" -framework "NIMSDK" -framework "QuartzCore" -framework "Security" -framework "SecurityEnvSDK" -framework "SystemConfiguration" -framework "UIKit" -framework "UMCommon" -framework "UMPush" -framework "UTDID" -framework "UserNotifications" -framework "VideoToolbox"
+HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/AFNetworking" "${PODS_ROOT}/Headers/Public/BRPickerView" "${PODS_ROOT}/Headers/Public/IQKeyboardManager" "${PODS_ROOT}/Headers/Public/M80AttributedLabel" "${PODS_ROOT}/Headers/Public/MBProgressHUD" "${PODS_ROOT}/Headers/Public/MJExtension" "${PODS_ROOT}/Headers/Public/MJRefresh" "${PODS_ROOT}/Headers/Public/NIMSDK" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/TOCropViewController" "${PODS_ROOT}/Headers/Public/TTGTagCollectionView" "${PODS_ROOT}/Headers/Public/Toast" "${PODS_ROOT}/Headers/Public/UICollectionViewLeftAlignedLayout" "${PODS_ROOT}/Headers/Public/UMCCommon" "${PODS_ROOT}/Headers/Public/UMCPush" "${PODS_ROOT}/Headers/Public/UMCSecurityPlugins" "${PODS_ROOT}/Headers/Public/WZLBadge" "${PODS_ROOT}/Headers/Public/WechatOpenSDK"
+LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/BRPickerView" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManager" "${PODS_CONFIGURATION_BUILD_DIR}/M80AttributedLabel" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJExtension" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController" "${PODS_CONFIGURATION_BUILD_DIR}/TTGTagCollectionView" "${PODS_CONFIGURATION_BUILD_DIR}/Toast" "${PODS_CONFIGURATION_BUILD_DIR}/UICollectionViewLeftAlignedLayout" "${PODS_CONFIGURATION_BUILD_DIR}/WZLBadge" "${PODS_ROOT}/NIMSDK/NIMAVChat/Libs" "${PODS_ROOT}/NIMSDK/NIMSDK/Libs" "${PODS_ROOT}/WechatOpenSDK/WeChatSDK1.8.3"
+OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/AFNetworking" -isystem "${PODS_ROOT}/Headers/Public/BRPickerView" -isystem "${PODS_ROOT}/Headers/Public/IQKeyboardManager" -isystem "${PODS_ROOT}/Headers/Public/M80AttributedLabel" -isystem "${PODS_ROOT}/Headers/Public/MBProgressHUD" -isystem "${PODS_ROOT}/Headers/Public/MJExtension" -isystem "${PODS_ROOT}/Headers/Public/MJRefresh" -isystem "${PODS_ROOT}/Headers/Public/NIMSDK" -isystem "${PODS_ROOT}/Headers/Public/SDWebImage" -isystem "${PODS_ROOT}/Headers/Public/TOCropViewController" -isystem "${PODS_ROOT}/Headers/Public/TTGTagCollectionView" -isystem "${PODS_ROOT}/Headers/Public/Toast" -isystem "${PODS_ROOT}/Headers/Public/UICollectionViewLeftAlignedLayout" -isystem "${PODS_ROOT}/Headers/Public/UMCCommon" -isystem "${PODS_ROOT}/Headers/Public/UMCPush" -isystem "${PODS_ROOT}/Headers/Public/UMCSecurityPlugins" -isystem "${PODS_ROOT}/Headers/Public/WZLBadge" -isystem "${PODS_ROOT}/Headers/Public/WechatOpenSDK"
+OTHER_LDFLAGS = $(inherited) -ObjC -l"AFNetworking" -l"BRPickerView" -l"GPUImage" -l"IQKeyboardManager" -l"M80AttributedLabel" -l"MBProgressHUD" -l"MJExtension" -l"MJRefresh" -l"NMCAudioModule" -l"NMCVideoModule" -l"SDWebImage" -l"TOCropViewController" -l"TTGTagCollectionView" -l"Toast" -l"UICollectionViewLeftAlignedLayout" -l"WZLBadge" -l"WeChatSDK" -l"aacplus" -l"c++" -l"crypto" -l"event" -l"nio" -l"nts" -l"nvs" -l"openh264" -l"opus" -l"sqlite3" -l"sqlite3.0" -l"ssl" -l"z" -framework "AVFoundation" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreGraphics" -framework "CoreMedia" -framework "CoreTelephony" -framework "CoreText" -framework "Foundation" -framework "ImageIO" -framework "MobileCoreServices" -framework "NIMAVChat" -framework "NIMSDK" -framework "QuartzCore" -framework "Security" -framework "SecurityEnvSDK" -framework "SystemConfiguration" -framework "UIKit" -framework "UMCommon" -framework "UMPush" -framework "UTDID" -framework "UserNotifications" -framework "VideoToolbox"
 PODS_BUILD_DIR = ${BUILD_DIR}
 PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
 PODS_PODFILE_DIR_PATH = ${SRCROOT}/.

+ 24 - 0
Pods/Target Support Files/TOCropViewController/ResourceBundle-TOCropViewControllerBundle-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.3.8</string>
+  <key>CFBundleSignature</key>
+  <string>????</string>
+  <key>CFBundleVersion</key>
+  <string>1</string>
+  <key>NSPrincipalClass</key>
+  <string></string>
+</dict>
+</plist>

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

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

+ 12 - 0
Pods/Target Support Files/TOCropViewController/TOCropViewController-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
+

+ 9 - 0
Pods/Target Support Files/TOCropViewController/TOCropViewController.xcconfig

@@ -0,0 +1,9 @@
+CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController
+GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
+HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/TOCropViewController" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/TOCropViewController"
+PODS_BUILD_DIR = ${BUILD_DIR}
+PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
+PODS_ROOT = ${SRCROOT}
+PODS_TARGET_SRCROOT = ${PODS_ROOT}/TOCropViewController
+PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
+SKIP_INSTALL = YES

+ 24 - 4
model.xcodeproj/project.pbxproj

@@ -15,6 +15,8 @@
 		2F168370210EA56100941193 /* MD5String.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F16836F210EA56100941193 /* MD5String.m */; };
 		2F168373210EA5EE00941193 /* Helper.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F168372210EA5EE00941193 /* Helper.m */; };
 		2F16837921115BB900941193 /* UIColor+Hex.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F16837821115BB900941193 /* UIColor+Hex.m */; };
+		2F2DC56573F0D91B44638027 /* WebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F2DCD440515C856BBDA9B1D /* WebViewController.m */; };
+		2F2DCCF423A7BCD5C7A73F2F /* WeakScriptMessageDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F2DC77CFE857F42892EDE51 /* WeakScriptMessageDelegate.m */; };
 		2F4A8922211ADACF006D7DD3 /* DetailOnlyTextViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F4A891C211ADACE006D7DD3 /* DetailOnlyTextViewController.m */; };
 		2F4A8923211ADACF006D7DD3 /* DetailOneImageViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F4A891D211ADACE006D7DD3 /* DetailOneImageViewController.m */; };
 		2F4A8924211ADACF006D7DD3 /* DetailOneImageViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2F4A891E211ADACE006D7DD3 /* DetailOneImageViewController.xib */; };
@@ -42,7 +44,6 @@
 		2F4A896C211D9F04006D7DD3 /* SignViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F4A895E211D9F03006D7DD3 /* SignViewCell.m */; };
 		2F4A896D211D9F04006D7DD3 /* SignUpViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F4A895F211D9F03006D7DD3 /* SignUpViewController.m */; };
 		2F4A896E211D9F04006D7DD3 /* SignViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2F4A8960211D9F03006D7DD3 /* SignViewCell.xib */; };
-		2F4A896F211D9F04006D7DD3 /* SignUpViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2F4A8961211D9F03006D7DD3 /* SignUpViewController.xib */; };
 		2F4A8970211D9F04006D7DD3 /* SignInfoCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F4A8962211D9F03006D7DD3 /* SignInfoCell.m */; };
 		2F4A8973211D9F04006D7DD3 /* SignInfoCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2F4A8969211D9F03006D7DD3 /* SignInfoCell.xib */; };
 		2F4A8976211D9F16006D7DD3 /* ActivityModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F4A8974211D9F16006D7DD3 /* ActivityModel.m */; };
@@ -361,6 +362,8 @@
 		3A44C132218A10800021DA3F /* TabControl.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3A44C131218A10800021DA3F /* TabControl.xib */; };
 		3A54C9FD218D960400EA297E /* ShareCardController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A54C9FB218D960400EA297E /* ShareCardController.m */; };
 		3A54C9FE218D960400EA297E /* ShareCardController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3A54C9FC218D960400EA297E /* ShareCardController.xib */; };
+		3A5C818D21DD9BF80019F82A /* UploadVideoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A5C818B21DD9BF80019F82A /* UploadVideoViewController.m */; };
+		3A5C818E21DD9BF80019F82A /* UploadVideoViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3A5C818C21DD9BF80019F82A /* UploadVideoViewController.xib */; };
 		3A5EC7472186AF5A00A9438E /* ShadowMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3A5EC7452186AF5900A9438E /* ShadowMenu.xib */; };
 		3A5EC7482186AF5A00A9438E /* ShadowMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A5EC7462186AF5A00A9438E /* ShadowMenu.m */; };
 		3A5EC74D2186F56C00A9438E /* MyTeamController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A5EC74B2186F56C00A9438E /* MyTeamController.m */; };
@@ -477,6 +480,10 @@
 		2F168372210EA5EE00941193 /* Helper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Helper.m; sourceTree = "<group>"; };
 		2F16837721115BB900941193 /* UIColor+Hex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIColor+Hex.h"; sourceTree = "<group>"; };
 		2F16837821115BB900941193 /* UIColor+Hex.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIColor+Hex.m"; sourceTree = "<group>"; };
+		2F2DC6350BD1314056B22F68 /* WeakScriptMessageDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WeakScriptMessageDelegate.h; sourceTree = "<group>"; };
+		2F2DC77CFE857F42892EDE51 /* WeakScriptMessageDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WeakScriptMessageDelegate.m; sourceTree = "<group>"; };
+		2F2DCAA19BC75E0F1AE6AA70 /* WebViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebViewController.h; sourceTree = "<group>"; };
+		2F2DCD440515C856BBDA9B1D /* WebViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WebViewController.m; sourceTree = "<group>"; };
 		2F4A891C211ADACE006D7DD3 /* DetailOnlyTextViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DetailOnlyTextViewController.m; sourceTree = "<group>"; };
 		2F4A891D211ADACE006D7DD3 /* DetailOneImageViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DetailOneImageViewController.m; sourceTree = "<group>"; };
 		2F4A891E211ADACE006D7DD3 /* DetailOneImageViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DetailOneImageViewController.xib; sourceTree = "<group>"; };
@@ -516,7 +523,6 @@
 		2F4A895E211D9F03006D7DD3 /* SignViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignViewCell.m; sourceTree = "<group>"; };
 		2F4A895F211D9F03006D7DD3 /* SignUpViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignUpViewController.m; sourceTree = "<group>"; };
 		2F4A8960211D9F03006D7DD3 /* SignViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SignViewCell.xib; sourceTree = "<group>"; };
-		2F4A8961211D9F03006D7DD3 /* SignUpViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SignUpViewController.xib; sourceTree = "<group>"; };
 		2F4A8962211D9F03006D7DD3 /* SignInfoCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignInfoCell.m; sourceTree = "<group>"; };
 		2F4A8964211D9F03006D7DD3 /* PlatformActivityController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlatformActivityController.h; sourceTree = "<group>"; };
 		2F4A8965211D9F03006D7DD3 /* SignViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignViewCell.h; sourceTree = "<group>"; };
@@ -1151,6 +1157,9 @@
 		3A54C9FA218D960400EA297E /* ShareCardController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ShareCardController.h; sourceTree = "<group>"; };
 		3A54C9FB218D960400EA297E /* ShareCardController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShareCardController.m; sourceTree = "<group>"; };
 		3A54C9FC218D960400EA297E /* ShareCardController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShareCardController.xib; sourceTree = "<group>"; };
+		3A5C818A21DD9BF80019F82A /* UploadVideoViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UploadVideoViewController.h; sourceTree = "<group>"; };
+		3A5C818B21DD9BF80019F82A /* UploadVideoViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UploadVideoViewController.m; sourceTree = "<group>"; };
+		3A5C818C21DD9BF80019F82A /* UploadVideoViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = UploadVideoViewController.xib; sourceTree = "<group>"; };
 		3A5EC7442186AF5900A9438E /* ShadowMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShadowMenu.h; sourceTree = "<group>"; };
 		3A5EC7452186AF5900A9438E /* ShadowMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShadowMenu.xib; sourceTree = "<group>"; };
 		3A5EC7462186AF5A00A9438E /* ShadowMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShadowMenu.m; sourceTree = "<group>"; };
@@ -1391,7 +1400,6 @@
 				3A9D325821DCB6950002E00E /* ActivityDetailViewController.xib */,
 				2F4A8966211D9F03006D7DD3 /* SignUpViewController.h */,
 				2F4A895F211D9F03006D7DD3 /* SignUpViewController.m */,
-				2F4A8961211D9F03006D7DD3 /* SignUpViewController.xib */,
 				2F4A8965211D9F03006D7DD3 /* SignViewCell.h */,
 				2F4A895E211D9F03006D7DD3 /* SignViewCell.m */,
 				2F4A8960211D9F03006D7DD3 /* SignViewCell.xib */,
@@ -1404,6 +1412,9 @@
 				3A9D326321DCB74D0002E00E /* ModelFansTableViewCell.h */,
 				3A9D326421DCB74D0002E00E /* ModelFansTableViewCell.m */,
 				3A9D326221DCB74C0002E00E /* ModelFansTableViewCell.xib */,
+				3A5C818A21DD9BF80019F82A /* UploadVideoViewController.h */,
+				3A5C818B21DD9BF80019F82A /* UploadVideoViewController.m */,
+				3A5C818C21DD9BF80019F82A /* UploadVideoViewController.xib */,
 			);
 			path = ActivityVC;
 			sourceTree = "<group>";
@@ -2789,6 +2800,10 @@
 				EF4A658820FDE84B00FFCD3C /* RemindingView.m */,
 				EF2167FB2101F701009F7343 /* ModelRootViewController.h */,
 				EF2167FC2101F701009F7343 /* ModelRootViewController.m */,
+				2F2DCAA19BC75E0F1AE6AA70 /* WebViewController.h */,
+				2F2DCD440515C856BBDA9B1D /* WebViewController.m */,
+				2F2DC77CFE857F42892EDE51 /* WeakScriptMessageDelegate.m */,
+				2F2DC6350BD1314056B22F68 /* WeakScriptMessageDelegate.h */,
 			);
 			path = Public;
 			sourceTree = "<group>";
@@ -3045,9 +3060,9 @@
 				3A5EC7562186F66500A9438E /* MyTeamCell.xib in Resources */,
 				3A54C9FE218D960400EA297E /* ShareCardController.xib in Resources */,
 				2F4A8950211ADB00006D7DD3 /* DetailOnlyTextCell.xib in Resources */,
-				2F4A896F211D9F04006D7DD3 /* SignUpViewController.xib in Resources */,
 				8848C66B210813D300EDB121 /* PhotoCollectionViewCell.xib in Resources */,
 				2F4A8973211D9F04006D7DD3 /* SignInfoCell.xib in Resources */,
+				3A5C818E21DD9BF80019F82A /* UploadVideoViewController.xib in Resources */,
 				2F4A8949211ADB00006D7DD3 /* OneImageCell.xib in Resources */,
 				3AC5CA04218BDDC700D4ACAC /* RankCell.xib in Resources */,
 				2F8D26C321183D7300CD7347 /* README.md in Resources */,
@@ -3141,6 +3156,7 @@
 				"${PODS_ROOT}/IQKeyboardManager/IQKeyboardManager/Resources/IQKeyboardManager.bundle",
 				"${PODS_ROOT}/MJRefresh/MJRefresh/MJRefresh.bundle",
 				"${PODS_ROOT}/NIMSDK/NIMAVChat/Resources/NMCVideoFilter.bundle",
+				"${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle",
 			);
 			name = "[CP] Copy Pods Resources";
 			outputPaths = (
@@ -3148,6 +3164,7 @@
 				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/IQKeyboardManager.bundle",
 				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MJRefresh.bundle",
 				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/NMCVideoFilter.bundle",
+				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle",
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
@@ -3504,6 +3521,7 @@
 				2F8D25312114691C00CD7347 /* PGDatePicker.m in Sources */,
 				2F8D27EC21183E2B00CD7347 /* NSDictionary+NTESJson.m in Sources */,
 				3A5EC74D2186F56C00A9438E /* MyTeamController.m in Sources */,
+				3A5C818D21DD9BF80019F82A /* UploadVideoViewController.m in Sources */,
 				2F7FC09A21242E6300492F0C /* NTESNavigationHandler.m in Sources */,
 				2F8D248921131A7A00CD7347 /* JSONModelError.m in Sources */,
 				2F8D272E21183D7400CD7347 /* NIMLocationViewController.m in Sources */,
@@ -3534,6 +3552,8 @@
 				8848C6612108103600EDB121 /* ModelHonorViewController.m in Sources */,
 				2F8D271C21183D7400CD7347 /* NIMKitLocationPoint.m in Sources */,
 				2F8D24592111895000CD7347 /* NSArray+Log.m in Sources */,
+				2F2DC56573F0D91B44638027 /* WebViewController.m in Sources */,
+				2F2DCCF423A7BCD5C7A73F2F /* WeakScriptMessageDelegate.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

+ 23 - 0
model/Assets.xcassets/icon_bofang.imageset/Contents.json

@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "filename" : "icon_bofang.png",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "icon_bofang@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "icon_bofang@3x.png",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}

binární
model/Assets.xcassets/icon_bofang.imageset/icon_bofang.png


binární
model/Assets.xcassets/icon_bofang.imageset/icon_bofang@2x.png


binární
model/Assets.xcassets/icon_bofang.imageset/icon_bofang@3x.png


+ 2 - 2
model/Classes/Application/AppDelegate.m

@@ -43,8 +43,8 @@ NSString *NTESNotificationLogout = @"NTESNotificationLogout";
     self.window.backgroundColor = [UIColor whiteColor];
     UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectZero];
     NSString *userAgent = [webView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"];
-    NSString *newUserAgent = [userAgent stringByAppendingString:@" chmo"];//自定义需要拼接的字符串
-    NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:newUserAgent, @"UserAgent", nil];
+    NSString *newUserAgent = [userAgent stringByAppendingString:@" chmo chmoModel"];//自定义需要拼接的字符串
+    NSDictionary *dictionary = @{@"UserAgent": newUserAgent};
     [[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];
 
     NSString *appKey = @"c545b95fd20d5a20d0d1220dc6831e4d";

+ 1 - 9
model/Classes/Application/ModelHeader.h

@@ -15,18 +15,10 @@
 #define imageURl @"http://app.chmo.net/thmodel/"
 #define apnsCernameStr @"model"
 
-// #define PublicUrl @"http://192.168.123.234:8080"
+// #define PublicUrl @"http://192.168.50.132:8081/thmodel"
 // #define imageURl @"http://app.chmo.net/thmodel/"
 // #define apnsCernameStr @"model"
 
-//#define PublicUrl @"http://192.168.50.132:8081"
-//#define imageURl @"http://192.168.50.132:8081/"
-//#define apnsCernameStr @"modelDev"
-
-//#define PublicUrl @"http://192.168.123.90:8080"
-//#define imageURl @"http://192.168.123.90:8080/"
-//#define apnsCernameStr @"modelDev"
-
 #define shareUrl(type,code) [NSString stringWithFormat:@"%@/share.jsp?type=%@&code=%@", PublicUrl, type, code]
 
 //应用下载地址 https://itunes.apple.com/cn/app/id1434011196?mt=8

+ 6 - 2
model/Classes/Controllers/ActivityVC/ActivityDetailViewController.m

@@ -10,6 +10,7 @@
 #import "ActivityModel.h"
 #import "SignUpViewController.h"
 #import "SignListViewController.h"
+#import "UploadVideoViewController.h"
 
 @interface ActivityDetailViewController ()
 @property(weak, nonatomic) IBOutlet UIImageView *headImg;
@@ -68,13 +69,16 @@
 }
 
 - (IBAction)signUp:(id)sender {
-
-    SignUpViewController *aVc = [[SignUpViewController alloc] init];
     if ([_model.issign isEqualToString:@"1"]) {
         [MBProgressHUD showTextHUD:@"不能重复报名" inView:self.view hideAfterDelay:1];
     } else if ([_model.issigndate isEqualToString:@"0"]) {
         [MBProgressHUD showTextHUD:@"不在报名时间内" inView:self.view hideAfterDelay:1];
+    } else if ([@"1" isEqualToString:_model.isTop]) {
+        UploadVideoViewController *vc = [[UploadVideoViewController alloc] init];
+        vc.model = _model;
+        [self.navigationController pushViewController:vc animated:YES];
     } else {
+        SignUpViewController *aVc = [[SignUpViewController alloc] init];
         aVc.model = _model;
         [self.navigationController pushViewController:aVc animated:YES];
     }

+ 12 - 3
model/Classes/Controllers/ActivityVC/PlatformActivityController.m

@@ -10,6 +10,7 @@
 #import "PlatformActivityCell.h"
 #import "ModelTitleView.h"   // 选择按钮
 #import "ActivityDetailViewController.h"
+#import "WebViewController.h"
 
 @interface PlatformActivityController () <UITableViewDelegate, UITableViewDataSource>
 @property(nonatomic, strong) UITableView *tableView;
@@ -216,9 +217,17 @@
     } else {
         model = self.twoModelArr[indexPath.section];
     }
-    ActivityDetailViewController *detailVC = [[ActivityDetailViewController alloc] init];
-    detailVC.model = model;
-    [self.navigationController pushViewController:detailVC animated:YES];
+
+    if (model.url.length) {
+        WebViewController *webViewController = [[WebViewController alloc] init];
+        webViewController.url = model.url;
+        webViewController.activityPK = model.pk;
+        [self.navigationController pushViewController:webViewController animated:YES];
+    } else {
+        ActivityDetailViewController *detailVC = [[ActivityDetailViewController alloc] init];
+        detailVC.model = model;
+        [self.navigationController pushViewController:detailVC animated:YES];
+    }
 }
 
 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

+ 1 - 2
model/Classes/Controllers/ActivityVC/SignUpViewController.h

@@ -8,7 +8,6 @@
 
 
 #import "ActivityModel.h"
-@interface SignUpViewController : UIViewController
-@property (weak, nonatomic) IBOutlet UIImageView *headView;
+@interface SignUpViewController : UIViewController <UITableViewDelegate>
 @property (strong, nonatomic) ActivityModel *model;
 @end

+ 56 - 64
model/Classes/Controllers/ActivityVC/SignUpViewController.m

@@ -8,35 +8,61 @@
 
 #import "SignUpViewController.h"
 #import "SignInfoCell.h"
-#import "PlatformActivityController.h"
-
-@interface SignUpViewController () <UITextFieldDelegate>
-@property(weak, nonatomic) IBOutlet UITableView *tableView;
+#import "Masonry.h"
 
+@interface SignUpViewController () <UITextFieldDelegate, UITableViewDelegate, UITableViewDataSource>
+@property(strong, nonatomic) UITableView *tableView;
+@property(nonatomic, strong) UIImageView *headView;
 @end
 
 @implementation SignUpViewController
 
 - (void)viewDidLoad {
     [super viewDidLoad];
-    self.tableView.tableHeaderView = self.headView;
-    _tableView.showsVerticalScrollIndicator = NO;
+
     self.navigationItem.title = @"活动报名";
-    UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
-    btn.frame = CGRectMake(0, 0, 40, 40);
-    [btn setImage:[UIImage imageNamed:@"fanhui2"] forState:UIControlStateNormal];
-    btn.imageEdgeInsets = UIEdgeInsetsMake(0, -20, 0, 0);
-    [btn addTarget:self action:@selector(backClick) forControlEvents:UIControlEventTouchUpInside];
-    UIBarButtonItem *leftItem = [[UIBarButtonItem alloc] initWithCustomView:btn];
-    UIBarButtonItem *nagetiveSpacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
-    nagetiveSpacer.width = -12;//这个值可以根据自己需要自己调整
-    self.navigationItem.leftBarButtonItems = @[nagetiveSpacer, leftItem];
-    [_headView sd_setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@%@", imageURl, _model.pic]] placeholderImage:[UIImage imageNamed:@"jiazai"]];
-
-    // Do any additional setup after loading the view.
+    UIBarButtonItem *leftItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"icon_back"] style:UIBarButtonItemStylePlain target:self action:@selector(backClick)];
+    self.navigationItem.leftBarButtonItem = leftItem;
+    self.navigationController.navigationBar.tintColor = [UIColor blackColor];
+
+    self.tableView = [[UITableView alloc] init];
+    [self.view addSubview:self.tableView];
+    [self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.right.equalTo(self.view);
+        make.top.equalTo(self.mas_topLayoutGuide);
+        make.bottom.equalTo(self.mas_bottomLayoutGuide);
+    }];
+    self.tableView.delegate = self;
+    self.tableView.dataSource = self;
+    self.tableView.tableHeaderView = self.headView;
+    self.tableView.contentInset = UIEdgeInsetsMake(0, 0, 64, 0);
+
+    UIButton *signBtn = [[UIButton alloc] init];
+    [self.view addSubview:signBtn];
+    [signBtn mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.equalTo(self.view).offset(20);
+        make.right.equalTo(self.view).offset(-20);
+        make.bottom.equalTo(self.mas_bottomLayoutGuide).offset(-10);
+        make.height.mas_equalTo(44);
+    }];
+    signBtn.layer.backgroundColor = [UIColor colorWithRed:1.f green:64 / 255.f blue:149 / 255.f alpha:1.0].CGColor;
+    signBtn.layer.cornerRadius = 22;
+    signBtn.layer.shadowColor = [UIColor colorWithRed:1.f green:64 / 255.f blue:149 / 255.f alpha:0.36].CGColor;
+    signBtn.layer.shadowOffset = CGSizeMake(0, 8);
+    signBtn.layer.shadowOpacity = 1;
+    signBtn.layer.shadowRadius = 10;
+    [signBtn addTarget:self action:@selector(sign:) forControlEvents:UIControlEventTouchUpInside];
 }
 
-- (IBAction)sign:(UIButton *)sender {
+- (UIImageView *)headView {
+    if (!_headView) {
+        _headView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, ScreenWidth, ScreenWidth)];
+        [_headView sd_setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@%@", imageURl, _model.pic]] placeholderImage:[UIImage imageNamed:@"jiazai"]];
+    }
+    return _headView;
+}
+
+- (void)sign:(UIButton *)sender {
     SignInfoCell *cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
     NSString *people = cell.name.text;
     NSString *tel = cell.phone.text;
@@ -89,61 +115,31 @@
     alert = nil;
 }
 
-- (void)getList {
-
-}
-
 - (void)backClick {
     [self.navigationController popViewControllerAnimated:YES];
 }
 
 - (void)viewWillAppear:(BOOL)animated {
     [self.navigationController setNavigationBarHidden:NO animated:YES];
+    self.navigationController.navigationBar.tintColor = [UIColor blackColor];
 }
 
 #pragma mark - deleDate
 
 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
-    if (self.model.isTop) {
-        return 3;
-    } else {
-        return 1;
-    }
+    return 1;
 }
 
 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
-    switch (indexPath.row) {
-        case 1: {
-            SignInfoCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SignInfoCell"];
-            if (!cell) {
-                cell = [[[UINib nibWithNibName:@"SignInfoCell" bundle:nil] instantiateWithOwner:self options:nil] lastObject];
-                cell.selectionStyle = UITableViewCellSelectionStyleNone;
-                cell.model = _model;
-                cell.name.delegate = self;
-                cell.phone.delegate = self;
-            }
-            return cell;
-            break;
-        }
-        case 2: {
-            UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"normalCell"];
-            if (!cell) {
-                cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"normalCell"];
-            }
-            cell.textLabel.text = @"选择图片";
-            return cell;
-        }
-        case 3: {
-            UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"normalCell"];
-            if (!cell) {
-                cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"normalCell"];
-            }
-            cell.textLabel.text = @"选择视频";
-            return cell;
-        }
-        default:
-            return nil;
+    SignInfoCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SignInfoCell"];
+    if (!cell) {
+        cell = [[[UINib nibWithNibName:@"SignInfoCell" bundle:nil] instantiateWithOwner:self options:nil] lastObject];
+        cell.selectionStyle = UITableViewCellSelectionStyleNone;
+        cell.model = _model;
+        cell.name.delegate = self;
+        cell.phone.delegate = self;
     }
+    return cell;
 }
 
 - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
@@ -151,13 +147,9 @@
 }
 
 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
-    if (indexPath.row == 1) {
-        return 465;
-    }
-    return 44;
+    return 465;
 }
 
--selectrow
 
 - (BOOL)textFieldShouldReturn:(UITextField *)theTextField {
     [theTextField resignFirstResponder];

+ 0 - 81
model/Classes/Controllers/ActivityVC/SignUpViewController.xib

@@ -1,81 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
-    <device id="retina4_7" orientation="portrait">
-        <adaptation id="fullscreen"/>
-    </device>
-    <dependencies>
-        <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
-        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
-        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
-    </dependencies>
-    <objects>
-        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="SignUpViewController">
-            <connections>
-                <outlet property="headView" destination="88e-sd-55f" id="bNT-a0-Gdw"/>
-                <outlet property="tableView" destination="ery-9I-YtQ" id="EEt-gc-9vp"/>
-                <outlet property="view" destination="iN0-l3-epB" id="aIN-DV-4pe"/>
-            </connections>
-        </placeholder>
-        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
-        <view contentMode="scaleToFill" id="iN0-l3-epB">
-            <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
-            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
-            <subviews>
-                <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="1" sectionFooterHeight="1" translatesAutoresizingMaskIntoConstraints="NO" id="ery-9I-YtQ">
-                    <rect key="frame" x="0.0" y="0.0" width="375" height="574"/>
-                    <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                    <inset key="separatorInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
-                    <connections>
-                        <outlet property="dataSource" destination="-1" id="duy-tx-I68"/>
-                        <outlet property="delegate" destination="-1" id="rtA-Tw-6Cd"/>
-                    </connections>
-                </tableView>
-                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="1FZ-9n-QPA">
-                    <rect key="frame" x="20.5" y="603" width="334" height="44"/>
-                    <constraints>
-                        <constraint firstAttribute="width" constant="334" id="5Vx-Rl-7g1"/>
-                        <constraint firstAttribute="height" constant="44" id="Ugr-39-bSh"/>
-                    </constraints>
-                    <state key="normal" image="woyaobaoming"/>
-                    <connections>
-                        <action selector="sign:" destination="-1" eventType="touchUpInside" id="CBk-ZB-TIq"/>
-                    </connections>
-                </button>
-                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="立即报名" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="KAl-LX-czT">
-                    <rect key="frame" x="137.5" y="615" width="100" height="21"/>
-                    <constraints>
-                        <constraint firstAttribute="width" constant="100" id="C7D-7C-mDf"/>
-                        <constraint firstAttribute="height" constant="21" id="rYF-Q0-CGp"/>
-                    </constraints>
-                    <fontDescription key="fontDescription" type="system" pointSize="16"/>
-                    <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
-                    <nil key="highlightedColor"/>
-                </label>
-                <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="jiazai" translatesAutoresizingMaskIntoConstraints="NO" id="88e-sd-55f">
-                    <rect key="frame" x="0.0" y="0.0" width="375" height="375"/>
-                    <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
-                </imageView>
-            </subviews>
-            <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
-            <constraints>
-                <constraint firstItem="ery-9I-YtQ" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="04m-C2-FNu"/>
-                <constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="KAl-LX-czT" secondAttribute="bottom" constant="31" id="2WM-1f-zjV"/>
-                <constraint firstItem="KAl-LX-czT" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="7jR-hb-Xxn"/>
-                <constraint firstItem="ery-9I-YtQ" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="A8S-m1-5Db"/>
-                <constraint firstItem="ery-9I-YtQ" firstAttribute="trailing" secondItem="vUN-kp-3ea" secondAttribute="trailing" id="DOd-QI-hbI"/>
-                <constraint firstItem="ery-9I-YtQ" firstAttribute="trailing" secondItem="vUN-kp-3ea" secondAttribute="trailing" id="GST-C3-MwT"/>
-                <constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="1FZ-9n-QPA" secondAttribute="bottom" constant="20" id="Hiw-IC-Xb9"/>
-                <constraint firstItem="ery-9I-YtQ" firstAttribute="bottom" secondItem="vUN-kp-3ea" secondAttribute="bottom" constant="-93" id="XYZ-L1-5sf"/>
-                <constraint firstItem="ery-9I-YtQ" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="aoF-xh-GwT"/>
-                <constraint firstItem="1FZ-9n-QPA" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="tzH-em-pis"/>
-            </constraints>
-            <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
-            <point key="canvasLocation" x="-152.5" y="90.5"/>
-        </view>
-    </objects>
-    <resources>
-        <image name="jiazai" width="375" height="375"/>
-        <image name="woyaobaoming" width="334" height="44"/>
-    </resources>
-</document>

+ 18 - 0
model/Classes/Controllers/ActivityVC/UploadVideoViewController.h

@@ -0,0 +1,18 @@
+//
+//  UploadVideoViewController.h
+//  model
+//
+//  Created by Drew on 2019/1/3.
+//  Copyright © 2019 Mine. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface UploadVideoViewController : UIViewController
+@property(strong, nonatomic) ActivityModel *model;
+@property(strong, nonatomic) NSString *activitypk;
+@end
+
+NS_ASSUME_NONNULL_END

+ 208 - 0
model/Classes/Controllers/ActivityVC/UploadVideoViewController.m

@@ -0,0 +1,208 @@
+//
+//  UploadVideoViewController.m
+//  model
+//
+//  Created by Drew on 2019/1/3.
+//  Copyright © 2019 Mine. All rights reserved.
+//
+
+#import <MediaPlayer/MediaPlayer.h>
+#import "UploadVideoViewController.h"
+#import "TOCropViewController.h"
+#import <Photos/Photos.h>
+#import "AFNetworking.h"
+
+@interface UploadVideoViewController () <UIImagePickerControllerDelegate, UINavigationControllerDelegate, TOCropViewControllerDelegate>
+@property(weak, nonatomic) IBOutlet UITextField *descText;
+@property(weak, nonatomic) IBOutlet UIButton *playBtn;
+@property(weak, nonatomic) IBOutlet UIButton *coverBtn;
+@property(weak, nonatomic) IBOutlet UIImageView *coverImg;
+@property(weak, nonatomic) IBOutlet UIView *videoView;
+@property(nonatomic, strong) NSURL *videoURL;
+@property(nonatomic, strong) NSString *isSgined;
+@end
+
+@implementation UploadVideoViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    self.navigationItem.title = @"活动报名";
+    UIBarButtonItem *leftItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"icon_back"] style:UIBarButtonItemStylePlain target:self action:@selector(backClick)];
+    self.navigationItem.leftBarButtonItem = leftItem;
+    self.navigationController.navigationBar.tintColor = [UIColor blackColor];
+
+    [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
+        switch (status) {
+            case PHAuthorizationStatusAuthorized:
+                NSLog(@"PHAuthorizationStatusAuthorized");
+                break;
+            case PHAuthorizationStatusDenied:
+                NSLog(@"PHAuthorizationStatusDenied");
+                break;
+            case PHAuthorizationStatusNotDetermined:
+                NSLog(@"PHAuthorizationStatusNotDetermined");
+                break;
+            case PHAuthorizationStatusRestricted:
+                NSLog(@"PHAuthorizationStatusRestricted");
+                break;
+        }
+    }];
+
+    self.isSgined = @"0";
+
+    NSString *url = [NSString stringWithFormat:@"%@/activity?action=getactivity&pk=%@&memberpk=%@", PublicUrl, self.activitypk, [Helper sharedAccount].accid];
+    [[AHHttpManager sharedManager]
+            POST:url
+      parameters:nil
+         success:^(id responseObject) {
+             if ([@"success" isEqualToString:responseObject[@"msg"]]) {
+                 NSDictionary *data = responseObject[@"data"];
+                 self.isSgined = [data[@"signed"] stringValue];
+                 ActivityModel *model = [[ActivityModel alloc] initWithDictionary:data error:nil];
+                 self.model = model;
+             }
+         }
+         failure:^(NSError *error) {
+
+         }];
+}
+
+- (void)backClick {
+    [self.navigationController popViewControllerAnimated:YES];
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+    [super viewWillAppear:animated];
+    self.navigationController.navigationBar.tintColor = [UIColor blackColor];
+    [self.navigationController.navigationBar setShadowImage:[UIImage new]];
+    [self.navigationController.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
+}
+
+- (void)viewWillDisappear:(BOOL)animated {
+    [super viewWillDisappear:animated];
+    [self.navigationController.navigationBar setBackgroundImage:nil forBarMetrics:UIBarMetricsDefault];
+    [self.navigationController.navigationBar setShadowImage:nil];
+}
+
+- (IBAction)chooseVideo:(id)sender {
+    UIImagePickerController *pickerController = [[UIImagePickerController alloc] init];
+    pickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
+    pickerController.mediaTypes = @[(NSString *) kUTTypeMovie];
+    pickerController.allowsEditing = YES;
+    pickerController.videoQuality = UIImagePickerControllerQualityTypeMedium;//视频质量
+    pickerController.videoMaximumDuration = 30.f;//视频最长长度
+    pickerController.delegate = self;
+    [self.navigationController presentViewController:pickerController animated:YES completion:nil];
+}
+
+- (IBAction)chooseCover:(id)sender {
+    UIImagePickerController *pickerController = [[UIImagePickerController alloc] init];
+    pickerController.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;//图片分组列表样式
+    pickerController.delegate = self;
+    [self.navigationController presentViewController:pickerController animated:YES completion:nil];
+}
+
+- (IBAction)play:(id)sender {
+    if (self.videoURL) {
+        MPMoviePlayerViewController *playerViewController = [[MPMoviePlayerViewController alloc] initWithContentURL:self.videoURL];
+        playerViewController.moviePlayer.scalingMode = MPMovieScalingModeAspectFit;
+        [self presentMoviePlayerViewControllerAnimated:playerViewController];
+    }
+}
+
+- (IBAction)confirm:(id)sender {
+    if (!self.model) {
+        return;
+    }
+    if ([self.isSgined intValue] > 0) {
+        [MBProgressHUD showInfo:@"请勿重复报名"];
+        return;
+    }
+    if (!self.videoURL) {
+        [MBProgressHUD showInfo:@"请选择视频"];
+    } else if (!self.descText.text.length) {
+        [MBProgressHUD showInfo:@"请填写描述"];
+    } else {
+        MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
+        hud.label.text = @"正在上传";
+        hud.mode = MBProgressHUDModeDeterminateHorizontalBar;
+        hud.removeFromSuperViewOnHide = YES;
+
+        NSString *parttypk = self.model.pk;
+        NSString *pk = [ModelUser user].pk;
+        NSString *url = [NSString stringWithFormat:@"%@/activity?action=doEnroll&PK=%@&ParttyPK=%@&people=%@&Tel=%@&pCount=%@&Fee=%@&desc=%@", PublicUrl, pk, parttypk, [ModelUser user].pet, @"", @"1", @"0", self.descText.text];
+        AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
+        manager.responseSerializer = [AFHTTPResponseSerializer serializer];
+        manager.requestSerializer = [AFJSONRequestSerializer serializer];
+        manager.securityPolicy.allowInvalidCertificates = YES;
+        [manager POST:[url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] parameters:@{} constructingBodyWithBlock:^(id <AFMultipartFormData> formData) {
+            NSData *imgData = UIImageJPEGRepresentation(self.coverImg.image, 0.5);
+            [formData appendPartWithFormData:imgData name:@"image"];
+            [formData appendPartWithFileData:imgData name:@"image" fileName:@"image.jpg" mimeType:@"image/jpeg"];
+            NSData *videoData = [NSData dataWithContentsOfURL:self.videoURL];
+            [formData appendPartWithFileData:videoData name:@"video" fileName:@"video.mov" mimeType:@"video/quicktime"];
+        }    progress:^(NSProgress *uploadProgress) {
+            NSLog(@"progress %lli / %lli", uploadProgress.completedUnitCount, uploadProgress.totalUnitCount);
+            hud.progress = uploadProgress.completedUnitCount / (float) uploadProgress.totalUnitCount;
+        }     success:^(NSURLSessionDataTask *task, id responseObject) {
+            [hud hideAnimated:YES];
+            [MBProgressHUD showInfo:@"报名成功"];
+            [self.navigationController popToRootViewControllerAnimated:YES];
+        }     failure:^(NSURLSessionDataTask *task, NSError *error) {
+            [hud hideAnimated:YES];
+            NSLog(@"error:%@", error);
+            [MBProgressHUD showInfo:@"上传失败,请稍后再试"];
+        }];
+    }
+
+}
+
+- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey, id> *)info {
+    if (picker.sourceType == UIImagePickerControllerSourceTypeSavedPhotosAlbum) {
+        UIImage *image = info[UIImagePickerControllerOriginalImage];
+        TOCropViewController *cropViewController = [[TOCropViewController alloc] initWithImage:image];
+        cropViewController.delegate = self;
+        cropViewController.customAspectRatio = CGSizeMake(300, 300);
+        cropViewController.aspectRatioLockEnabled = YES;
+        cropViewController.resetAspectRatioEnabled = NO;
+        [picker dismissViewControllerAnimated:YES completion:nil];
+        [self presentViewController:cropViewController animated:YES completion:nil];
+    } else {
+        NSURL *url = info[UIImagePickerControllerMediaURL];//获得视频的URL
+        NSLog(@"url %@", url);
+        self.videoURL = url;
+        // MPMoviePlayerController *playerController = [[MPMoviePlayerController alloc] initWithContentURL:url];
+        // playerController.view.frame = CGRectMake(0, 0, self.videoView.frame.size.width, self.videoView.frame.size.height);
+        // [self.videoView addSubview:playerController.view];//第四步:设置播放器属性
+        // playerController.controlStyle = MPMovieControlStyleDefault;
+        // playerController.shouldAutoplay = NO;
+        // playerController.scalingMode = MPMovieScalingModeAspectFit;
+        // playerController.repeatMode = MPMovieRepeatModeOne;
+        // [playerController prepareToPlay];
+        // [playerController play];
+        [picker dismissViewControllerAnimated:YES completion:nil];
+
+        AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:url options:nil];
+        CMTime audioDuration = asset.duration;
+        NSNumber *duration = @([@(CMTimeGetSeconds(audioDuration)) intValue]);
+        AVAssetImageGenerator *gen = [[AVAssetImageGenerator alloc] initWithAsset:asset];
+        gen.appliesPreferredTrackTransform = YES;
+        CMTime time = CMTimeMakeWithSeconds(0.0, 600);
+        NSError *error = nil;
+        CMTime actualTime;
+        CGImageRef image = [gen copyCGImageAtTime:time actualTime:&actualTime error:&error];
+        UIImage *thumbnail = [[UIImage alloc] initWithCGImage:image];
+        CGImageRelease(image);
+        self.coverImg.image = thumbnail;
+    }
+}
+
+- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
+    [picker dismissViewControllerAnimated:YES completion:nil];
+}
+
+- (void)cropViewController:(TOCropViewController *)cropViewController didCropToImage:(UIImage *)image withRect:(CGRect)cropRect angle:(NSInteger)angle {
+    self.coverImg.image = image;
+    [cropViewController dismissViewControllerAnimated:YES completion:nil];
+}
+@end

+ 206 - 0
model/Classes/Controllers/ActivityVC/UploadVideoViewController.xib

@@ -0,0 +1,206 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+    <device id="retina4_7" orientation="portrait">
+        <adaptation id="fullscreen"/>
+    </device>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.14"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="UploadVideoViewController">
+            <connections>
+                <outlet property="coverBtn" destination="VX2-dq-LY2" id="Lcz-J7-A1k"/>
+                <outlet property="coverImg" destination="gwn-uz-mb3" id="8EU-Ll-kiL"/>
+                <outlet property="descText" destination="428-xi-hYF" id="B77-fE-RPA"/>
+                <outlet property="playBtn" destination="Vf9-Sv-Kvm" id="5C9-xh-ugE"/>
+                <outlet property="videoView" destination="25h-8w-VAE" id="JIJ-Ki-RzI"/>
+                <outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
+            </connections>
+        </placeholder>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
+            <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+            <subviews>
+                <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="bbH-5I-OCD">
+                    <rect key="frame" x="0.0" y="20" width="375" height="647"/>
+                    <subviews>
+                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kZR-XD-Mqh">
+                            <rect key="frame" x="0.0" y="0.0" width="375" height="526.5"/>
+                            <subviews>
+                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="上传视频,时长30秒之内" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3He-v3-Kti">
+                                    <rect key="frame" x="20" y="15" width="160.5" height="17"/>
+                                    <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                                    <color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                                    <nil key="highlightedColor"/>
+                                </label>
+                                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="gfT-jx-WqX">
+                                    <rect key="frame" x="289" y="9.5" width="66" height="28"/>
+                                    <color key="backgroundColor" red="1" green="0.25098039220000001" blue="0.58431372550000005" alpha="1" colorSpace="calibratedRGB"/>
+                                    <constraints>
+                                        <constraint firstAttribute="width" constant="66" id="W3O-Fc-a9t"/>
+                                        <constraint firstAttribute="height" constant="28" id="nQC-7V-VfG"/>
+                                    </constraints>
+                                    <fontDescription key="fontDescription" type="system" pointSize="11"/>
+                                    <state key="normal" title="选择视频">
+                                        <color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                    </state>
+                                    <userDefinedRuntimeAttributes>
+                                        <userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
+                                            <integer key="value" value="14"/>
+                                        </userDefinedRuntimeAttribute>
+                                    </userDefinedRuntimeAttributes>
+                                    <connections>
+                                        <action selector="chooseVideo:" destination="-1" eventType="touchUpInside" id="1Xl-4Q-MUv"/>
+                                    </connections>
+                                </button>
+                                <view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="25h-8w-VAE">
+                                    <rect key="frame" x="20" y="47.5" width="335" height="335"/>
+                                    <subviews>
+                                        <imageView userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="gwn-uz-mb3">
+                                            <rect key="frame" x="0.0" y="0.0" width="335" height="335"/>
+                                        </imageView>
+                                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="VX2-dq-LY2">
+                                            <rect key="frame" x="259" y="297" width="66" height="28"/>
+                                            <color key="backgroundColor" red="1" green="0.25098039220000001" blue="0.58431372550000005" alpha="1" colorSpace="calibratedRGB"/>
+                                            <constraints>
+                                                <constraint firstAttribute="width" constant="66" id="sJM-cv-mGE"/>
+                                                <constraint firstAttribute="height" constant="28" id="wTJ-zB-nI6"/>
+                                            </constraints>
+                                            <fontDescription key="fontDescription" type="system" pointSize="11"/>
+                                            <state key="normal" title="更换封面">
+                                                <color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                            </state>
+                                            <userDefinedRuntimeAttributes>
+                                                <userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
+                                                    <integer key="value" value="14"/>
+                                                </userDefinedRuntimeAttribute>
+                                            </userDefinedRuntimeAttributes>
+                                            <connections>
+                                                <action selector="chooseCover:" destination="-1" eventType="touchUpInside" id="Ap8-re-GaQ"/>
+                                            </connections>
+                                        </button>
+                                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Vf9-Sv-Kvm">
+                                            <rect key="frame" x="142.5" y="142.5" width="50" height="50"/>
+                                            <state key="normal" image="icon_bofang"/>
+                                            <connections>
+                                                <action selector="play:" destination="-1" eventType="touchUpInside" id="KTT-Ub-zyz"/>
+                                            </connections>
+                                        </button>
+                                    </subviews>
+                                    <color key="backgroundColor" red="0.67843137254901964" green="0.67843137254901964" blue="0.67843137254901964" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                                    <constraints>
+                                        <constraint firstAttribute="bottom" secondItem="VX2-dq-LY2" secondAttribute="bottom" constant="10" id="3eO-po-kX6"/>
+                                        <constraint firstItem="Vf9-Sv-Kvm" firstAttribute="centerY" secondItem="25h-8w-VAE" secondAttribute="centerY" id="4I3-Hy-uSc"/>
+                                        <constraint firstAttribute="bottom" secondItem="gwn-uz-mb3" secondAttribute="bottom" id="8Ef-tO-V5N"/>
+                                        <constraint firstAttribute="width" secondItem="25h-8w-VAE" secondAttribute="height" multiplier="1:1" id="8QK-57-wP5"/>
+                                        <constraint firstItem="gwn-uz-mb3" firstAttribute="top" secondItem="25h-8w-VAE" secondAttribute="top" id="Psk-yy-CX1"/>
+                                        <constraint firstItem="Vf9-Sv-Kvm" firstAttribute="centerX" secondItem="25h-8w-VAE" secondAttribute="centerX" id="Rms-kQ-2oH"/>
+                                        <constraint firstAttribute="trailing" secondItem="gwn-uz-mb3" secondAttribute="trailing" id="tki-36-Ypd"/>
+                                        <constraint firstAttribute="trailing" secondItem="VX2-dq-LY2" secondAttribute="trailing" constant="10" id="txt-s3-NvT"/>
+                                        <constraint firstItem="gwn-uz-mb3" firstAttribute="leading" secondItem="25h-8w-VAE" secondAttribute="leading" id="wft-k3-1Hg"/>
+                                    </constraints>
+                                    <userDefinedRuntimeAttributes>
+                                        <userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
+                                            <integer key="value" value="8"/>
+                                        </userDefinedRuntimeAttribute>
+                                    </userDefinedRuntimeAttributes>
+                                </view>
+                                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Ka1-5H-MRl">
+                                    <rect key="frame" x="20" y="402.5" width="335" height="60"/>
+                                    <subviews>
+                                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="视频描述" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9PI-nj-uvQ">
+                                            <rect key="frame" x="15" y="21.5" width="57.5" height="17"/>
+                                            <fontDescription key="fontDescription" type="system" weight="medium" pointSize="14"/>
+                                            <nil key="textColor"/>
+                                            <nil key="highlightedColor"/>
+                                        </label>
+                                        <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="428-xi-hYF">
+                                            <rect key="frame" x="92.5" y="21.5" width="222.5" height="17"/>
+                                            <nil key="textColor"/>
+                                            <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                                            <textInputTraits key="textInputTraits"/>
+                                        </textField>
+                                    </subviews>
+                                    <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                    <constraints>
+                                        <constraint firstItem="428-xi-hYF" firstAttribute="leading" secondItem="9PI-nj-uvQ" secondAttribute="trailing" constant="20" id="AwK-2c-5Dx"/>
+                                        <constraint firstItem="428-xi-hYF" firstAttribute="centerY" secondItem="Ka1-5H-MRl" secondAttribute="centerY" id="EzO-td-GkF"/>
+                                        <constraint firstItem="9PI-nj-uvQ" firstAttribute="centerY" secondItem="Ka1-5H-MRl" secondAttribute="centerY" id="FoP-SU-4GU"/>
+                                        <constraint firstAttribute="trailing" secondItem="428-xi-hYF" secondAttribute="trailing" constant="20" id="eca-Pm-3Im"/>
+                                        <constraint firstAttribute="height" constant="60" id="qTJ-Kl-ZOW"/>
+                                        <constraint firstItem="9PI-nj-uvQ" firstAttribute="leading" secondItem="Ka1-5H-MRl" secondAttribute="leading" constant="15" id="x8r-9p-4cr"/>
+                                    </constraints>
+                                    <userDefinedRuntimeAttributes>
+                                        <userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
+                                            <integer key="value" value="8"/>
+                                        </userDefinedRuntimeAttribute>
+                                    </userDefinedRuntimeAttributes>
+                                </view>
+                            </subviews>
+                            <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                            <constraints>
+                                <constraint firstAttribute="trailing" secondItem="gfT-jx-WqX" secondAttribute="trailing" constant="20" id="0aR-0o-YAh"/>
+                                <constraint firstAttribute="trailing" secondItem="25h-8w-VAE" secondAttribute="trailing" constant="20" id="30G-IJ-wLE"/>
+                                <constraint firstItem="3He-v3-Kti" firstAttribute="leading" secondItem="kZR-XD-Mqh" secondAttribute="leading" constant="20" id="4oj-Nb-J45"/>
+                                <constraint firstAttribute="trailing" secondItem="Ka1-5H-MRl" secondAttribute="trailing" constant="20" id="B5w-VZ-o5Z"/>
+                                <constraint firstItem="gfT-jx-WqX" firstAttribute="centerY" secondItem="3He-v3-Kti" secondAttribute="centerY" id="Cu8-qi-dVg"/>
+                                <constraint firstItem="25h-8w-VAE" firstAttribute="top" secondItem="gfT-jx-WqX" secondAttribute="bottom" constant="10" id="GBL-4Y-l0y"/>
+                                <constraint firstItem="3He-v3-Kti" firstAttribute="top" secondItem="kZR-XD-Mqh" secondAttribute="top" constant="15" id="MeV-J8-Uqj"/>
+                                <constraint firstAttribute="bottom" secondItem="Ka1-5H-MRl" secondAttribute="bottom" constant="64" id="SJP-Yg-UWp"/>
+                                <constraint firstItem="Ka1-5H-MRl" firstAttribute="leading" secondItem="kZR-XD-Mqh" secondAttribute="leading" constant="20" id="jth-oR-z1n"/>
+                                <constraint firstItem="Ka1-5H-MRl" firstAttribute="top" secondItem="25h-8w-VAE" secondAttribute="bottom" constant="20" id="k3f-zb-ngl"/>
+                                <constraint firstItem="25h-8w-VAE" firstAttribute="leading" secondItem="kZR-XD-Mqh" secondAttribute="leading" constant="20" id="tjK-ZC-NEZ"/>
+                            </constraints>
+                        </view>
+                    </subviews>
+                    <constraints>
+                        <constraint firstAttribute="trailing" secondItem="kZR-XD-Mqh" secondAttribute="trailing" id="8UC-lO-RBr"/>
+                        <constraint firstItem="kZR-XD-Mqh" firstAttribute="top" secondItem="bbH-5I-OCD" secondAttribute="top" id="Htk-JG-hAr"/>
+                        <constraint firstItem="kZR-XD-Mqh" firstAttribute="leading" secondItem="bbH-5I-OCD" secondAttribute="leading" id="Pq2-TC-u23"/>
+                        <constraint firstAttribute="bottom" secondItem="kZR-XD-Mqh" secondAttribute="bottom" id="u6j-sX-IXn"/>
+                    </constraints>
+                </scrollView>
+                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="E0a-5b-KfF">
+                    <rect key="frame" x="20" y="613" width="335" height="44"/>
+                    <color key="backgroundColor" red="1" green="0.25098039220000001" blue="0.58431372550000005" alpha="1" colorSpace="calibratedRGB"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="44" id="YJz-Xl-9YW"/>
+                    </constraints>
+                    <fontDescription key="fontDescription" type="system" weight="medium" pointSize="16"/>
+                    <state key="normal" title="确认报名">
+                        <color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                    </state>
+                    <userDefinedRuntimeAttributes>
+                        <userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
+                            <integer key="value" value="22"/>
+                        </userDefinedRuntimeAttribute>
+                    </userDefinedRuntimeAttributes>
+                    <connections>
+                        <action selector="confirm:" destination="-1" eventType="touchUpInside" id="WRD-PX-g66"/>
+                    </connections>
+                </button>
+            </subviews>
+            <color key="backgroundColor" red="0.94901960784313721" green="0.95686274509803915" blue="0.96078431372549022" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <constraints>
+                <constraint firstItem="kZR-XD-Mqh" firstAttribute="width" secondItem="i5M-Pr-FkT" secondAttribute="width" id="3QG-WA-LjW"/>
+                <constraint firstItem="bbH-5I-OCD" firstAttribute="trailing" secondItem="Q5M-cg-NOt" secondAttribute="trailing" id="AuB-AS-Eus"/>
+                <constraint firstItem="bbH-5I-OCD" firstAttribute="leading" secondItem="Q5M-cg-NOt" secondAttribute="leading" id="BSs-xv-4ii"/>
+                <constraint firstItem="bbH-5I-OCD" firstAttribute="top" secondItem="Q5M-cg-NOt" secondAttribute="top" id="CCT-DM-L5b"/>
+                <constraint firstItem="Q5M-cg-NOt" firstAttribute="bottom" secondItem="E0a-5b-KfF" secondAttribute="bottom" constant="10" id="Fbh-D7-4V5"/>
+                <constraint firstItem="bbH-5I-OCD" firstAttribute="bottom" secondItem="Q5M-cg-NOt" secondAttribute="bottom" id="Kpl-7e-ZUR"/>
+                <constraint firstItem="Q5M-cg-NOt" firstAttribute="trailing" secondItem="E0a-5b-KfF" secondAttribute="trailing" constant="20" id="Mht-Km-wqC"/>
+                <constraint firstItem="kZR-XD-Mqh" firstAttribute="height" secondItem="i5M-Pr-FkT" secondAttribute="height" priority="250" id="khE-wI-t76"/>
+                <constraint firstItem="E0a-5b-KfF" firstAttribute="leading" secondItem="Q5M-cg-NOt" secondAttribute="leading" constant="20" id="uWI-ML-2MT"/>
+            </constraints>
+            <viewLayoutGuide key="safeArea" id="Q5M-cg-NOt"/>
+            <point key="canvasLocation" x="132" y="154.27286356821591"/>
+        </view>
+    </objects>
+    <resources>
+        <image name="icon_bofang" width="50" height="50"/>
+    </resources>
+</document>

+ 1 - 1
model/Classes/Controllers/SettingsVC/SettingsViewController.m

@@ -144,7 +144,7 @@
         
         self.avatar.image = smallPhoto;
         NSDictionary *dic =[NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers| NSJSONReadingMutableLeaves error:nil];
-        NSString * img = [dic objectForKey:@"img"];
+        NSString * img = dic[@"img"];
         [[NIMSDK sharedSDK].userManager updateMyUserInfo:@{@(NIMUserInfoUpdateTagAvatar) : [NSString stringWithFormat:@"%@%@",imageURl,img]} completion:nil];
         
     } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {

+ 13 - 0
model/Classes/Public/WeakScriptMessageDelegate.h

@@ -0,0 +1,13 @@
+//
+// Created by Drew on 2018/12/27.
+// Copyright (c) 2018 MUMEI. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <WebKit/WebKit.h>
+
+
+@interface WeakScriptMessageDelegate : NSObject <WKScriptMessageHandler>
+@property(nonatomic, assign) id <WKScriptMessageHandler> scriptDelegate;
+- (instancetype)initWithDelegate:(id <WKScriptMessageHandler>)scriptDelegate;
+@end

+ 24 - 0
model/Classes/Public/WeakScriptMessageDelegate.m

@@ -0,0 +1,24 @@
+//
+// Created by Drew on 2018/12/27.
+// Copyright (c) 2018 MUMEI. All rights reserved.
+//
+
+#import "WeakScriptMessageDelegate.h"
+
+
+@implementation WeakScriptMessageDelegate {
+
+}
+- (instancetype)initWithDelegate:(id <WKScriptMessageHandler>)scriptDelegate {
+    self = [super init];
+    if (self) {
+        _scriptDelegate = scriptDelegate;
+    }
+    return self;
+}
+
+- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
+    [self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message];
+}
+
+@end

+ 19 - 0
model/Classes/Public/WebViewController.h

@@ -0,0 +1,19 @@
+//
+//  WebViewController.h
+//  千模
+//
+//  Created by Drew on 2018/12/19.
+//  Copyright © 2018 MUMEI. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface WebViewController : UIViewController
+@property(nonatomic, strong) NSString *url;
+@property(nonatomic, strong) NSString *activityPK;
+@property(nonatomic, strong) NSString *modelPK;
+@end
+
+NS_ASSUME_NONNULL_END

+ 123 - 0
model/Classes/Public/WebViewController.m

@@ -0,0 +1,123 @@
+//
+//  WebViewController.m
+//  千模
+//
+//  Created by Drew on 2018/12/19.
+//  Copyright © 2018 MUMEI. All rights reserved.
+//
+
+#import "WebViewController.h"
+#import "Masonry.h"
+#import <WebKit/WebKit.h>
+#import <WXApi.h>
+#import "SharePopViewController.h"
+#import "WeakScriptMessageDelegate.h"
+#import "UploadVideoViewController.h"
+
+@interface WebViewController () <ShareDelegate, WKUIDelegate, WKScriptMessageHandler>
+@property(nonatomic, strong) WKWebView *webView;
+@end
+
+@implementation WebViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    UIBarButtonItem *leftItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"icon_back"] style:UIBarButtonItemStylePlain target:self action:@selector(backClick)];
+    self.navigationItem.leftBarButtonItem = leftItem;
+    UIBarButtonItem *rightItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"icon_share"] style:UIBarButtonItemStylePlain target:self action:@selector(share)];
+    self.navigationItem.rightBarButtonItem = rightItem;
+    self.navigationController.navigationBar.tintColor = [UIColor blackColor];
+
+    WKWebView *webView = [[WKWebView alloc] init];
+    [self.view addSubview:webView];
+    [webView mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.equalTo(self.view.mas_left);
+        make.right.equalTo(self.view.mas_right);
+        make.top.equalTo(self.mas_topLayoutGuide);
+        make.bottom.equalTo(self.mas_bottomLayoutGuide);
+    }];
+    webView.UIDelegate = self;
+    [[webView configuration].userContentController addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"chmo"];
+
+    [webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:NULL];
+    if ([self.url containsString:@"?"]) {
+        [NSString stringWithFormat:@"%@&memberpk=%@&activitypk=%@", self.url, [Helper sharedAccount].accid, self.activityPK];
+    } else {
+        self.url = [NSString stringWithFormat:@"%@?memberpk=%@&activitypk=%@", self.url, [Helper sharedAccount].accid, self.activityPK];
+    }
+    if (self.modelPK) {
+        self.url = [self.url stringByAppendingFormat:@"&modelpk=%@", self.modelPK];
+    }
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.url]]];
+    self.webView = webView;
+}
+
+- (void)backClick {
+    [self.navigationController popViewControllerAnimated:YES];
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+    [super viewWillAppear:animated];
+    [self.navigationController setNavigationBarHidden:NO animated:YES];
+    self.navigationController.navigationBar.tintColor = [UIColor blackColor];
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
+    if ([keyPath isEqualToString:@"title"]) {
+        if (object == self.webView) {
+            self.title = self.webView.title;
+        } else {
+            [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
+        }
+    } else {
+        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
+    }
+
+}
+
+- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
+    NSLog(@"name = %@, body = %@", message.name, message.body);
+    NSString *action = message.body[@"action"];
+    if ([@"signUp" isEqualToString:action]) {
+        NSString *activitypk = message.body[@"activitypk"];
+        UploadVideoViewController *vc = [[UploadVideoViewController alloc] init];
+        vc.activitypk = activitypk;
+        [self.navigationController pushViewController:vc animated:YES];
+    }
+}
+
+- (void)share {
+    SharePopViewController *shareVC = [[SharePopViewController alloc] init];
+    shareVC.delegate = self;
+    [self presentViewController:shareVC animated:NO completion:nil];
+}
+
+- (void)shareWxSession {
+    [self shareToWechat:WXSceneSession withTitle:@"千模通告" description:self.title url:self.url];
+}
+
+- (void)shareWxTimeline {
+    [self shareToWechat:WXSceneTimeline withTitle:self.title description:self.title url:self.url];
+}
+
+- (void)shareToWechat:(enum WXScene)scene withTitle:(NSString *)title description:(NSString *)description url:(NSString *)url {
+    SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init];
+    req.bText = NO;
+    req.scene = scene;
+    WXMediaMessage *urlMessage = [WXMediaMessage message];
+    urlMessage.title = title;
+    urlMessage.description = description;
+    [urlMessage setThumbImage:[UIImage imageNamed:@"share_icon"]];
+    WXWebpageObject *webObj = [WXWebpageObject object];
+    webObj.webpageUrl = url;
+    urlMessage.mediaObject = webObj;
+    req.message = urlMessage;
+    [WXApi sendReq:req];
+}
+
+- (void)dealloc {
+    [[self.webView configuration].userContentController removeScriptMessageHandlerForName:@"chmo"];
+    [self.webView removeObserver:self forKeyPath:@"title"];
+}
+
+@end

+ 1 - 1
model/Info.plist

@@ -19,7 +19,7 @@
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
-	<string>1.0.6</string>
+	<string>1.0.7</string>
 	<key>CFBundleURLTypes</key>
 	<array>
 		<dict>

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů