如何使用Swift创建带属性的字符串?

374

我正在尝试制作一个简单的咖啡计算器。我需要显示以克为单位的咖啡重量,并将“g”符号附加到我用于显示该数量的UILabel上。这个UILabel中的数字根据用户输入动态变化得很好,但是我需要在格式不同的字符串末尾添加一个小写的“g”。这个“g”需要与数字结合在一起,以便随着数字大小和位置的变化,“g”能够“移动”到相应位置。我相信这个问题以前肯定已经有解决办法了,所以如果您能提供指引,那将非常有帮助。

我已经搜索了关于属性字符串的文档,并从应用商店下载了一个“属性字符串创建工具”,但生成的代码是Objective-C的,而我正在使用Swift。对于其他学习这种语言的开发人员来说,最好的情况是提供一个明确的在Swift中使用属性字符串创建自定义字体和自定义属性的示例。由于相关文档并不十分清晰,因此这可能会有所帮助。我的计划是创建属性字符串并将其添加到我的coffeeAmount字符串的末尾。

var coffeeAmount: String = calculatedCoffee + attributedText

计算出的咖啡数是一个转换为字符串的Int类型,而"attributedText"是我正在尝试创建的带有自定义字体的小写字母"g"。也许我走错了路。感谢任何帮助!

31个回答

1085

enter image description here

本答案已更新至Swift 4.2。

快速参考

创建和设置富文本字符串的一般形式如下。您可以在下面找到其他常见选项。

// create attributed string
let myString = "Swift Attributed String"
let myAttribute = [ NSAttributedString.Key.foregroundColor: UIColor.blue ]
let myAttrString = NSAttributedString(string: myString, attributes: myAttribute) 

// set attributed text on a UILabel
myLabel.attributedText = myAttrString

Text Color

let myAttribute = [ NSAttributedString.Key.foregroundColor: UIColor.blue ]

Background Color

let myAttribute = [ NSAttributedString.Key.backgroundColor: UIColor.yellow ]

Font

let myAttribute = [ NSAttributedString.Key.font: UIFont(name: "Chalkduster", size: 18.0)! ]

enter image description here

let myAttribute = [ NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue ]

enter image description here

let myShadow = NSShadow()
myShadow.shadowBlurRadius = 3
myShadow.shadowOffset = CGSize(width: 3, height: 3)
myShadow.shadowColor = UIColor.gray

let myAttribute = [ NSAttributedString.Key.shadow: myShadow ]

这篇文章的其余部分提供了更多细节,供有兴趣的人参考。

属性

字符串属性只是一个以 [NSAttributedString.Key: Any] 形式的字典,其中 NSAttributedString.Key 是属性的键名,Any 是某种类型的值。该值可以是字体、颜色、整数或其他内容。在 Swift 中已经预定义了许多标准属性。例如:

  • 键名:NSAttributedString.Key.font,值为:UIFont
  • 键名:NSAttributedString.Key.foregroundColor,值为:UIColor
  • 键名:NSAttributedString.Key.link,值为:NSURLNSString

还有许多其他属性。请参见 this link 了解更多信息。您甚至可以创建自己的自定义属性,例如:

  • key name: NSAttributedString.Key.myName, value: some Type.
    if you make an extension:

    extension NSAttributedString.Key {
        static let myName = NSAttributedString.Key(rawValue: "myCustomAttributeKey")
    }
    

在Swift中创建属性

您可以像声明任何其他字典一样声明属性。

// single attributes declared one at a time
let singleAttribute1 = [ NSAttributedString.Key.foregroundColor: UIColor.green ]
let singleAttribute2 = [ NSAttributedString.Key.backgroundColor: UIColor.yellow ]
let singleAttribute3 = [ NSAttributedString.Key.underlineStyle: NSUnderlineStyle.double.rawValue ]

// multiple attributes declared at once
let multipleAttributes: [NSAttributedString.Key : Any] = [
    NSAttributedString.Key.foregroundColor: UIColor.green,
    NSAttributedString.Key.backgroundColor: UIColor.yellow,
    NSAttributedString.Key.underlineStyle: NSUnderlineStyle.double.rawValue ]

// custom attribute
let customAttribute = [ NSAttributedString.Key.myName: "Some value" ]

