richy
richy

Reputation: 2815

NSKernAttributeName space at end of line in an NSAttributedString

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:

text with kerning at the end of a line

You can see the spaces at the end of each of the lines.

Upvotes: 13

Views: 4886

Answers (3)

Erhan Demirci
Erhan Demirci

Reputation: 4209

maybe it's can help

textAttributes[.kern] = calculatedKern as AnyObject
            
let attributedString = NSMutableAttributedString(string: self.text, attributes: textAttributes)
// Remove kern attribute for last character
if self.text.count > 0 {
     let lastCharacterRange = NSRange(location: self.text.count - 1, length: 1)
     attributedString.removeAttribute(NSAttributedString.Key.kern, range: lastCharacterRange)
 }
            


self.attributedText = attributedString

Upvotes: 0

Mtoklitz113
Mtoklitz113

Reputation: 3878

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

Upvotes: -1

Alex Curylo
Alex Curylo

Reputation: 4772

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.

Upvotes: 2

Related Questions