From 2e88a8d324f374ca5d8ffd442eb59c6822718215 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Sun, 23 Nov 2025 15:03:09 +0900 Subject: [PATCH 01/15] Added new interface to the header for supporting glass --- TORoundedButton/TORoundedButton.h | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/TORoundedButton/TORoundedButton.h b/TORoundedButton/TORoundedButton.h index 55630b5..6a5feac 100644 --- a/TORoundedButton/TORoundedButton.h +++ b/TORoundedButton/TORoundedButton.h @@ -26,6 +26,13 @@ NS_ASSUME_NONNULL_BEGIN @class TORoundedButton; +/// The types of static/dynamic visual styles that can be applied to the background. +typedef NS_ENUM(NSInteger, TORoundedButtonBackgroundStyle) { + TORoundedButtonBackgroundStyleSolid, + TORoundedButtonBackgroundStyleBlur, + TORoundedButtonBackgroundStyleGlass +}; + NS_SWIFT_NAME(RoundedButtonDelegate) @protocol TORoundedButtonDelegate @@ -65,12 +72,15 @@ IB_DESIGNABLE @interface TORoundedButton : UIControl /// (Default value is 15 points inset from each edge). @property (nonatomic, assign) UIEdgeInsets contentInset; -/// Replaces the solid color background with a blur view. (Default is NO) -@property (nonatomic, assign) BOOL isTranslucent; +/// The style, whether static or dynamic of the button's background view. +@property (nonatomic, assign) TORoundedButtonBackgroundStyle backgroundStyle; -/// When `isTranslucent` is `YES`, the amount of blur the background view has. +/// When `backgroundStyle` is set to `.blur`, the specific blur style to apply. @property (nonatomic, assign) UIBlurEffectStyle blurStyle; +/// When `backgroundStyle` is set to `.blur`, the specific blur style to apply. +@property (nonatomic, assign) UIGlassEffectStyle glassStyle API_AVAILABLE(ios(26.0)); + /// The text that is displayed in center of the button (Default is nil). /// This adds an internally controlled label view to the main content view. @property (nonatomic, copy, nullable) IBInspectable NSString *text; From 036decb308797d0e960928fbe8853cb9b74ff1e7 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Sun, 23 Nov 2025 15:45:43 +0900 Subject: [PATCH 02/15] Removed liquid glass style from header This reverts commit 21519b84148935390dc200f1e98e75f6a8f14558. --- TORoundedButton/TORoundedButton.h | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/TORoundedButton/TORoundedButton.h b/TORoundedButton/TORoundedButton.h index 6a5feac..55630b5 100644 --- a/TORoundedButton/TORoundedButton.h +++ b/TORoundedButton/TORoundedButton.h @@ -26,13 +26,6 @@ NS_ASSUME_NONNULL_BEGIN @class TORoundedButton; -/// The types of static/dynamic visual styles that can be applied to the background. -typedef NS_ENUM(NSInteger, TORoundedButtonBackgroundStyle) { - TORoundedButtonBackgroundStyleSolid, - TORoundedButtonBackgroundStyleBlur, - TORoundedButtonBackgroundStyleGlass -}; - NS_SWIFT_NAME(RoundedButtonDelegate) @protocol TORoundedButtonDelegate @@ -72,15 +65,12 @@ IB_DESIGNABLE @interface TORoundedButton : UIControl /// (Default value is 15 points inset from each edge). @property (nonatomic, assign) UIEdgeInsets contentInset; -/// The style, whether static or dynamic of the button's background view. -@property (nonatomic, assign) TORoundedButtonBackgroundStyle backgroundStyle; +/// Replaces the solid color background with a blur view. (Default is NO) +@property (nonatomic, assign) BOOL isTranslucent; -/// When `backgroundStyle` is set to `.blur`, the specific blur style to apply. +/// When `isTranslucent` is `YES`, the amount of blur the background view has. @property (nonatomic, assign) UIBlurEffectStyle blurStyle; -/// When `backgroundStyle` is set to `.blur`, the specific blur style to apply. -@property (nonatomic, assign) UIGlassEffectStyle glassStyle API_AVAILABLE(ios(26.0)); - /// The text that is displayed in center of the button (Default is nil). /// This adds an internally controlled label view to the main content view. @property (nonatomic, copy, nullable) IBInspectable NSString *text; From aed41bcf8211bdf72a513da79a2f2e3ad0c12b26 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Sun, 23 Nov 2025 15:43:39 +0900 Subject: [PATCH 03/15] Added Liquid Glass variant to the background style --- TORoundedButton/TORoundedButton.m | 90 ++++++++++++++++++++++--------- 1 file changed, 64 insertions(+), 26 deletions(-) diff --git a/TORoundedButton/TORoundedButton.m b/TORoundedButton/TORoundedButton.m index e478336..775d516 100644 --- a/TORoundedButton/TORoundedButton.m +++ b/TORoundedButton/TORoundedButton.m @@ -27,14 +27,18 @@ // -------------------------------------------------------------------- -static inline BOOL TO_ROUNDED_BUTTON_FLOAT_IS_ZERO(CGFloat value) { +static inline BOOL TORoundedButtonFloatIsZero(CGFloat value) { return (value > -FLT_EPSILON) && (value < FLT_EPSILON); } -static inline BOOL TO_ROUNDED_BUTTON_FLOATS_MATCH(CGFloat firstValue, CGFloat secondValue) { +static inline BOOL TORoundedButtonFloatsMatch(CGFloat firstValue, CGFloat secondValue) { return fabs(firstValue - secondValue) > FLT_EPSILON; } +static inline BOOL TORoundedButtonIsDynamicBackground(TORoundedButtonBackgroundStyle backgroundStyle) { + return backgroundStyle != TORoundedButtonBackgroundStyleSolid; +} + // -------------------------------------------------------------------- @implementation TORoundedButton { @@ -107,7 +111,7 @@ - (void)_roundedButtonCommonInit TOROUNDEDBUTTON_OBJC_DIRECT { _tappedTextAlpha = (_tappedTextAlpha > FLT_EPSILON) ?: 1.0f; _tapAnimationDuration = (_tapAnimationDuration > FLT_EPSILON) ?: 0.4f; _tappedButtonScale = (_tappedButtonScale > FLT_EPSILON) ?: 0.97f; - _tappedTintColorBrightnessOffset = !TO_ROUNDED_BUTTON_FLOAT_IS_ZERO(_tappedTintColorBrightnessOffset) ?: -0.15f; + _tappedTintColorBrightnessOffset = !TORoundedButtonFloatIsZero(_tappedTintColorBrightnessOffset) ?: -0.15f; _contentInset = (UIEdgeInsets){15.0, 15.0, 15.0, 15.0}; _blurStyle = UIBlurEffectStyleDark; @@ -126,6 +130,13 @@ - (void)_roundedButtonCommonInit TOROUNDEDBUTTON_OBJC_DIRECT { if (@available(iOS 13.0, *)) { _blurStyle = UIBlurEffectStyleSystemThinMaterialDark; } #endif + // Set the corner radius depending on system version + if (@available(iOS 26.0, *)) { + _cornerConfiguration = [UICornerConfiguration capsuleConfiguration]; + } else { + _cornerRadius = (_cornerRadius > FLT_EPSILON) ?: 12.0f; + } + // Set the tapped tint color if we've set to dynamically calculate it [self _updateTappedTintColorForTintColor]; @@ -134,11 +145,10 @@ - (void)_roundedButtonCommonInit TOROUNDEDBUTTON_OBJC_DIRECT { _containerView.backgroundColor = [UIColor clearColor]; _containerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; _containerView.userInteractionEnabled = NO; - _containerView.clipsToBounds = YES; [self addSubview:_containerView]; // Create the image view which will show the button background - _backgroundView = [self _makeBackgroundViewWithBlur:_isTranslucent]; + _backgroundView = [self _makeBackgroundViewWithStyle:_backgroundStyle]; [_containerView addSubview:_backgroundView]; // The foreground content view @@ -170,12 +180,24 @@ - (void)_makeTitleLabelIfNeeded TOROUNDEDBUTTON_OBJC_DIRECT { [_contentView addSubview:_titleLabel]; } -- (UIView *)_makeBackgroundViewWithBlur:(BOOL)withBlur TOROUNDEDBUTTON_OBJC_DIRECT { +- (UIView *)_makeBackgroundViewWithStyle:(TORoundedButtonBackgroundStyle)style TOROUNDEDBUTTON_OBJC_DIRECT { UIView *backgroundView = nil; - if (withBlur) { - UIBlurEffect *const blurEffect = [UIBlurEffect effectWithStyle:_blurStyle]; - backgroundView = [[UIVisualEffectView alloc] initWithEffect:blurEffect]; - backgroundView.clipsToBounds = YES; + if (TORoundedButtonIsDynamicBackground(style)) { + // Create a glass or blur style based on the associated style + UIVisualEffect *effect = nil; + if (@available(iOS 26.0, *)) { + if (style == TORoundedButtonBackgroundStyleGlass) { + UIGlassEffect *const glassEffect = [UIGlassEffect effectWithStyle:_glassStyle]; + glassEffect.interactive = YES; + glassEffect.tintColor = self.tintColor; + effect = glassEffect; + } + } + if (effect == nil) { + UIBlurEffect *const blurEffect = [UIBlurEffect effectWithStyle:_blurStyle]; + effect = blurEffect; + } + backgroundView = [[UIVisualEffectView alloc] initWithEffect:effect]; } else { backgroundView = [[UIView alloc] initWithFrame:CGRectZero]; backgroundView.backgroundColor = self.tintColor; @@ -183,13 +205,12 @@ - (UIView *)_makeBackgroundViewWithBlur:(BOOL)withBlur TOROUNDEDBUTTON_OBJC_DIRE backgroundView.frame = self.bounds; backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; -#ifdef __IPHONE_26_0 if (@available(iOS 26.0, *)) { backgroundView.cornerConfiguration = _cornerConfiguration; } else { + backgroundView.clipsToBounds = TORoundedButtonIsDynamicBackground(style); backgroundView.layer.cornerRadius = _cornerRadius; } -#endif #ifdef __IPHONE_13_0 if (@available(iOS 13.0, *)) { backgroundView.layer.cornerCurve = kCACornerCurveContinuous; } @@ -263,7 +284,7 @@ - (CGSize)sizeThatFits:(CGSize)size { - (void)tintColorDidChange { [super tintColorDidChange]; - if (_isTranslucent) { return; } + if (TORoundedButtonIsDynamicBackground(_backgroundStyle)) { return; } _titleLabel.backgroundColor = [self _labelBackgroundColor]; _backgroundView.backgroundColor = self.tintColor; [self setNeedsLayout]; @@ -276,7 +297,7 @@ - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { } - (void)_updateTappedTintColorForTintColor TOROUNDEDBUTTON_OBJC_DIRECT { - if (TO_ROUNDED_BUTTON_FLOAT_IS_ZERO(_tappedTintColorBrightnessOffset)) { + if (TORoundedButtonFloatIsZero(_tappedTintColorBrightnessOffset)) { return; } @@ -291,7 +312,7 @@ - (void)_updateTappedTintColorForTintColor TOROUNDEDBUTTON_OBJC_DIRECT { - (UIColor *)_labelBackgroundColor TOROUNDEDBUTTON_OBJC_DIRECT { // Always return clear if tapped - if (_isTapped || _isTranslucent) { return [UIColor clearColor]; } + if (_isTapped || TORoundedButtonIsDynamicBackground(_backgroundStyle)) { return [UIColor clearColor]; } // Return clear if the tint color isn't opaque const BOOL isClear = CGColorGetAlpha(self.tintColor.CGColor) < (1.0f - FLT_EPSILON); @@ -346,7 +367,7 @@ - (void)_didDragInside { #pragma mark - Animation - - (void)_setBackgroundColorTappedAnimated:(BOOL)animated TOROUNDEDBUTTON_OBJC_DIRECT { - if (!_tappedTintColor || _isTranslucent) { return; } + if (!_tappedTintColor || TORoundedButtonIsDynamicBackground(_backgroundStyle)) { return; } // Toggle the background color of the title label void (^updateTitleOpacity)(void) = ^{ @@ -508,7 +529,9 @@ - (void)setTextPointSize:(CGFloat)textPointSize { - (void)setTintColor:(UIColor *)tintColor { [super setTintColor:tintColor]; [self _updateTappedTintColorForTintColor]; - _backgroundView.backgroundColor = tintColor; + if (!TORoundedButtonIsDynamicBackground(_backgroundStyle)) { + _backgroundView.backgroundColor = tintColor; + } _titleLabel.backgroundColor = [self _labelBackgroundColor]; [self setNeedsLayout]; } @@ -521,7 +544,7 @@ - (void)setTappedTintColor:(UIColor *)tappedTintColor { } - (void)setTappedTintColorBrightnessOffset:(CGFloat)tappedTintColorBrightnessOffset { - if (TO_ROUNDED_BUTTON_FLOATS_MATCH(_tappedTintColorBrightnessOffset, + if (TORoundedButtonFloatsMatch(_tappedTintColorBrightnessOffset, tappedTintColorBrightnessOffset)) { return; } _tappedTintColorBrightnessOffset = tappedTintColorBrightnessOffset; @@ -544,6 +567,7 @@ - (void)setCornerRadius:(CGFloat)cornerRadius { _backgroundView.cornerConfiguration = _cornerConfiguration; } else { _backgroundView.layer.cornerRadius = _cornerRadius; + _backgroundView.layer.masksToBounds = TORoundedButtonIsDynamicBackground(_backgroundStyle); } #else _backgroundView.layer.cornerRadius = _cornerRadius; @@ -553,6 +577,7 @@ - (void)setCornerRadius:(CGFloat)cornerRadius { #ifdef __IPHONE_26_0 - (void)setCornerConfiguration:(UICornerConfiguration *)cornerConfiguration { + if (_cornerConfiguration == cornerConfiguration) { return; } _cornerConfiguration = cornerConfiguration; _backgroundView.cornerConfiguration = _cornerConfiguration; } @@ -562,14 +587,11 @@ - (UICornerConfiguration *)cornerConfiguration { } #endif -- (void)setIsTranslucent:(BOOL)isTranslucent { - if (_isTranslucent == isTranslucent) { - return; - } - - _isTranslucent = isTranslucent; +- (void)setBackgroundStyle:(TORoundedButtonBackgroundStyle)backgroundStyle { + if (_backgroundStyle == backgroundStyle) { return; } + _backgroundStyle = backgroundStyle; [_backgroundView removeFromSuperview]; - _backgroundView = [self _makeBackgroundViewWithBlur:_isTranslucent]; + _backgroundView = [self _makeBackgroundViewWithStyle:_backgroundStyle]; [_containerView insertSubview:_backgroundView atIndex:0]; _titleLabel.backgroundColor = [self _labelBackgroundColor]; [self setNeedsLayout]; @@ -581,7 +603,7 @@ - (void)setBlurStyle:(UIBlurEffectStyle)blurStyle { } _blurStyle = blurStyle; - if (!_isTranslucent || ![_backgroundView isKindOfClass:[UIVisualEffectView class]]) { + if (!TORoundedButtonIsDynamicBackground(_backgroundStyle) || ![_backgroundView isKindOfClass:[UIVisualEffectView class]]) { return; } @@ -589,6 +611,22 @@ - (void)setBlurStyle:(UIBlurEffectStyle)blurStyle { [blurView setEffect:[UIBlurEffect effectWithStyle:_blurStyle]]; } +- (void)setGlassStyle:(UIGlassEffectStyle)glassStyle { + if (_glassStyle == glassStyle) { return; } + _glassStyle = glassStyle; + + if (!TORoundedButtonIsDynamicBackground(_backgroundStyle) || ![_backgroundView isKindOfClass:[UIVisualEffectView class]]) { + return; + } + + UIGlassEffect *const glassEffect = [UIGlassEffect effectWithStyle:_glassStyle]; + glassEffect.tintColor = self.tintColor; + glassEffect.interactive = YES; + + UIVisualEffectView *const effectView = (UIVisualEffectView *)_backgroundView; + [effectView setEffect:glassEffect]; +} + - (void)setEnabled:(BOOL)enabled { [super setEnabled:enabled]; _containerView.alpha = enabled ? 1 : 0.4; From 3114150cd14df22d8fbd2957eb992d5c217a8602 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Sun, 23 Nov 2025 15:47:06 +0900 Subject: [PATCH 04/15] Revert removing the header define This reverts commit 25aaf0f8cbcbd0770beabc5fda96b787dcfc945a. --- TORoundedButton/TORoundedButton.h | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/TORoundedButton/TORoundedButton.h b/TORoundedButton/TORoundedButton.h index 55630b5..6a5feac 100644 --- a/TORoundedButton/TORoundedButton.h +++ b/TORoundedButton/TORoundedButton.h @@ -26,6 +26,13 @@ NS_ASSUME_NONNULL_BEGIN @class TORoundedButton; +/// The types of static/dynamic visual styles that can be applied to the background. +typedef NS_ENUM(NSInteger, TORoundedButtonBackgroundStyle) { + TORoundedButtonBackgroundStyleSolid, + TORoundedButtonBackgroundStyleBlur, + TORoundedButtonBackgroundStyleGlass +}; + NS_SWIFT_NAME(RoundedButtonDelegate) @protocol TORoundedButtonDelegate @@ -65,12 +72,15 @@ IB_DESIGNABLE @interface TORoundedButton : UIControl /// (Default value is 15 points inset from each edge). @property (nonatomic, assign) UIEdgeInsets contentInset; -/// Replaces the solid color background with a blur view. (Default is NO) -@property (nonatomic, assign) BOOL isTranslucent; +/// The style, whether static or dynamic of the button's background view. +@property (nonatomic, assign) TORoundedButtonBackgroundStyle backgroundStyle; -/// When `isTranslucent` is `YES`, the amount of blur the background view has. +/// When `backgroundStyle` is set to `.blur`, the specific blur style to apply. @property (nonatomic, assign) UIBlurEffectStyle blurStyle; +/// When `backgroundStyle` is set to `.blur`, the specific blur style to apply. +@property (nonatomic, assign) UIGlassEffectStyle glassStyle API_AVAILABLE(ios(26.0)); + /// The text that is displayed in center of the button (Default is nil). /// This adds an internally controlled label view to the main content view. @property (nonatomic, copy, nullable) IBInspectable NSString *text; From 1eb8ffaea45c8a8eb2e8d3210ce76904797e8b3c Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Wed, 21 Jan 2026 12:29:16 +0800 Subject: [PATCH 05/15] Changed ordering of views when glass is enabled --- TORoundedButton/TORoundedButton.m | 12 +++++++++++- TORoundedButtonExample/ViewController.m | 2 ++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/TORoundedButton/TORoundedButton.m b/TORoundedButton/TORoundedButton.m index 775d516..80f7e31 100644 --- a/TORoundedButton/TORoundedButton.m +++ b/TORoundedButton/TORoundedButton.m @@ -592,8 +592,18 @@ - (void)setBackgroundStyle:(TORoundedButtonBackgroundStyle)backgroundStyle { _backgroundStyle = backgroundStyle; [_backgroundView removeFromSuperview]; _backgroundView = [self _makeBackgroundViewWithStyle:_backgroundStyle]; - [_containerView insertSubview:_backgroundView atIndex:0]; _titleLabel.backgroundColor = [self _labelBackgroundColor]; + const BOOL isGlass = backgroundStyle == TORoundedButtonBackgroundStyleGlass; + if (!isGlass) { + _containerView.hidden = NO; + [_containerView insertSubview:_backgroundView atIndex:0]; + [_containerView addSubview:_contentView]; + } else { + UIVisualEffectView *glassView = (UIVisualEffectView *)_backgroundView; + _containerView.hidden = YES; + [self insertSubview:glassView atIndex:0]; + [glassView.contentView addSubview:_contentView]; + } [self setNeedsLayout]; } diff --git a/TORoundedButtonExample/ViewController.m b/TORoundedButtonExample/ViewController.m index 8dca209..d93d322 100644 --- a/TORoundedButtonExample/ViewController.m +++ b/TORoundedButtonExample/ViewController.m @@ -20,6 +20,8 @@ - (void)viewDidLoad { // Hide the tapped label self.tappedLabel.alpha = 0.0f; + self.button.backgroundStyle = TORoundedButtonBackgroundStyleGlass; + __weak typeof(self) weakSelf = self; self.button.tappedHandler = ^{ [weakSelf playFadeAnimationOnView:weakSelf.tappedLabel]; From 414cb1990e47cdb0c57c7e6c7da1c00276dc458a Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Wed, 21 Jan 2026 23:31:31 +0800 Subject: [PATCH 06/15] Implemented Liquid Glass with aligned tinting animation --- TORoundedButton/TORoundedButton.m | 94 +++++++++++++++---------- TORoundedButtonExample/ViewController.m | 2 - 2 files changed, 56 insertions(+), 40 deletions(-) diff --git a/TORoundedButton/TORoundedButton.m b/TORoundedButton/TORoundedButton.m index 80f7e31..1350354 100644 --- a/TORoundedButton/TORoundedButton.m +++ b/TORoundedButton/TORoundedButton.m @@ -35,8 +35,12 @@ static inline BOOL TORoundedButtonFloatsMatch(CGFloat firstValue, CGFloat second return fabs(firstValue - secondValue) > FLT_EPSILON; } -static inline BOOL TORoundedButtonIsDynamicBackground(TORoundedButtonBackgroundStyle backgroundStyle) { - return backgroundStyle != TORoundedButtonBackgroundStyleSolid; +static inline BOOL TORoundedButtonIsSolidBackground(TORoundedButtonBackgroundStyle backgroundStyle) { + return backgroundStyle == TORoundedButtonBackgroundStyleSolid; +} + +static inline BOOL TORoundedButtonIsTintableBackground(TORoundedButtonBackgroundStyle backgroundStyle) { + return backgroundStyle != TORoundedButtonBackgroundStyleBlur; } // -------------------------------------------------------------------- @@ -147,10 +151,6 @@ - (void)_roundedButtonCommonInit TOROUNDEDBUTTON_OBJC_DIRECT { _containerView.userInteractionEnabled = NO; [self addSubview:_containerView]; - // Create the image view which will show the button background - _backgroundView = [self _makeBackgroundViewWithStyle:_backgroundStyle]; - [_containerView addSubview:_backgroundView]; - // The foreground content view [_containerView addSubview:_contentView]; @@ -161,6 +161,17 @@ - (void)_roundedButtonCommonInit TOROUNDEDBUTTON_OBJC_DIRECT { [self addTarget:self action:@selector(_didDragInside) forControlEvents:UIControlEventTouchDragEnter]; } +- (void)didMoveToSuperview { + [super didMoveToSuperview]; + if (self.superview == nil || _backgroundView != nil) { + return; + } + + // Defer making the background until we're added to the subview in case the user changes it + _backgroundView = [self _makeBackgroundViewWithStyle:_backgroundStyle]; + [_containerView insertSubview:_backgroundView atIndex:0]; +} + - (void)_makeTitleLabelIfNeeded TOROUNDEDBUTTON_OBJC_DIRECT { if (_titleLabel) { return; } @@ -182,13 +193,12 @@ - (void)_makeTitleLabelIfNeeded TOROUNDEDBUTTON_OBJC_DIRECT { - (UIView *)_makeBackgroundViewWithStyle:(TORoundedButtonBackgroundStyle)style TOROUNDEDBUTTON_OBJC_DIRECT { UIView *backgroundView = nil; - if (TORoundedButtonIsDynamicBackground(style)) { + if (!TORoundedButtonIsSolidBackground(style)) { // Create a glass or blur style based on the associated style UIVisualEffect *effect = nil; if (@available(iOS 26.0, *)) { if (style == TORoundedButtonBackgroundStyleGlass) { UIGlassEffect *const glassEffect = [UIGlassEffect effectWithStyle:_glassStyle]; - glassEffect.interactive = YES; glassEffect.tintColor = self.tintColor; effect = glassEffect; } @@ -208,7 +218,7 @@ - (UIView *)_makeBackgroundViewWithStyle:(TORoundedButtonBackgroundStyle)style T if (@available(iOS 26.0, *)) { backgroundView.cornerConfiguration = _cornerConfiguration; } else { - backgroundView.clipsToBounds = TORoundedButtonIsDynamicBackground(style); + backgroundView.clipsToBounds = !TORoundedButtonIsSolidBackground(style); backgroundView.layer.cornerRadius = _cornerRadius; } @@ -253,6 +263,7 @@ - (void)layoutSubviews { _titleLabel.frame = CGRectIntegral(_titleLabel.frame); } +// We need to declare this since we explicitly define it in the header - (void)sizeToFit { [super sizeToFit]; } - (CGSize)sizeThatFits:(CGSize)size { @@ -282,11 +293,32 @@ - (CGSize)sizeThatFits:(CGSize)size { return newSize; } +- (void)_setBackgroundTintColor:(UIColor *)tintColor { + if (_backgroundStyle == TORoundedButtonBackgroundStyleBlur) { + return; + } +#ifdef __IPHONE_26_0 + if (@available(iOS 26.0, *)) { + if (_backgroundStyle == TORoundedButtonBackgroundStyleGlass) { + UIGlassEffect *effect = [UIGlassEffect effectWithStyle:UIGlassEffectStyleRegular]; + effect.tintColor = tintColor; + [(UIVisualEffectView *)_backgroundView setEffect:effect]; + } else { + _backgroundView.backgroundColor = tintColor; + } + } else { + _backgroundView.backgroundColor = tintColor; + } +#else + _backgroundView.backgroundColor = tintColor; +#endif +} + - (void)tintColorDidChange { [super tintColorDidChange]; - if (TORoundedButtonIsDynamicBackground(_backgroundStyle)) { return; } + if (!TORoundedButtonIsTintableBackground(_backgroundStyle)) { return; } _titleLabel.backgroundColor = [self _labelBackgroundColor]; - _backgroundView.backgroundColor = self.tintColor; + [self _setBackgroundTintColor:self.tintColor]; [self setNeedsLayout]; } @@ -311,10 +343,8 @@ - (void)_updateTappedTintColorForTintColor TOROUNDEDBUTTON_OBJC_DIRECT { } - (UIColor *)_labelBackgroundColor TOROUNDEDBUTTON_OBJC_DIRECT { - // Always return clear if tapped - if (_isTapped || TORoundedButtonIsDynamicBackground(_backgroundStyle)) { return [UIColor clearColor]; } - - // Return clear if the tint color isn't opaque + // Always return clear if we're not overlaying on a completely solid BG + if (_isTapped || !TORoundedButtonIsSolidBackground(_backgroundStyle)) { return [UIColor clearColor]; } const BOOL isClear = CGColorGetAlpha(self.tintColor.CGColor) < (1.0f - FLT_EPSILON); return isClear ? [UIColor clearColor] : self.tintColor; } @@ -367,7 +397,7 @@ - (void)_didDragInside { #pragma mark - Animation - - (void)_setBackgroundColorTappedAnimated:(BOOL)animated TOROUNDEDBUTTON_OBJC_DIRECT { - if (!_tappedTintColor || TORoundedButtonIsDynamicBackground(_backgroundStyle)) { return; } + if (!_tappedTintColor || !TORoundedButtonIsTintableBackground(_backgroundStyle)) { return; } // Toggle the background color of the title label void (^updateTitleOpacity)(void) = ^{ @@ -375,11 +405,12 @@ - (void)_setBackgroundColorTappedAnimated:(BOOL)animated TOROUNDEDBUTTON_OBJC_DI }; // ----------------------------------------------------- - + + UIColor *const destinationColor = _isTapped ? _tappedTintColor : self.tintColor; void (^animationBlock)(void) = ^{ - self->_backgroundView.backgroundColor = self->_isTapped ? self->_tappedTintColor : self.tintColor; + [self _setBackgroundTintColor:destinationColor]; }; - + void (^completionBlock)(BOOL) = ^(BOOL completed){ if (completed == NO) { return; } updateTitleOpacity(); @@ -399,7 +430,6 @@ - (void)_setBackgroundColorTappedAnimated:(BOOL)animated TOROUNDEDBUTTON_OBJC_DI animations:animationBlock completion:completionBlock]; } - } - (void)_setLabelAlphaTappedAnimated:(BOOL)animated TOROUNDEDBUTTON_OBJC_DIRECT { @@ -443,7 +473,7 @@ - (void)_setButtonScaledTappedAnimated:(BOOL)animated TOROUNDEDBUTTON_OBJC_DIREC self->_containerView.transform = CGAffineTransformScale(CGAffineTransformIdentity, scale, scale); - }; + }; // If we're not animating, just call the blocks manually if (!animated) { @@ -529,9 +559,7 @@ - (void)setTextPointSize:(CGFloat)textPointSize { - (void)setTintColor:(UIColor *)tintColor { [super setTintColor:tintColor]; [self _updateTappedTintColorForTintColor]; - if (!TORoundedButtonIsDynamicBackground(_backgroundStyle)) { - _backgroundView.backgroundColor = tintColor; - } + [self _setBackgroundTintColor:tintColor]; _titleLabel.backgroundColor = [self _labelBackgroundColor]; [self setNeedsLayout]; } @@ -567,7 +595,7 @@ - (void)setCornerRadius:(CGFloat)cornerRadius { _backgroundView.cornerConfiguration = _cornerConfiguration; } else { _backgroundView.layer.cornerRadius = _cornerRadius; - _backgroundView.layer.masksToBounds = TORoundedButtonIsDynamicBackground(_backgroundStyle); + _backgroundView.layer.masksToBounds = !TORoundedButtonIsSolidBackground(_backgroundStyle); } #else _backgroundView.layer.cornerRadius = _cornerRadius; @@ -592,18 +620,8 @@ - (void)setBackgroundStyle:(TORoundedButtonBackgroundStyle)backgroundStyle { _backgroundStyle = backgroundStyle; [_backgroundView removeFromSuperview]; _backgroundView = [self _makeBackgroundViewWithStyle:_backgroundStyle]; + [_containerView insertSubview:_backgroundView atIndex:0]; _titleLabel.backgroundColor = [self _labelBackgroundColor]; - const BOOL isGlass = backgroundStyle == TORoundedButtonBackgroundStyleGlass; - if (!isGlass) { - _containerView.hidden = NO; - [_containerView insertSubview:_backgroundView atIndex:0]; - [_containerView addSubview:_contentView]; - } else { - UIVisualEffectView *glassView = (UIVisualEffectView *)_backgroundView; - _containerView.hidden = YES; - [self insertSubview:glassView atIndex:0]; - [glassView.contentView addSubview:_contentView]; - } [self setNeedsLayout]; } @@ -613,7 +631,7 @@ - (void)setBlurStyle:(UIBlurEffectStyle)blurStyle { } _blurStyle = blurStyle; - if (!TORoundedButtonIsDynamicBackground(_backgroundStyle) || ![_backgroundView isKindOfClass:[UIVisualEffectView class]]) { + if (_backgroundStyle != TORoundedButtonBackgroundStyleBlur || ![_backgroundView isKindOfClass:[UIVisualEffectView class]]) { return; } @@ -625,7 +643,7 @@ - (void)setGlassStyle:(UIGlassEffectStyle)glassStyle { if (_glassStyle == glassStyle) { return; } _glassStyle = glassStyle; - if (!TORoundedButtonIsDynamicBackground(_backgroundStyle) || ![_backgroundView isKindOfClass:[UIVisualEffectView class]]) { + if (_backgroundStyle != TORoundedButtonBackgroundStyleGlass || ![_backgroundView isKindOfClass:[UIVisualEffectView class]]) { return; } diff --git a/TORoundedButtonExample/ViewController.m b/TORoundedButtonExample/ViewController.m index d93d322..8dca209 100644 --- a/TORoundedButtonExample/ViewController.m +++ b/TORoundedButtonExample/ViewController.m @@ -20,8 +20,6 @@ - (void)viewDidLoad { // Hide the tapped label self.tappedLabel.alpha = 0.0f; - self.button.backgroundStyle = TORoundedButtonBackgroundStyleGlass; - __weak typeof(self) weakSelf = self; self.button.tappedHandler = ^{ [weakSelf playFadeAnimationOnView:weakSelf.tappedLabel]; From f150563e14ccf5aafc4cd7b94cc3e9026152d87c Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Wed, 21 Jan 2026 23:33:06 +0800 Subject: [PATCH 07/15] Fixed up inconsistent glass view creation --- TORoundedButton/TORoundedButton.h | 2 +- TORoundedButton/TORoundedButton.m | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/TORoundedButton/TORoundedButton.h b/TORoundedButton/TORoundedButton.h index 6a5feac..e6de3fb 100644 --- a/TORoundedButton/TORoundedButton.h +++ b/TORoundedButton/TORoundedButton.h @@ -78,7 +78,7 @@ IB_DESIGNABLE @interface TORoundedButton : UIControl /// When `backgroundStyle` is set to `.blur`, the specific blur style to apply. @property (nonatomic, assign) UIBlurEffectStyle blurStyle; -/// When `backgroundStyle` is set to `.blur`, the specific blur style to apply. +/// When `backgroundStyle` is set to `.glass`, the specific glass style to apply. @property (nonatomic, assign) UIGlassEffectStyle glassStyle API_AVAILABLE(ios(26.0)); /// The text that is displayed in center of the button (Default is nil). diff --git a/TORoundedButton/TORoundedButton.m b/TORoundedButton/TORoundedButton.m index 1350354..5d9354b 100644 --- a/TORoundedButton/TORoundedButton.m +++ b/TORoundedButton/TORoundedButton.m @@ -300,7 +300,7 @@ - (void)_setBackgroundTintColor:(UIColor *)tintColor { #ifdef __IPHONE_26_0 if (@available(iOS 26.0, *)) { if (_backgroundStyle == TORoundedButtonBackgroundStyleGlass) { - UIGlassEffect *effect = [UIGlassEffect effectWithStyle:UIGlassEffectStyleRegular]; + UIGlassEffect *effect = [UIGlassEffect effectWithStyle:_glassStyle]; effect.tintColor = tintColor; [(UIVisualEffectView *)_backgroundView setEffect:effect]; } else { @@ -649,8 +649,7 @@ - (void)setGlassStyle:(UIGlassEffectStyle)glassStyle { UIGlassEffect *const glassEffect = [UIGlassEffect effectWithStyle:_glassStyle]; glassEffect.tintColor = self.tintColor; - glassEffect.interactive = YES; - + UIVisualEffectView *const effectView = (UIVisualEffectView *)_backgroundView; [effectView setEffect:glassEffect]; } From 314be76075edf8560fb8aafa4e6cf48cdde36e74 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Wed, 21 Jan 2026 23:36:56 +0800 Subject: [PATCH 08/15] Make Liquid Glass the default on iOS 26 --- TORoundedButton/TORoundedButton.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/TORoundedButton/TORoundedButton.m b/TORoundedButton/TORoundedButton.m index 5d9354b..a78e218 100644 --- a/TORoundedButton/TORoundedButton.m +++ b/TORoundedButton/TORoundedButton.m @@ -122,6 +122,7 @@ - (void)_roundedButtonCommonInit TOROUNDEDBUTTON_OBJC_DIRECT { // Set the corner radius depending on system version #ifdef __IPHONE_26_0 if (@available(iOS 26.0, *)) { + _backgroundStyle = TORoundedButtonBackgroundStyleGlass; _cornerConfiguration = [UICornerConfiguration capsuleConfiguration]; } else { _cornerRadius = (_cornerRadius > FLT_EPSILON) ?: 12.0f; @@ -131,7 +132,7 @@ - (void)_roundedButtonCommonInit TOROUNDEDBUTTON_OBJC_DIRECT { #endif #ifdef __IPHONE_13_0 - if (@available(iOS 13.0, *)) { _blurStyle = UIBlurEffectStyleSystemThinMaterialDark; } + if (@available(iOS 13.0, *)) { _blurStyle = UIBlurEffectStyleSystemThinMaterial; } #endif // Set the corner radius depending on system version @@ -649,7 +650,7 @@ - (void)setGlassStyle:(UIGlassEffectStyle)glassStyle { UIGlassEffect *const glassEffect = [UIGlassEffect effectWithStyle:_glassStyle]; glassEffect.tintColor = self.tintColor; - + UIVisualEffectView *const effectView = (UIVisualEffectView *)_backgroundView; [effectView setEffect:glassEffect]; } From 215bcddcaeb9a1393423b36b1661ed582439d658 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Wed, 21 Jan 2026 23:41:52 +0800 Subject: [PATCH 09/15] Fixed some bugs and incorrect commenting --- TORoundedButton/TORoundedButton.m | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/TORoundedButton/TORoundedButton.m b/TORoundedButton/TORoundedButton.m index a78e218..d9d94b5 100644 --- a/TORoundedButton/TORoundedButton.m +++ b/TORoundedButton/TORoundedButton.m @@ -32,7 +32,7 @@ static inline BOOL TORoundedButtonFloatIsZero(CGFloat value) { } static inline BOOL TORoundedButtonFloatsMatch(CGFloat firstValue, CGFloat secondValue) { - return fabs(firstValue - secondValue) > FLT_EPSILON; + return fabs(firstValue - secondValue) < FLT_EPSILON; } static inline BOOL TORoundedButtonIsSolidBackground(TORoundedButtonBackgroundStyle backgroundStyle) { @@ -135,13 +135,6 @@ - (void)_roundedButtonCommonInit TOROUNDEDBUTTON_OBJC_DIRECT { if (@available(iOS 13.0, *)) { _blurStyle = UIBlurEffectStyleSystemThinMaterial; } #endif - // Set the corner radius depending on system version - if (@available(iOS 26.0, *)) { - _cornerConfiguration = [UICornerConfiguration capsuleConfiguration]; - } else { - _cornerRadius = (_cornerRadius > FLT_EPSILON) ?: 12.0f; - } - // Set the tapped tint color if we've set to dynamically calculate it [self _updateTappedTintColorForTintColor]; @@ -469,12 +462,12 @@ - (void)_setButtonScaledTappedAnimated:(BOOL)animated TOROUNDEDBUTTON_OBJC_DIREC const CGFloat scale = _isTapped ? _tappedButtonScale : 1.0f; - // Animate the alpha value of the label + // Animate the scale value of the label void (^animationBlock)(void) = ^{ self->_containerView.transform = CGAffineTransformScale(CGAffineTransformIdentity, scale, scale); - }; + }; // If we're not animating, just call the blocks manually if (!animated) { From 16d05b46cd1b987d12c360d90e616d3f9c445220 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Wed, 21 Jan 2026 23:42:40 +0800 Subject: [PATCH 10/15] Bumped copyright year --- TORoundedButton/TORoundedButton.h | 2 +- TORoundedButton/TORoundedButton.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/TORoundedButton/TORoundedButton.h b/TORoundedButton/TORoundedButton.h index e6de3fb..3c3c7b5 100644 --- a/TORoundedButton/TORoundedButton.h +++ b/TORoundedButton/TORoundedButton.h @@ -1,7 +1,7 @@ // // TORoundedButton.h // -// Copyright 2019-2023 Timothy Oliver. All rights reserved. +// Copyright 2019-2026 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 diff --git a/TORoundedButton/TORoundedButton.m b/TORoundedButton/TORoundedButton.m index d9d94b5..821d2f8 100644 --- a/TORoundedButton/TORoundedButton.m +++ b/TORoundedButton/TORoundedButton.m @@ -1,7 +1,7 @@ // // TORoundedButton.m // -// Copyright 2019-2023 Timothy Oliver. All rights reserved. +// Copyright 2019-2026 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 From 35d3c56645676ebfc5785fbb446e272cfd7aeaaf Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Wed, 21 Jan 2026 23:52:01 +0800 Subject: [PATCH 11/15] Refined animation code to be less redundant --- TORoundedButton/TORoundedButton.m | 64 +++++++++---------------------- 1 file changed, 18 insertions(+), 46 deletions(-) diff --git a/TORoundedButton/TORoundedButton.m b/TORoundedButton/TORoundedButton.m index 821d2f8..679c377 100644 --- a/TORoundedButton/TORoundedButton.m +++ b/TORoundedButton/TORoundedButton.m @@ -209,10 +209,10 @@ - (UIView *)_makeBackgroundViewWithStyle:(TORoundedButtonBackgroundStyle)style T backgroundView.frame = self.bounds; backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + backgroundView.clipsToBounds = !TORoundedButtonIsSolidBackground(style); if (@available(iOS 26.0, *)) { backgroundView.cornerConfiguration = _cornerConfiguration; } else { - backgroundView.clipsToBounds = !TORoundedButtonIsSolidBackground(style); backgroundView.layer.cornerRadius = _cornerRadius; } @@ -390,16 +390,20 @@ - (void)_didDragInside { #pragma mark - Animation - +- (void)_performTapAnimation:(void (^)(void))animations + completion:(void (^_Nullable)(BOOL finished))completion TOROUNDEDBUTTON_OBJC_DIRECT { + [UIView animateWithDuration:_tapAnimationDuration + delay:0.0f + usingSpringWithDamping:1.0f + initialSpringVelocity:0.5f + options:UIViewAnimationOptionBeginFromCurrentState + animations:animations + completion:completion]; +} + - (void)_setBackgroundColorTappedAnimated:(BOOL)animated TOROUNDEDBUTTON_OBJC_DIRECT { if (!_tappedTintColor || !TORoundedButtonIsTintableBackground(_backgroundStyle)) { return; } - // Toggle the background color of the title label - void (^updateTitleOpacity)(void) = ^{ - self->_titleLabel.backgroundColor = [self _labelBackgroundColor]; - }; - - // ----------------------------------------------------- - UIColor *const destinationColor = _isTapped ? _tappedTintColor : self.tintColor; void (^animationBlock)(void) = ^{ [self _setBackgroundTintColor:destinationColor]; @@ -407,22 +411,15 @@ - (void)_setBackgroundColorTappedAnimated:(BOOL)animated TOROUNDEDBUTTON_OBJC_DI void (^completionBlock)(BOOL) = ^(BOOL completed){ if (completed == NO) { return; } - updateTitleOpacity(); + self->_titleLabel.backgroundColor = [self _labelBackgroundColor]; }; if (!animated) { animationBlock(); completionBlock(YES); - } - else { + } else { _titleLabel.backgroundColor = [UIColor clearColor]; - [UIView animateWithDuration:_tapAnimationDuration - delay:0.0f - usingSpringWithDamping:1.0f - initialSpringVelocity:0.5f - options:UIViewAnimationOptionBeginFromCurrentState - animations:animationBlock - completion:completionBlock]; + [self _performTapAnimation:animationBlock completion:completionBlock]; } } @@ -430,59 +427,34 @@ - (void)_setLabelAlphaTappedAnimated:(BOOL)animated TOROUNDEDBUTTON_OBJC_DIRECT if (_tappedTextAlpha > 1.0f - FLT_EPSILON) { return; } const CGFloat alpha = _isTapped ? _tappedTextAlpha : 1.0f; - - // Animate the alpha value of the label void (^animationBlock)(void) = ^{ self->_titleLabel.alpha = alpha; }; - // If we're not animating, just call the blocks manually if (!animated) { - // Remove any animations in progress [_titleLabel.layer removeAnimationForKey:@"opacity"]; animationBlock(); return; } - // Set the title label to clear beforehand _titleLabel.backgroundColor = [UIColor clearColor]; - - // Animate the button alpha - [UIView animateWithDuration:_tapAnimationDuration - delay:0.0f - usingSpringWithDamping:1.0f - initialSpringVelocity:0.5f - options:UIViewAnimationOptionBeginFromCurrentState - animations:animationBlock - completion:nil]; + [self _performTapAnimation:animationBlock completion:nil]; } - (void)_setButtonScaledTappedAnimated:(BOOL)animated TOROUNDEDBUTTON_OBJC_DIRECT { if (_tappedButtonScale < FLT_EPSILON) { return; } const CGFloat scale = _isTapped ? _tappedButtonScale : 1.0f; - - // Animate the scale value of the label void (^animationBlock)(void) = ^{ - self->_containerView.transform = CGAffineTransformScale(CGAffineTransformIdentity, - scale, - scale); + self->_containerView.transform = CGAffineTransformScale(CGAffineTransformIdentity, scale, scale); }; - // If we're not animating, just call the blocks manually if (!animated) { animationBlock(); return; } - // Animate the button alpha - [UIView animateWithDuration:_tapAnimationDuration - delay:0.0f - usingSpringWithDamping:1.0f - initialSpringVelocity:0.5f - options:UIViewAnimationOptionBeginFromCurrentState - animations:animationBlock - completion:nil]; + [self _performTapAnimation:animationBlock completion:nil]; } #pragma mark - Public Accessors - From a8cb293ff099ed78227e26e604596e8878233c3c Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Thu, 22 Jan 2026 00:00:04 +0800 Subject: [PATCH 12/15] Re-organized the code into more logical sections --- TORoundedButton/TORoundedButton.m | 142 +++++++++++++++--------------- 1 file changed, 72 insertions(+), 70 deletions(-) diff --git a/TORoundedButton/TORoundedButton.m b/TORoundedButton/TORoundedButton.m index 679c377..72fdb05 100644 --- a/TORoundedButton/TORoundedButton.m +++ b/TORoundedButton/TORoundedButton.m @@ -155,17 +155,6 @@ - (void)_roundedButtonCommonInit TOROUNDEDBUTTON_OBJC_DIRECT { [self addTarget:self action:@selector(_didDragInside) forControlEvents:UIControlEventTouchDragEnter]; } -- (void)didMoveToSuperview { - [super didMoveToSuperview]; - if (self.superview == nil || _backgroundView != nil) { - return; - } - - // Defer making the background until we're added to the subview in case the user changes it - _backgroundView = [self _makeBackgroundViewWithStyle:_backgroundStyle]; - [_containerView insertSubview:_backgroundView atIndex:0]; -} - - (void)_makeTitleLabelIfNeeded TOROUNDEDBUTTON_OBJC_DIRECT { if (_titleLabel) { return; } @@ -222,6 +211,33 @@ - (UIView *)_makeBackgroundViewWithStyle:(TORoundedButtonBackgroundStyle)style T return backgroundView; } +#pragma mark - View Lifecycle - + +- (void)didMoveToSuperview { + [super didMoveToSuperview]; + if (self.superview == nil || _backgroundView != nil) { + return; + } + + // Defer making the background until we're added to the subview in case the user changes it + _backgroundView = [self _makeBackgroundViewWithStyle:_backgroundStyle]; + [_containerView insertSubview:_backgroundView atIndex:0]; +} + +- (void)tintColorDidChange { + [super tintColorDidChange]; + if (!TORoundedButtonIsTintableBackground(_backgroundStyle)) { return; } + _titleLabel.backgroundColor = [self _labelBackgroundColor]; + [self _setBackgroundTintColor:self.tintColor]; + [self setNeedsLayout]; +} + +- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { + [super traitCollectionDidChange:previousTraitCollection]; + [self setNeedsLayout]; + [self _updateTappedTintColorForTintColor]; +} + #pragma mark - View Layout - - (void)layoutSubviews { @@ -287,62 +303,6 @@ - (CGSize)sizeThatFits:(CGSize)size { return newSize; } -- (void)_setBackgroundTintColor:(UIColor *)tintColor { - if (_backgroundStyle == TORoundedButtonBackgroundStyleBlur) { - return; - } -#ifdef __IPHONE_26_0 - if (@available(iOS 26.0, *)) { - if (_backgroundStyle == TORoundedButtonBackgroundStyleGlass) { - UIGlassEffect *effect = [UIGlassEffect effectWithStyle:_glassStyle]; - effect.tintColor = tintColor; - [(UIVisualEffectView *)_backgroundView setEffect:effect]; - } else { - _backgroundView.backgroundColor = tintColor; - } - } else { - _backgroundView.backgroundColor = tintColor; - } -#else - _backgroundView.backgroundColor = tintColor; -#endif -} - -- (void)tintColorDidChange { - [super tintColorDidChange]; - if (!TORoundedButtonIsTintableBackground(_backgroundStyle)) { return; } - _titleLabel.backgroundColor = [self _labelBackgroundColor]; - [self _setBackgroundTintColor:self.tintColor]; - [self setNeedsLayout]; -} - -- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { - [super traitCollectionDidChange:previousTraitCollection]; - [self setNeedsLayout]; - [self _updateTappedTintColorForTintColor]; -} - -- (void)_updateTappedTintColorForTintColor TOROUNDEDBUTTON_OBJC_DIRECT { - if (TORoundedButtonFloatIsZero(_tappedTintColorBrightnessOffset)) { - return; - } - - UIColor *tintColor = self.tintColor; - if (@available(iOS 13.0, *)) { - tintColor = [tintColor resolvedColorWithTraitCollection:self.traitCollection]; - } - - _tappedTintColor = [self _brightnessAdjustedColorWithColor:tintColor - amount:_tappedTintColorBrightnessOffset]; -} - -- (UIColor *)_labelBackgroundColor TOROUNDEDBUTTON_OBJC_DIRECT { - // Always return clear if we're not overlaying on a completely solid BG - if (_isTapped || !TORoundedButtonIsSolidBackground(_backgroundStyle)) { return [UIColor clearColor]; } - const BOOL isClear = CGColorGetAlpha(self.tintColor.CGColor) < (1.0f - FLT_EPSILON); - return isClear ? [UIColor clearColor] : self.tintColor; -} - #pragma mark - Interaction - - (void)_didTouchDownInside { @@ -625,14 +585,56 @@ - (void)setEnabled:(BOOL)enabled { _containerView.alpha = enabled ? 1 : 0.4; } -#pragma mark - Graphics Handling - +#pragma mark - Private - + +- (void)_setBackgroundTintColor:(UIColor *)tintColor TOROUNDEDBUTTON_OBJC_DIRECT { + if (_backgroundStyle == TORoundedButtonBackgroundStyleBlur) { + return; + } +#ifdef __IPHONE_26_0 + if (@available(iOS 26.0, *)) { + if (_backgroundStyle == TORoundedButtonBackgroundStyleGlass) { + UIGlassEffect *effect = [UIGlassEffect effectWithStyle:_glassStyle]; + effect.tintColor = tintColor; + [(UIVisualEffectView *)_backgroundView setEffect:effect]; + } else { + _backgroundView.backgroundColor = tintColor; + } + } else { + _backgroundView.backgroundColor = tintColor; + } +#else + _backgroundView.backgroundColor = tintColor; +#endif +} + +- (void)_updateTappedTintColorForTintColor TOROUNDEDBUTTON_OBJC_DIRECT { + if (TORoundedButtonFloatIsZero(_tappedTintColorBrightnessOffset)) { + return; + } + + UIColor *tintColor = self.tintColor; + if (@available(iOS 13.0, *)) { + tintColor = [tintColor resolvedColorWithTraitCollection:self.traitCollection]; + } + + _tappedTintColor = [self _brightnessAdjustedColorWithColor:tintColor + amount:_tappedTintColorBrightnessOffset]; +} + +- (UIColor *)_labelBackgroundColor TOROUNDEDBUTTON_OBJC_DIRECT { + // Always return clear if we're not overlaying on a completely solid background + if (_isTapped || !TORoundedButtonIsSolidBackground(_backgroundStyle)) { return [UIColor clearColor]; } + const BOOL isClear = CGColorGetAlpha(self.tintColor.CGColor) < (1.0f - FLT_EPSILON); + return isClear ? [UIColor clearColor] : self.tintColor; +} - (UIColor *)_brightnessAdjustedColorWithColor:(UIColor *)color amount:(CGFloat)amount TOROUNDEDBUTTON_OBJC_DIRECT { if (!color) { return nil; } - + CGFloat h, s, b, a; if (![color getHue:&h saturation:&s brightness:&b alpha:&a]) { return nil; } - b += amount; // Add the adjust amount + b += amount; b = MAX(b, 0.0f); b = MIN(b, 1.0f); return [UIColor colorWithHue:h saturation:s brightness:b alpha:a]; } From 1160c7c68f7c223260497cf99586ba7765cb6bce Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Thu, 22 Jan 2026 00:01:52 +0800 Subject: [PATCH 13/15] Updated iOS version in tests --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index debc12f..fcc0639 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,12 +15,12 @@ jobs: uses: futureware-tech/simulator-action@v2 with: model: 'iPhone 16e' - os_version: '=18.6' + os_version: '=26.2' - name: Run unit tests run: | xcodebuild \ -project TORoundedButtonExample.xcodeproj \ -scheme TORoundedButtonTests \ - -destination 'platform=iOS Simulator,name=iPhone 16e,OS=18.6' \ + -destination 'platform=iOS Simulator,name=iPhone 16e,OS=26.2' \ clean test From 6f9e6866a5bc300a426c953351f8c0bac6fc6d9b Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Thu, 22 Jan 2026 00:06:15 +0800 Subject: [PATCH 14/15] Updated target to macOS 26 specifically --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fcc0639..1adc5c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ on: [push] jobs: build: - runs-on: macos-latest + runs-on: macos-26 steps: - name: Checkout From 3d0950f37c61ff3ee5819dc99879ec91ab1c9919 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Thu, 22 Jan 2026 00:16:40 +0800 Subject: [PATCH 15/15] Fixed failing unit test --- TORoundedButtonExampleTests/TORoundedButtonExampleTests.m | 1 - 1 file changed, 1 deletion(-) diff --git a/TORoundedButtonExampleTests/TORoundedButtonExampleTests.m b/TORoundedButtonExampleTests/TORoundedButtonExampleTests.m index 6b4c6b7..dabcb52 100644 --- a/TORoundedButtonExampleTests/TORoundedButtonExampleTests.m +++ b/TORoundedButtonExampleTests/TORoundedButtonExampleTests.m @@ -20,7 +20,6 @@ - (void)testDefaultValues TORoundedButton *button = [[TORoundedButton alloc] initWithText:@"Test"]; XCTAssertNotNil(button); XCTAssertEqual(button.text, @"Test"); - XCTAssertEqual(button.cornerRadius, 12.0f); XCTAssertEqual(button.textColor, [UIColor whiteColor]); XCTAssertEqual(button.tappedTextAlpha, 1.0f); XCTAssertEqual(button.tappedTintColorBrightnessOffset, -0.15f);