请注意需要下划线样式值所需的rawValue
因为属性只是字典,所以您也可以通过创建空字典,然后向其中添加键值对来创建它们。如果值将包含多种类型,则必须使用Any作为类型。以下是在此方式下重新创建上述示例中的multipleAttributes
var multipleAttributes = [NSAttributedString.Key : Any]()
multipleAttributes[NSAttributedString.Key.foregroundColor] = UIColor.green
multipleAttributes[NSAttributedString.Key.backgroundColor] = UIColor.yellow
multipleAttributes[NSAttributedString.Key.underlineStyle] = NSUnderlineStyle.double.rawValue

属性字符串

现在您了解了属性,就可以创建属性字符串。

初始化

有几种方法可以创建属性字符串。如果您只需要一个只读字符串,则可以使用NSAttributedString。以下是一些初始化方法:

// Initialize with a string only
let attrString1 = NSAttributedString(string: "Hello.")

// Initialize with a string and inline attribute(s)
let attrString2 = NSAttributedString(string: "Hello.", attributes: [NSAttributedString.Key.myName: "A value"])

// Initialize with a string and separately declared attribute(s)
let myAttributes1 = [ NSAttributedString.Key.foregroundColor: UIColor.green ]
let attrString3 = NSAttributedString(string: "Hello.", attributes: myAttributes1)

如果您需要稍后更改属性或字符串内容,则应使用NSMutableAttributedString。声明非常相似:
// Create a blank attributed string
let mutableAttrString1 = NSMutableAttributedString()

// Initialize with a string only
let mutableAttrString2 = NSMutableAttributedString(string: "Hello.")

// Initialize with a string and inline attribute(s)
let mutableAttrString3 = NSMutableAttributedString(string: "Hello.", attributes: [NSAttributedString.Key.myName: "A value"])

// Initialize with a string and separately declared attribute(s)
let myAttributes2 = [ NSAttributedString.Key.foregroundColor: UIColor.green ]
let mutableAttrString4 = NSMutableAttributedString(string: "Hello.", attributes: myAttributes2)

更改属性字符串

举个例子,让我们创建这篇文章顶部的属性字符串。

首先创建一个带有新字体属性的NSMutableAttributedString

let myAttribute = [ NSAttributedString.Key.font: UIFont(name: "Chalkduster", size: 18.0)! ]
let myString = NSMutableAttributedString(string: "Swift", attributes: myAttribute )

如果您是单独工作,请将属性字符串设置为UITextView(或UILabel),如下所示:
textView.attributedText = myString

你不使用 textView.text

这是结果:

enter image description here

然后添加另一个没有设置任何属性的 attributed string。(注意,即使我在上面使用了 let 来声明 myString,我仍然可以修改它,因为它是 NSMutableAttributedString。这似乎与 Swift 不太相符,如果将来发生变化,我不会感到惊讶。当那种情况发生时,请给我留言。)
let attrString = NSAttributedString(string: " Attributed Strings")
myString.append(attrString)

enter image description here

接下来我们只需选择从索引17开始,长度为7的“Strings”单词。请注意,这是一个NSRange而不是SwiftRange。(有关范围的更多信息,请参见this answer。)addAttribute方法使我们能够将属性键名放在第一位,属性值放在第二位,范围放在第三位。
var myRange = NSRange(location: 17, length: 7) // range starting at location 17 with a lenth of 7: "Strings"
myString.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.red, range: myRange)

enter image description here

最后,让我们添加一个背景颜色。为了增加变化性,我们将使用addAttributes方法(注意s)。我可以通过这种方法一次添加多个属性,但是我只会再次添加一个。

myRange = NSRange(location: 3, length: 17)
let anotherAttribute = [ NSAttributedString.Key.backgroundColor: UIColor.yellow ]
myString.addAttributes(anotherAttribute, range: myRange)

enter image description here

注意,有些地方属性是重叠的。添加属性不会覆盖已经存在的属性。

相关

进一步阅读


4
请注意,您可以结合多种下划线样式,例如 NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue | NSUnderlineStyle.PatternDot.rawValue - beeb
3
无法在 NSAttributedString 上使用 appendAttributedString 方法,只能在 NSMutableAttributedString 上使用。请更新您的答案以反映这一点。 - Joseph Astrahan
3
  1. 非常感谢你的回答。
  2. 我建议你将textView.atrributedtText = myString或者myLabel.attributedText = myString放在你的回答开头。作为一个新手,我只是使用了myLabel.text,没有想到需要阅读你的整个回答。
  3. 这是否意味着你只能选择attributedTexttext,同时使用它们会没有意义?
  4. 我建议你在你的回答中加入lineSpacing示例,例如这个,因为它非常有用。
  5. Sorry, 请提供更多上下文信息,以便我正确理解并进行翻译。
