NSKernAttributeName space at end of line in an NSAttributedString - ios

When using NSKernAttributeName it puts a space at the end of each line, is there any way to fix this? I can set the attributed to be in the range of:
NSRange(location: 0, length: self.text!.characters.count-1)
But I don't want to set this for every line.
This is the test code in the a playground I am using
//: Playground - noun: a place where people can play
import UIKit
import XCPlayground
var text = "Hello, playground\nhow are you?"
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.paragraphSpacing = 50
paragraphStyle.alignment = NSTextAlignment.Left
paragraphStyle.lineBreakMode = NSLineBreakMode.ByTruncatingTail
let attributes = [
NSParagraphStyleAttributeName: paragraphStyle
, NSKernAttributeName: 20
]
let attributedString = NSAttributedString(string: text, attributes: attributes)
let label = UILabel()
label.attributedText = attributedString
label.numberOfLines = 0
label.textColor = UIColor.greenColor()
label.backgroundColor = UIColor.orangeColor()
label.sizeToFit()
label.center = CGPoint(x: 500, y: 100)
var text2 = "What's up\nWhere are you?"
let attributedString2 = NSAttributedString(string: text2, attributes: attributes)
let label2 = UILabel()
label2.attributedText = attributedString2
label2.numberOfLines = 0
label2.textColor = UIColor.greenColor()
label2.backgroundColor = UIColor.orangeColor()
label2.sizeToFit()
label2.center = CGPoint(x: 500, y: 250)
var text3 = "Hello"
let attributedString3 = NSAttributedString(string: text3, attributes: attributes)
let label3 = UILabel()
label3.attributedText = attributedString3
label3.numberOfLines = 0
label3.textColor = UIColor.greenColor()
label3.backgroundColor = UIColor.orangeColor()
label3.sizeToFit()
label3.center = CGPoint(x: 500, y: 400)
let holderView = UIView(frame: CGRect(x: 0, y: 0, width: 1000, height: 500))
holderView.backgroundColor = UIColor.lightGrayColor()
holderView.addSubview(label)
holderView.addSubview(label2)
holderView.addSubview(label3)
XCPlaygroundPage.currentPage.liveView = holderView
With the result looking like this:
You can see the spaces at the end of each of the lines.

This is actually the definition of how kerning works; it adjusts the space between the kerned character and where the next character will be. Whether a next character proceeds to be drawn or not is irrelevant.
Standard Attributes
The kerning attribute indicates how much the following character should be shifted from its default offset as defined by the current character’s font; a positive kern indicates a shift farther along and a negative kern indicates a shift closer to the current character.
If it helps, think about doing this in a word processor. If kerning is on, and you type a character, where would you expect the insertion point to be then? The expected answer would be "offset from the just typed character by the amount of kern" as that's what happens in the default case of kern being 0, correct? Well, that's exactly what's happening here: if you kern the last character of a string, the string therefore includes the last kern.
So the correct thing to do here is to wrap up your dropLast() logic in an extension and call it a day.

Create an extension
import UIKit
extension UILabel {
#IBInspectable var kerning: Float {
get {
var range = NSMakeRange(0, (text ?? "").characters.count)
guard let kern = attributedText?.attribute(NSKernAttributeName, atIndex: 0, effectiveRange: &range),
value = kern as? NSNumber
else {
return 0
}
return value.floatValue
}
set {
var attText:NSMutableAttributedString?
if let attributedText = attributedText {
attText = NSMutableAttributedString(attributedString: attributedText)
} else if let text = text {
attText = NSMutableAttributedString(string: text)
} else {
attText = NSMutableAttributedString(string: "")
}
let range = NSMakeRange(0, attText!.length)
attText!.addAttribute(NSKernAttributeName, value: NSNumber(float: newValue), range: range)
self.attributedText = attText
}
}
}
This was answered here

Related

Not Getting Correct Line Count For UILabel

