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
11 changes: 11 additions & 0 deletions TORoundedButton/TORoundedButton.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ typedef NS_ENUM(NSInteger, TORoundedButtonBackgroundStyle) {
TORoundedButtonBackgroundStyleGlass
};

/// The types of impacts that the button can generate when tapped by the user.
typedef NS_ENUM(NSInteger, TORoundedButtonImpactStyle) {
TORoundedButtonImpactStyleNone = -1,
TORoundedButtonImpactStyleLight = UIImpactFeedbackStyleLight,
TORoundedButtonImpactStyleMedium = UIImpactFeedbackStyleMedium,
TORoundedButtonImpactStyleHeavy = UIImpactFeedbackStyleHeavy
};

NS_SWIFT_NAME(RoundedButtonDelegate)
@protocol TORoundedButtonDelegate <NSObject>

Expand Down Expand Up @@ -75,6 +83,9 @@ IB_DESIGNABLE @interface TORoundedButton : UIControl
/// The style, whether static or dynamic of the button's background view.
@property (nonatomic, assign) TORoundedButtonBackgroundStyle backgroundStyle;

/// The haptic impact style that this button will generate when tapped by the user. (Default is medium)
@property (nonatomic, assign) TORoundedButtonImpactStyle impactStyle;

/// When `backgroundStyle` is set to `.blur`, the specific blur style to apply.
@property (nonatomic, assign) UIBlurEffectStyle blurStyle;

Expand Down
43 changes: 41 additions & 2 deletions TORoundedButton/TORoundedButton.m
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ @implementation TORoundedButton {
/** Maintain a reference to the corner configuration in case we swap out the background view */
UICornerConfiguration *_cornerConfiguration API_AVAILABLE(ios(26.0));
#endif

/** The current haptic feedback generator that will play vibrations when the button is tapped. */
UIImpactFeedbackGenerator *_impactGenerator;
}

#pragma mark - View Creation -
Expand Down Expand Up @@ -118,6 +121,7 @@ - (void)_roundedButtonCommonInit TOROUNDEDBUTTON_OBJC_DIRECT {
_tappedTintColorBrightnessOffset = !TORoundedButtonFloatIsZero(_tappedTintColorBrightnessOffset) ?: -0.15f;
_contentInset = (UIEdgeInsets){15.0, 15.0, 15.0, 15.0};
_blurStyle = UIBlurEffectStyleDark;
_impactStyle = TORoundedButtonImpactStyleMedium;

// Set the corner radius depending on system version
#ifdef __IPHONE_26_0
Expand Down Expand Up @@ -150,7 +154,7 @@ - (void)_roundedButtonCommonInit TOROUNDEDBUTTON_OBJC_DIRECT {

// Create action events for all possible interactions with this control
[self addTarget:self action:@selector(_didTouchDownInside) forControlEvents:UIControlEventTouchDown|UIControlEventTouchDownRepeat];
[self addTarget:self action:@selector(_didTouchUpInside) forControlEvents:UIControlEventTouchUpInside];
[self addTarget:self action:@selector(_didTouchUpInside:event:) forControlEvents:UIControlEventTouchUpInside];
[self addTarget:self action:@selector(_didDragOutside) forControlEvents:UIControlEventTouchDragExit|UIControlEventTouchCancel];
[self addTarget:self action:@selector(_didDragInside) forControlEvents:UIControlEventTouchDragEnter];
}
Expand Down Expand Up @@ -211,6 +215,19 @@ - (UIView *)_makeBackgroundViewWithStyle:(TORoundedButtonBackgroundStyle)style T
return backgroundView;
}

- (UIImpactFeedbackGenerator *)_makeImpactGeneratorWithStyle:(TORoundedButtonImpactStyle)style TOROUNDEDBUTTON_OBJC_DIRECT {
if (style == TORoundedButtonImpactStyleNone) {
return nil;
}

const UIImpactFeedbackStyle impactStyle = (UIImpactFeedbackStyle)style;
if (@available(iOS 17.5, *)) {
return [UIImpactFeedbackGenerator feedbackGeneratorWithStyle:impactStyle forView:self];
}

return [[UIImpactFeedbackGenerator alloc] initWithStyle:impactStyle];
}

#pragma mark - View Lifecycle -

- (void)didMoveToSuperview {
Expand All @@ -222,6 +239,11 @@ - (void)didMoveToSuperview {
// 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];

// If the button has been configured to set up an impact generator, create it now.
if (_impactStyle != TORoundedButtonImpactStyleNone && !_impactGenerator) {
_impactGenerator = [self _makeImpactGeneratorWithStyle:_impactStyle];
}
}

- (void)tintColorDidChange {
Expand Down Expand Up @@ -308,15 +330,26 @@ - (CGSize)sizeThatFits:(CGSize)size {
- (void)_didTouchDownInside {
_isTapped = YES;

// Preheat the impact generator to prepare playing it when we tap up.
[_impactGenerator prepare];

// The user touched their finger down into the button bounds
[self _setLabelAlphaTappedAnimated:NO];
[self _setBackgroundColorTappedAnimated:YES];
[self _setButtonScaledTappedAnimated:YES];
}

- (void)_didTouchUpInside {
- (void)_didTouchUpInside:(id)sender event:(UIEvent *)event {
_isTapped = NO;

// Play the impact to lock in that the user committed to this action.
if (@available(iOS 17.5, *)) {
const CGPoint touchPoint = [event.allTouches.anyObject locationInView:self];
[_impactGenerator impactOccurredAtLocation:touchPoint];
} else {
[_impactGenerator impactOccurred];
}

// The user lifted their finger up from inside the button bounds
[self _setLabelAlphaTappedAnimated:YES];
[self _setBackgroundColorTappedAnimated:YES];
Expand Down Expand Up @@ -585,6 +618,12 @@ - (void)setEnabled:(BOOL)enabled {
_containerView.alpha = enabled ? 1 : 0.4;
}

- (void)setImpactStyle:(TORoundedButtonImpactStyle)impactStyle {
if (_impactStyle == impactStyle) { return; }
_impactStyle = impactStyle;
_impactGenerator = [self _makeImpactGeneratorWithStyle:impactStyle];
}

#pragma mark - Private -

- (void)_setBackgroundTintColor:(UIColor *)tintColor TOROUNDEDBUTTON_OBJC_DIRECT {
Expand Down