4

I have a simple custom UIView (The view with white background on the screenshot) using xib and custom class derived from UIView.

Custom view

Inside I have a UILabel and a button on the same line ant that's all.

The size of the button is fixed. The width of the label must be adjusted on its content.

What I would like is : - fit the size of the label to its content - set the position of the button always just after the label - fit the custom to its content

Like this :

Correct Cutsom view

How can I proceed to do this ?

Add content :

class Keyword: UIView {

@IBOutlet weak var label: UILabel!
@IBOutlet var contentView: UIView!

override init(frame: CGRect) {
    super .init(frame: frame)
    commonInit()
}

required init?(coder aDecoder: NSCoder) {
    super .init(coder: aDecoder)
    commonInit()
}

fileprivate func commonInit() {
    Bundle.main.loadNibNamed("Keyword", owner: self, options: nil)
    addSubview(contentView)
    contentView.frame = self.bounds
}

override var intrinsicContentSize: CGSize {
    let height = CGFloat(21)
    return CGSize(width: UIViewNoIntrinsicMetric, height: height)
}

/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
    // Drawing code
}
*/

}

UILabel contraints :

UILabel contraints

Tick UIButton constraints :

enter image description here

gduh
  • 1,079
  • 12
  • 30
  • Use autolayout and intrinsic content size. – matt Sep 24 '18 at 12:59
  • I try to use intrinsic content size, but it doesn't work – gduh Sep 24 '18 at 13:03
  • why negative vote its a genuine question ? I am voting up – Rizwan Sep 24 '18 at 13:03
  • This can help https://stackoverflow.com/questions/30450434/figure-out-size-of-uilabel-based-on-string-in-swift – Ashish Sep 24 '18 at 13:10
  • “I try to use intrinsic content size, but it doesn't work” what did you try? What didn’t work? That’s the way to do it, and it’s simple, so please show your code and your constraints under autolayout. – matt Sep 24 '18 at 13:19
  • @matt I added code and constraints for details – gduh Sep 24 '18 at 13:34
  • 1
    Okay, so instead of `width: UIViewNoIntrinsicMetric`, the width needs to be based on the intrinsic widths of the label and the button. Note that you might need to invalidate when the label text changes. – matt Sep 24 '18 at 13:36
  • auto layout is checked, but in fact intrinsicContentSize is never called. I'm trying to know why. – gduh Sep 24 '18 at 13:57
  • it works ! thanks @matt – gduh Sep 24 '18 at 14:07
  • @gduh Feel free to answer your own question, explaining your solution. Perfectly legal (and common) on StackOverflow! – matt Sep 24 '18 at 14:25

2 Answers2

2

Thanks to @matt, I found the solution.

There were just 2 errors in my code.

  1. The following line code translatesAutoresizingMaskIntoConstraints = false in my custom view was missing, so intrinsicContentSizewas never called.
  2. As suggested by @matt, inside intrinsicContentSize, I need to provide a value based on the intrinsic widths of the label and the button for the width parameters instead of UIViewNoIntrinsicMetric

Now, the right code is :

class Keyword: UIView {

@IBOutlet weak var label: UILabel!
@IBOutlet weak var btn: UIButton!
@IBOutlet var contentView: UIView!

override init(frame: CGRect) {
    super .init(frame: frame)
    commonInit()
}

required init?(coder aDecoder: NSCoder) {
    super .init(coder: aDecoder)
    commonInit()
}

fileprivate func commonInit() {
    translatesAutoresizingMaskIntoConstraints = false
    Bundle.main.loadNibNamed("Keyword", owner: self, options: nil)
    addSubview(contentView)
    contentView.frame = self.bounds
}

override var intrinsicContentSize: CGSize {
    let height = btn.frame.size.height
    let width =  btn.frame.origin.x + btn.frame.size.width
    return CGSize(width: width, height: height)
}


// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
    // Drawing code
}

}
gduh
  • 1,079
  • 12
  • 30
0

The following extension can be used to calculate the height and width of label.

extension String {

//Calculate height and width for label with string
func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
    let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
    let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font: font], context: nil)

    return ceil(boundingBox.height)
}

func width(withConstrainedHeight height: CGFloat, font: UIFont) -> CGFloat {
    let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
    let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font: font], context: nil)

    return ceil(boundingBox.width)
}

}

In your case, since the button size is fixed, you can calculate the width of label using the above extension and can be used to set the width constraint of the entire view. If your label has a leading constraint of 8 and a trailing constraint of 8 to the button and the button has a trailing constraint of 8 to the container view, then you can calculate the required width for your view as:

viewWidthConstraint.constant = 8+<width of string from the above extension>+8+<width of button>+8

Here the only changing parameter will be the width of the label.