UIButton that resizes to fit its titleLabel

前端 未结 12 1282
既然无缘
既然无缘 2020-12-01 09:13

I have a UIButton that I add to my view controller\'s view in a storyboard. I add centering constraints to position it and leading space constraints to limit it

相关标签:
12条回答
  • 2020-12-01 09:43
    class SFResizableButton: UIButton {
      override var intrinsicContentSize: CGSize {
        get {
          var labelSize = CGSize.zero
            if let text = titleLabel?.text, let font = titleLabel?.font {
              labelSize.width = text.width(constrained: .greatestFiniteMagnitude, font: font)
            } else if let att = titleLabel?.attributedText {
              labelSize.width = att.width(constrained: .greatestFiniteMagnitude)
            }
          if let imageView = imageView {
            labelSize.width = labelSize.width + imageView.frame.width
          }
          let desiredButtonSize = CGSize(width: ceil(labelSize.width) + titleEdgeInsets.left + titleEdgeInsets.right + imageEdgeInsets.left + imageEdgeInsets.right, height: labelSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom + imageEdgeInsets.top + imageEdgeInsets.bottom)
    
            return desiredButtonSize
        }
      }
    }
    extesion String {
      func width(constrained height: CGFloat, font: UIFont) -> CGFloat {
        let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
          let boundingBox = (self as NSString).boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
    
          return boundingBox.width
      }
    }
    
    extension NSAttributedString {
      func width(constrained height: CGFloat) -> CGFloat {
        let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
          let boundingBox = boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil)
    
          return boundingBox.width
      }
    }
    
    0 讨论(0)
  • 2020-12-01 09:44

    I've gotten this to work, but you have to use a custom button, not a system type. Give the button both width and height constraints, and make an IBOutlet to the height constraint (heightCon in my code) so you can adjust it in code.

    - (void)viewDidLoad {
        [super viewDidLoad];
        self.button.titleLabel.numberOfLines = 0;
        self.button.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
        [self.button setTitle:@"A real real real real real real real real long long name." forState:UIControlStateNormal];
        [self.button addTarget:self action:@selector(doStuff:) forControlEvents:UIControlEventTouchUpInside];
        self.button.backgroundColor = [UIColor redColor];
        self.button.titleLabel.backgroundColor = [UIColor blueColor];
        [self.button layoutIfNeeded]; // need this to update the button's titleLabel's size
        self.heightCon.constant = self.button.titleLabel.frame.size.height;
    }
    

    After Edit:

    I found that you can also do this more simply, and with a system button if you make a subclass, and use this code,

    @implementation RDButton
    
    -(CGSize)intrinsicContentSize {
        return CGSizeMake(self.frame.size.width, self.titleLabel.frame.size.height);
    }
    

    The overridden intrinsicContentSize method is called when you set the title. You shouldn't set a height constraint in this case.

    0 讨论(0)
  • 2020-12-01 09:47

    Swift 4.x version of Kubba's answer:

    Need to Update Line Break as Clip/WordWrap/ in Interface builder to corresponding buttons.

    class ResizableButton: UIButton {
        override var intrinsicContentSize: CGSize {
           let labelSize = titleLabel?.sizeThatFits(CGSize(width: frame.width, height: .greatestFiniteMagnitude)) ?? .zero
           let desiredButtonSize = CGSize(width: labelSize.width + titleEdgeInsets.left + titleEdgeInsets.right, height: labelSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom)
    
           return desiredButtonSize
        }
    }
    
    0 讨论(0)
  • 2020-12-01 09:49

    What I would suggest is to calculate the width of the text, and calculate the frame by yourself. It's not complicated anyway, get the width of the text first:

    [NSString sizeWithFont:font];
    

    Do a mod operation and you'll easily find out the number of lines for the text.

    Note this method is for pre iOS7, for iOS 7 and after you might want to try

    [NSString sizeWithAttributes:aDictionary];
    
    0 讨论(0)
  • 2020-12-01 09:50
    /*
    In SWIFT : Create an IBDesignable sub class of UIButton and override the intrinsicContentSize as shown below. It will resize appropriately for text and images. Then change your line break property to Word Wrap. Add a "fixedWidth" inspectable property that will adjust the height if you need buttons with fixed widths or when set to false will adjust the width and keep the height fixed.
    */
    
    import UIKit
    
    @IBDesignable
    class UIButtonX: UIButton {
        
        //...
    
        @IBInspectable var fixedWidth : Bool = true
    override var intrinsicContentSize: CGSize {
        let labelSize = titleLabel?.sizeThatFits(CGSize(width: fixedWidth ? frame.width : .greatestFiniteMagnitude, height: fixedWidth ? .greatestFiniteMagnitude : frame.height)) ?? .zero
    
        let wImage = image(for: [])?.size.width ?? 0
        let wTitleInset = titleEdgeInsets.left + titleEdgeInsets.right
        let wImageInset = imageEdgeInsets.left + imageEdgeInsets.right
        let wContentInset = contentEdgeInsets.left + contentEdgeInsets.right
        let width : CGFloat = labelSize.width + wImage + wTitleInset + wImageInset + wContentInset
        
        let biggerHeight = max(image(for: [])?.size.height ?? 0, labelSize.height)
        let hTitleInset = titleEdgeInsets.top + titleEdgeInsets.bottom
        let hImageInset = imageEdgeInsets.top + imageEdgeInsets.bottom
        let hContentInset = contentEdgeInsets.top + contentEdgeInsets.bottom
        let height : CGFloat = biggerHeight + hTitleInset + hImageInset + hContentInset
        
        let desiredButtonSize = CGSize(width: width, height: height)
        
        return desiredButtonSize
    }
        
        //...
    
    }
    
    0 讨论(0)
  • 2020-12-01 09:51

    I had the same problem with UIButton with multilined text, and it also had an image. I used sizeThatFits: to calculate the size but it calculated wrong height.

    I did not make it UIButtonTypeCustom, instead of that I called sizeThatFits: on button's titleLabel with size with smaller width (due to image in button):

    CGSize buttonSize = [button sizeThatFits:CGSizeMake(maxWidth, maxHeight)];
    CGSize labelSize = [button.titleLabel sizeThatFits:CGSizeMake(maxWidth - offset, maxHeight)]; // offset for image
    buttonSize.height = labelSize.height;
    buttonFrame.size = buttonSize;
    

    And then I used height from that size to set button's frame correctly, and it WORKED :)

    Maybe they have some bug in internal sizing of UIButton.

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