From 02700ec38ba977d5d34b355a08fc74723cc06773 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Mon, 2 Oct 2023 00:40:32 +0900 Subject: [PATCH 1/7] Added new APIs for content view integration --- TORoundedButton/TORoundedButton.h | 47 +++++++++++----- TORoundedButton/TORoundedButton.m | 90 +++++++++++++++++++------------ 2 files changed, 91 insertions(+), 46 deletions(-) diff --git a/TORoundedButton/TORoundedButton.h b/TORoundedButton/TORoundedButton.h index f129257..a5997ad 100644 --- a/TORoundedButton/TORoundedButton.h +++ b/TORoundedButton/TORoundedButton.h @@ -27,28 +27,38 @@ NS_ASSUME_NONNULL_BEGIN NS_SWIFT_NAME(RoundedButton) IB_DESIGNABLE @interface TORoundedButton : UIControl -/// The text that is displayed in center of the button (Default is "Button"). -@property (nonatomic, copy) IBInspectable NSString *text; - -/// The attributed string used in the label of this button. See `UILabel.attributedText` documentation for full details (Default is nil). -@property (nonatomic, copy, nullable) NSAttributedString *attributedText; - /// The radius of the corners of this button (Default is 12.0f). @property (nonatomic, assign) IBInspectable CGFloat cornerRadius; +/// The hosting container that manages all of the foreground views in this button. +/// You can either add your custom views to this view by default, or you can set +/// this property to your own custom UIView subclass in order to more efficiently manage sizing and layout. +@property (nonatomic, strong, null_resettable) UIView *contentView; + +/// The text that is displayed in center of the button (Default is nil). +@property (nonatomic, copy, nullable) IBInspectable NSString *text; + +/// The attributed string used in the label of this button. +/// See `UILabel.attributedText` documentation for full details (Default is nil). +@property (nonatomic, copy, nullable) NSAttributedString *attributedText; + /// The color of the text in this button (Default is white). @property (nonatomic, strong) IBInspectable UIColor *textColor; -/// When tapped, the level of transparency that the text label animates to. (Defaults to off with 1.0f). -@property (nonatomic, assign) IBInspectable CGFloat tappedTextAlpha; - -/// The font of the text in the button (Default is size UIFontTextStyleBody with bold). +/// The font of the text in the button +/// (Default is size UIFontTextStyleBody with bold). @property (nonatomic, strong) UIFont *textFont; -/// Because IB cannot handle fonts, this can alternatively be used to set the font size. (Default is off with 0.0). +/// Because IB cannot handle fonts, this can alternatively be used to set the font size. +/// (Default is off with 0.0). @property (nonatomic, assign) IBInspectable CGFloat textPointSize; -/// Taking the default button background color apply a brightness offset for the tapped color (Default is -0.1f. Set 0.0 for off). +/// When tapped, the level of transparency that the text label animates to. +/// (Defaults to off with 1.0f). +@property (nonatomic, assign) IBInspectable CGFloat tappedTextAlpha; + +/// Taking the default button background color apply a brightness offset for the tapped color +/// (Default is -0.1f. Set 0.0 for off). @property (nonatomic, assign) IBInspectable CGFloat tappedTintColorBrightnessOffset; /// If desired, explicity set the background color of the button when tapped (Default is nil). @@ -66,9 +76,20 @@ IB_DESIGNABLE @interface TORoundedButton : UIControl /// A callback handler triggered each time the button is tapped. @property (nonatomic, copy) void (^tappedHandler)(void); -/// Create a new instance of a button with the provided text shown in the center. The size will be 288 points wide, and 50 tall. +/// Create a new instance of a button that can be further configured with either text or custom subviews. +/// The size will be 288 points wide, and 50 tall by default. +- (instancetype)init; + +/// Create a new instance of a button that can be further configured with either text or custom subviews. +- (instancetype)initWithFrame:(CGRect)frame; + +/// Create a new instance of a button with the provided text shown in the center. +/// The size will be 288 points wide, and 50 tall. - (instancetype)initWithText:(NSString *)text; +/// Create a new instance of a button with the provided view set as the hosting content view. +- (instancetype)initWithContentView:(__kindof UIView *)contentView; + @end NS_ASSUME_NONNULL_END diff --git a/TORoundedButton/TORoundedButton.m b/TORoundedButton/TORoundedButton.m index 542552f..b3e27d2 100644 --- a/TORoundedButton/TORoundedButton.m +++ b/TORoundedButton/TORoundedButton.m @@ -41,9 +41,6 @@ @implementation TORoundedButton { or not because the state can change before blocks complete. */ BOOL _isTapped; - /** A container view that holds all of the content view and performs the clipping. */ - UIView *_containerView; - /** The title label displaying the text in the center of the button. */ UILabel *_titleLabel; @@ -53,13 +50,10 @@ @implementation TORoundedButton { #pragma mark - View Creation - -- (instancetype)initWithText:(NSString *)text { +- (instancetype)init { if (self = [super initWithFrame:(CGRect){0,0, 288.0f, 50.0f}]) { [self _roundedButtonCommonInit]; - _titleLabel.text = text; - [_titleLabel sizeToFit]; } - return self; } @@ -79,6 +73,25 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { return self; } +- (instancetype)initWithContentView:(__kindof UIView *)contentView { + if (self = [super initWithFrame:contentView.bounds]) { + _contentView = contentView; + [self _roundedButtonCommonInit]; + } + return self; +} + +- (instancetype)initWithText:(NSString *)text { + if (self = [super initWithFrame:(CGRect){0,0, 288.0f, 50.0f}]) { + [self _roundedButtonCommonInit]; + [self _makeTitleLabelIfNeeded]; + _titleLabel.text = text; + [_titleLabel sizeToFit]; + } + + return self; +} + - (void)_roundedButtonCommonInit TOROUNDEDBUTTON_OBJC_DIRECT { // Default properties (Make sure they're not overriding IB) _cornerRadius = (_cornerRadius > FLT_EPSILON) ?: 12.0f; @@ -91,12 +104,12 @@ - (void)_roundedButtonCommonInit TOROUNDEDBUTTON_OBJC_DIRECT { [self _updateTappedTintColorForTintColor]; // Create the container view that manages the image view and text - _containerView = [[UIView alloc] initWithFrame:self.bounds]; - _containerView.backgroundColor = [UIColor clearColor]; - _containerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - _containerView.userInteractionEnabled = NO; - _containerView.clipsToBounds = YES; - [self addSubview:_containerView]; + _contentView = [[UIView alloc] initWithFrame:self.bounds]; + _contentView.backgroundColor = [UIColor clearColor]; + _contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + _contentView.userInteractionEnabled = NO; + _contentView.clipsToBounds = YES; + [self addSubview:_contentView]; // Create the image view which will show the button background _backgroundView = [[UIView alloc] initWithFrame:self.bounds]; @@ -106,16 +119,23 @@ - (void)_roundedButtonCommonInit TOROUNDEDBUTTON_OBJC_DIRECT { #ifdef __IPHONE_13_0 if (@available(iOS 13.0, *)) { _backgroundView.layer.cornerCurve = kCACornerCurveContinuous; } #endif - [_containerView addSubview:_backgroundView]; - - // Create the title label that will display the button text - UIFont *buttonFont = [UIFont systemFontOfSize:17.0f weight:UIFontWeightBold]; - if (@available(iOS 11.0, *)) { - // Apply resizable button metrics to font - UIFontMetrics *metrics = [[UIFontMetrics alloc] initForTextStyle:UIFontTextStyleBody]; - buttonFont = [metrics scaledFontForFont:buttonFont]; - } - + [_contentView addSubview:_backgroundView]; + + // 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(_didDragOutside) forControlEvents:UIControlEventTouchDragExit|UIControlEventTouchCancel]; + [self addTarget:self action:@selector(_didDragInside) forControlEvents:UIControlEventTouchDragEnter]; +} + +- (void)_makeTitleLabelIfNeeded TOROUNDEDBUTTON_OBJC_DIRECT { + if (_titleLabel) { return; } + + // Make the font bold, and opt it into Dynamic Type sizing + UIFontMetrics *const metrics = [[UIFontMetrics alloc] initForTextStyle:UIFontTextStyleBody]; + UIFont *const buttonFont = [metrics scaledFontForFont:[UIFont systemFontOfSize:17.0f weight:UIFontWeightBold]]; + + // Configure the title label _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; _titleLabel.textAlignment = NSTextAlignmentCenter; _titleLabel.textColor = [UIColor whiteColor]; @@ -124,13 +144,7 @@ - (void)_roundedButtonCommonInit TOROUNDEDBUTTON_OBJC_DIRECT { _titleLabel.backgroundColor = [self _labelBackgroundColor]; _titleLabel.text = @"Button"; _titleLabel.numberOfLines = 0; - [_containerView addSubview:_titleLabel]; - - // 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(_didDragOutside) forControlEvents:UIControlEventTouchDragExit|UIControlEventTouchCancel]; - [self addTarget:self action:@selector(_didDragInside) forControlEvents:UIControlEventTouchDragEnter]; + [_contentView addSubview:_titleLabel]; } #pragma mark - View Displaying - @@ -140,7 +154,7 @@ - (void)layoutSubviews { // Configure the button text [_titleLabel sizeToFit]; - _titleLabel.center = _containerView.center; + _titleLabel.center = _contentView.center; _titleLabel.frame = CGRectIntegral(_titleLabel.frame); } @@ -300,7 +314,7 @@ - (void)_setButtonScaledTappedAnimated:(BOOL)animated TOROUNDEDBUTTON_OBJC_DIREC // Animate the alpha value of the label void (^animationBlock)(void) = ^{ - self->_containerView.transform = CGAffineTransformScale(CGAffineTransformIdentity, + self->_contentView.transform = CGAffineTransformScale(CGAffineTransformIdentity, scale, scale); }; @@ -323,6 +337,16 @@ - (void)_setButtonScaledTappedAnimated:(BOOL)animated TOROUNDEDBUTTON_OBJC_DIREC #pragma mark - Public Accessors - +- (void)setContentView:(UIView *)contentView { + if (_contentView == contentView) { return; } + + _titleLabel = nil; + [_contentView removeFromSuperview]; + _contentView = contentView ?: [UIView new]; + [self addSubview:_contentView]; + [self setNeedsLayout]; +} + - (void)setAttributedText:(NSAttributedString *)attributedText { _titleLabel.attributedText = attributedText; [_titleLabel sizeToFit]; @@ -394,7 +418,7 @@ - (void)setCornerRadius:(CGFloat)cornerRadius { - (void)setEnabled:(BOOL)enabled { [super setEnabled:enabled]; - _containerView.alpha = enabled ? 1 : 0.4; + _contentView.alpha = enabled ? 1 : 0.4; } - (CGFloat)minimumWidth { From 19083f035d394ef50dd837974ae3a5b1474c7d40 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Mon, 2 Oct 2023 00:58:47 +0900 Subject: [PATCH 2/7] Added additional properties to header --- TORoundedButton/TORoundedButton.h | 11 +++++++++++ TORoundedButton/TORoundedButton.m | 3 +++ TORoundedButtonExample/Base.lproj/Main.storyboard | 8 ++++---- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/TORoundedButton/TORoundedButton.h b/TORoundedButton/TORoundedButton.h index a5997ad..44f6c50 100644 --- a/TORoundedButton/TORoundedButton.h +++ b/TORoundedButton/TORoundedButton.h @@ -35,6 +35,10 @@ IB_DESIGNABLE @interface TORoundedButton : UIControl /// this property to your own custom UIView subclass in order to more efficiently manage sizing and layout. @property (nonatomic, strong, null_resettable) UIView *contentView; +/// The amount of inset padding between the content view and the edges of the button. +/// (Default value is 15 points inset from each edge). +@property (nonatomic, assign) UIEdgeInsets contentInset; + /// The text that is displayed in center of the button (Default is nil). @property (nonatomic, copy, nullable) IBInspectable NSString *text; @@ -90,6 +94,13 @@ IB_DESIGNABLE @interface TORoundedButton : UIControl /// Create a new instance of a button with the provided view set as the hosting content view. - (instancetype)initWithContentView:(__kindof UIView *)contentView; +/// Resizes the button to fit the bounding size of all of the subviews in `contentView`, plus content insetting. +/// If a custom view was provided as the content view, or if the content view only has one subview this will also be called on it. +- (void)sizeToFit; + +/// Calculates and returns the appropriate minimum size this button needs to be to fit into the provided size. +- (CGSize)sizeThatFits:(CGSize)size; + @end NS_ASSUME_NONNULL_END diff --git a/TORoundedButton/TORoundedButton.m b/TORoundedButton/TORoundedButton.m index b3e27d2..6e45486 100644 --- a/TORoundedButton/TORoundedButton.m +++ b/TORoundedButton/TORoundedButton.m @@ -99,6 +99,7 @@ - (void)_roundedButtonCommonInit TOROUNDEDBUTTON_OBJC_DIRECT { _tapAnimationDuration = (_tapAnimationDuration > FLT_EPSILON) ?: 0.4f; _tappedButtonScale = (_tappedButtonScale > FLT_EPSILON) ?: 0.97f; _tappedTintColorBrightnessOffset = !TO_ROUNDED_BUTTON_FLOAT_IS_ZERO(_tappedTintColorBrightnessOffset) ?: -0.15f; + _contentInset = (UIEdgeInsets){15.0, 15.0, 15.0, 15.0}; // Set the tapped tint color if we've set to dynamically calculate it [self _updateTappedTintColorForTintColor]; @@ -348,6 +349,7 @@ - (void)setContentView:(UIView *)contentView { } - (void)setAttributedText:(NSAttributedString *)attributedText { + [self _makeTitleLabelIfNeeded]; _titleLabel.attributedText = attributedText; [_titleLabel sizeToFit]; [self setNeedsLayout]; @@ -356,6 +358,7 @@ - (void)setAttributedText:(NSAttributedString *)attributedText { - (NSAttributedString *)attributedText { return _titleLabel.attributedText; } - (void)setText:(NSString *)text { + [self _makeTitleLabelIfNeeded]; _titleLabel.text = text; [_titleLabel sizeToFit]; [self setNeedsLayout]; diff --git a/TORoundedButtonExample/Base.lproj/Main.storyboard b/TORoundedButtonExample/Base.lproj/Main.storyboard index e0a4987..9bb8f0a 100644 --- a/TORoundedButtonExample/Base.lproj/Main.storyboard +++ b/TORoundedButtonExample/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -18,7 +18,7 @@ - + @@ -29,7 +29,7 @@