13

I've seen answers to vertical resizing that involve autolayout, but the UILabels I'm creating are only needed at runtime. (I might need anywhere from zero to many of these labels.)

Examples (ignore the color)

  1. Short text (note same width as longer text):

enter image description here

  1. Longer text (note same width as shorter text example with more lines for add'l text):

enter image description here

If the text can fit in one line of fixed width, the label shouldn't need to resize vertically. But if there are more characters, the label should keep expanding vertically to fit these additional characters. The text should keep wrapping around line after line. The text should start in the top left corner of the label.

To be more specific:

let marker = GMSMarker(position: myLatLng)
// see http://stackoverflow.com/a/40211383/1168364 for imageWithView
marker.icon = imageWithView(label) // **how do i create this label?**
marker.map = map // map is a GMSMapView

These labels can be anywhere on the screen. This is for a map application where each label will be placed at a random location. The labels' locations have no relationship to one another.

lf215
  • 1,185
  • 7
  • 41
  • 83
  • Possible duplicate of [Adjust UILabel height depending on the text](http://stackoverflow.com/questions/446405/adjust-uilabel-height-depending-on-the-text) – alexburtnik Nov 12 '16 at 21:29
  • @alexburtnik can you please reconsider this as not being a duplicate? That solution doesn't seem to work here. Notice the addition requirement of fixed width. Maybe you have some ideas of why the solution below is not working. – lf215 Nov 13 '16 at 19:02
  • I've retracted close vote, but it is still a duplicate. The solution below assumes you're using autolayout constraints. My solution below is an alternative, it is updating frame manually. – alexburtnik Nov 13 '16 at 20:30

2 Answers2

16

There are two usefull methods of UIView: sizeToFit() and sizeThatFits(_:)

The first one resizes a view to a minimal size to fit subviews' content and the second one doesn't change frame at all, but returns calculated size which: (1) fit all subviews and (2) doesn't exceed parameter size

So you can use sizeThatFits for you purpose:

let label = UILabel()

override func viewDidLoad() {
    super.viewDidLoad()

    label.backgroundColor = UIColor.orange
    label.textColor = UIColor.white
//  label.text = "ultimate Frisbee"
    label.text = "ultimate Frisbee\nin 3 minutes,\nall welcome|2"
    label.numberOfLines = 10
    view.addSubview(label)

    updateLabelFrame()
}

func updateLabelFrame() {
    let maxSize = CGSize(width: 150, height: 300)
    let size = label.sizeThatFits(maxSize)
    label.frame = CGRect(origin: CGPoint(x: 100, y: 100), size: size)
}

Output:

enter image description here enter image description here

P.S. You also can solve your problem with autolayout constraints, but I am not a big fan of using them programmatically.

alexburtnik
  • 7,661
  • 4
  • 32
  • 70
5

Set the label's numberOfLines property to zero and fix the width of your labels with constraints (either by constraining the width explicitly or by constraining the leading and trailing edges to some other view like the label's superview). Then the labels will resize vertically automatically to the height that fits the text. You'll need to pin one label via its top constraint so the view system knows where to start the layout, then the top of all your other labels should be constrained to the bottom of the previous label. This way they will all layout relative to the height of the previous label.

edit in response to your comment:

You can set the width of a view explicitly on iOS 9 and later by using the widthAnchor property. You can set it on older iOS versions using NSLayoutConstraints (search SO for examples).

e.g.:

label.widthAnchor.constraintEqualToConstant(50.0).active = true

This would set the label's width to 50 points wide, but not fix its height, so with numberOfLines = 0 then the label will auto-resize vertically when you set the text.

par
  • 17,361
  • 4
  • 65
  • 80
  • my labels' locations have no relationship to each other – lf215 Nov 12 '16 at 21:35
  • @lf215 then you just need to set the `numberOfLines` property to zero to get dynamic height (presuming the width is fixed/constrained, otherwise it'll just lay out as one long line) – par Nov 12 '16 at 21:38
  • I've updated my question with code. It's not clear to me how to only fix width "by constraining the width explicitly" as you say. Do you mean something like [this](http://stackoverflow.com/a/27682006/1168364)? – lf215 Nov 12 '16 at 23:01
  • 1
    @lf215 yes exactly, or using the `widthAnchor`, see the edit to my answer. – par Nov 13 '16 at 00:44
  • I've tried your code: `let label = UILabel()` followed by `label.widthAnchor.constraintEqualToConstant(50.0).active = true;`. It says unable to satisfy constraints. This occurs when I try to simply add it as a subview to test it. (When I try to use the method [here](http://stackoverflow.com/a/40211383/1168364), I get a different error probably related.) What am I missing? – lf215 Nov 13 '16 at 01:26
  • Try setting the constraint after you've added it to a superview. – par Nov 13 '16 at 05:09
  • Still getting [this error](https://www.dropbox.com/s/pkykc36w78wvoho/Screenshot%202016-11-13%2012.58.21.png). – lf215 Nov 13 '16 at 17:59
  • 2
    You probably want to set `label.translatesAutoresizingMaskIntoConstraints = false`. – rob mayoff Nov 13 '16 at 20:32