Skip to content

Commit 414cb19

Browse files
committed
Implemented Liquid Glass with aligned tinting animation
1 parent 1eb8ffa commit 414cb19

File tree

2 files changed

+56
-40
lines changed

2 files changed

+56
-40
lines changed

TORoundedButton/TORoundedButton.m

Lines changed: 56 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,12 @@ static inline BOOL TORoundedButtonFloatsMatch(CGFloat firstValue, CGFloat second
3535
return fabs(firstValue - secondValue) > FLT_EPSILON;
3636
}
3737

38-
static inline BOOL TORoundedButtonIsDynamicBackground(TORoundedButtonBackgroundStyle backgroundStyle) {
39-
return backgroundStyle != TORoundedButtonBackgroundStyleSolid;
38+
static inline BOOL TORoundedButtonIsSolidBackground(TORoundedButtonBackgroundStyle backgroundStyle) {
39+
return backgroundStyle == TORoundedButtonBackgroundStyleSolid;
40+
}
41+
42+
static inline BOOL TORoundedButtonIsTintableBackground(TORoundedButtonBackgroundStyle backgroundStyle) {
43+
return backgroundStyle != TORoundedButtonBackgroundStyleBlur;
4044
}
4145

4246
// --------------------------------------------------------------------
@@ -147,10 +151,6 @@ - (void)_roundedButtonCommonInit TOROUNDEDBUTTON_OBJC_DIRECT {
147151
_containerView.userInteractionEnabled = NO;
148152
[self addSubview:_containerView];
149153

150-
// Create the image view which will show the button background
151-
_backgroundView = [self _makeBackgroundViewWithStyle:_backgroundStyle];
152-
[_containerView addSubview:_backgroundView];
153-
154154
// The foreground content view
155155
[_containerView addSubview:_contentView];
156156

@@ -161,6 +161,17 @@ - (void)_roundedButtonCommonInit TOROUNDEDBUTTON_OBJC_DIRECT {
161161
[self addTarget:self action:@selector(_didDragInside) forControlEvents:UIControlEventTouchDragEnter];
162162
}
163163

164+
- (void)didMoveToSuperview {
165+
[super didMoveToSuperview];
166+
if (self.superview == nil || _backgroundView != nil) {
167+
return;
168+
}
169+
170+
// Defer making the background until we're added to the subview in case the user changes it
171+
_backgroundView = [self _makeBackgroundViewWithStyle:_backgroundStyle];
172+
[_containerView insertSubview:_backgroundView atIndex:0];
173+
}
174+
164175
- (void)_makeTitleLabelIfNeeded TOROUNDEDBUTTON_OBJC_DIRECT {
165176
if (_titleLabel) { return; }
166177

@@ -182,13 +193,12 @@ - (void)_makeTitleLabelIfNeeded TOROUNDEDBUTTON_OBJC_DIRECT {
182193

183194
- (UIView *)_makeBackgroundViewWithStyle:(TORoundedButtonBackgroundStyle)style TOROUNDEDBUTTON_OBJC_DIRECT {
184195
UIView *backgroundView = nil;
185-
if (TORoundedButtonIsDynamicBackground(style)) {
196+
if (!TORoundedButtonIsSolidBackground(style)) {
186197
// Create a glass or blur style based on the associated style
187198
UIVisualEffect *effect = nil;
188199
if (@available(iOS 26.0, *)) {
189200
if (style == TORoundedButtonBackgroundStyleGlass) {
190201
UIGlassEffect *const glassEffect = [UIGlassEffect effectWithStyle:_glassStyle];
191-
glassEffect.interactive = YES;
192202
glassEffect.tintColor = self.tintColor;
193203
effect = glassEffect;
194204
}
@@ -208,7 +218,7 @@ - (UIView *)_makeBackgroundViewWithStyle:(TORoundedButtonBackgroundStyle)style T
208218
if (@available(iOS 26.0, *)) {
209219
backgroundView.cornerConfiguration = _cornerConfiguration;
210220
} else {
211-
backgroundView.clipsToBounds = TORoundedButtonIsDynamicBackground(style);
221+
backgroundView.clipsToBounds = !TORoundedButtonIsSolidBackground(style);
212222
backgroundView.layer.cornerRadius = _cornerRadius;
213223
}
214224

@@ -253,6 +263,7 @@ - (void)layoutSubviews {
253263
_titleLabel.frame = CGRectIntegral(_titleLabel.frame);
254264
}
255265

266+
// We need to declare this since we explicitly define it in the header
256267
- (void)sizeToFit { [super sizeToFit]; }
257268

258269
- (CGSize)sizeThatFits:(CGSize)size {
@@ -282,11 +293,32 @@ - (CGSize)sizeThatFits:(CGSize)size {
282293
return newSize;
283294
}
284295

296+
- (void)_setBackgroundTintColor:(UIColor *)tintColor {
297+
if (_backgroundStyle == TORoundedButtonBackgroundStyleBlur) {
298+
return;
299+
}
300+
#ifdef __IPHONE_26_0
301+
if (@available(iOS 26.0, *)) {
302+
if (_backgroundStyle == TORoundedButtonBackgroundStyleGlass) {
303+
UIGlassEffect *effect = [UIGlassEffect effectWithStyle:UIGlassEffectStyleRegular];
304+
effect.tintColor = tintColor;
305+
[(UIVisualEffectView *)_backgroundView setEffect:effect];
306+
} else {
307+
_backgroundView.backgroundColor = tintColor;
308+
}
309+
} else {
310+
_backgroundView.backgroundColor = tintColor;
311+
}
312+
#else
313+
_backgroundView.backgroundColor = tintColor;
314+
#endif
315+
}
316+
285317
- (void)tintColorDidChange {
286318
[super tintColorDidChange];
287-
if (TORoundedButtonIsDynamicBackground(_backgroundStyle)) { return; }
319+
if (!TORoundedButtonIsTintableBackground(_backgroundStyle)) { return; }
288320
_titleLabel.backgroundColor = [self _labelBackgroundColor];
289-
_backgroundView.backgroundColor = self.tintColor;
321+
[self _setBackgroundTintColor:self.tintColor];
290322
[self setNeedsLayout];
291323
}
292324

@@ -311,10 +343,8 @@ - (void)_updateTappedTintColorForTintColor TOROUNDEDBUTTON_OBJC_DIRECT {
311343
}
312344

313345
- (UIColor *)_labelBackgroundColor TOROUNDEDBUTTON_OBJC_DIRECT {
314-
// Always return clear if tapped
315-
if (_isTapped || TORoundedButtonIsDynamicBackground(_backgroundStyle)) { return [UIColor clearColor]; }
316-
317-
// Return clear if the tint color isn't opaque
346+
// Always return clear if we're not overlaying on a completely solid BG
347+
if (_isTapped || !TORoundedButtonIsSolidBackground(_backgroundStyle)) { return [UIColor clearColor]; }
318348
const BOOL isClear = CGColorGetAlpha(self.tintColor.CGColor) < (1.0f - FLT_EPSILON);
319349
return isClear ? [UIColor clearColor] : self.tintColor;
320350
}
@@ -367,19 +397,20 @@ - (void)_didDragInside {
367397
#pragma mark - Animation -
368398

369399
- (void)_setBackgroundColorTappedAnimated:(BOOL)animated TOROUNDEDBUTTON_OBJC_DIRECT {
370-
if (!_tappedTintColor || TORoundedButtonIsDynamicBackground(_backgroundStyle)) { return; }
400+
if (!_tappedTintColor || !TORoundedButtonIsTintableBackground(_backgroundStyle)) { return; }
371401

372402
// Toggle the background color of the title label
373403
void (^updateTitleOpacity)(void) = ^{
374404
self->_titleLabel.backgroundColor = [self _labelBackgroundColor];
375405
};
376406

377407
// -----------------------------------------------------
378-
408+
409+
UIColor *const destinationColor = _isTapped ? _tappedTintColor : self.tintColor;
379410
void (^animationBlock)(void) = ^{
380-
self->_backgroundView.backgroundColor = self->_isTapped ? self->_tappedTintColor : self.tintColor;
411+
[self _setBackgroundTintColor:destinationColor];
381412
};
382-
413+
383414
void (^completionBlock)(BOOL) = ^(BOOL completed){
384415
if (completed == NO) { return; }
385416
updateTitleOpacity();
@@ -399,7 +430,6 @@ - (void)_setBackgroundColorTappedAnimated:(BOOL)animated TOROUNDEDBUTTON_OBJC_DI
399430
animations:animationBlock
400431
completion:completionBlock];
401432
}
402-
403433
}
404434

405435
- (void)_setLabelAlphaTappedAnimated:(BOOL)animated TOROUNDEDBUTTON_OBJC_DIRECT {
@@ -443,7 +473,7 @@ - (void)_setButtonScaledTappedAnimated:(BOOL)animated TOROUNDEDBUTTON_OBJC_DIREC
443473
self->_containerView.transform = CGAffineTransformScale(CGAffineTransformIdentity,
444474
scale,
445475
scale);
446-
};
476+
};
447477

448478
// If we're not animating, just call the blocks manually
449479
if (!animated) {
@@ -529,9 +559,7 @@ - (void)setTextPointSize:(CGFloat)textPointSize {
529559
- (void)setTintColor:(UIColor *)tintColor {
530560
[super setTintColor:tintColor];
531561
[self _updateTappedTintColorForTintColor];
532-
if (!TORoundedButtonIsDynamicBackground(_backgroundStyle)) {
533-
_backgroundView.backgroundColor = tintColor;
534-
}
562+
[self _setBackgroundTintColor:tintColor];
535563
_titleLabel.backgroundColor = [self _labelBackgroundColor];
536564
[self setNeedsLayout];
537565
}
@@ -567,7 +595,7 @@ - (void)setCornerRadius:(CGFloat)cornerRadius {
567595
_backgroundView.cornerConfiguration = _cornerConfiguration;
568596
} else {
569597
_backgroundView.layer.cornerRadius = _cornerRadius;
570-
_backgroundView.layer.masksToBounds = TORoundedButtonIsDynamicBackground(_backgroundStyle);
598+
_backgroundView.layer.masksToBounds = !TORoundedButtonIsSolidBackground(_backgroundStyle);
571599
}
572600
#else
573601
_backgroundView.layer.cornerRadius = _cornerRadius;
@@ -592,18 +620,8 @@ - (void)setBackgroundStyle:(TORoundedButtonBackgroundStyle)backgroundStyle {
592620
_backgroundStyle = backgroundStyle;
593621
[_backgroundView removeFromSuperview];
594622
_backgroundView = [self _makeBackgroundViewWithStyle:_backgroundStyle];
623+
[_containerView insertSubview:_backgroundView atIndex:0];
595624
_titleLabel.backgroundColor = [self _labelBackgroundColor];
596-
const BOOL isGlass = backgroundStyle == TORoundedButtonBackgroundStyleGlass;
597-
if (!isGlass) {
598-
_containerView.hidden = NO;
599-
[_containerView insertSubview:_backgroundView atIndex:0];
600-
[_containerView addSubview:_contentView];
601-
} else {
602-
UIVisualEffectView *glassView = (UIVisualEffectView *)_backgroundView;
603-
_containerView.hidden = YES;
604-
[self insertSubview:glassView atIndex:0];
605-
[glassView.contentView addSubview:_contentView];
606-
}
607625
[self setNeedsLayout];
608626
}
609627

@@ -613,7 +631,7 @@ - (void)setBlurStyle:(UIBlurEffectStyle)blurStyle {
613631
}
614632

615633
_blurStyle = blurStyle;
616-
if (!TORoundedButtonIsDynamicBackground(_backgroundStyle) || ![_backgroundView isKindOfClass:[UIVisualEffectView class]]) {
634+
if (_backgroundStyle != TORoundedButtonBackgroundStyleBlur || ![_backgroundView isKindOfClass:[UIVisualEffectView class]]) {
617635
return;
618636
}
619637

@@ -625,7 +643,7 @@ - (void)setGlassStyle:(UIGlassEffectStyle)glassStyle {
625643
if (_glassStyle == glassStyle) { return; }
626644
_glassStyle = glassStyle;
627645

628-
if (!TORoundedButtonIsDynamicBackground(_backgroundStyle) || ![_backgroundView isKindOfClass:[UIVisualEffectView class]]) {
646+
if (_backgroundStyle != TORoundedButtonBackgroundStyleGlass || ![_backgroundView isKindOfClass:[UIVisualEffectView class]]) {
629647
return;
630648
}
631649

TORoundedButtonExample/ViewController.m

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ - (void)viewDidLoad {
2020
// Hide the tapped label
2121
self.tappedLabel.alpha = 0.0f;
2222

23-
self.button.backgroundStyle = TORoundedButtonBackgroundStyleGlass;
24-
2523
__weak typeof(self) weakSelf = self;
2624
self.button.tappedHandler = ^{
2725
[weakSelf playFadeAnimationOnView:weakSelf.tappedLabel];

0 commit comments

Comments
 (0)