el-flor
el-flor

Reputation: 1476

iOS - Autolayout with multiple UILabel's and UIImage's of dynamic size

Here is my situation. I have 3 different data (ignore the money one at the end, it won't be included), laid out like this:

enter image description here

But the first data (music types) can be longer, and it should end up being something like this:

enter image description here

So there are multiple constraints I have to respect: Music types should be followed by the event type data, on whatever lines are required. The logo of the event type has to be included to. The total width shouldn't be bigger than it's superviews width of course.

I simply don't know where to start with the music types and event types. I have tried but I don't know how to use the <= width constraint with dynamic width.

I am working on Storyboard.

Upvotes: 3

Views: 681

Answers (3)

rob mayoff
rob mayoff

Reputation: 385610

I think this will be easiest if you put your icons in a custom font and use a single label for all the text and icons. Here's my result with this approach:

result

I used http://fontello.com/ to create a custom font with a record player, a pair of champagne glasses, and a clock. I put it in a file named appfont.ttf.

Here's how I load the font:

let appFontURL = NSBundle.mainBundle().URLForResource("appfont", withExtension: "ttf")!
let appFontData = NSData(contentsOfURL: appFontURL)!
let appFontDescriptor = CTFontManagerCreateFontDescriptorFromData(appFontData)!

iOS has a default “cascade list” it uses to find glyphs for code points not in the font, but it's easy to override if you want to choose your own fallback font for the plain text. I used the Papyrus font to demonstrate:

let baseFontDescriptor = CTFontDescriptorCreateWithNameAndSize("Papyrus", 18)
let fontAttributes: [NSString:AnyObject] = [
    kCTFontCascadeListAttribute : [ baseFontDescriptor ]
]
let fontDescriptor = CTFontDescriptorCreateCopyWithAttributes(appFontDescriptor, fontAttributes)
let font = CTFontCreateWithFontDescriptor(fontDescriptor, 18, nil) as UIFont

Next, I assemble all the text into one big string. You can see here how easy it is to replace different parts of the final string:

let recordPlayer = "\u{E802}"
let champagneGlasses = "\u{E801}"
let clock = "\u{E800}"
let nonBreakingSpace = "\u{00A0}"

let music1 = "Electro, Disco, 80's"
let music2 = "Jazz, Latino, Rock and roll, Electro, Beat Music"
let occasion = "After Office"
let time = "9:00 PM - 2:00 AM"

let text1 = "\(recordPlayer) \(music1) \(champagneGlasses)\(nonBreakingSpace)\(occasion)\n\(clock) \(time)"
let text2 = "\(recordPlayer) \(music2) \(champagneGlasses)\(nonBreakingSpace)\(occasion)\n\(clock) \(time)"
let text = text1 + "\n\n\n" + text2

I create a paragraph style to indent wrapped lines by 25 points:

let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.headIndent = 25

Now I can create the NSAttributedString that applies the custom font and the paragraph style to the plain text:

let richText = NSAttributedString(string: text, attributes: [
        NSFontAttributeName: font,
        NSParagraphStyleAttributeName: paragraphStyle
    ])

At this point you would set label.attributedText = richText, where label is an outlet connected to the label in your storyboard. I'm doing this all in a playground, so to see how it looks, I create a label and size it to fit:

let label = UILabel(frame: CGRectMake(0, 0, 320, 0))
label.backgroundColor = UIColor.whiteColor()
label.attributedText = richText
label.numberOfLines = 0
label.bounds.size.height = label.sizeThatFits(CGSizeMake(label.bounds.size.width, CGFloat.infinity)).height

Finally, I draw the label into an image to see it:

UIGraphicsBeginImageContextWithOptions(label.bounds.size, true, 1)
label.drawViewHierarchyInRect(label.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
image

You saw the result at the top of my answer. I've posted all the code and the fontello config in this gist.

Upvotes: 2

Flying_Banana
Flying_Banana

Reputation: 2910

This is probably the most straightforward solution, thought it might seem like a hack...

Idea is, it's hard to position the two labels correctly. Wouldn't it be nice to just make it one label, and somehow overlay the icon?

Indeed! So let's say, you insert 10 spaces between the first string and the second. Now the problem is, where should the icon go?

It turns out there is this convenient method that lets you find where a specific character is drawn on the screen. Refer to this SO post.

So more concretely, a good approach would be:

First format the string. Suppose your icon's width is about 10 spaces. Let's make the string such that it is [NSString stringWithFormat:@"%@*spaces here*%@", @"1st string", @"2nd string"]. Then what we need to do is to find the position of the spaces with the method in that SO post above, and move the icon to that position.

Some corner cases: Handling when the spaces are on two different lines. This means there's not enough space to fit the icon on the first line. You may want to move it to a new line. To detect this case, check the position for your first space and the last in that middle section. If their y values differ, then they are on two lines. Then we simply modify the string to add a new line before the first space, and we are done!

Perhaps you want finer control - afterall it might not look good if theres just enough space to display the icon, but not the text afterwards!

In that case, you can detect the x value of the final space to see if it is too close to the width of the superview. Insert newline as above solves this as well.

Hope this helps!

Upvotes: 0

TwoStraws
TwoStraws

Reputation: 13127

It's only tough if you try to solve it using lots of Auto Layout rules. If you are able to use it, there is a much simpler suggestion: NSAttributedString and NSTextAttachment. Attributed strings are strings with formatting attached (bold, italics, alignment, colours, etc), but you can also attach images inside attributed strings, and they just get drawn right along with the text.

Here's an example to help you get started:

// create an NSMutableAttributedString that we'll append everything to
let fullString = NSMutableAttributedString(string: "Start of text")

// create our NSTextAttachment
let image1Attachment = NSTextAttachment()
image1Attachment.image = UIImage(named: "awesomeIcon.png")

// wrap the attachment in its own attributed string so we can append it
let image1String = NSAttributedString(attachment: image1Attachment)

// add the NSTextAttachment wrapper to our full string, then add some more text.
fullString.appendAttributedString(image1String)
fullString.appendAttributedString(NSAttributedString(string: "End of text"))

// draw the result in a label
yourLabel.attributedText = fullString

Upvotes: 6

Related Questions