17

I'm an Android developer trying my hand at Xcode and it's been unpleasant so far. What I'm trying to do is have a custom view that has three sub views:

  • UIImageView (for an icon)
  • UILabel (for the title)
  • UILabel (for the content)

I want it such that the content label's height grows and shrinks to wrap the text it contains (like Android's wrap_content). And then, I want the custom view to also grow and shrink to wrap all three sub views.

However, I cannot, for the life of me, figure out how these auto layouts/constraints work.

01) How would I make my UILabel's height grow/shrink to match its contained text?

02) How would I make my custom view's height grow/shrink to match its contained sub views?

override func layoutSubviews() {
    super.layoutSubviews()
    img_icon = UIImageView()
    txt_title = UILabel()
    txt_content = UILabel()

    img_icon.backgroundColor = Palette.white
    img_icon.image = icon
    txt_title.text = title
    txt_title.textAlignment = .Center
    txt_title.font = UIFont(name: "Roboto-Bold", size:14)
    txt_title.textColor = Palette.txt_heading1
    txt_content.text = content
    txt_content.textAlignment = .Center
    txt_content.font = UIFont(name: "Roboto-Regular", size:12)
    txt_content.textColor = Palette.txt_dark
    txt_content.numberOfLines = 0
    txt_content.preferredMaxLayoutWidth = self.frame.width
    txt_content.lineBreakMode = NSLineBreakMode.ByWordWrapping
    self.backgroundColor = Palette.white
    addSubview(img_icon)
    addSubview(txt_title)
    addSubview(txt_content)

    /*snip img_icon and txt_title constraints*/

    let txt_content_x = NSLayoutConstraint(item: txt_content, attribute: .CenterX, relatedBy: .Equal, toItem: self, attribute: .CenterX, multiplier: 1, constant: 0)
    let txt_content_y = NSLayoutConstraint(item: txt_content, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 80)
    let txt_content_w = NSLayoutConstraint(item: txt_content, attribute: .Width, relatedBy: .Equal, toItem: self, attribute: .Width, multiplier: 1, constant: 0)
    let txt_content_h = NSLayoutConstraint(item: txt_content, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 40)
    txt_content.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activateConstraints([
        txt_content_x,
        txt_content_y,
        txt_content_w,
        txt_content_h
    ])
}

I understand that, in the above code I've tried, I have the height set to a constant 40. This is only because I don't know how to achieve what I want.

[EDIT]

I've tried setting the height constraint to greater than or equal to but it just crashes Xcode.

[EDIT]

It crashes Xcode if I try to view it but works perfectly fine in the simulator. Question now is, why?

My height constraint is now:

let txt_content_h = NSLayoutConstraint(item: txt_content, attribute: .Height, relatedBy: .GreaterThanOrEqual, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 40)

It works in the simulator and has the desired behaviour. However, if I open the storyboard that contains the view, it crashes. It's definitely that line of code because changing it back to .Equal resolves the crash.

[EDIT]

My temporary fix is:

#if TARGET_INTERFACE_BUILDER
    //use .Equal for height constraint
#else
    //use .GreaterThanOrEqual for height constraint
#endif

This way, it doesn't crash Xcode and still renders the way I want it on the simulator.

[EDIT]

I removed the pre-processor check because I realized there's no actual thing like that defined and it still works now. I swear I've changed nothing else.

I am this close to giving up on iOS development because the interface builder keeps crashing Xcode without a reason when everything works in the simulator. Then, I do some nonsense edits and it works fine again.

Justin AnyhowStep
  • 1,130
  • 3
  • 12
  • 19

4 Answers4

5

01) How would I make my UILabel's height grow/shrink to match its contained text?

Just set top, left and right-constraint to the labels superview. Set the property number of lines to 0. Then it will start wrapping text.

02) How would I make my custom view's height grow/shrink to match its contained sub views?

By using interface builder this is much easier to achieve.

My suggestion to you is to start with your constraints in storyboard. You will not need to compile your code to see what the constraints will result in. Also you will get warnings and errors directly in the interface builder.

If you WANT to use programmatic constraints, my suggestion is to start using a framework for it. For example: https://github.com/SnapKit/SnapKit

ullstrm
  • 9,812
  • 7
  • 52
  • 83
  • Thanks for the advice, man. I thought the only way was to set the preferred width to the superview's width programmatically. I didn't think of that. My only issue now is that the interface builder and Xcode will repeatedly, arbitrarily decide to stop working until I shift some code around which affects nothing. It happens every few minutes and I can keep restarting Xcode and it doesn't help until I decide to shift some code around like changing the height from "45" to "125" or some random stuff like that. Or, as seen in my edits, adding a check to a value that doesn't exist and then removing. – Justin AnyhowStep Feb 12 '16 at 13:27
  • 1
    "By using interface builder this is much easier to achieve." But it is not obvious to me how to do this and there doesn't appear to be a way to achieve Android's equivalent of "wrap-content" for text views. The "constraint" system can only specify percentages of the parent window. – pete Aug 14 '20 at 04:12
  • Still does not explain how this can be done using interface builder – Archid Apr 20 '22 at 09:38
2

You can use a trick with constraints to achieve wrap-content. For example :

let maximumWidth = frame / 4  //For example
yourView.widthAnchor.constraint(lessThanOrEqualToConstant: maximumWidth).isActive = true

The "maximumWidth" depends on your UI and your design and you can change it.

Also, you should set "lineBreakMode" in StoryBoard or in code like :

yourBtn.titleLabel?.lineBreakMode = .byCharWrapping //For UIButton or
yourTxt.textContainer.lineBreakMode = .byCharWrapping //For UITextView
0

I understand there is no direct application of wrap content in iOS just like we have in Android and thats a big problem, I resolved it through manual anchors like this.

create a function with where in you calculate the height of the view using

mainView.contentSize.height

and then set anchors based on the total height to the enclosing view, call this function inside

 override func viewWillLayoutSubviews()

And this would work, the viewWillLayoutSubviews() is a lifecycle method and whenever you override you have to do

super.viewWillLayoutSubviews()

This worked in my case, might work with yours too, if there is a better approach please do comment.

Prajval Singh
  • 501
  • 3
  • 9
-1

Often clean will do a lot of good when code jams for no reason ar all, cmd-shift-k if i remember correctly

sKhan
  • 9,694
  • 16
  • 55
  • 53
sakumatto
  • 157
  • 1
  • 9