- mfaani
1
首先,appendadd之间的区别令人困惑。appendAttributedString类似于“字符串连接”。而addAttribute是向您的字符串添加新属性。 - mfaani
“Value of type 'String' has no member 'addAttribute'” - “Value of type 'NSAttributedString' has no member 'addAttribute'” - 我感到困惑。 - user5306470
2
@Daniel,“addAttribute”是“NSMutableAttributedString”的一个方法。您是正确的,您无法将其与“String”或“NSAttributedString”一起使用。(请检查本文中“更改带属性字符串”部分中“myString”的定义。我认为我把您搞糊涂了,因为我在文章的第一部分中也使用了“myString”作为变量名,其中它是一个“NSAttributedString”。) - Suragch

118

Swift使用与Obj-C相同的NSMutableAttributedString。您可以通过将计算值作为字符串传递来实例化它:

var attributedString = NSMutableAttributedString(string:"\(calculatedCoffee)")

现在创建属性为g的字符串(嘿)。注意:UIFont.systemFontOfSize(_)现在是可失败的初始化器,因此必须在使用之前进行解包:

var attrs = [NSFontAttributeName : UIFont.systemFontOfSize(19.0)!]
var gString = NSMutableAttributedString(string:"g", attributes:attrs)

然后将其添加:

attributedString.appendAttributedString(gString)

你可以像这样将UILabel设置为显示NSAttributedString:

myLabel.attributedText = attributedString

//第一部分 设置小写字母g var coffeeText = NSMutableAttributedString(string:"(calculateCoffee())")//第二部分 设置小写字母g的字体属性 var coffeeTypeFaceAttributes = [NSFontAttributeName : UIFont.systemFontOfSize(18)]//第三部分 创建“g”字符并赋予其属性 var coffeeG = NSMutableAttributedString(string:"g", attributes:coffeeTypeFaceAttributes)当我将UILabel.text设置为coffeeText时,出现错误“NSMutableAttributedString无法转换为'String'。是否有办法使UILabel接受NSMutableAttributedString? - dcbenji
11
当您有一个属性字符串时,您需要设置标签的attributedText属性而不是text属性。 - NRitH
1
这个程序正常工作了,现在我的小写字母"g"已经添加到了我的咖啡数量文本的末尾。 - dcbenji
2
由于某些原因,我在NSAttributedString的那一行上收到了一个“调用中的额外参数”错误。只有当我将UIFont.systemFontOfSize(18)切换为UIFont(name: "Arial", size: 20)时才会发生这种情况。有什么想法吗? - Unome
UIFont(name: size:) 是一个可失败的初始化器,可能会返回 nil。你可以在末尾添加 ! 来显式解包它,或者在将其插入字典之前使用 if/let 语句将其绑定到变量中。 - Ash
给 g 字符串加 1(嘿)。在工作中大声笑倒在地上。 - Mochi

23

我强烈建议使用富文本字符串库。例如,当您想要一个带有四种不同颜色和四种不同字体的字符串时,它可以使得这个过程变得更加容易这里是我的最爱。 它叫做SwiftyAttributes。

如果您想使用SwiftyAttributes创建一个带有四种不同颜色和不同字体的字符串:

let magenta = "Hello ".withAttributes([
    .textColor(.magenta),
    .font(.systemFont(ofSize: 15.0))
    ])
let cyan = "Sir ".withAttributes([
    .textColor(.cyan),
    .font(.boldSystemFont(ofSize: 15.0))
    ])
let green = "Lancelot".withAttributes([
    .textColor(.green),
    .font(.italicSystemFont(ofSize: 15.0))

    ])
let blue = "!".withAttributes([
    .textColor(.blue),
    .font(.preferredFont(forTextStyle: UIFontTextStyle.headline))

    ])
let finalString = magenta + cyan + green + blue
< p >最终字符串< code >finalString< /code >将显示为< /p > < p >显示为图像< /p>

21

Swift 5

let attrStri = NSMutableAttributedString.init(string:"This is red")
let nsRange = NSString(string: "This is red")
        .range(of: "red", options: String.CompareOptions.caseInsensitive)
attrStri.addAttributes([
    NSAttributedString.Key.foregroundColor : UIColor.red,
    NSAttributedString.Key.font: UIFont.init(name: "PTSans-Regular", size: 15.0) as Any
], range: nsRange)
self.label.attributedText = attrStri

