在 Swift 中将 UILabel 的一部分设为粗体

Make part of a UILabel bold in Swift

我有一个 UILabel 我以编程方式制作为:

var label = UILabel()

然后我为标签声明了一些样式,包括字体,例如:

label.frame = CGRect(x: 20, y: myHeaderView.frame.height / 2, width: 300, height: 30)
label.font = UIFont(name: "Typo GeoSlab Regular Demo", size: 15)
label.textColor = UIColor(hue: 0/360, saturation: 0/100, brightness: 91/100, alpha: 1)

标签的第一部分将始终显示为:"Filter:",然后是字符串的另一部分,例如 "Most popular"

我希望过滤器这个词是粗体的,所以整个事情看起来像:

筛选条件:最受欢迎

我想要创建这种效果的最简单方法。我一直在互联网上搜索如何实现这一点,有很多方法,有些看起来就像代码页。而且大部分似乎都在 Objective-C 中。我希望在 Swift 中得到它:)

我不知道我的思路是否正确,但这就是 NSRange 可以帮助实现的目标吗?提前致谢

更新

我使用一系列 if 语句来更改我的 label 变量。如:

if indexArray == 1 {

    label.text = "Filter: Film name"

} else if indexArray == 2 {

    label.text = "Filter: Most popular"

} else if indexArray == 3 {

    label.text = "Filter: Star rating"

}

您将需要使用 attributedString,它允许您设置字符串等部分的样式。这可以通过两种样式来完成,一种是普通样式,一种是粗体,然后将它们附加在一起:

let boldText = "Filter:"
let attrs = [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 15)]
let attributedString = NSMutableAttributedString(string:boldText, attributes:attrs)

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

attributedString.append(normalString)

当您想将其分配给标签时:

label.attributedText = attributedString

您可以使用 NSMutableAttributedString 和 NSAttributedString 创建自定义字符串。下面的函数使给定字符串中的给定 boldString 变为粗体。

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
}

用法示例

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 和 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
}

Swift 4个选择

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

只是在 Swift 4.0 中分享我自己的 quite-flexible 实现。因为有一些要求,比如我目前的要求,您不仅需要将标签文本的一部分设置为粗体,还需要设置斜体。

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.2 & 5.0:

首先我们创建一个协议,UILabelUITextFieldUITextView 可以采用。

public protocol ChangableFont: AnyObject {
    var rangedAttributes: [RangedAttributes] { get }
    func getText() -> String?
    func set(text: String?)
    func getAttributedText() -> NSAttributedString?
    func set(attributedText: NSAttributedString?)
    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()
}

我们希望能够对我们的文本添加多个更改,因此我们创建了 rangedAttributes 属性。它是一个自定义结构,包含属性及其应用范围。

public struct RangedAttributes {

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

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

还有一个问题就是UILabel它的font属性强而UITextField它的font属性是weak/optional .为了让它们都与我们的 ChangableFont 协议一起工作,我们包含了 getFont() -> UIFont? 方法。这也算作 UITextView 的 textattributedText 属性。这就是为什么我们也为它们实现 getter 和 setter 方法。

extension UILabel: ChangableFont {

    public func getText() -> String? {
        return text
    }

    public func set(text: String?) {
        self.text = text
    }

    public func getAttributedText() -> NSAttributedString? {
        return attributedText
    }

    public func set(attributedText: NSAttributedString?) {
        self.attributedText = attributedText
    }

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

extension UITextField: ChangableFont {

    public func getText() -> String? {
        return text
    }

    public func set(text: String?) {
        self.text = text
    }

    public func getAttributedText() -> NSAttributedString? {
        return attributedText
    }

    public func set(attributedText: NSAttributedString?) {
        self.attributedText = attributedText
    }

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

extension UITextView: ChangableFont {

    public func getText() -> String? {
        return text
    }

    public func set(text: String?) {
        self.text = text
    }

    public func getAttributedText() -> NSAttributedString? {
        return attributedText
    }

    public func set(attributedText: NSAttributedString?) {
        self.attributedText = attributedText
    }

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

现在我们可以继续为 UILabelUITextFieldUITextView 创建默认实现,方法是扩展我们的协议。

public extension ChangableFont {

    var rangedAttributes: [RangedAttributes] {
        guard let attributedText = getAttributedText() 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
    }

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

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

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

    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 = getAttributedText() {
            attributedString = NSMutableAttributedString(attributedString: attributedText)
        } else if let text = getText() {
            attributedString = NSMutableAttributedString(string: text)
        } else {
            return
        }

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

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

        set(attributedText: attributedString)
    }

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

在默认实现中,我使用了一些辅助方法来获取 substringNSRange

public extension String {

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

大功告成!您现在可以更改部分文本的字体和文本颜色。

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.0 解决方案

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
}

如果您愿意,可以直接对 String 进行操作:

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
}}

用法:

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

对于喜欢扩展的人

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
    }

并使用:

label.setRegualAndBoldText(regualText: "height: ", boldiText: "1.65 :(")

如果您知道要加粗的字符位置值,我创建了一个函数,它接受字符范围和可选字体(如果您只想使用 12 号的标准系统字体,请使用 nil),以及 returns 一个 NSAttributedString ,您可以将其附加到标签作为其属性文本。我想加粗字符串的第 0、10、22-23、30 和 34 个字符,所以我使用了 [[0,0]、[10,10]、[22,23]、[30,30]、[34] ,34]] 用于我的 boldCharactersRanges 值。

用法:

func boldenParts(string: String, boldCharactersRanges: [[Int]], regularFont: UIFont?, boldFont: UIFont?) -> NSAttributedString {
    let attributedString = NSMutableAttributedString(string: string, attributes: [NSAttributedString.Key.font: regularFont ?? UIFont.systemFont(ofSize: 12)])
    let boldFontAttribute: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: boldFont ?? UIFont.boldSystemFont(ofSize: regularFont?.pointSize ?? UIFont.systemFontSize)]
    for range in boldCharactersRanges {
        let currentRange = NSRange(location: range[0], length: range[1]-range[0]+1)
        attributedString.addAttributes(boldFontAttribute, range: currentRange)
        }
    return attributedString
}

override func viewDidLoad() {
    super.viewDidLoad()
    let label = UILabel()
    label.frame = CGRect(x: 0, y: 0, width: 180, height: 50)
    label.numberOfLines = 0
    label.center = view.center
    let text = "Under the pillow is a vogue article"
    let secretMessage = boldenParts(string: text, boldCharactersRanges: [[0,0], [10,10], [22,23], [30,30], [34,34]], regularFont: UIFont(name: "Avenir", size: 15), boldFont: UIFont(name: "Avenir-Black", size: 15))
    label.attributedText = secretMessage
    view.addSubview(label)
}