google1254 google1254 - 4 months ago 15
Swift Question

Why can't I bold just part of a string in Swift with NSMutableString?

The problem is that I can color half of the string but I cannot bold that half of the string.

Here's the code

var string = "blah blah blah blah blah"
var range = NSRange(location:0, length: 4)
//Apply to the label
myMutableString.addAttribute(NSFontAttributeName,
value: UIFont.boldSystemFontOfSize([X size]), range: range)

myMutableString.addAttribute(NSForegroundColorAttributeName,
value: UIColor.green(),
range: range)
UILabel.text.attributedText = myMutableString


Any ideas why bolding bolds the entire string instead of just the first "blah"? Only the first "blah" is colored green.

Answer

I'm just posting an answer because it was fun to use the playgrounds interactively and see the UILabel changing in real time.

import UIKit

let label = UILabel(frame: CGRectMake(0, 0, 300, 100))
let text = "black green small"
var mutableString = NSMutableAttributedString(string: text)

let textCount = text.utf16.count // Thanks OOPer
mutableString.addAttribute(NSFontAttributeName,
    value: UIFont.boldSystemFontOfSize(20),
    range: NSRange(location:0, length: textCount*2/3)
)

mutableString.addAttribute(NSForegroundColorAttributeName,
    value: UIColor.greenColor(),
    range: NSRange(textCount/3..<textCount)
)

label.attributedText = mutableString
label

Image of interactive .playground file


To please OOPer, I learned a little bit more about character indexes

//: Playground - noun: a place where people can play

import UIKit

extension String { // tools
    func characterIndex(ofFraction fraction: Float) -> String.CharacterView.Index {
        let amountOfCharacters = Int(Float(self.characters.count) * fraction)
        return self.characters.startIndex.advancedBy(amountOfCharacters, limit: self.characters.endIndex)
    }

    func utf16Index(characterIndex index: String.CharacterView.Index) -> String.UTF16View.Index {
        return String.UTF16View.Index(index, within: self.utf16)
    }

    func utf16Offset(toIndex index: String.UTF16View.Index) -> String.UTF16View.Index.Distance {
        return self.utf16.startIndex.distanceTo(index)
    }
}

extension String { // convenience
    func utf16Offset(ofFraction fraction: Float) -> String.UTF16View.Index.Distance {
        let correctIndexOfFraction = self.characterIndex(ofFraction: fraction)
        let translatedToUTF16 = self.utf16Index(characterIndex: correctIndexOfFraction)
        let absolutePosition = self.utf16Offset(toIndex: translatedToUTF16)
        return absolutePosition
    }
}

func paintedString(text: String) -> NSAttributedString {
    let mutableString = NSMutableAttributedString(string: text)

    let firstTwoThirds = 0..<text.utf16Offset(ofFraction: 2/3)
    let lastTwoThirds = text.utf16Offset(ofFraction: 1/3)..<text.utf16.count

    mutableString.addAttribute(NSFontAttributeName,
                               value: UIFont.boldSystemFontOfSize(30),
                               range: NSRange(firstTwoThirds)
    )

    mutableString.addAttribute(NSForegroundColorAttributeName,
                               value: UIColor.greenColor(),
                               range: NSRange(lastTwoThirds)
    )

    return mutableString
}

func paintedLabel(text text: String) -> UILabel {
    let label = UILabel(frame: CGRectMake(0, 0, 300, 50))
    label.attributedText = paintedString(text)
    return label
}

paintedLabel(text: "black green small")
paintedLabel(text: "