输入图片说明


21

Xcode 6 版本:

let attriString = NSAttributedString(string:"attriString", attributes:
[NSForegroundColorAttributeName: UIColor.lightGrayColor(), 
            NSFontAttributeName: AttriFont])

Xcode 9.3 版本:

let attriString = NSAttributedString(string:"attriString", attributes:
[NSAttributedStringKey.foregroundColor: UIColor.lightGray, 
            NSAttributedStringKey.font: AttriFont])

Xcode 10,iOS 12,Swift 4

let attriString = NSAttributedString(string:"attriString", attributes:
[NSAttributedString.Key.foregroundColor: UIColor.lightGray, 
            NSAttributedString.Key.font: AttriFont])

20

Swift 4:

let attributes = [NSAttributedStringKey.font: UIFont(name: "HelveticaNeue-Bold", size: 17)!, 
                  NSAttributedStringKey.foregroundColor: UIColor.white]

它无法编译:'NSAttributedStringKey'类型(又名'NSString')没有成员'font'。 - bibscy
我刚在最新的XCode(10 beta 6)中尝试了它,它确实可以编译,你确定你正在使用Swift 4吗? - Adam Bardon
我正在使用Swift 3。 - bibscy
4
问题就在这里,我的回答有一个粗体标题“Swift 4”,我强烈建议您更新到Swift 4。 - Adam Bardon
@bibscy 你可以使用NSAttributedString.Key.***。 - Hatim

15

Swift: Xcode 6.1

    let font:UIFont? = UIFont(name: "Arial", size: 12.0)

    let attrString = NSAttributedString(
        string: titleData,
        attributes: NSDictionary(
            object: font!,
            forKey: NSFontAttributeName))

14

细节

  • Swift 5.2,Xcode 11.4(11E146)

解决方案

protocol AttributedStringComponent {
    var text: String { get }
    func getAttributes() -> [NSAttributedString.Key: Any]?
}

// MARK: String extensions

extension String: AttributedStringComponent {
    var text: String { self }
    func getAttributes() -> [NSAttributedString.Key: Any]? { return nil }
}

extension String {
    func toAttributed(with attributes: [NSAttributedString.Key: Any]?) -> NSAttributedString {
        .init(string: self, attributes: attributes)
    }
}

// MARK: NSAttributedString extensions

extension NSAttributedString: AttributedStringComponent {
    var text: String { string }

    func getAttributes() -> [Key: Any]? {
        if string.isEmpty { return nil }
        var range = NSRange(location: 0, length: string.count)
        return attributes(at: 0, effectiveRange: &range)
    }
}

extension NSAttributedString {

    convenience init?(from attributedStringComponents: [AttributedStringComponent],
                      defaultAttributes: [NSAttributedString.Key: Any],
                      joinedSeparator: String = " ") {
        switch attributedStringComponents.count {
        case 0: return nil
        default:
            var joinedString = ""
            typealias SttributedStringComponentDescriptor = ([NSAttributedString.Key: Any], NSRange)
            let sttributedStringComponents = attributedStringComponents.enumerated().flatMap { (index, component) -> [SttributedStringComponentDescriptor] in
                var components = [SttributedStringComponentDescriptor]()
                if index != 0 {
                    components.append((defaultAttributes,
                                       NSRange(location: joinedString.count, length: joinedSeparator.count)))
                    joinedString += joinedSeparator
                }
                components.append((component.getAttributes() ?? defaultAttributes,
                                   NSRange(location: joinedString.count, length: component.text.count)))
                joinedString += component.text
                return components
            }

            let attributedString = NSMutableAttributedString(string: joinedString)
            sttributedStringComponents.forEach { attributedString.addAttributes($0, range: $1) }
            self.init(attributedString: attributedString)
        }
    }
}

使用方法

let defaultAttributes = [
    .font: UIFont.systemFont(ofSize: 16, weight: .regular),
    .foregroundColor: UIColor.blue
] as [NSAttributedString.Key : Any]

let marketingAttributes = [
    .font: UIFont.systemFont(ofSize: 20.0, weight: .bold),
    .foregroundColor: UIColor.black
] as [NSAttributedString.Key : Any]