value = UILabel(frame: CGRect(x: 5 ,y: 5, width:Int(self.view.frame.width - 140) ,height: 16 ))
value.numberOfLines = 0
value.font = UIFont.systemFont(ofSize: 14.0)
value.lineBreakMode = .byWordWrapping
value.text = ((items["comment"] as? String) ?? "")!
value.setLineHeight(lineHeight: CGFloat(1))
var lineCount = 0;
let textSize = CGSize(width: value.frame.size.width, height: CGFloat(Float.infinity));
let rHeight = lroundf(Float(value.sizeThatFits(textSize).height))
let charSize = lroundf(Float(value.font.lineHeight));
lineCount = rHeight/charSize
print(lineCount)
}
When Trying to get number of lines in that UILabel. it is always giving me one more that the actual line count..
extension UILabel {
func setLineHeight(lineHeight: CGFloat) {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 1.0
paragraphStyle.lineHeightMultiple = lineHeight
paragraphStyle.alignment = self.textAlignment
let attrString = NSMutableAttributedString(string: self.text!)
attrString.addAttribute(NSFontAttributeName, value: self.font, range: NSMakeRange(0, attrString.length))
attrString.addAttribute(NSParagraphStyleAttributeName, value:paragraphStyle, range:NSMakeRange(0, attrString.length))
self.attributedText = attrString
}
}
Check this code. This calculates the no of lines correctly. I have tested it in XCode 8
value = UILabel(frame: CGRect(x: 50 ,y: 50, width:Int(self.view.frame.width - 140) ,height: 400 ))
value.numberOfLines = 0
value.font = UIFont.systemFont(ofSize: 14.0)
value.lineBreakMode = .byWordWrapping
value.text = ((items["comment"] as? String) ?? "")!
let textSize = CGSize(width: value.frame.size.width, height: CGFloat(Float.infinity));
let rHeight = Float(value.sizeThatFits(textSize).height)
var lineCount:Float = 0;
let charSize = Float(value.font.lineHeight)
lineCount = floor(rHeight / charSize)
print("lineCount \(lineCount)")
I know this is probably way too late for your needs, but I think the issue is your lineHeightMultiple.

NSAttributedString not displaying correctly

I want my UILabel to be able to split a long name into two lines and add the hyphen. Currently my code is like so :
let usernameX = profilePhoto.frame.size.width+horizontalMargin
let username = UILabel(frame: CGRect(x: usernameX, y: (self.frame.size.height/2)-21, width: self.frame.size.width-usernameX-horizontalMargin, height: 42))
username.adjustsFontSizeToFitWidth = true
username.font = UIFont(name: "SFUIDisplay-Regular", size: 20)
username.minimumScaleFactor = 0.8
username.numberOfLines = 2
username.translatesAutoresizingMaskIntoConstraints = false
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.hyphenationFactor = 0.5
let attributedString = NSMutableAttributedString(string: "Areallylongname", attributes: [NSParagraphStyleAttributeName: paragraphStyle])
However the label is only displaying "Areally-". What am I doing wrong here?
You can re-check the height of superview of label.
Fixing 42 as height is not the correct way to do it. You can calculate height of the label required dynamically and assign it. You can do it following way.
let attributedString = NSMutableAttributedString(string: "Areallylongname", attributes: [NSParagraphStyleAttributeName: paragraphStyle, NSFontAttributeName : UIFont(name: "SFUIDisplay-Regular", size: 20)])
var newFrame = attributedString.boundingRectWithSize(CGSizeMake(username.frame.size.width, 9999), options: NSStringDrawingOptions.UsesLineFragmentOrigin , context: nil)
newFrame.origin.x = username.frame.origin.x
newFrame.origin.y = username.frame.origin.y
You can set the new frame in viewDidLayoutSubviews

How to adjust a UILabel line spacing programmatically in Swift?

