Make part of a UILabel bold in Swift

守給你的承諾、 提交于 2019-11-28 17:50:30
Joe Benton

You will want to use attributedString which allows you to style parts of a string etc. This can be done like this by having two styles, one normal, one bold, and then attaching them together:

let boldText  = "Filter:"
let attrs = [NSFontAttributeName : UIFont.boldSystemFontOfSize(15)]
let attributedString = NSMutableAttributedString(string:boldText, attributes:attrs)

let normalText = "Hi am normal"
let normalString = NSMutableAttributedString(string:normalText)

attributedString.appendAttributedString(normalString)

When you want to assign it to a label:

label.attributedText = attributedString

You can use NSMutableAttributedString and NSAttributedString to create customized string. The function below makes given boldString bold in given string.

Swift 3

func attributedText(withString string: String, boldString: String, font: UIFont) -> NSAttributedString {
    let attributedString = NSMutableAttributedString(string: string,
                                                     attributes: [NSFontAttributeName: font])
    let boldFontAttribute: [String: Any] = [NSFontAttributeName: UIFont.boldSystemFont(ofSize: font.pointSize)]
    let range = (string as NSString).range(of: boldString)
    attributedString.addAttributes(boldFontAttribute, range: range)
    return attributedString
}

Example usage

authorLabel.attributedText = attributedText(withString: String(format: "Author : %@", user.name), boldString: "Author", font: authorLabel.font)

Swift 4

func attributedText(withString string: String, boldString: String, font: UIFont) -> NSAttributedString {
    let attributedString = NSMutableAttributedString(string: string,
                                                     attributes: [NSAttributedStringKey.font: font])
    let boldFontAttribute: [NSAttributedStringKey: Any] = [NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: font.pointSize)]
    let range = (string as NSString).range(of: boldString)
    attributedString.addAttributes(boldFontAttribute, range: range)
    return attributedString
}

Swift 4.2 and 5

func attributedText(withString string: String, boldString: String, font: UIFont) -> NSAttributedString {
    let attributedString = NSMutableAttributedString(string: string,
                                                 attributes: [NSAttributedString.Key.font: font])
    let boldFontAttribute: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: font.pointSize)]
    let range = (string as NSString).range(of: boldString)
    attributedString.addAttributes(boldFontAttribute, range: range)
    return attributedString
}

Result:

Swift 4.2 & 5.0:

First off we create a protocol that both UILabel and UITextField can adopt.

public protocol ChangableFont: AnyObject {
    var text: String? { get set }
    var attributedText: NSAttributedString? { get set }
    var rangedAttributes: [RangedAttributes] { get }
    func getFont() -> UIFont?
    func changeFont(ofText text: String, with font: UIFont)
    func changeFont(inRange range: NSRange, with font: UIFont)
    func changeTextColor(ofText text: String, with color: UIColor)
    func changeTextColor(inRange range: NSRange, with color: UIColor)
    func resetFontChanges()
}

We want to be able to add multiple changes to our text, therefore we create the rangedAttributes property. It's a custom struct that holds attributes and the range in which they are applied.

public struct RangedAttributes {

    let attributes: [NSAttributedString.Key: Any]
    let range: NSRange

    public init(_ attributes: [NSAttributedString.Key: Any], inRange range: NSRange) {
        self.attributes = attributes
        self.range = range
    }
}

Another problem is that UILabel its font property is strong and UITextField its font property is weak/optional. To make them both work with our ChangableFont protocol we include the getFont() -> UIFont? method.

extension UILabel: ChangableFont {

    public func getFont() -> UIFont? {
        return font
    }
}

extension UITextField: ChangableFont {

    public func getFont() -> UIFont? {
        return font
    }
}

Now we can go ahead and create the default implementation for both UILabel and UITextField by extending our protocol.

public extension ChangableFont {

    public var rangedAttributes: [RangedAttributes] {
        guard let attributedText = attributedText else {
            return []
        }
        var rangedAttributes: [RangedAttributes] = []
        let fullRange = NSRange(
            location: 0,
            length: attributedText.string.count
        )
        attributedText.enumerateAttributes(
            in: fullRange,
            options: []
        ) { (attributes, range, stop) in
            guard range != fullRange, !attributes.isEmpty else { return }
            rangedAttributes.append(RangedAttributes(attributes, inRange: range))
        }
        return rangedAttributes
    }

    public func changeFont(ofText text: String, with font: UIFont) {
        guard let range = (self.attributedText?.string ?? self.text)?.range(ofText: text) else { return }
        changeFont(inRange: range, with: font)
    }

    public func changeFont(inRange range: NSRange, with font: UIFont) {
        add(attributes: [.font: font], inRange: range)
    }

    public func changeTextColor(ofText text: String, with color: UIColor) {
        guard let range = (self.attributedText?.string ?? self.text)?.range(ofText: text) else { return }
        changeTextColor(inRange: range, with: color)
    }

    public func changeTextColor(inRange range: NSRange, with color: UIColor) {
        add(attributes: [.foregroundColor: color], inRange: range)
    }