let attributedStringComponents = [
    "pay for",
    NSAttributedString(string: "one",
                       attributes: marketingAttributes),
    "and get",
    "three!\n".toAttributed(with: marketingAttributes),
    "Only today!".toAttributed(with: [
        .font: UIFont.systemFont(ofSize: 16.0, weight: .bold),
        .foregroundColor: UIColor.red
    ])
] as [AttributedStringComponent]
let attributedText = NSAttributedString(from: attributedStringComponents, defaultAttributes: defaultAttributes)

完整示例

不要忘记在此处粘贴解决方案的代码

import UIKit

class ViewController: UIViewController {

    private weak var label: UILabel!
    override func viewDidLoad() {
        super.viewDidLoad()
        let label = UILabel(frame: .init(x: 40, y: 40, width: 300, height: 80))
        label.numberOfLines = 2
        view.addSubview(label)
        self.label = label

        let defaultAttributes = [
            .font: UIFont.systemFont(ofSize: 16, weight: .regular),
            .foregroundColor: UIColor.blue
        ] as [NSAttributedString.Key : Any]

        let marketingAttributes = [
            .font: UIFont.systemFont(ofSize: 20.0, weight: .bold),
            .foregroundColor: UIColor.black
        ] as [NSAttributedString.Key : Any]

        let attributedStringComponents = [
            "pay for",
            NSAttributedString(string: "one",
                               attributes: marketingAttributes),
            "and get",
            "three!\n".toAttributed(with: marketingAttributes),
            "Only today!".toAttributed(with: [
                .font: UIFont.systemFont(ofSize: 16.0, weight: .bold),
                .foregroundColor: UIColor.red
            ])
        ] as [AttributedStringComponent]
        label.attributedText = NSAttributedString(from: attributedStringComponents, defaultAttributes: defaultAttributes)
        label.textAlignment = .center
    }
}

结果

在此输入图片描述


11

Swift 2.0

这里有一个示例:

let newsString: NSMutableAttributedString = NSMutableAttributedString(string: "Tap here to read the latest Football News.")
newsString.addAttributes([NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleDouble.rawValue], range: NSMakeRange(4, 4))
sampleLabel.attributedText = newsString.copy() as? NSAttributedString

Swift 5.x

let newsString: NSMutableAttributedString = NSMutableAttributedString(string: "Tap here to read the latest Football News.")
newsString.addAttributes([NSAttributedString.Key.underlineStyle: NSUnderlineStyle.double.rawValue], range: NSMakeRange(4, 4))
sampleLabel.attributedText = newsString.copy() as? NSAttributedString
let stringAttributes = [
    NSFontAttributeName : UIFont(name: "Helvetica Neue", size: 17.0)!,
    NSUnderlineStyleAttributeName : 1,
    NSForegroundColorAttributeName : UIColor.orangeColor(),
    NSTextEffectAttributeName : NSTextEffectLetterpressStyle,
    NSStrokeWidthAttributeName : 2.0]
let atrributedString = NSAttributedString(string: "Sample String: Attributed", attributes: stringAttributes)
sampleLabel.attributedText = atrributedString

11

在iOS上处理属性字符串的最佳方式是使用界面生成器中内置的属性文本编辑器,并避免在源文件中不必要地硬编码NSAtrributedStringKeys。

您可以使用此扩展程序在运行时动态替换占位符:

extension NSAttributedString {
    func replacing(placeholder:String, with valueString:String) -> NSAttributedString {

        if let range = self.string.range(of:placeholder) {
            let nsRange = NSRange(range,in:valueString)
            let mutableText = NSMutableAttributedString(attributedString: self)
            mutableText.replaceCharacters(in: nsRange, with: valueString)
            return mutableText as NSAttributedString
        }
        return self
    }
}

添加一个带有属性文本的故事板标签,其外观类似于此图像。

在此输入图片描述

然后,每次需要更新值时,只需执行以下操作:

label.attributedText = initalAttributedString.replacing(placeholder: "<price>", with: newValue)

请确保将原始值保存到initialAttributedString中。

通过阅读此文章,您可以更好地了解这种方法: https://medium.com/mobile-appetite/text-attributes-on-ios-the-effortless-approach-ff086588173e


这对我的情况非常有帮助,我有一个Storyboard,只想在标签中的一部分字符串中添加粗体。比手动设置所有属性要简单得多。 - Marc Attinasi
这个扩展程序以前对我来说完美无缺,但在Xcode 11中,在let nsRange = NSRange(range,in:valueString)这一行导致了我的应用程序崩溃。 - Lucas P.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接