I have a multiline UILabel as shown here:
I achieved this using the following code:
label.lineBreakMode = .ByWordWrapping
label.numberOfLines = 2
I'm trying to "decrease" the line spacing between the 1st line and 2nd line, and I tried to use the following code:
let text = label.attributedText
let mas = NSMutableAttributedString(attributedString:text!)
mas.replaceCharactersInRange(NSMakeRange(0, mas.string.utf16.count),
withString: label.text!)
label.attributedText = mas
However, it does not seem to work.
Thanks
Programmatically with Swift 4
Using label extension
extension UILabel {
// Pass value for any one of both parameters and see result
func setLineSpacing(lineSpacing: CGFloat = 0.0, lineHeightMultiple: CGFloat = 0.0) {
guard let labelText = self.text else { return }
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = lineSpacing
paragraphStyle.lineHeightMultiple = lineHeightMultiple
let attributedString:NSMutableAttributedString
if let labelattributedText = self.attributedText {
attributedString = NSMutableAttributedString(attributedString: labelattributedText)
} else {
attributedString = NSMutableAttributedString(string: labelText)
}
// Line spacing attribute
attributedString.addAttribute(NSAttributedStringKey.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, attributedString.length))
self.attributedText = attributedString
}
}
Now call extension function
let label = UILabel()
let stringValue = "How\nto\nadjust\na\nUILabel\nline\nspacing\nprogrammatically\nin\nSwift"
// Pass value for any one argument - lineSpacing or lineHeightMultiple
label.setLineSpacing(lineSpacing: 2.0) . // try values 1.0 to 5.0
// or try lineHeightMultiple
//label.setLineSpacing(lineHeightMultiple = 2.0) // try values 0.5 to 2.0
Or using label instance (Just copy & execute this code to see result)
let label = UILabel()
let stringValue = "How\nto\nadjust\na\nUILabel\nline\nspacing\nprogrammatically\nin\nSwift"
let attrString = NSMutableAttributedString(string: stringValue)
var style = NSMutableParagraphStyle()
style.lineSpacing = 24 // change line spacing between paragraph like 36 or 48
style.minimumLineHeight = 20 // change line spacing between each line like 30 or 40
// Line spacing attribute
attrString.addAttribute(NSAttributedStringKey.paragraphStyle, value: style, range: NSRange(location: 0, length: stringValue.characters.count))
// Character spacing attribute
attrString.addAttribute(NSAttributedStringKey.kern, value: 2, range: NSMakeRange(0, attrString.length))
label.attributedText = attrString
Swift 3
let label = UILabel()
let stringValue = "How to\ncontrol\nthe\nline spacing\nin UILabel"
let attrString = NSMutableAttributedString(string: stringValue)
var style = NSMutableParagraphStyle()
style.lineSpacing = 24 // change line spacing between paragraph like 36 or 48
style.minimumLineHeight = 20 // change line spacing between each line like 30 or 40
attrString.addAttribute(NSParagraphStyleAttributeName, value: style, range: NSRange(location: 0, length: stringValue.characters.count))
label.attributedText = attrString
From Interface Builder:
You're on the right track with NSAttributedString. You need to set the line spacing of the paragraph style:
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 30 // Whatever line spacing you want in points
attributedString.addAttribute(NSParagraphStyleAttributeName, value:paragraphStyle, range:NSMakeRange(0, attributedString.length))
label.attributedText = attributedString;
Do this in the storyboard.....
func updateLabel(with title: String) {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineHeightMultiple = 0.8
paragraphStyle.alignment = .center
let string = NSAttributedString(string: title, attributes: [.paragraphStyle: paragraphStyle])
label.attributedText = string
}

centering text in a UILabel with an NSAttributedString

Going through some basic improvements to a application I am working on. Still new to the iOS swift development scene. I figured that the lines of text in my code would automatically be centered because I set the label to center. After a little bit of research I discovered this is not the case. How would I align code like this to center:
let atrString = try NSAttributedString(
data: assetDetails!.cardDescription.dataUsingEncoding(NSUTF8StringEncoding)!,
options: [NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType],
documentAttributes: nil)
assetDescription.attributedText = atrString
You need to create a paragraph style specifying center alignment, and set that paragraph style as an attribute on your text. Example playground:
import UIKit
import PlaygroundSupport
let style = NSMutableParagraphStyle()
style.alignment = NSTextAlignment.center
let richText = NSMutableAttributedString(string: "Going through some basic improvements to a application I am working on. Still new to the iOS swift development scene. I figured that the lines of text in my code would automatically be centered because I set the label to center.",
attributes: [ NSParagraphStyleAttributeName: style ])
// In Swift 4, use `.paragraphStyle` instead of `NSParagraphStyleAttributeName`.
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 400))
label.backgroundColor = UIColor.white
label.attributedText = richText
label.numberOfLines = 0
PlaygroundPage.current.liveView = label
Result:
Since you're parsing an HTML document to create your attributed string, you'll need to add the attribute after creation, like this:
let style = NSMutableParagraphStyle()
style.alignment = NSTextAlignment.center
let richText = try NSMutableAttributedString(
data: assetDetails!.cardDescription.data(using: String.Encoding.utf8)!,
options: [NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType],
documentAttributes: nil)
richText.addAttributes([ NSParagraphStyleAttributeName: style ],
range: NSMakeRange(0, richText.length))
// In Swift 4, use `.paragraphStyle` instead of `NSParagraphStyleAttributeName`.
assetDescription.attributedText = richText
Update for Swift 4
In Swift 4, attribute names are now of type NSAttributeStringKey and the standard attribute names are static members of that type. So you can add the attribute like this:
richText.addAttribute(.paragraphStyle, value: style, range: NSMakeRange(0, richText.length))
In Swift 4.1 :
let style = NSMutableParagraphStyle()
style.alignment = NSTextAlignment.center
lbl.centerAttributedText = NSAttributedString(string: "Total Balance",attributes: [.paragraphStyle: style])
(edited for code block)
You can use this utility function to do all common configuration for label
#discardableResult
public func DULabel(text: String, frame: CGRect = .zero, parent:UIView? = nil , font:UIFont, textColor:UIColor = .black, numOfLines:Int = 0 ,textAlignment: NSTextAlignment = .center,lineSpaceing:CGFloat = 0, cb: ((UILabel)->Void)? = nil )-> UILabel! {
let label = UILabel()
label.frame = frame
label.font = font
label.textColor = textColor
label.textAlignment = textAlignment
label.numberOfLines = numOfLines
if( lineSpaceing == 0 ){
label.text = text
}
else {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = lineSpaceing
paragraphStyle.alignment = textAlignment
let attrString = NSMutableAttributedString(string: text)
attrString.addAttribute(.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, attrString.length))
label.attributedText = attrString
}
if let parent = parent {
parent.addSubview(label)
}
cb?(label)
return label
}

