0

I created a custom UIButton subclassing from UIControl (It is called "OZCustomButton"). It works fine in Storyboard, however when I was trying to use it to replace the leftBarButtonItem back button programmatically, it had a problem with its layout.

Here is the code I use to replace the leftBarButtonItem.

OZCustomButton *customBackButton = [[OZCustomButton alloc] initWithFrame:CGRectZero];
customBackButton.buttonImage = [UIImage imageNamed:@"BackArrow"];
customBackButton.buttonText = @"Back";
[customBackButton sizeToFit];  

NSLog(@"this size: %@", NSStringFromCGRect(customBackButton.frame));

UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:customBackButton];
self.navigationItem.leftBarButtonItem = item;

But nothing is showing on the left side of the navigation bar.

Navigation bar:

Navigation bar

And the Dubug View Hierarchy tool shows these warning messages:

Warning error 1

Warning error 2

It seems the customBackButton is in the View Hierarchy, but the layout is not correct.

This is the code for my OZCustomButton.

OZCustomButton.h:

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

IB_DESIGNABLE
@interface OZCustomButton : UIControl <NSCoding>

@property (assign, nonatomic) IBInspectable CGFloat borderWidth;
@property (assign, nonatomic) IBInspectable CGFloat borderRadius;
@property (strong, nonatomic) IBInspectable UIColor *borderColor;
@property (strong, nonatomic) IBInspectable UIColor *fillColor;
@property (strong, nonatomic) IBInspectable UIColor *tintColor;

@property (strong, nonatomic) IBInspectable NSString *buttonText;
@property (assign, nonatomic) IBInspectable CGFloat textSize;
@property (assign, nonatomic) IBInspectable BOOL isTextBold;
@property (strong, nonatomic) UIFont *textFont;

@property (nullable, strong, nonatomic) IBInspectable UIImage *buttonImage;
@property (nullable, strong, nonatomic) NSArray *gradientColors;

@end

NS_ASSUME_NONNULL_END

OZCustomButton.m

#import "OZCustomButton.h"
#import "UIColor+Custom.h"
#import "CAGradientLayer+Utilities.h"

@interface OZCustomButton ()

@property (strong, nonatomic) UILabel *buttonLabel;
@property (nullable, strong, nonatomic) UIImageView *buttonImageView;
@property (nullable, strong, nonatomic) CAGradientLayer *gradientLayer;
@property (nullable, strong, nonatomic) UIStackView *stackView;

@end

@implementation OZCustomButton

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self setupDefaults];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self setupDefaults];
    }
    return self;
}

- (void)layoutLabelAndImageView {
    _buttonLabel = [[UILabel alloc] initWithFrame:CGRectZero];
    _buttonLabel.numberOfLines = 1; // need to set to 1
    _buttonLabel.textAlignment = NSTextAlignmentCenter;
    //_buttonLabel.backgroundColor = [UIColor redColor];

    _buttonImageView = [[UIImageView alloc] initWithFrame:CGRectZero];
    _buttonImageView.contentMode = UIViewContentModeScaleAspectFit;
    //_buttonImageView.backgroundColor = [UIColor redColor];

    _stackView = [[UIStackView alloc] init];
    _stackView.axis = UILayoutConstraintAxisHorizontal;
    _stackView.alignment = UIStackViewAlignmentCenter;
    _stackView.distribution = UIStackViewDistributionFillProportionally;
    _stackView.spacing = 8;
    _stackView.userInteractionEnabled = NO;
    [_stackView addArrangedSubview:_buttonImageView];
    [_stackView addArrangedSubview:_buttonLabel];
    _stackView.translatesAutoresizingMaskIntoConstraints = false;
    [self addSubview:_stackView];
    [[_stackView.centerXAnchor constraintEqualToAnchor:self.centerXAnchor] setActive:YES];
    [[_stackView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor] setActive:YES];
}