    private func add(attributes: [NSAttributedString.Key: Any], inRange range: NSRange) {
        guard !attributes.isEmpty else { return }

        var rangedAttributes: [RangedAttributes] = self.rangedAttributes

        var attributedString: NSMutableAttributedString

        if let attributedText = attributedText {
            attributedString = NSMutableAttributedString(attributedString: attributedText)
        } else if let text = text {
            attributedString = NSMutableAttributedString(string: text)
        } else {
            return
        }

        rangedAttributes.append(RangedAttributes(attributes, inRange: range))

        rangedAttributes.forEach { (rangedAttributes) in
            attributedString.addAttributes(
                rangedAttributes.attributes,
                range: rangedAttributes.range
            )
        }

        attributedText = attributedString
    }

    public func resetFontChanges() {
        guard let text = text else { return }
        attributedText = NSMutableAttributedString(string: text)
    }
}

With in the default implementation I use a little helper method for getting the NSRange of a substring.

public extension String {

    public func range(ofText text: String) -> NSRange {
        let fullText = self
        let range = (fullText as NSString).range(of: text)
        return range
    }
}

We're done! You can now change parts of the text its font and text color.

titleLabel.text = "Welcome"
titleLabel.font = UIFont.systemFont(ofSize: 70, weight: .bold)
titleLabel.textColor = UIColor.black
titleLabel.changeFont(ofText: "lc", with: UIFont.systemFont(ofSize: 60, weight: .light))
titleLabel.changeTextColor(ofText: "el", with: UIColor.blue)
titleLabel.changeTextColor(ofText: "co", with: UIColor.red)
titleLabel.changeTextColor(ofText: "m", with: UIColor.green)

Swift 4 alternative:

let attrs = [NSAttributedStringKey.font : UIFont.boldSystemFont(ofSize: 14)]
let attributedString = NSMutableAttributedString(string: "BOLD TEXT", attributes:attrs)
let normalString = NSMutableAttributedString(string: "normal text")
attributedString.append(normalString)
myLabel.attributedText = attributedString

Just sharing my own quite-flexible implementation in Swift 4.0. Cause there are some requirements, like mine currently, that you need to set not only bold but italic the part of a label's text.

import UIKit

extension UILabel {

    /** Sets up the label with two different kinds of attributes in its attributed text.
     *  @params:
     *  - primaryString: the normal attributed string.
     *  - secondaryString: the bold or highlighted string.
     */

    func setAttributedText(primaryString: String, textColor: UIColor, font: UIFont, secondaryString: String, secondaryTextColor: UIColor, secondaryFont: UIFont) {

        let completeString = "\(primaryString) \(secondaryString)"

        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.alignment = .center

        let completeAttributedString = NSMutableAttributedString(
            string: completeString, attributes: [
                .font: font,
                .foregroundColor: textColor,
                .paragraphStyle: paragraphStyle
            ]
        )

        let secondStringAttribute: [NSAttributedStringKey: Any] = [
            .font: secondaryFont,
            .foregroundColor: secondaryTextColor,
            .paragraphStyle: paragraphStyle
        ]

        let range = (completeString as NSString).range(of: secondaryString)

        completeAttributedString.addAttributes(secondStringAttribute, range: range)

        self.attributedText = completeAttributedString
    }
}

Swift 4.0 solution

let font = UIFont.systemFont(ofSize: 14)

func boldSearchResult(searchString: String, resultString: String) -> NSMutableAttributedString {
    let attributedString: NSMutableAttributedString = NSMutableAttributedString(string: resultString)

    guard let regex  = try? NSRegularExpression(pattern: searchString.lowercased(), options: []) else {
        return attributedString
    }

    let range: NSRange = NSMakeRange(0, resultString.count)

    regex.enumerateMatches(in: resultString.lowercased(), options: [], range: range) { (textCheckingResult, matchingFlags, stop) in
        guard let subRange = textCheckingResult?.range else {
            return
        }

        attributedString.addAttributes([NSAttributedString.Key.font : font], range: subRange)
    }

    return attributedString
}
Stefano Pedroli

You can directly do on String if you prefer:

extension String {
func withBoldText(text: String, font: UIFont? = nil) -> NSAttributedString {
  let _font = font ?? UIFont.systemFont(ofSize: 14, weight: .regular)
  let fullString = NSMutableAttributedString(string: self, attributes: [NSAttributedString.Key.font: _font])
  let boldFontAttribute: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: _font.pointSize)]
  let range = (self as NSString).range(of: text)
  fullString.addAttributes(boldFontAttribute, range: range)
  return fullString
}}

Usage:

label.attributeString = "my full string".withBoldText(text: "full")

for the ones who prefer extensions

Swift 5.0

    /// will set a regual and a bold text in the same label
    public func setRegualAndBoldText(regualText: String,
                                       boldiText: String) {

        let attrs = [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: font.pointSize)]
        let regularString = NSMutableAttributedString(string: regualText)
        let boldiString = NSMutableAttributedString(string: boldiText, attributes:attrs)
        regularString.append(boldiString)
        attributedText = regularString
    }

and use:

label.setRegualAndBoldText(regualText: "height: ", boldiText: "1.65 :(")
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!