How to concatenate two UITextView

I'm trying to concatenate two UItextView and it work.
They have different properties (for example different UIFont) but in the final UITextView they have the same properties. How to fix this?
textViewFirst!.text = "\n Example"
textViewFirst!.font = UIFont(name: "Helvetica Neue", size: 10);
textViewSecond.text = textViewSecond.text + textViewFirst.text
for example : this makes your text bold from the 4th char to 7th
let myFullString:String = textViewSecond.text + textViewFirst.text as String
var attributedText: NSMutableAttributedString = NSMutableAttributedString(string: myFullString)
attributedText.addAttributes([NSFontAttributeName: UIFont.boldSystemFontOfSize(14)], range: NSRange(location: 3, length: 3))
textViewSecond.attributedText = attributedText
Try this:
var attributedString = NSMutableAttributedString(string: textView2.text, attributes: [NSFontAttributeName : textView2.font])
attributedString.appendAttributedString(NSAttributedString(string: textView1.text, attributes: [NSFontAttributeName : textView1.font]))
textView2.attributedText = attributedString
In order to preserve both fonts and maybe other attributes (like text color) you must make use of NSAttributedString
let font = UIFont(name: "Helvetica Neue", size: 10.0) ?? UIFont.systemFontOfSize(18.0)
let textFont = [NSFontAttributeName:font]
// Create a string that will be our paragraph
let para1 = NSMutableAttributedString()
let para2 = NSMutableAttributedString()
// Create locally formatted strings
let attrString1 = NSAttributedString(string: "Hello ", attributes:textFont)
let attrString2 = NSAttributedString(string: "World ", attributes:textFont)
// Add locally formatted strings to paragraph
para1.appendAttributedString(attrString1)
para2.appendAttributedString(attrString2)
// Define paragraph styling
let paraStyle = NSMutableParagraphStyle()
paraStyle.firstLineHeadIndent = 15.0
paraStyle.paragraphSpacingBefore = 10.0
// Apply paragraph styles to paragraph
para1.addAttribute(NSParagraphStyleAttributeName, value: paraStyle, range: NSRange(location: 0,length: para1.length))
para2.addAttribute(NSParagraphStyleAttributeName, value: paraStyle, range: NSRange(location: 0,length: para1.length))
// Create UITextView
let view1 = UITextView(frame: CGRect(x: 0, y: 20, width: CGRectGetWidth(self.view.frame), height: 100))
let view2 = UITextView(frame: CGRect(x: 0, y: 100, width: CGRectGetWidth(self.view.frame), height: 100))
let view3 = UITextView(frame: CGRect(x: 0, y: 200, width: CGRectGetWidth(self.view.frame), height: 100))
// Add string to UITextView
view1.attributedText = para1
view2.attributedText = para2
var attributedString = NSMutableAttributedString(string: view1.text, attributes: [NSFontAttributeName : view1.font])
attributedString.appendAttributedString(NSAttributedString(string: view2.text, attributes: [NSFontAttributeName : view2.font]))
view3.attributedText = attributedString
// Add UITextView to main view
self.view.addSubview(view1)
self.view.addSubview(view2)
self.view.addSubview(view3)

Resources