@onekeyfe/react-native-image-crop-picker 3.0.147

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -0
  3. package/ReactNativeImageCropPicker.podspec +40 -0
  4. package/android/CMakeLists.txt +24 -0
  5. package/android/build.gradle +144 -0
  6. package/android/gradle.properties +4 -0
  7. package/android/src/main/AndroidManifest.xml +24 -0
  8. package/android/src/main/cpp/cpp-adapter.cpp +6 -0
  9. package/android/src/main/java/com/margelo/nitro/reactnativeimagecroppicker/ImageCropPickerException.kt +45 -0
  10. package/android/src/main/java/com/margelo/nitro/reactnativeimagecroppicker/ImageCropPickerImageProcessor.kt +308 -0
  11. package/android/src/main/java/com/margelo/nitro/reactnativeimagecroppicker/ImageCropPickerSession.kt +297 -0
  12. package/android/src/main/java/com/margelo/nitro/reactnativeimagecroppicker/ReactNativeImageCropPicker.kt +69 -0
  13. package/android/src/main/java/com/margelo/nitro/reactnativeimagecroppicker/ReactNativeImageCropPickerPackage.kt +24 -0
  14. package/ios/ImageCropPickerError.swift +57 -0
  15. package/ios/ImageCropPickerImageProcessor.swift +343 -0
  16. package/ios/ImageCropPickerSession.swift +407 -0
  17. package/ios/ReactNativeImageCropPicker.swift +65 -0
  18. package/ios/TOCropViewController/Categories/UIImage+CropRotate.h +39 -0
  19. package/ios/TOCropViewController/Categories/UIImage+CropRotate.m +94 -0
  20. package/ios/TOCropViewController/Constants/TOCropViewConstants.h +63 -0
  21. package/ios/TOCropViewController/LICENSE +21 -0
  22. package/ios/TOCropViewController/Models/TOActivityCroppedImageProvider.h +38 -0
  23. package/ios/TOCropViewController/Models/TOActivityCroppedImageProvider.m +73 -0
  24. package/ios/TOCropViewController/Models/TOCropViewControllerAspectRatioPreset.h +64 -0
  25. package/ios/TOCropViewController/Models/TOCropViewControllerAspectRatioPreset.m +121 -0
  26. package/ios/TOCropViewController/Models/TOCropViewControllerTransitioning.h +49 -0
  27. package/ios/TOCropViewController/Models/TOCropViewControllerTransitioning.m +125 -0
  28. package/ios/TOCropViewController/Models/TOCroppedImageAttributes.h +38 -0
  29. package/ios/TOCropViewController/Models/TOCroppedImageAttributes.m +45 -0
  30. package/ios/TOCropViewController/Resources/Base.lproj/TOCropViewControllerLocalizable.strings +8 -0
  31. package/ios/TOCropViewController/Resources/PrivacyInfo.xcprivacy +14 -0
  32. package/ios/TOCropViewController/Resources/ar.lproj/TOCropViewControllerLocalizable.strings +9 -0
  33. package/ios/TOCropViewController/Resources/ca.lproj/TOCropViewControllerLocalizable.strings +8 -0
  34. package/ios/TOCropViewController/Resources/cs.lproj/TOCropViewControllerLocalizable.strings +8 -0
  35. package/ios/TOCropViewController/Resources/da-DK.lproj/TOCropViewControllerLocalizable.strings +8 -0
  36. package/ios/TOCropViewController/Resources/de.lproj/TOCropViewControllerLocalizable.strings +8 -0
  37. package/ios/TOCropViewController/Resources/en.lproj/TOCropViewControllerLocalizable.strings +8 -0
  38. package/ios/TOCropViewController/Resources/es.lproj/TOCropViewControllerLocalizable.strings +8 -0
  39. package/ios/TOCropViewController/Resources/fa-IR.lproj/TOCropViewControllerLocalizable.strings +8 -0
  40. package/ios/TOCropViewController/Resources/fa.lproj/TOCropViewControllerLocalizable.strings +8 -0
  41. package/ios/TOCropViewController/Resources/fi.lproj/TOCropViewControllerLocalizable.strings +8 -0
  42. package/ios/TOCropViewController/Resources/fr.lproj/TOCropViewControllerLocalizable.strings +8 -0
  43. package/ios/TOCropViewController/Resources/hu.lproj/TOCropViewControllerLocalizable.strings +9 -0
  44. package/ios/TOCropViewController/Resources/id.lproj/TOCropViewControllerLocalizable.strings +8 -0
  45. package/ios/TOCropViewController/Resources/it.lproj/TOCropViewControllerLocalizable.strings +8 -0
  46. package/ios/TOCropViewController/Resources/ja.lproj/TOCropViewControllerLocalizable.strings +8 -0
  47. package/ios/TOCropViewController/Resources/ko.lproj/TOCropViewControllerLocalizable.strings +8 -0
  48. package/ios/TOCropViewController/Resources/ms.lproj/TOCropViewControllerLocalizable.strings +8 -0
  49. package/ios/TOCropViewController/Resources/nl.lproj/TOCropViewControllerLocalizable.strings +8 -0
  50. package/ios/TOCropViewController/Resources/pl.lproj/TOCropViewControllerLocalizable.strings +8 -0
  51. package/ios/TOCropViewController/Resources/pt-BR.lproj/TOCropViewControllerLocalizable.strings +8 -0
  52. package/ios/TOCropViewController/Resources/pt.lproj/TOCropViewControllerLocalizable.strings +8 -0
  53. package/ios/TOCropViewController/Resources/ro.lproj/TOCropViewControllerLocalizable.strings +8 -0
  54. package/ios/TOCropViewController/Resources/ru.lproj/TOCropViewControllerLocalizable.strings +8 -0
  55. package/ios/TOCropViewController/Resources/sk.lproj/TOCropViewControllerLocalizable.strings +8 -0
  56. package/ios/TOCropViewController/Resources/tr.lproj/TOCropViewControllerLocalizable.strings +9 -0
  57. package/ios/TOCropViewController/Resources/uk.lproj/TOCropViewControllerLocalizable.strings +8 -0
  58. package/ios/TOCropViewController/Resources/vi.lproj/TOCropViewControllerLocalizable.strings +9 -0
  59. package/ios/TOCropViewController/Resources/zh-Hans.lproj/TOCropViewControllerLocalizable.strings +9 -0
  60. package/ios/TOCropViewController/Resources/zh-Hant.lproj/TOCropViewControllerLocalizable.strings +9 -0
  61. package/ios/TOCropViewController/TOCropViewController.h +479 -0
  62. package/ios/TOCropViewController/TOCropViewController.m +1355 -0
  63. package/ios/TOCropViewController/Views/TOCropOverlayView.h +43 -0
  64. package/ios/TOCropViewController/Views/TOCropOverlayView.m +240 -0
  65. package/ios/TOCropViewController/Views/TOCropScrollView.h +39 -0
  66. package/ios/TOCropViewController/Views/TOCropScrollView.m +48 -0
  67. package/ios/TOCropViewController/Views/TOCropToolbar.h +104 -0
  68. package/ios/TOCropViewController/Views/TOCropToolbar.m +783 -0
  69. package/ios/TOCropViewController/Views/TOCropView.h +311 -0
  70. package/ios/TOCropViewController/Views/TOCropView.m +1787 -0
  71. package/lib/module/ReactNativeImageCropPicker.nitro.js +4 -0
  72. package/lib/module/ReactNativeImageCropPicker.nitro.js.map +1 -0
  73. package/lib/module/index.js +83 -0
  74. package/lib/module/index.js.map +1 -0
  75. package/lib/module/package.json +1 -0
  76. package/lib/typescript/package.json +1 -0
  77. package/lib/typescript/src/ReactNativeImageCropPicker.nitro.d.ts +54 -0
  78. package/lib/typescript/src/ReactNativeImageCropPicker.nitro.d.ts.map +1 -0
  79. package/lib/typescript/src/index.d.ts +31 -0
  80. package/lib/typescript/src/index.d.ts.map +1 -0
  81. package/nitro.json +23 -0
  82. package/nitrogen/generated/android/c++/JCropRect.hpp +69 -0
  83. package/nitrogen/generated/android/c++/JHybridReactNativeImageCropPickerSpec.cpp +125 -0
  84. package/nitrogen/generated/android/c++/JHybridReactNativeImageCropPickerSpec.hpp +66 -0
  85. package/nitrogen/generated/android/c++/JImageCropPickerOptions.hpp +154 -0
  86. package/nitrogen/generated/android/c++/JPickedImage.hpp +88 -0
  87. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativeimagecroppicker/CropRect.kt +66 -0
  88. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativeimagecroppicker/HybridReactNativeImageCropPickerSpec.kt +69 -0
  89. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativeimagecroppicker/ImageCropPickerOptions.kt +171 -0
  90. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativeimagecroppicker/PickedImage.kt +86 -0
  91. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativeimagecroppicker/reactnativeimagecroppickerOnLoad.kt +35 -0
  92. package/nitrogen/generated/android/reactnativeimagecroppicker+autolinking.cmake +81 -0
  93. package/nitrogen/generated/android/reactnativeimagecroppicker+autolinking.gradle +27 -0
  94. package/nitrogen/generated/android/reactnativeimagecroppickerOnLoad.cpp +54 -0
  95. package/nitrogen/generated/android/reactnativeimagecroppickerOnLoad.hpp +34 -0
  96. package/nitrogen/generated/ios/ReactNativeImageCropPicker+autolinking.rb +62 -0
  97. package/nitrogen/generated/ios/ReactNativeImageCropPicker-Swift-Cxx-Bridge.cpp +57 -0
  98. package/nitrogen/generated/ios/ReactNativeImageCropPicker-Swift-Cxx-Bridge.hpp +221 -0
  99. package/nitrogen/generated/ios/ReactNativeImageCropPicker-Swift-Cxx-Umbrella.hpp +55 -0
  100. package/nitrogen/generated/ios/ReactNativeImageCropPickerAutolinking.mm +33 -0
  101. package/nitrogen/generated/ios/ReactNativeImageCropPickerAutolinking.swift +26 -0
  102. package/nitrogen/generated/ios/c++/HybridReactNativeImageCropPickerSpecSwift.cpp +11 -0
  103. package/nitrogen/generated/ios/c++/HybridReactNativeImageCropPickerSpecSwift.hpp +116 -0
  104. package/nitrogen/generated/ios/swift/CropRect.swift +44 -0
  105. package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  106. package/nitrogen/generated/ios/swift/Func_void_PickedImage.swift +46 -0
  107. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  108. package/nitrogen/generated/ios/swift/HybridReactNativeImageCropPickerSpec.swift +58 -0
  109. package/nitrogen/generated/ios/swift/HybridReactNativeImageCropPickerSpec_cxx.swift +202 -0
  110. package/nitrogen/generated/ios/swift/ImageCropPickerOptions.swift +474 -0
  111. package/nitrogen/generated/ios/swift/PickedImage.swift +96 -0
  112. package/nitrogen/generated/shared/c++/CropRect.hpp +95 -0
  113. package/nitrogen/generated/shared/c++/HybridReactNativeImageCropPickerSpec.cpp +24 -0
  114. package/nitrogen/generated/shared/c++/HybridReactNativeImageCropPickerSpec.hpp +71 -0
  115. package/nitrogen/generated/shared/c++/ImageCropPickerOptions.hpp +180 -0
  116. package/nitrogen/generated/shared/c++/PickedImage.hpp +114 -0
  117. package/package.json +173 -0
  118. package/src/ReactNativeImageCropPicker.nitro.ts +72 -0
  119. package/src/index.tsx +134 -0
