Reputation: 2386
I'm trying to create a messaging application and am encountering a very strange issue.
The reason there is so much space between "Thomas" and the bottom of the text bubble is becasue the UILabel
is creating another line. Currently I'm setting the label's text using the attributedText
property, and passing in a NSMutableParagraphStyle
with a line spacing
of 8
. If I set the line spacing
to 0
, the space between "Thomas" and the bottom of the text bubble goes away like so:
Here's where it gets strange though. If I set the paragraph line spacing
back to 8
, and add a couple more characters to the line, the text bubble appears without the extra line:
All help is greatly appreciated :)
Here is my code:
class MessageTableViewCell: UITableViewCell {
var didSetupConstraints = false
var thumbnail = UIImageView.newAutoLayoutView()
let messageTailIcon = UIImageView.newAutoLayoutView()
var messageView = UIView.newAutoLayoutView()
var messageLabel = UILabel.newAutoLayoutView()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
thumbnail.image = UIImage(named: "ThomasBaldwin")
thumbnail.layer.cornerRadius = 17.5
thumbnail.clipsToBounds = true
messageTailIcon.image = UIImage(named: "MessageTailIcon")
messageView.backgroundColor = Application.greyColor
messageView.layer.cornerRadius = 10
let paragraphStyle = NSMutableParagraphStyle.new()
paragraphStyle.lineSpacing = 8
messageLabel.numberOfLines = 0
messageLabel.layer.cornerRadius = 10
messageLabel.attributedText = NSMutableAttributedString(
string: "Thomas says hello",
attributes: [
NSFontAttributeName: UIFont(name: "AvenirNextLTPro-Regular", size: 12.5)!,
NSForegroundColorAttributeName: UIColor.colorFromCode(0x262626),
NSBackgroundColorAttributeName: Application.greyColor,
NSKernAttributeName: 0.5,
NSParagraphStyleAttributeName: paragraphStyle
]
)
contentView.addSubview(thumbnail)
contentView.addSubview(messageView)
messageView.addSubview(messageTailIcon)
messageView.addSubview(messageLabel)
updateConstraints()
}
override func updateConstraints() {
if !didSetupConstraints {
thumbnail.autoPinEdgeToSuperviewEdge(.Top, withInset: 15)
thumbnail.autoPinEdgeToSuperviewEdge(.Leading, withInset: 8.5)
thumbnail.autoSetDimensionsToSize(CGSize(width: 35, height: 35))
messageView.autoPinEdgeToSuperviewEdge(.Top, withInset: 17.5)
messageView.autoPinEdge(.Leading, toEdge: .Trailing, ofView: thumbnail, withOffset: 10)
messageView.autoPinEdgeToSuperviewEdge(.Trailing, withInset: 24.5)
messageView.autoPinEdgeToSuperviewEdge(.Bottom)
messageTailIcon.autoPinEdgeToSuperviewEdge(.Top, withInset: 15)
messageTailIcon.autoPinEdgeToSuperviewEdge(.Leading, withInset: -10)
messageTailIcon.autoSetDimensionsToSize(CGSize(width: 18, height: 9))
messageLabel.autoPinEdgesToSuperviewEdgesWithInsets(UIEdgeInsets(top: 8.5, left: 10, bottom: 8.5, right: 5), excludingEdge: .Trailing)
messageLabel.autoPinEdgeToSuperviewEdge(.Trailing, withInset: 0, relation: .GreaterThanOrEqual)
didSetupConstraints = true
}
super.updateConstraints()
}
}
If you would like to view a sample project demonstrating the issue, I've pushed one to github
Upvotes: 5
Views: 7058
Reputation: 4646
Okay, so finally locked down and easy answer, this has to do with your KERNING attributed only. watch this:
Also disregard the sizing of the red cell, this is NOT happening like this in the app, this is just a product of my screenshots being different sizes, but please do try this for yourself. Comment out the kerning and the reapply it and you'll see the same thing
with kerning with "Thomas"
without kerning with "Thomas"
with kerning with "Thomas says hello"
without kerning with "Thomas says hello"
I've done everything possible to check the code, use different constraints, and I even played around with ALL options of an NSAttributedString, and the only thing that changes this bad behavior is the kerning attribute, and it's doing this to all types of fonts, not just Avenir. In fact, the font you used in this example is system font when you didn't set a font at all, but I've tried it with 3 fonts now, same effect, the kerning seems to be broken or perhaps its working as intended for Swift and/or ObjC, but I think this is actually a bug.
Most NSAttributedString options, if you want to mess around with stuff:
var myString1 = NSMutableAttributedString(string:"Thomas asdfadsf asdfasdfasdf asdfasdf asdfasdf \n asdfasdf asdf \n")
let myString1Font1 = UIFont.systemFontOfSize(12.0)
let originalNSString = myString1.string as NSString
let myString1Range1 = originalNSString.rangeOfString(myString1.string)
var myString1ParaStyle1 = NSMutableParagraphStyle()
myString1ParaStyle1.alignment = NSTextAlignment.Natural
myString1ParaStyle1.baseWritingDirection = NSWritingDirection.Natural
myString1ParaStyle1.defaultTabInterval = 0.0
myString1ParaStyle1.firstLineHeadIndent = 0.0
myString1ParaStyle1.headIndent = 0.0
myString1ParaStyle1.hyphenationFactor = 0.0
myString1ParaStyle1.lineBreakMode = NSLineBreakMode.ByWordWrapping
myString1ParaStyle1.lineHeightMultiple = 0.0
myString1ParaStyle1.lineSpacing = 8.0
myString1ParaStyle1.maximumLineHeight = 0.0
myString1ParaStyle1.minimumLineHeight = 0.0
myString1ParaStyle1.paragraphSpacing = 0.0
myString1ParaStyle1.paragraphSpacingBefore = 0.0
myString1ParaStyle1.tailIndent = 0.0
myString1.addAttribute(NSKernAttributeName, value:0.5, range:myString1Range1)
myString1.addAttribute(NSFontAttributeName, value:myString1Font1, range:myString1Range1)
myString1.addAttribute(NSParagraphStyleAttributeName, value:myString1ParaStyle1, range:myString1Range1)
myString1.addAttribute(NSBackgroundColorAttributeName, value:UIColor.redColor(), range:myString1Range1)
myString1.addAttribute(NSForegroundColorAttributeName, value:UIColor.blackColor(), range:myString1Range1)
Again, this isn't an constraints issue, I was wrong, this is only a KERNING issue, and this sucks, but such is life, perhaps this needs to be reported to RADAR.
Also, you can try this for yourself, anything BUT a 0 or 0.00000 or as many zeros as you want will produce the wrong results with Kerning, i tried this and it messes up your label field the same way that kerning would mess up the field with a larger value:
NSKernAttributeName: 0.00000000000001
HOLD up, I solved it, from what it looks like, set this value, just add this to your paragraphStyle variable that you set up in the example project, its working with the kerning, not sure if this is working for all fonts, but it fixes your example project at least:
paragraphStyle.lineHeightMultiple = 1.5
The only problem with this method is that it works for lines with one word or one line, you'll have to do a word count adjustment to set this "lineHeightMultiple" based on when a new line appears, this sucks, but it works, obviously, not a very good method to use, but works if you have a 1 liner string, needs adjusting if you have more, otherwise just turn off kerning, and it will be solved wihout this line height multiple.
It's as if the line height is changing internally and pushing characters to a new line but apple isn't automatically accounting for this change in character width.
And as a matter of fact, I think the answer you are looking for isn't kerning at all but tracking, which will push the letters apart from each other. The problem with kerning is that kerning screws around with the glyphs of the fonts and overrides some of their behaviors and as such it can be anoticeable effect like we are seeeing here.
From Apple:
preferredFontForTextStyle:, the specific font returned includes traits which vary according to user preferences and context, including tracking (letter-spacing) adjustments, in addition to being tuned for the use specified by the particular text style constant. The fonts returned using text style constants are meant to be used for all text in an app other than text in user interface elements, such as buttons, bars, and labels. Naturally, you need to choose text styles that look right in your app. It’s also important to observe the UIContentSizeCategoryDidChangeNotification so that you can re–lay out the text when the user changes the content size category. When your app receives that notification, it should send the invalidateIntrinsicContentSize message to views positioned by Auto Layout or send setNeedsLayout to user interface elements positioned manually. And it should invalidate preferred fonts or font descriptors and acquire new ones as needed.
If you truly need kerning, then you should probably be tweaking the kerning values of the ligatures if the font has any available to play with.
Other things to consider, this does work, but it's also bold so it's already not something that matches your style above, but it's something you can toy around with:
let sytemDynamicFontDescriptor = UIFontDescriptor.preferredFontDescriptorWithTextStyle(UIFontTextStyleHeadline)
let size = sytemDynamicFontDescriptor.pointSize
let myString1Font1 = UIFont(descriptor: sytemDynamicFontDescriptor, size:size)
println(sytemDynamicFontDescriptor.fontAttributes())
messageLabel.numberOfLines = 0
messageLabel.layer.cornerRadius = 10
messageLabel.attributedText = NSMutableAttributedString(
string: "Thomas asdfad as ",
// string: "Thomas says hello", // switch back to this to see it display the text properly on one
attributes: [
NSFontAttributeName: myString1Font1,
NSForegroundColorAttributeName: UIColor.blackColor(),
NSBackgroundColorAttributeName: UIColor.redColor(),
NSKernAttributeName: 0.5,
NSParagraphStyleAttributeName: paragraphStyle
]
)
Upvotes: 7