Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ x.y.z Release Notes (yyyy-MM-dd)
- Touch cancellation from incoming calls or system alerts left the crop view stuck in its editing appearance.
- Swift: replacing the `delegate` left the previous delegate receiving callbacks, and clearing it discarded closures the host had assigned directly.
- Swift: the `cropViewControllerDidTapDone` bridge added in 3.1.2 captured the delegate strongly, the same retain cycle as the other callbacks.
- iOS 26-only layout symbols are compiled out on older SDKs, restoring Xcode 16 buildability.
- The private display-corner-radius lookup on iOS 26 now fails gracefully if the key ever disappears.
- Setting `showOnlyIcons` to false on iOS 26 hid the icon buttons without creating any text ones, leaving no way to commit or cancel the crop. The property is now ignored where the toolbar is icon-only.
- On iOS 26, hiding every toolbar action button left an empty glass capsule stranded on screen.

3.1.2 Release Notes (2026-04-07)

Expand Down
2 changes: 1 addition & 1 deletion Objective-C/TOCropViewController/TOCropViewController.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@
/**
If true, button icons are visible in portairt instead button text.

Default is NO.
Default is NO. Has no effect on iOS 26 and up, where the toolbar is always icon-only.
*/
@property (nonatomic, assign) BOOL showOnlyIcons;

Expand Down
27 changes: 20 additions & 7 deletions Objective-C/TOCropViewController/TOCropViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ - (CGRect)frameForToolbarWithVerticalLayout:(BOOL)verticalLayout {
UIEdgeInsets insets = self.statusBarSafeInsets;

// fix: On iOS 26, overlay with iPadOS windowingControl area.
#if defined(__IPHONE_26_0)
if (@available(iOS 26.0, *)) {
if (!verticalLayout) {
#if __IPHONE_OS_VERSION_MAX_ALLOWED < 180000
Expand All @@ -286,6 +287,7 @@ - (CGRect)frameForToolbarWithVerticalLayout:(BOOL)verticalLayout {
#endif
}
}
#endif

CGRect frame = CGRectZero;
if (!verticalLayout) { // In landscape laying out toolbar to the left
Expand Down Expand Up @@ -328,13 +330,24 @@ - (CGRect)frameForToolbarWithVerticalLayout:(BOOL)verticalLayout {
}

#if !TARGET_OS_VISION
const char *components[] = {"Radius", "Corner", "display", "_"};
NSString *selectorName = @"";
for (NSInteger i = 3; i >= 0; i--) {
selectorName = [selectorName stringByAppendingString:[NSString stringWithCString:components[i]
encoding:NSUTF8StringEncoding]];
}
const CGFloat cornerRadius = [[UIScreen.mainScreen valueForKey:selectorName] floatValue];
// Look the value up only once since it can't change for the life of the
// process, and fall back to a radius matching current-generation devices
// in case the private key is ever renamed or removed
static CGFloat cornerRadius = 44.0f;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
const char *components[] = {"Radius", "Corner", "display", "_"};
NSString *selectorName = @"";
for (NSInteger i = 3; i >= 0; i--) {
selectorName = [selectorName stringByAppendingString:[NSString stringWithCString:components[i]
encoding:NSUTF8StringEncoding]];
}
@try {
cornerRadius = [[UIScreen.mainScreen valueForKey:selectorName] floatValue];
} @catch (NSException *exception) {
NSLog(@"TOCropViewController: Unable to read the display corner radius. Falling back to a default value.");
}
});
#else
const CGFloat cornerRadius = 64.0f;
#endif
Expand Down
9 changes: 6 additions & 3 deletions Objective-C/TOCropViewController/Views/TOCropToolbar.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,24 @@ NS_ASSUME_NONNULL_BEGIN
@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. */
in portrait mode and the icon one, in landscape.
The text button is nil on iOS 26 and up, where the toolbar is always icon-only. */
@property (nonatomic, strong, readonly) UIButton *doneTextButton;
@property (nonatomic, strong, readonly) UIButton *doneIconButton;
@property (nonatomic, copy) NSString *doneTextButtonTitle;
@property (null_resettable, nonatomic, copy) UIColor *doneButtonColor;

/* The 'Cancel' buttons to cancel the crop. The text button is displayed
in portrait mode and the icon one, in landscape. */
in portrait mode and the icon one, in landscape.
The text button is nil on iOS 26 and up, where the toolbar is always icon-only. */
@property (nonatomic, strong, readonly) UIButton *cancelTextButton;
@property (nonatomic, strong, readonly) UIButton *cancelIconButton;
@property (nonatomic, readonly) UIView *visibleCancelButton;
@property (nonatomic, copy) NSString *cancelTextButtonTitle;
@property (nullable, nonatomic, copy) UIColor *cancelButtonColor;

/* Show the tick and cross buttons instead of 'Done' and 'Cancel'. */
/* Show the tick and cross buttons instead of 'Done' and 'Cancel'.
Always YES, and not settable, on iOS 26 and up. */
@property (nonatomic, assign) BOOL showOnlyIcons API_DEPRECATED("iOS 26 uses icons only", ios(7.0, 18.0));

/* The cropper control buttons */
Expand Down
14 changes: 14 additions & 0 deletions Objective-C/TOCropViewController/Views/TOCropToolbar.m
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,12 @@ - (void)layoutSubviews {

// The convenience method for calculating button's frame inside of the container rect
- (void)layoutToolbarButtons:(NSArray<UIButton *> *)buttons withSameButtonSize:(CGSize)size inContainerRect:(CGRect)containerRect horizontally:(BOOL)horizontally {
// With no buttons to hold, collapse the glass container instead of leaving an
// empty capsule stranded at the frame it had when the last button was visible
if (@available(iOS 26.0, *)) {
_glassView.hidden = (buttons.count == 0);
}

if (!buttons.count) {
return;
}
Expand Down Expand Up @@ -495,6 +501,14 @@ - (CGRect)doneButtonFrame {
}

- (void)setShowOnlyIcons:(BOOL)showOnlyIcons {
// The text variants of the Done and Cancel buttons aren't created at all on iOS 26
// and up, where the toolbar is permanently icon-only. Honouring a NO in that case
// would hide the icon buttons with nothing to replace them, leaving no way to
// commit or cancel the crop at all.
if (_doneTextButton == nil || _cancelTextButton == nil) {
return;
}

if (_showOnlyIcons == showOnlyIcons)
return;

Expand Down
Loading