Label under image in UIButton

后端 未结 30 1975
佛祖请我去吃肉
佛祖请我去吃肉 2020-11-29 15:53

I\'m trying to create a button which has some text beneath the icon (sorta like the app buttons) however it seems to be quite difficult to achieve. Any ideas how can I go ab

相关标签:
30条回答
  • 2020-11-29 16:32

    iOS 11 - Objective-C

    -(void)layoutSubviews {
    [super layoutSubviews];
    
    CGRect titleLabelFrame = self.titleLabel.frame;
    CGSize labelSize = [self.titleLabel.text sizeWithAttributes:@{NSFontAttributeName: [UIFont systemFontOfSize:12.0f]}];
    CGSize adjustedLabelSize = CGSizeMake(ceilf(labelSize.width), ceilf(labelSize.height));
    
    CGRect imageFrame = self.imageView.frame;
    
    CGSize fitBoxSize = (CGSize){.height = adjustedLabelSize.height + kTextTopPadding +  imageFrame.size.height, .width = MAX(imageFrame.size.width, adjustedLabelSize.width)};
    
    CGRect fitBoxRect = CGRectInset(self.bounds, (self.bounds.size.width - fitBoxSize.width)/2, (self.bounds.size.height - fitBoxSize.height)/2);
    
    imageFrame.origin.y = fitBoxRect.origin.y;
    imageFrame.origin.x = CGRectGetMidX(fitBoxRect) - (imageFrame.size.width/2);
    self.imageView.frame = imageFrame;
    
    // Adjust the label size to fit the text, and move it below the image
    
    titleLabelFrame.size.width = adjustedLabelSize.width;
    titleLabelFrame.size.height = adjustedLabelSize.height;
    titleLabelFrame.origin.x = (self.frame.size.width / 2) - (adjustedLabelSize.width / 2);
    titleLabelFrame.origin.y = fitBoxRect.origin.y + imageFrame.size.height + kTextTopPadding;
    self.titleLabel.frame = titleLabelFrame; }
    
    0 讨论(0)
  • 2020-11-29 16:33

    I took a combination of the answers here and came up with one that seems to be working for me, in Swift. I don't love how I just overrode the insets, but it works. I'd be open to suggested improvements in the comments. It seems to work correctly with sizeToFit() and with auto layout.

    import UIKit
    
    /// A button that displays an image centered above the title.  This implementation 
    /// only works when both an image and title are set, and ignores
    /// any changes you make to edge insets.
    class CenteredButton: UIButton
    {
        let padding: CGFloat = 0.0
    
        override func layoutSubviews() {
            if imageView?.image != nil && titleLabel?.text != nil {
                let imageSize: CGSize = imageView!.image!.size
                titleEdgeInsets = UIEdgeInsetsMake(0.0, -imageSize.width, -(imageSize.height + padding), 0.0)
                let labelString = NSString(string: titleLabel!.text!)
                let titleSize = labelString.sizeWithAttributes([NSFontAttributeName: titleLabel!.font])
                imageEdgeInsets = UIEdgeInsetsMake(-(titleSize.height + padding), 0.0, 0.0, -titleSize.width)
                let edgeOffset = abs(titleSize.height - imageSize.height) / 2.0;
                contentEdgeInsets = UIEdgeInsetsMake(edgeOffset, 0.0, edgeOffset, 0.0)
            }
            super.layoutSubviews()
        }
    
        override func sizeThatFits(size: CGSize) -> CGSize {
            let defaultSize = super.sizeThatFits(size)
            if let titleSize = titleLabel?.sizeThatFits(size),
            let imageSize = imageView?.sizeThatFits(size) {
                return CGSize(width: ceil(max(imageSize.width, titleSize.width)), height: ceil(imageSize.height + titleSize.height + padding))
            }
            return defaultSize
        }
    
        override func intrinsicContentSize() -> CGSize {
            let size = sizeThatFits(CGSize(width: CGFloat.max, height: CGFloat.max))
            return size
        }
    }
    
    0 讨论(0)
  • 2020-11-29 16:34

    Dave's solution in Swift:

    override func layoutSubviews() {
        super.layoutSubviews()
        if let imageView = self.imageView {
            imageView.frame.origin.x = (self.bounds.size.width - imageView.frame.size.width) / 2.0
            imageView.frame.origin.y = 0.0
        }
        if let titleLabel = self.titleLabel {
            titleLabel.frame.origin.x = (self.bounds.size.width - titleLabel.frame.size.width) / 2.0
            titleLabel.frame.origin.y = self.bounds.size.height - titleLabel.frame.size.height
        }
    }
    
    0 讨论(0)
  • 2020-11-29 16:34

    Updated Kenny Winker's answer since sizeWithFont was deprecated in iOS 7.

    -(void)layoutSubviews {
    [super layoutSubviews];
    
    int kTextTopPadding = 3;
    
    CGRect titleLabelFrame = self.titleLabel.frame;
    
    CGRect labelSize = [self.titleLabel.text boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGRectGetHeight(self.bounds)) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:self.titleLabel.font} context:nil];
    
    CGRect imageFrame = self.imageView.frame;
    
    CGSize fitBoxSize = (CGSize){.height = labelSize.size.height + kTextTopPadding +  imageFrame.size.height, .width = MAX(imageFrame.size.width, labelSize.size.width)};
    
    CGRect fitBoxRect = CGRectInset(self.bounds, (self.bounds.size.width - fitBoxSize.width)/2, (self.bounds.size.height - fitBoxSize.height)/2);
    
    imageFrame.origin.y = fitBoxRect.origin.y;
    imageFrame.origin.x = CGRectGetMidX(fitBoxRect) - (imageFrame.size.width/2);
    self.imageView.frame = imageFrame;
    
    // Adjust the label size to fit the text, and move it below the image
    
    titleLabelFrame.size.width = labelSize.size.width;
    titleLabelFrame.size.height = labelSize.size.height;
    titleLabelFrame.origin.x = (self.frame.size.width / 2) - (labelSize.size.width / 2);
    titleLabelFrame.origin.y = fitBoxRect.origin.y + imageFrame.size.height + kTextTopPadding;
    self.titleLabel.frame = titleLabelFrame;
    }
    
    0 讨论(0)
  • 2020-11-29 16:34

    Here is my subclass of UIButton which solves this problem:

    @implementation MyVerticalButton
    
    @synthesize titleAtBottom; // BOOL property
    
    - (id)initWithFrame:(CGRect)frame
    {
      self = [super initWithFrame:frame];
      if (self) {
        self.titleAtBottom = YES;
      }
      return self;
    }
    
    - (CGSize)sizeThatFits:(CGSize)size {
      self.titleLabel.text = [self titleForState: self.state];
    
      UIEdgeInsets imageInsets = self.imageEdgeInsets;
      UIEdgeInsets titleInsets = self.titleEdgeInsets;
    
      CGSize imageSize = [self imageForState: self.state].size;
      if (!CGSizeEqualToSize(imageSize, CGSizeZero)) {
        imageSize.width += imageInsets.left + imageInsets.right;
        imageSize.height += imageInsets.top + imageInsets.bottom;
    
      }
    
      CGSize textSize = [self.titleLabel sizeThatFits: CGSizeMake(size.width - titleInsets.left - titleInsets.right,
                                                                  size.height -(imageSize.width +
                                                                                titleInsets.top+titleInsets.bottom))];
      if (!CGSizeEqualToSize(textSize, CGSizeZero)) {
        textSize.width += titleInsets.left + titleInsets.right;
        textSize.height += titleInsets.top + titleInsets.bottom;
      }
    
      CGSize result = CGSizeMake(MAX(textSize.width, imageSize.width),
                                 textSize.height + imageSize.height);
      return result;
    }
    
    - (void)layoutSubviews {
      // needed to update all properities of child views:
      [super layoutSubviews];
    
      CGRect bounds = self.bounds;
    
      CGRect titleFrame = UIEdgeInsetsInsetRect(bounds, self.titleEdgeInsets);
      CGRect imageFrame = UIEdgeInsetsInsetRect(bounds, self.imageEdgeInsets);
      if (self.titleAtBottom) {
        CGFloat titleHeight = [self.titleLabel sizeThatFits: titleFrame.size].height;
        titleFrame.origin.y = CGRectGetMaxY(titleFrame)-titleHeight;
        titleFrame.size.height = titleHeight;
        titleFrame = CGRectStandardize(titleFrame);
        self.titleLabel.frame = titleFrame;
    
        CGFloat imageBottom = CGRectGetMinY(titleFrame)-(self.titleEdgeInsets.top+self.imageEdgeInsets.bottom);
        imageFrame.size.height = imageBottom - CGRectGetMinY(imageFrame);
        self.imageView.frame = CGRectStandardize(imageFrame);
      } else {
        CGFloat titleHeight = [self.titleLabel sizeThatFits: titleFrame.size].height;
        titleFrame.size.height = titleHeight;
        titleFrame = CGRectStandardize(titleFrame);
        self.titleLabel.frame = titleFrame;
    
        CGFloat imageTop = CGRectGetMaxY(titleFrame)+(self.titleEdgeInsets.bottom+self.imageEdgeInsets.top);
        imageFrame.size.height = CGRectGetMaxY(imageFrame) - imageTop;
        self.imageView.frame = CGRectStandardize(imageFrame);
      }
    }
    
    - (void)setTitleAtBottom:(BOOL)newTitleAtBottom {
      if (titleAtBottom!=newTitleAtBottom) {
        titleAtBottom=newTitleAtBottom;
        [self setNeedsLayout];
      }
    }
    
    @end
    

    That's it. Works like charm. Problem may appear if button will be to small to fit title and text.

    0 讨论(0)
  • 2020-11-29 16:35

    In Xcode, you can simply set the Edge Title Left Inset to negative the width of the image. This will display the label in the center of the image.

    To get the label to display below the image (sorta like the app buttons), you may need to set the Edge Title Top Inset to some positive number.

    Edit: Here is some code to achieve that without using Interface Builder:

    /// This will move the TitleLabel text of a UIButton to below it's Image and Centered.
    /// Note: No point in calling this function before autolayout lays things out.
    /// - Parameter padding: Some extra padding to be applied
    func centerVertically(padding: CGFloat = 18.0) {
        // No point in doing anything if we don't have an imageView size
        guard let imageFrame = imageView?.frame else { return }
        titleLabel?.numberOfLines = 0
        titleEdgeInsets.left = -(imageFrame.width + padding)
        titleEdgeInsets.top = (imageFrame.height + padding)
    }
    

    Please note this won't work if you're using autolayout and the button didn't get layed out in the screen yet via constraints.

    0 讨论(0)
提交回复
热议问题