@@ -0,0 +1,783 @@
1
+ //
2
+ // TOCropToolbar.h
3
+ //
4
+ // Copyright 2015-2026 Timothy Oliver. All rights reserved.
5
+ //
6
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ // of this software and associated documentation files (the "Software"), to
8
+ // deal in the Software without restriction, including without limitation the
9
+ // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10
+ // sell copies of the Software, and to permit persons to whom the Software is
11
+ // furnished to do so, subject to the following conditions:
12
+ //
13
+ // The above copyright notice and this permission notice shall be included in
14
+ // all copies or substantial portions of the Software.
15
+ //
16
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17
+ // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
21
+ // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ #import "TOCropToolbar.h"
24
+
25
+ #define TOCROPTOOLBAR_DEBUG_SHOWING_BUTTONS_CONTAINER_RECT 0 // convenience debug toggle
26
+
27
+ @interface TOCropToolbar ()
28
+
29
+ @property (nonatomic, strong) UIView *backgroundView;
30
+
31
+ @property (nonatomic, strong, readwrite) UIButton *doneTextButton;
32
+ @property (nonatomic, strong, readwrite) UIButton *doneIconButton;
33
+
34
+ @property (nonatomic, strong, readwrite) UIButton *cancelTextButton;
35
+ @property (nonatomic, strong, readwrite) UIButton *cancelIconButton;
36
+
37
+ @property (nonatomic, strong) UIButton *resetButton;
38
+ @property (nonatomic, strong) UIButton *clampButton;
39
+
40
+ @property (nonatomic, strong) UIButton *rotateButton; // defaults to counterclockwise button for legacy compatibility
41
+
42
+ @property (nonatomic, strong) UIVisualEffectView *glassView;
43
+
44
+ @end
45
+
46
+ @implementation TOCropToolbar
47
+
48
+ - (instancetype)initWithFrame:(CGRect)frame {
49
+ if (self = [super initWithFrame:frame]) {
50
+ [self setup];
51
+ }
52
+
53
+ return self;
54
+ }
55
+
56
+ - (void)setup {
57
+ self.backgroundView = [[UIView alloc] initWithFrame:self.bounds];
58
+
59
+ UIView *containerView = self;
60
+ #ifdef __IPHONE_26_0
61
+ if (@available(iOS 26.0, *)) {
62
+ UIVisualEffect *effect = nil;
63
+ #if !TARGET_OS_VISION
64
+ // We've been getting reports that some devices have been crashing with the error message
65
+ // that `effectWithStyle` was unrecognized. The ONLY way this seems to be possible
66
+ // is if there are users out there using the beta versions of iOS 26.0 before this method
67
+ // was introduced. Either way, we're going to need to manually confirm the selector exists
68
+ // in order to fix these crashes.
69
+
70
+ UIGlassEffect *glassEffect = nil;
71
+ SEL effectSelector = NSSelectorFromString(@"effectWithStyle:");
72
+ if ([[UIGlassEffect class] respondsToSelector:effectSelector]) {
73
+ glassEffect = [UIGlassEffect effectWithStyle:UIGlassEffectStyleClear];
74
+ } else {
75
+ glassEffect = [UIGlassEffect new];
76
+ }
77
+ glassEffect.interactive = YES;
78
+ effect = glassEffect;
79
+ #else
80
+ UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleProminent];
81
+ effect = blurEffect;
82
+ #endif
83
+ _glassView = [[UIVisualEffectView alloc] initWithEffect:effect];
84
+ _glassView.cornerConfiguration = [UICornerConfiguration capsuleConfiguration];
85
+ _glassView.userInteractionEnabled = YES;
86
+ [self addSubview:_glassView];
87
+
88
+ containerView = _glassView.contentView;
89
+ _showOnlyIcons = YES;
90
+ } else {
91
+ self.backgroundView.backgroundColor = [UIColor colorWithWhite:0.12f alpha:1.0f];
92
+ [self addSubview:self.backgroundView];
93
+ }
94
+ #else
95
+ self.backgroundView.backgroundColor = [UIColor colorWithWhite:0.12f alpha:1.0f];
96
+ [self addSubview:self.backgroundView];
97
+ #endif
98
+
99
+ // On iOS 9 and up, we can use the new layout features to determine whether we're in an 'Arabic' style language mode
100
+ _reverseContentLayout = ([UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft);
101
+
102
+ // Get the resource bundle depending on the framework/dependency manager we're using
103
+ NSBundle *resourceBundle = TO_CROP_VIEW_RESOURCE_BUNDLE_FOR_OBJECT(self);
104
+
105
+ if (@available(iOS 26.0, *)) {
106
+ } else {
107
+ _doneTextButton = [UIButton buttonWithType:UIButtonTypeSystem];
108
+ [_doneTextButton setTitle:_doneTextButtonTitle ? _doneTextButtonTitle : NSLocalizedStringFromTableInBundle(@"Done", @"TOCropViewControllerLocalizable", resourceBundle, nil)
109
+ forState:UIControlStateNormal];
110
+ [_doneTextButton setTitleColor:[UIColor colorWithRed:1.0f green:0.8f blue:0.0f alpha:1.0f] forState:UIControlStateNormal];
111
+ [_doneTextButton.titleLabel setFont:[UIFont systemFontOfSize:17.0f weight:UIFontWeightMedium]];
112
+ [_doneTextButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
113
+ [_doneTextButton sizeToFit];
114
+ [self addSubview:_doneTextButton];
115
+ }
116
+
117
+ _doneIconButton = [UIButton buttonWithType:UIButtonTypeSystem];
118
+ [_doneIconButton setImage:[TOCropToolbar doneImage] forState:UIControlStateNormal];
119
+ [_doneIconButton setTintColor:[UIColor colorWithRed:1.0f green:0.8f blue:0.0f alpha:1.0f]];
120
+ [_doneIconButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
121
+ #if defined(__IPHONE_26_0)
122
+ if (@available(iOS 26.0, *)) {
123
+ #if !TARGET_OS_VISION
124
+ UIButtonConfiguration *configuration = [UIButtonConfiguration prominentGlassButtonConfiguration];
125
+ configuration.baseForegroundColor = [UIColor labelColor];
126
+ #else
127
+ UIButtonConfiguration *configuration = [UIButtonConfiguration filledButtonConfiguration];
128
+ #endif
129
+ _doneIconButton.configuration = configuration;
130
+ }
131
+ #endif
132
+ [self addSubview:_doneIconButton];
133
+
134
+ // Set the default color for the done buttons
135
+ self.doneButtonColor = nil;
136
+
137
+ if (@available(iOS 26.0, *)) {
138
+ } else {
139
+ _cancelTextButton = [UIButton buttonWithType:UIButtonTypeSystem];
140
+
141
+ [_cancelTextButton setTitle:_cancelTextButtonTitle ? _cancelTextButtonTitle : NSLocalizedStringFromTableInBundle(@"Cancel", @"TOCropViewControllerLocalizable", resourceBundle, nil)
142
+ forState:UIControlStateNormal];
143
+ [_cancelTextButton.titleLabel setFont:[UIFont systemFontOfSize:17.0f]];
144
+ [_cancelTextButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
145
+ [_cancelTextButton sizeToFit];
146
+ [self addSubview:_cancelTextButton];
147
+ }
148
+
149
+ _cancelIconButton = [UIButton buttonWithType:UIButtonTypeSystem];
150
+ [_cancelIconButton setImage:[TOCropToolbar cancelImage] forState:UIControlStateNormal];
151
+ [_cancelIconButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
152
+ #if defined(__IPHONE_26_0)
153
+ if (@available(iOS 26.0, *)) {
154
+ #if !TARGET_OS_VISION
155
+ UIButtonConfiguration *cancelConfiguration = [UIButtonConfiguration clearGlassButtonConfiguration];
156
+ cancelConfiguration.baseForegroundColor = [UIColor labelColor];
157
+ _cancelIconButton.configuration = cancelConfiguration;
158
+ #else
159
+ _cancelIconButton.configuration = [UIButtonConfiguration filledButtonConfiguration];
160
+ #endif
161
+ }
162
+ #endif
163
+ [self addSubview:_cancelIconButton];
164
+
165
+ _clampButton = [UIButton buttonWithType:UIButtonTypeSystem];
166
+ _clampButton.contentMode = UIViewContentModeCenter;
167
+ _clampButton.tintColor = [UIColor whiteColor];
168
+ [_clampButton setImage:[TOCropToolbar clampImage] forState:UIControlStateNormal];
169
+ [_clampButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
170
+ [containerView addSubview:_clampButton];
171
+
172
+ _rotateCounterclockwiseButton = [UIButton buttonWithType:UIButtonTypeSystem];
173
+ _rotateCounterclockwiseButton.contentMode = UIViewContentModeCenter;
174
+ _rotateCounterclockwiseButton.tintColor = [UIColor whiteColor];
175
+ [_rotateCounterclockwiseButton setImage:[TOCropToolbar rotateCCWImage] forState:UIControlStateNormal];
176
+ [_rotateCounterclockwiseButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
177
+ [containerView addSubview:_rotateCounterclockwiseButton];
178
+
179
+ _rotateClockwiseButton = [UIButton buttonWithType:UIButtonTypeSystem];
180
+ _rotateClockwiseButton.contentMode = UIViewContentModeCenter;
181
+ _rotateClockwiseButton.tintColor = [UIColor whiteColor];
182
+ [_rotateClockwiseButton setImage:[TOCropToolbar rotateCWImage] forState:UIControlStateNormal];
183
+ [_rotateClockwiseButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
184
+ [containerView addSubview:_rotateClockwiseButton];
185
+
186
+ _resetButton = [UIButton buttonWithType:UIButtonTypeSystem];
187
+ _resetButton.contentMode = UIViewContentModeCenter;
188
+ _resetButton.tintColor = [UIColor whiteColor];
189
+ _resetButton.enabled = NO;
190
+ [_resetButton setImage:[TOCropToolbar resetImage] forState:UIControlStateNormal];
191
+ [_resetButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
192
+ _resetButton.accessibilityLabel = NSLocalizedStringFromTableInBundle(@"Reset",
193
+ @"TOCropViewControllerLocalizable",
194
+ resourceBundle,
195
+ nil);
196
+ [containerView addSubview:_resetButton];
197
+ }
198
+
199
+ - (void)layoutSubviews {
200
+ [super layoutSubviews];
201
+
202
+ BOOL verticalLayout = (CGRectGetWidth(self.bounds) < CGRectGetHeight(self.bounds));
203
+ CGSize boundsSize = self.bounds.size;
204
+
205
+ self.cancelIconButton.hidden = self.cancelButtonHidden || (_showOnlyIcons ? false : !verticalLayout);
206
+ self.cancelTextButton.hidden = self.cancelButtonHidden || (_showOnlyIcons ? true : verticalLayout);
207
+ self.doneIconButton.hidden = self.doneButtonHidden || (_showOnlyIcons ? false : !verticalLayout);
208
+ self.doneTextButton.hidden = self.doneButtonHidden || (_showOnlyIcons ? true : verticalLayout);
209
+
210
+ CGRect frame = self.bounds;
211
+ frame.origin.x -= self.backgroundViewOutsets.left;
212
+ frame.size.width += self.backgroundViewOutsets.left;
213
+ frame.size.width += self.backgroundViewOutsets.right;
214
+ frame.origin.y -= self.backgroundViewOutsets.top;
215
+ frame.size.height += self.backgroundViewOutsets.top;
216
+ frame.size.height += self.backgroundViewOutsets.bottom;
217
+ self.backgroundView.frame = frame;
218
+
219
+ #if TOCROPTOOLBAR_DEBUG_SHOWING_BUTTONS_CONTAINER_RECT
220
+ static UIView *containerView = nil;
221
+ if (!containerView) {
222
+ containerView = [[UIView alloc] initWithFrame:CGRectZero];
223
+ containerView.backgroundColor = [UIColor redColor];
224
+ containerView.alpha = 0.1;
225
+ [self addSubview:containerView];
226
+ }
227
+ #endif
228
+
229
+ if (verticalLayout == NO) {
230
+ CGFloat insetPadding = 10.0f;
231
+ if (@available(iOS 26.0, *)) {
232
+ insetPadding = 0.0f;
233
+ }
234
+
235
+ // Work out the cancel button frame
236
+ CGRect frame = CGRectZero;
237
+ frame.size.height = 44.0f;
238
+ frame.size.width = _showOnlyIcons ? 44.0f : MIN(self.frame.size.width / 3.0, self.cancelTextButton.frame.size.width);
239
+
240
+ // If normal layout, place on the left side, else place on the right
241
+ if (self.reverseContentLayout == NO) {
242
+ frame.origin.x = insetPadding;
243
+ } else {
244
+ frame.origin.x = boundsSize.width - (frame.size.width + insetPadding);
245
+ }
246
+ (_showOnlyIcons ? self.cancelIconButton : self.cancelTextButton).frame = frame;
247
+
248
+ // Work out the Done button frame
249
+ frame.size.width = _showOnlyIcons ? 44.0f : MIN(self.frame.size.width / 3.0, self.doneTextButton.frame.size.width);
250
+
251
+ if (self.reverseContentLayout == NO) {
252
+ frame.origin.x = boundsSize.width - (frame.size.width + insetPadding);
253
+ } else {
254
+ frame.origin.x = insetPadding;
255
+ }
256
+ (_showOnlyIcons ? self.doneIconButton : self.doneTextButton).frame = frame;
257
+
258
+ // Work out the frame between the two buttons where we can layout our action buttons
259
+ CGFloat x = self.reverseContentLayout ? CGRectGetMaxX((_showOnlyIcons ? self.doneIconButton : self.doneTextButton).frame) : CGRectGetMaxX((_showOnlyIcons ? self.cancelIconButton : self.cancelTextButton).frame);
260
+ CGFloat width = 0.0f;
261
+
262
+ if (self.reverseContentLayout == NO) {
263
+ width = CGRectGetMinX((_showOnlyIcons ? self.doneIconButton : self.doneTextButton).frame) - CGRectGetMaxX((_showOnlyIcons ? self.cancelIconButton : self.cancelTextButton).frame);
264
+ } else {
265
+ width = CGRectGetMinX((_showOnlyIcons ? self.cancelIconButton : self.cancelTextButton).frame) - CGRectGetMaxX((_showOnlyIcons ? self.doneIconButton : self.doneTextButton).frame);
266
+ }
267
+
268
+ CGRect containerRect = CGRectIntegral((CGRect){x, frame.origin.y, width, 44.0f});
269
+
270
+ #if TOCROPTOOLBAR_DEBUG_SHOWING_BUTTONS_CONTAINER_RECT
271
+ containerView.frame = containerRect;
272
+ #endif
273
+
274
+ CGSize buttonSize = (CGSize){44.0f, 44.0f};
275
+
276
+ NSMutableArray *buttonsInOrderHorizontally = [NSMutableArray new];
277
+ if (!self.rotateCounterclockwiseButtonHidden) {
278
+ [buttonsInOrderHorizontally addObject:self.rotateCounterclockwiseButton];
279
+ }
280
+
281
+ if (!self.resetButtonHidden) {
282
+ [buttonsInOrderHorizontally addObject:self.resetButton];
283
+ }
284
+
285
+ if (!self.clampButtonHidden) {
286
+ [buttonsInOrderHorizontally addObject:self.clampButton];
287
+ }
288
+
289
+ if (!self.rotateClockwiseButtonHidden) {
290
+ [buttonsInOrderHorizontally addObject:self.rotateClockwiseButton];
291
+ }
292
+ [self layoutToolbarButtons:buttonsInOrderHorizontally withSameButtonSize:buttonSize inContainerRect:containerRect horizontally:YES];
293
+ } else {
294
+ CGRect frame = CGRectZero;
295
+ frame.size.height = 44.0f;
296
+ frame.size.width = 44.0f;
297
+ frame.origin.y = CGRectGetHeight(self.bounds) - 44.0f;
298
+ self.cancelIconButton.frame = frame;
299
+
300
+ frame.origin.y = self.statusBarHeightInset;
301
+ frame.size.width = 44.0f;
302
+ frame.size.height = 44.0f;
303
+ self.doneIconButton.frame = frame;
304
+
305
+ CGRect containerRect = (CGRect){0, CGRectGetMaxY(self.doneIconButton.frame), 44.0f, CGRectGetMinY(self.cancelIconButton.frame) - CGRectGetMaxY(self.doneIconButton.frame)};
306
+
307
+ #if TOCROPTOOLBAR_DEBUG_SHOWING_BUTTONS_CONTAINER_RECT
308
+ containerView.frame = containerRect;
309
+ #endif
310
+
311
+ CGSize buttonSize = (CGSize){44.0f, 44.0f};
312
+
313
+ NSMutableArray *buttonsInOrderVertically = [NSMutableArray new];
314
+ if (!self.rotateCounterclockwiseButtonHidden) {
315
+ [buttonsInOrderVertically addObject:self.rotateCounterclockwiseButton];
316
+ }
317
+
318
+ if (!self.resetButtonHidden) {
319
+ [buttonsInOrderVertically addObject:self.resetButton];
320
+ }
321
+
322
+ if (!self.clampButtonHidden) {
323
+ [buttonsInOrderVertically addObject:self.clampButton];
324
+ }
325
+
326
+ if (!self.rotateClockwiseButtonHidden) {
327
+ [buttonsInOrderVertically addObject:self.rotateClockwiseButton];
328
+ }
329
+
330
+ [self layoutToolbarButtons:buttonsInOrderVertically withSameButtonSize:buttonSize inContainerRect:containerRect horizontally:NO];
331
+ }
332
+ }
333
+
334
+ // The convenience method for calculating button's frame inside of the container rect
335
+ - (void)layoutToolbarButtons:(NSArray<UIButton *> *)buttons withSameButtonSize:(CGSize)size inContainerRect:(CGRect)containerRect horizontally:(BOOL)horizontally {
336
+ // With no buttons to hold, collapse the glass container instead of leaving an
337
+ // empty capsule stranded at the frame it had when the last button was visible
338
+ if (@available(iOS 26.0, *)) {
339
+ _glassView.hidden = (buttons.count == 0);
340
+ }
341
+
342
+ if (!buttons.count) {
343
+ return;
344
+ }
345
+
346
+ const CGFloat buttonSize = 44.0f;
347
+
348
+ if (@available(iOS 26.0, *)) {
349
+ CGFloat glassPadding = 6.0f;
350
+ CGFloat buttonPadding = 12.0f;
351
+ CGFloat maxExtent = buttons.count * buttonSize + (buttonPadding * (buttons.count - 1)) + (glassPadding * 2.0f);
352
+
353
+ CGRect glassFrame = CGRectZero;
354
+ glassFrame.size.width = horizontally ? maxExtent : buttonSize;
355
+ glassFrame.size.height = horizontally ? buttonSize : maxExtent;
356
+ glassFrame.origin.x = horizontally ? CGRectGetMidX(containerRect) - (glassFrame.size.width / 2.0f) : 0.0f;
357
+ glassFrame.origin.y = horizontally ? 0.0f : CGRectGetMidY(containerRect) - (glassFrame.size.height / 2.0f);
358
+ _glassView.frame = glassFrame;
359
+
360
+ CGFloat position = glassPadding;
361
+ for (UIButton *button in buttons) {
362
+ CGRect buttonFrame = CGRectMake(0.0, 0.0, buttonSize, buttonSize);
363
+ if (horizontally) {
364
+ buttonFrame.origin.x = position;
365
+ } else {
366
+ buttonFrame.origin.y = position;
367
+ }
368
+ button.frame = buttonFrame;
369
+ position += buttonSize + buttonPadding;
370
+ }
371
+ } else {
372
+ NSInteger count = buttons.count;
373
+ CGFloat fixedSize = horizontally ? size.width : size.height;
374
+ CGFloat maxLength = horizontally ? CGRectGetWidth(containerRect) : CGRectGetHeight(containerRect);
375
+ CGFloat padding = (maxLength - fixedSize * count) / (count + 1);
376
+
377
+ for (NSInteger i = 0; i < count; i++) {
378
+ UIButton *button = buttons[i];
379
+ CGFloat sameOffset = horizontally ? fabs(CGRectGetHeight(containerRect) - 44.0f) : fabs(CGRectGetWidth(containerRect) - size.width);
380
+ CGFloat diffOffset = padding + i * (fixedSize + padding);
381
+ CGPoint origin = horizontally ? CGPointMake(diffOffset, sameOffset) : CGPointMake(sameOffset, diffOffset);
382
+ if (horizontally) {
383
+ origin.x += CGRectGetMinX(containerRect);
384
+ if (@available(iOS 15.0, *)) {
385
+ // iOS 15+: Use UIButtonConfiguration
386
+ UIButtonConfiguration *config = button.configuration ?: [UIButtonConfiguration plainButtonConfiguration];
387
+
388
+ // Position image and text
389
+ config.imagePlacement = NSDirectionalRectEdgeLeading;
390
+ config.imagePadding = 8;
391
+
392
+ UIImage *image = button.imageView.image;
393
+ config.contentInsets = NSDirectionalEdgeInsetsMake(0, 0, image.baselineOffsetFromBottom, 0);
394
+
395
+ button.configuration = config;
396
+
397
+ } else if (@available(iOS 13.0, *)) {
398
+ UIImage *image = button.imageView.image;
399
+ button.imageEdgeInsets = UIEdgeInsetsMake(0, 0, image.baselineOffsetFromBottom, 0);
400
+ }
401
+ } else {
402
+ origin.y += CGRectGetMinY(containerRect);
403
+ }
404
+ button.frame = (CGRect){origin, size};
405
+ }
406
+ }
407
+ }
408
+
409
+ - (void)buttonTapped:(id)button {
410
+ if (button == self.cancelTextButton || button == self.cancelIconButton) {
411
+ if (self.cancelButtonTapped)
412
+ self.cancelButtonTapped();
413
+ } else if (button == self.doneTextButton || button == self.doneIconButton) {
414
+ if (self.doneButtonTapped)
415
+ self.doneButtonTapped();
416
+ } else if (button == self.resetButton && self.resetButtonTapped) {
417
+ self.resetButtonTapped();
418
+ } else if (button == self.rotateCounterclockwiseButton && self.rotateCounterclockwiseButtonTapped) {
419
+ self.rotateCounterclockwiseButtonTapped();
420
+ } else if (button == self.rotateClockwiseButton && self.rotateClockwiseButtonTapped) {
421
+ self.rotateClockwiseButtonTapped();
422
+ } else if (button == self.clampButton && self.clampButtonTapped) {
423
+ self.clampButtonTapped();
424
+ return;
425
+ }
426
+ }
427
+
428
+ - (CGRect)clampButtonFrame {
429
+ // Convert into the toolbar's space; on iOS 26 the button lives inside the glass container
430
+ return [self convertRect:self.clampButton.bounds fromView:self.clampButton];
431
+ }
432
+
433
+ - (void)setReverseContentLayout:(BOOL)reverseContentLayout {
434
+ if (_reverseContentLayout == reverseContentLayout)
435
+ return;
436
+
437
+ _reverseContentLayout = reverseContentLayout;
438
+ [self setNeedsLayout];
439
+ }
440
+
441
+ - (void)setClampButtonHidden:(BOOL)clampButtonHidden {
442
+ if (_clampButtonHidden == clampButtonHidden)
443
+ return;
444
+
445
+ _clampButtonHidden = clampButtonHidden;
446
+ [self setNeedsLayout];
447
+ }
448
+
449
+ - (void)setClampButtonGlowing:(BOOL)clampButtonGlowing {
450
+ if (_clampButtonGlowing == clampButtonGlowing)
451
+ return;
452
+
453
+ _clampButtonGlowing = clampButtonGlowing;
454
+
455
+ if (_clampButtonGlowing)
456
+ self.clampButton.tintColor = nil;
457
+ else
458
+ self.clampButton.tintColor = [UIColor whiteColor];
459
+ }
460
+
461
+ - (void)setRotateCounterclockwiseButtonHidden:(BOOL)rotateButtonHidden {
462
+ if (_rotateCounterclockwiseButtonHidden == rotateButtonHidden)
463
+ return;
464
+
465
+ _rotateCounterclockwiseButtonHidden = rotateButtonHidden;
466
+ [self setNeedsLayout];
467
+ }
468
+
469
+ - (BOOL)resetButtonEnabled {
470
+ return self.resetButton.enabled;
471
+ }
472
+
473
+ - (void)setResetButtonEnabled:(BOOL)resetButtonEnabled {
474
+ self.resetButton.enabled = resetButtonEnabled;
475
+ }
476
+
477
+ - (void)setDoneButtonHidden:(BOOL)doneButtonHidden {
478
+ if (_doneButtonHidden == doneButtonHidden)
479
+ return;
480
+
481
+ _doneButtonHidden = doneButtonHidden;
482
+ [self setNeedsLayout];
483
+ }
484
+
485
+ - (void)setCancelButtonHidden:(BOOL)cancelButtonHidden {
486
+ if (_cancelButtonHidden == cancelButtonHidden)
487
+ return;
488
+
489
+ _cancelButtonHidden = cancelButtonHidden;
490
+ [self setNeedsLayout];
491
+ }
492
+
493
+ - (CGRect)doneButtonFrame {
494
+ if (self.doneIconButton.hidden == NO)
495
+ return self.doneIconButton.frame;
496
+
497
+ return self.doneTextButton.frame;
498
+ }
499
+
500
+ - (void)setShowOnlyIcons:(BOOL)showOnlyIcons {
501
+ // The text variants of the Done and Cancel buttons aren't created at all on iOS 26
502
+ // and up, where the toolbar is permanently icon-only. Honouring a NO in that case
503
+ // would hide the icon buttons with nothing to replace them, leaving no way to
504
+ // commit or cancel the crop at all.
505
+ if (_doneTextButton == nil || _cancelTextButton == nil) {
506
+ return;
507
+ }
508
+
509
+ if (_showOnlyIcons == showOnlyIcons)
510
+ return;
511
+
512
+ _showOnlyIcons = showOnlyIcons;
513
+ [_doneIconButton sizeToFit];
514
+ [_cancelIconButton sizeToFit];
515
+ [self setNeedsLayout];
516
+ }
517
+
518
+ - (void)setCancelTextButtonTitle:(NSString *)cancelTextButtonTitle {
519
+ _cancelTextButtonTitle = cancelTextButtonTitle;
520
+ [_cancelTextButton setTitle:_cancelTextButtonTitle forState:UIControlStateNormal];
521
+ [_cancelTextButton sizeToFit];
522
+ }
523
+
524
+ - (void)setDoneTextButtonTitle:(NSString *)doneTextButtonTitle {
525
+ _doneTextButtonTitle = doneTextButtonTitle;
526
+ [_doneTextButton setTitle:_doneTextButtonTitle forState:UIControlStateNormal];
527
+ [_doneTextButton sizeToFit];
528
+ }
529
+
530
+ - (void)setCancelButtonColor:(UIColor *)cancelButtonColor {
531
+ // Default color is app tint color
532
+ if (cancelButtonColor == _cancelButtonColor) {
533
+ return;
534
+ }
535
+ _cancelButtonColor = cancelButtonColor;
536
+ [_cancelTextButton setTitleColor:_cancelButtonColor forState:UIControlStateNormal];
537
+ [_cancelIconButton setTintColor:_cancelButtonColor];
538
+ [_cancelTextButton sizeToFit];
539
+ }
540
+
541
+ - (void)setDoneButtonColor:(UIColor *)doneButtonColor {
542
+ // Set the default color when nil is specified
543
+ if (doneButtonColor == nil) {
544
+ doneButtonColor = [UIColor colorWithRed:1.0f green:0.8f blue:0.0f alpha:1.0f];
545
+ }
546
+
547
+ if (doneButtonColor == _doneButtonColor) {
548
+ return;
549
+ }
550
+
551
+ _doneButtonColor = doneButtonColor;
552
+ [_doneTextButton setTitleColor:_doneButtonColor forState:UIControlStateNormal];
553
+ [_doneIconButton setTintColor:_doneButtonColor];
554
+ [_doneTextButton sizeToFit];
555
+ }
556
+
557
+ #pragma mark - Image Generation -
558
+ + (UIImage *)doneImage {
559
+ if (@available(iOS 13.0, *)) {
560
+ return [UIImage systemImageNamed:@"checkmark"
561
+ withConfiguration:[UIImageSymbolConfiguration configurationWithWeight:UIImageSymbolWeightSemibold]];
562
+ }
563
+
564
+ UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:(CGSize){17, 14}];
565
+ UIImage *doneImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *rendererContext) {
566
+ UIBezierPath *rectanglePath = UIBezierPath.bezierPath;
567
+ [rectanglePath moveToPoint:CGPointMake(1, 7)];
568
+ [rectanglePath addLineToPoint:CGPointMake(6, 12)];
569
+ [rectanglePath addLineToPoint:CGPointMake(16, 1)];
570
+ [UIColor.whiteColor setStroke];
571
+ rectanglePath.lineWidth = 2;
572
+ [rectanglePath stroke];
573
+ }];
574
+
575
+ return doneImage;
576
+ }
577
+
578
+ + (UIImage *)cancelImage {
579
+ if (@available(iOS 13.0, *)) {
580
+ return [UIImage systemImageNamed:@"xmark"
581
+ withConfiguration:[UIImageSymbolConfiguration configurationWithWeight:UIImageSymbolWeightSemibold]];
582
+ }
583
+
584
+ UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:(CGSize){16, 16}];
585
+ UIImage *cancelImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *rendererContext) {
586
+ UIBezierPath *bezierPath = UIBezierPath.bezierPath;
587
+ [bezierPath moveToPoint:CGPointMake(15, 15)];
588
+ [bezierPath addLineToPoint:CGPointMake(1, 1)];
589
+ [UIColor.whiteColor setStroke];
590
+ bezierPath.lineWidth = 2;
591
+ [bezierPath stroke];
592
+
593
+ UIBezierPath *bezier2Path = UIBezierPath.bezierPath;
594
+ [bezier2Path moveToPoint:CGPointMake(1, 15)];
595
+ [bezier2Path addLineToPoint:CGPointMake(15, 1)];
596
+ [UIColor.whiteColor setStroke];
597
+ bezier2Path.lineWidth = 2;
598
+ [bezier2Path stroke];
599
+ }];
600
+
601
+ return cancelImage;
602
+ }
603
+
604
+ + (UIImage *)rotateCCWImage {
605
+ if (@available(iOS 13.0, *)) {
606
+ return [[UIImage systemImageNamed:@"rotate.left.fill"
607
+ withConfiguration:[UIImageSymbolConfiguration configurationWithWeight:UIImageSymbolWeightSemibold]]
608
+ imageWithBaselineOffsetFromBottom:4];
609
+ }
610
+
611
+ UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:(CGSize){18, 21}];
612
+ UIImage *rotateImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *rendererContext) {
613
+ UIBezierPath *rectangle2Path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 9, 12, 12)];
614
+ [UIColor.whiteColor setFill];
615
+ [rectangle2Path fill];
616
+
617
+ UIBezierPath *rectangle3Path = UIBezierPath.bezierPath;
618
+ [rectangle3Path moveToPoint:CGPointMake(5, 3)];
619
+ [rectangle3Path addLineToPoint:CGPointMake(10, 6)];
620
+ [rectangle3Path addLineToPoint:CGPointMake(10, 0)];
621
+ [rectangle3Path addLineToPoint:CGPointMake(5, 3)];
622
+ [rectangle3Path closePath];
623
+ [UIColor.whiteColor setFill];
624
+ [rectangle3Path fill];
625
+
626
+ UIBezierPath *bezierPath = UIBezierPath.bezierPath;
627
+ [bezierPath moveToPoint:CGPointMake(10, 3)];
628
+ [bezierPath addCurveToPoint:CGPointMake(17.5, 11) controlPoint1:CGPointMake(15, 3) controlPoint2:CGPointMake(17.5, 5.91)];
629
+ [UIColor.whiteColor setStroke];
630
+ bezierPath.lineWidth = 1;
631
+ [bezierPath stroke];
632
+ }];
633
+
634
+ return rotateImage;
635
+ }
636
+
637
+ + (UIImage *)rotateCWImage {
638
+ if (@available(iOS 13.0, *)) {
639
+ return [[UIImage systemImageNamed:@"rotate.right.fill"
640
+ withConfiguration:[UIImageSymbolConfiguration configurationWithWeight:UIImageSymbolWeightSemibold]]
641
+ imageWithBaselineOffsetFromBottom:4];
642
+ }
643
+
644
+ UIImage *rotateCCWImage = [self.class rotateCCWImage];
645
+ UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:rotateCCWImage.size];
646
+ UIImage *rotateCWImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *rendererContext) {
647
+ CGContextRef context = rendererContext.CGContext;
648
+ CGContextTranslateCTM(context, rotateCCWImage.size.width, rotateCCWImage.size.height);
649
+ CGContextRotateCTM(context, M_PI);
650
+ CGContextDrawImage(context, CGRectMake(0, 0, rotateCCWImage.size.width, rotateCCWImage.size.height), rotateCCWImage.CGImage);
651
+ }];
652
+
653
+ return rotateCWImage;
654
+ }
655
+
656
+ + (UIImage *)resetImage {
657
+ if (@available(iOS 13.0, *)) {
658
+ return [[UIImage systemImageNamed:@"arrow.counterclockwise"
659
+ withConfiguration:[UIImageSymbolConfiguration configurationWithWeight:UIImageSymbolWeightSemibold]]
660
+ imageWithBaselineOffsetFromBottom:0];
661
+ ;
662
+ }
663
+
664
+ UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:(CGSize){22, 18}];
665
+ UIImage *resetImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *rendererContext) {
666
+ UIBezierPath *bezier2Path = UIBezierPath.bezierPath;
667
+ [bezier2Path moveToPoint:CGPointMake(22, 9)];
668
+ [bezier2Path addCurveToPoint:CGPointMake(13, 18) controlPoint1:CGPointMake(22, 13.97) controlPoint2:CGPointMake(17.97, 18)];
669
+ [bezier2Path addCurveToPoint:CGPointMake(13, 16) controlPoint1:CGPointMake(13, 17.35) controlPoint2:CGPointMake(13, 16.68)];
670
+ [bezier2Path addCurveToPoint:CGPointMake(20, 9) controlPoint1:CGPointMake(16.87, 16) controlPoint2:CGPointMake(20, 12.87)];
671
+ [bezier2Path addCurveToPoint:CGPointMake(13, 2) controlPoint1:CGPointMake(20, 5.13) controlPoint2:CGPointMake(16.87, 2)];
672
+ [bezier2Path addCurveToPoint:CGPointMake(6.55, 6.27) controlPoint1:CGPointMake(10.1, 2) controlPoint2:CGPointMake(7.62, 3.76)];
673
+ [bezier2Path addCurveToPoint:CGPointMake(6, 9) controlPoint1:CGPointMake(6.2, 7.11) controlPoint2:CGPointMake(6, 8.03)];
674
+ [bezier2Path addLineToPoint:CGPointMake(4, 9)];
675
+ [bezier2Path addCurveToPoint:CGPointMake(4.65, 5.63) controlPoint1:CGPointMake(4, 7.81) controlPoint2:CGPointMake(4.23, 6.67)];
676
+ [bezier2Path addCurveToPoint:CGPointMake(7.65, 1.76) controlPoint1:CGPointMake(5.28, 4.08) controlPoint2:CGPointMake(6.32, 2.74)];
677
+ [bezier2Path addCurveToPoint:CGPointMake(13, 0) controlPoint1:CGPointMake(9.15, 0.65) controlPoint2:CGPointMake(11, 0)];
678
+ [bezier2Path addCurveToPoint:CGPointMake(22, 9) controlPoint1:CGPointMake(17.97, 0) controlPoint2:CGPointMake(22, 4.03)];
679
+ [bezier2Path closePath];
680
+ [UIColor.whiteColor setFill];
681
+ [bezier2Path fill];
682
+
683
+ UIBezierPath *polygonPath = UIBezierPath.bezierPath;
684
+ [polygonPath moveToPoint:CGPointMake(5, 15)];
685
+ [polygonPath addLineToPoint:CGPointMake(10, 9)];
686
+ [polygonPath addLineToPoint:CGPointMake(0, 9)];
687
+ [polygonPath addLineToPoint:CGPointMake(5, 15)];
688
+ [polygonPath closePath];
689
+ [UIColor.whiteColor setFill];
690
+ [polygonPath fill];
691
+ }];
692
+
693
+ return resetImage;
694
+ }
695
+
696
+ + (UIImage *)clampImage {
697
+ if (@available(iOS 13.0, *)) {
698
+ return [[UIImage systemImageNamed:@"aspectratio.fill"
699
+ withConfiguration:[UIImageSymbolConfiguration configurationWithWeight:UIImageSymbolWeightSemibold]]
700
+ imageWithBaselineOffsetFromBottom:0];
701
+ }
702
+
703
+ UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:(CGSize){22, 16}];
704
+ UIImage *clampImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *rendererContext) {
705
+ //// Color Declarations
706
+ UIColor *outerBox = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.553];
707
+ UIColor *innerBox = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.773];
708
+
709
+ //// Rectangle Drawing
710
+ UIBezierPath *rectanglePath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 3, 13, 13)];
711
+ [UIColor.whiteColor setFill];
712
+ [rectanglePath fill];
713
+
714
+ //// Outer
715
+ {
716
+ //// Top Drawing
717
+ UIBezierPath *topPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 22, 2)];
718
+ [outerBox setFill];
719
+ [topPath fill];
720
+
721
+ //// Side Drawing
722
+ UIBezierPath *sidePath = [UIBezierPath bezierPathWithRect:CGRectMake(19, 2, 3, 14)];
723
+ [outerBox setFill];
724
+ [sidePath fill];
725
+ }
726
+
727
+ //// Rectangle 2 Drawing
728
+ UIBezierPath *rectangle2Path = [UIBezierPath bezierPathWithRect:CGRectMake(14, 3, 4, 13)];
729
+ [innerBox setFill];
730
+ [rectangle2Path fill];
731
+ }];
732
+
733
+ return clampImage;
734
+ }
735
+
736
+ #pragma mark - Accessors -
737
+
738
+ - (void)setRotateClockwiseButtonHidden:(BOOL)rotateClockwiseButtonHidden {
739
+ if (_rotateClockwiseButtonHidden == rotateClockwiseButtonHidden) {
740
+ return;
741
+ }
742
+
743
+ _rotateClockwiseButtonHidden = rotateClockwiseButtonHidden;
744
+
745
+ [self setNeedsLayout];
746
+ }
747
+
748
+ - (void)setResetButtonHidden:(BOOL)resetButtonHidden {
749
+ if (_resetButtonHidden == resetButtonHidden) {
750
+ return;
751
+ }
752
+
753
+ _resetButtonHidden = resetButtonHidden;
754
+
755
+ [self setNeedsLayout];
756
+ }
757
+ - (UIButton *)rotateButton {
758
+ return self.rotateCounterclockwiseButton;
759
+ }
760
+
761
+ - (void)setStatusBarHeightInset:(CGFloat)statusBarHeightInset {
762
+ _statusBarHeightInset = statusBarHeightInset;
763
+ [self setNeedsLayout];
764
+ }
765
+
766
+ - (UIView *)visibleCancelButton {
767
+ if (self.cancelIconButton.hidden == NO) {
768
+ return self.cancelIconButton;
769
+ }
770
+
771
+ return self.cancelTextButton;
772
+ }
773
+
774
+ - (void)setDisableRotationButtons:(BOOL)disableRotationButtons {
775
+ if (_disableRotationButtons == disableRotationButtons) {
776
+ return;
777
+ }
778
+ _disableRotationButtons = disableRotationButtons;
779
+ _rotateClockwiseButton.enabled = !disableRotationButtons;
780
+ _rotateCounterclockwiseButton.enabled = !disableRotationButtons;
781
+ }
782
+
783
+ @end