- (void)layoutGradientLayer {
    _gradientLayer = [CAGradientLayer createGradientLayerWithBounds:self.bounds
                                                             colors:nil
                                                          direction:GradientFromLeftTopToRightBottom
                                                          locations:@[@0.0, @1.0]];
    _gradientLayer.anchorPoint = CGPointMake(0, 0);
    [self.layer insertSublayer:_gradientLayer below:_stackView.layer];
}

- (void)setupDefaults {
    _borderWidth = 0.0f;
    _borderRadius = 0.0f;
    _borderColor = [UIColor blackColor];
    _fillColor = [UIColor whiteColor];
    _buttonText = @"Button";
    _tintColor = [UIColor blackColor];
    _textSize = 17.0f;
    _isTextBold = false;
    _textFont = _isTextBold ? [UIFont fontWithName:@"AlteHaasGrotesk_Bold" size:_textSize] : [UIFont fontWithName:@"AlteHaasGrotesk" size:_textSize];
    _gradientColors = nil;
    _buttonImage = nil;
    [self layoutLabelAndImageView];
    [self updateView];
}

- (void)updateView {
    self.layer.borderColor = _borderColor.CGColor;
    self.layer.borderWidth = _borderWidth;
    self.layer.cornerRadius = _borderRadius;
    self.layer.masksToBounds = true;
    self.layer.backgroundColor = _fillColor.CGColor;

    // update button text label
    _buttonLabel.text = _buttonText;
    _buttonLabel.textColor = _tintColor;
    _textFont = _isTextBold ? [UIFont fontWithName:@"AlteHaasGrotesk_Bold" size:_textSize] : [UIFont fontWithName:@"AlteHaasGrotesk" size:_textSize];
    _buttonLabel.font = _textFont;
    _buttonLabel.textAlignment = NSTextAlignmentCenter;
    [_buttonLabel sizeToFit];

    // update button image
    if (_buttonImage != nil) {
        _buttonImageView.hidden = NO;
        _buttonImageView.image = [_buttonImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
        _buttonImageView.tintColor = _tintColor;
        [_buttonImageView sizeToFit];
    } else {
        _buttonImageView.image = nil;
        _buttonImageView.hidden = YES;
        [_buttonImageView sizeToFit];
    }

    // update gradient layer
    if (_gradientColors != nil) {
        // if gradient layer is not initialized, call layoutGradientLayer()
        if (_gradientLayer == nil) {
            [self layoutGradientLayer];
        }
        // transform the UIColor to CGColorRef
        NSMutableArray *colors = [NSMutableArray arrayWithCapacity:_gradientColors.count];
        for (UIColor *color in _gradientColors) {
            [colors addObject:(id)[color CGColor]];
        }
        if (colors.count > 0) {
            _gradientLayer.colors = [colors copy];
        }
    }
}

#pragma mark - setters

- (void)setTextSize:(CGFloat)textSize {
    if (_textSize != textSize) {
        _textSize = textSize;
        _textFont = _isTextBold ? [UIFont fontWithName:@"AlteHaasGrotesk_Bold" size:_textSize] : [UIFont fontWithName:@"AlteHaasGrotesk" size:_textSize];
        [self updateView];
    }
}

- (void)setBorderColor:(UIColor *)borderColor {
    if (_borderColor != borderColor) {
        _borderColor = borderColor;
        [self updateView];
    }
}

- (void)setBorderWidth:(CGFloat)borderWidth {
    if (_borderWidth != borderWidth) {
        _borderWidth = borderWidth;
        [self updateView];
    }
}

- (void)setBorderRadius:(CGFloat)borderRadius {
    if (_borderRadius != borderRadius) {
        _borderRadius = borderRadius;
        [self updateView];
    }
}

- (void)setFillColor:(UIColor *)fillColor {
    if (_fillColor != fillColor) {
        _fillColor = fillColor;
        [self updateView];
    }
}


- (void)setTintColor:(UIColor *)tintColor {
    if (_tintColor != tintColor) {
        _tintColor = tintColor;
        [self updateView];
    }
}

- (void)setButtonText:(NSString *)buttonText {
    if (_buttonText != buttonText) {
        _buttonText = buttonText;
        [self updateView];
    }
}

- (void)setTextFont:(UIFont *)textFont {
    if (_textFont != textFont) {
        _textFont = textFont;
        [self updateView];
    }
}

- (void)setGradientColors:(NSArray *)gradientColors {
    if (_gradientColors != gradientColors) {
        _gradientColors = gradientColors;
        [self updateView];
    }
}

- (void)setButtonImage:(UIImage *)buttonImage {
    if (_buttonImage != buttonImage) {
        _buttonImage = buttonImage;
        [self updateView];
    }
}

- (void)setIsTextBold:(BOOL)isTextBold {
    if (_isTextBold != isTextBold) {
        _isTextBold = isTextBold;
        [self updateView];
    }
}

#pragma mark - UIControl actions

- (void)setHighlighted:(BOOL)highlighted {
    [super setHighlighted:highlighted];
    CABasicAnimation *fadeAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    fadeAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
    fadeAnimation.duration = 0.2f;
    if (highlighted) {
        fadeAnimation.toValue = @0.6f;
    } else {
        fadeAnimation.toValue = @1.0f;
    }

    self.buttonLabel.layer.opacity = [fadeAnimation.toValue floatValue];
    self.layer.opacity = [fadeAnimation.toValue floatValue];
    [self.buttonLabel.layer addAnimation:fadeAnimation forKey:@"textFadeAnimation"];
    [self.layer addAnimation:fadeAnimation forKey:@"backgroundFadeAnimation"];
}

- (void)setEnabled:(BOOL)enabled {
    [super setEnabled:enabled];

    if (enabled) {
        self.layer.backgroundColor = self.fillColor.CGColor;
        self.buttonLabel.textColor = self.tintColor;
    } else {
        self.layer.backgroundColor = [UIColor lighterColorForColor:self.fillColor].CGColor;
        self.buttonLabel.textColor = [UIColor lightGrayColor];
    }
}

#pragma mark - Override functions
- (void)layoutSubviews {
    [super layoutSubviews];
    [self updateView];
}

- (CGSize)sizeThatFits:(CGSize)size {
    CGFloat minWidth = _buttonImageView.frame.size.width + 8 + _buttonLabel.frame.size.width;
    CGFloat minHeight = MAX(_buttonImageView.frame.size.height, _buttonLabel.frame.size.height);
    return CGSizeMake(minWidth + 6, minHeight + 4);
}

@end
karel
  • 5,489
  • 46
  • 45
  • 50
  • set the frame and try once `CGRect setFframe = CGRectMake(15,5, 25,25); OZCustomButton *customBackButton = [[OZCustomButton alloc] initWithFrame:setFframe];` – Anbu.Karthik Mar 22 '19 at 07:21
  • Check text color. It should not be white. and what is the color of your image that you are setting. ? also, see [this answer](https://stackoverflow.com/a/18844785/7698092) – Awais Fayyaz Mar 22 '19 at 07:29
  • As shown in the viewDebugger your custom button has constraint issue and not able to figure out the x position. I would suggest you to use the `NSLayoutConstraints` for all the subviews properly to make it work. – Aks Mar 22 '19 at 08:37
  • I found the problem that was caused by the auto-layout code for the stack view. If I set a fixed frame to the stack view, it will be able to display on leftBarButtonItem. But, why is the auto-layout code not working correctly?? – Juncheng Han Mar 22 '19 at 18:51

1 Answers1

0

I found one solution. Instead of setting the centenX anchor and centerY anchor, I change it to using left, right, top and bottom anchors:

[[_stackView.topAnchor constraintEqualToAnchor:_stackView.superview.topAnchor] setActive:YES];
[[_stackView.bottomAnchor constraintEqualToAnchor:_stackView.superview.bottomAnchor] setActive:YES];
[[_stackView.leftAnchor constraintEqualToAnchor:_stackView.superview.leftAnchor] setActive:YES];
[[_stackView.rightAnchor constraintEqualToAnchor:_stackView.superview.rightAnchor] setActive:YES];

Then it got the layout correctly.

If you have any other methods to solve the problem, please let me know.