@@ -63,6 +63,9 @@ @implementation TORoundedButton {
6363 /* * Maintain a reference to the corner configuration in case we swap out the background view */
6464 UICornerConfiguration *_cornerConfiguration API_AVAILABLE (ios (26.0 ));
6565#endif
66+
67+ /* * The current haptic feedback generator that will play vibrations when the button is tapped. */
68+ UIImpactFeedbackGenerator *_impactGenerator;
6669}
6770
6871#pragma mark - View Creation -
@@ -118,6 +121,7 @@ - (void)_roundedButtonCommonInit TOROUNDEDBUTTON_OBJC_DIRECT {
118121 _tappedTintColorBrightnessOffset = !TORoundedButtonFloatIsZero (_tappedTintColorBrightnessOffset) ?: -0 .15f ;
119122 _contentInset = (UIEdgeInsets){15.0 , 15.0 , 15.0 , 15.0 };
120123 _blurStyle = UIBlurEffectStyleDark;
124+ _impactStyle = TORoundedButtonImpactStyleMedium;
121125
122126 // Set the corner radius depending on system version
123127#ifdef __IPHONE_26_0
@@ -150,7 +154,7 @@ - (void)_roundedButtonCommonInit TOROUNDEDBUTTON_OBJC_DIRECT {
150154
151155 // Create action events for all possible interactions with this control
152156 [self addTarget: self action: @selector (_didTouchDownInside ) forControlEvents: UIControlEventTouchDown|UIControlEventTouchDownRepeat];
153- [self addTarget: self action: @selector (_didTouchUpInside ) forControlEvents: UIControlEventTouchUpInside];
157+ [self addTarget: self action: @selector (_didTouchUpInside:event: ) forControlEvents: UIControlEventTouchUpInside];
154158 [self addTarget: self action: @selector (_didDragOutside ) forControlEvents: UIControlEventTouchDragExit|UIControlEventTouchCancel];
155159 [self addTarget: self action: @selector (_didDragInside ) forControlEvents: UIControlEventTouchDragEnter];
156160}
@@ -211,6 +215,19 @@ - (UIView *)_makeBackgroundViewWithStyle:(TORoundedButtonBackgroundStyle)style T
211215 return backgroundView;
212216}
213217
218+ - (UIImpactFeedbackGenerator *)_makeImpactGeneratorWithStyle : (TORoundedButtonImpactStyle)style TOROUNDEDBUTTON_OBJC_DIRECT {
219+ if (style == TORoundedButtonImpactStyleNone) {
220+ return nil ;
221+ }
222+
223+ const UIImpactFeedbackStyle impactStyle = (UIImpactFeedbackStyle)style;
224+ if (@available (iOS 17.5 , *)) {
225+ return [UIImpactFeedbackGenerator feedbackGeneratorWithStyle: impactStyle forView: self ];
226+ }
227+
228+ return [[UIImpactFeedbackGenerator alloc ] initWithStyle: impactStyle];
229+ }
230+
214231#pragma mark - View Lifecycle -
215232
216233- (void )didMoveToSuperview {
@@ -222,6 +239,11 @@ - (void)didMoveToSuperview {
222239 // Defer making the background until we're added to the subview in case the user changes it
223240 _backgroundView = [self _makeBackgroundViewWithStyle: _backgroundStyle];
224241 [_containerView insertSubview: _backgroundView atIndex: 0 ];
242+
243+ // If the button has been configured to set up an impact generator, create it now.
244+ if (_impactStyle != TORoundedButtonImpactStyleNone && !_impactGenerator) {
245+ _impactGenerator = [self _makeImpactGeneratorWithStyle: _impactStyle];
246+ }
225247}
226248
227249- (void )tintColorDidChange {
@@ -308,15 +330,26 @@ - (CGSize)sizeThatFits:(CGSize)size {
308330- (void )_didTouchDownInside {
309331 _isTapped = YES ;
310332
333+ // Preheat the impact generator to prepare playing it when we tap up.
334+ [_impactGenerator prepare ];
335+
311336 // The user touched their finger down into the button bounds
312337 [self _setLabelAlphaTappedAnimated: NO ];
313338 [self _setBackgroundColorTappedAnimated: YES ];
314339 [self _setButtonScaledTappedAnimated: YES ];
315340}
316341
317- - (void )_didTouchUpInside {
342+ - (void )_didTouchUpInside : ( id ) sender event : (UIEvent *) event {
318343 _isTapped = NO ;
319344
345+ // Play the impact to lock in that the user committed to this action.
346+ if (@available (iOS 17.5 , *)) {
347+ const CGPoint touchPoint = [event.allTouches.anyObject locationInView: self ];
348+ [_impactGenerator impactOccurredAtLocation: touchPoint];
349+ } else {
350+ [_impactGenerator impactOccurred ];
351+ }
352+
320353 // The user lifted their finger up from inside the button bounds
321354 [self _setLabelAlphaTappedAnimated: YES ];
322355 [self _setBackgroundColorTappedAnimated: YES ];
@@ -585,6 +618,12 @@ - (void)setEnabled:(BOOL)enabled {
585618 _containerView.alpha = enabled ? 1 : 0.4 ;
586619}
587620
621+ - (void )setImpactStyle : (TORoundedButtonImpactStyle)impactStyle {
622+ if (_impactStyle == impactStyle) { return ; }
623+ _impactStyle = impactStyle;
624+ _impactGenerator = [self _makeImpactGeneratorWithStyle: impactStyle];
625+ }
626+
588627#pragma mark - Private -
589628
590629- (void )_setBackgroundTintColor : (UIColor *)tintColor TOROUNDEDBUTTON_OBJC_DIRECT {
0 commit comments