SwiftLocation

Swift 风格的优雅富文本字符串组合

SwiftRichString 是一个轻量级的库,可以轻松地在 iOS、macOS、tvOS 甚至 watchOS 中创建和操作富文本字符串。它提供了一种方便的方式来存储可以在应用程序的 UI 元素中重用的样式,允许复杂的基于标签的字符串渲染,并且还包含与 Interface Builder 的集成。

主要功能

功能亮点
🦄 使用简洁的声明式语法轻松进行样式和排版管理
🏞 在文本中附加本地图像(惰性/静态)和远程图像
🧬 快速且高度可定制的 XML/HTML 标记字符串渲染
🌟 在样式中应用文本转换
📐 原生支持 iOS 11 Dynamic Type
🖇 支持 Swift 5.1 的函数构建器来组合字符串
紧凑的代码库,没有外部依赖项。
🐦 完全由 Swift 5 中的 Swift 爱好者制作

轻松样式

let style = Style {
	$0.font = SystemFonts.AmericanTypewriter.font(size: 25) // just pass a string, one of the SystemFonts or an UIFont
	$0.color = "#0433FF" // you can use UIColor or HEX string!
	$0.underline = (.patternDot, UIColor.red)
	$0.alignment = .center
}
let attributedText = "Hello World!".set(style: style) // et voilà!

基于 XML/HTML 标签的渲染

SwiftRichString 允许你通过解析文本标签来渲染复杂的字符串:每个样式将通过一个唯一的名称(在标签内使用)来标识,并且你可以创建一个 StyleXML (之前是 StyleGroup),它允许你封装所有这些样式并根据需要重复使用(显然,你可以全局注册它)。

// Create your own styles

let normal = Style {
	$0.font = SystemFonts.Helvetica_Light.font(size: 15)
}
		
let bold = Style {
	$0.font = SystemFonts.Helvetica_Bold.font(size: 20)
	$0.color = UIColor.red
	$0.backColor = UIColor.yellow
}
		
let italic = normal.byAdding {
	$0.traitVariants = .italic
}

let myGroup = StyleXML(base: normal, ["bold": bold, "italic": italic])
let str = "Hello <bold>Daniele!</bold>. You're ready to <italic>play with us!</italic>"
self.label?.attributedText = str.set(style: myGroup)

这就是结果!

文档

其他信息

StyleStyleXMLStyleRegEx 介绍

SwiftRichString 背后的主要概念是使用 StyleProtocol 作为可以应用于 StringNSMutableAttributedString 的属性的通用容器。从 StyleProtocol 派生的具体类是:StyleStyleXMLStyleRegEx

这些类中的每一个都可以用作可以应用于字符串、子字符串或富文本字符串的样式的来源。

Style:将样式应用于字符串或富文本字符串

Style 是一个类,它封装了你可以应用于字符串的所有属性。目前,AppKit/UIKit 的绝大多数属性都可以通过此类的类型安全属性获得。

创建一个 Style 实例非常简单;使用构建器模式方法,init 类需要一个回调,其中传递 self 实例,并允许你通过保持代码的整洁性和可读性来配置你的属性

let style = Style {
	$0.font = SystemFonts.Helvetica_Bold.font(size: 20)
	$0.color = UIColor.green
	// ... set any other attribute
}

let attrString = "Some text".set(style: style) // attributed string

StyleXML:为基于标签的复杂字符串应用样式

Style 实例是匿名的;如果你想使用一个样式实例来渲染一个基于标签的纯字符串,你需要将其包含在 StyleXML 中。你可以将 StyleXML 视为 Styles 的容器(但实际上,由于符合通用的 StyleProtocol 协议,你的组也可能包含其他子组)。

let bodyStyle: Style = ...
let h1Style: Style = ...
let h2Style: Style = ...
let group = StyleXML(base: bodyStyle, ["h1": h1Style, "h2": h2Style])

let attrString = "Some <h1>text</h1>, <h2>welcome here</h2>".set(style: group)

以下代码定义了一个组,其中

StyleRegEx:通过正则表达式应用样式

StyleRegEx 允许你定义一个样式,当在目标字符串/富文本字符串中匹配到特定正则表达式时,该样式将被应用。

let emailPattern = "([A-Za-z0-9_\\-\\.\\+])+\\@([A-Za-z0-9_\\-\\.])+\\.([A-Za-z]+)"
let style = StyleRegEx(pattern: emailPattern) {
	$0.color = UIColor.red
	$0.backColor = UIColor.yellow
}
		
let attrString = "My email is hello@danielemargutti.com and my website is http://www.danielemargutti.com".(style: style!)

结果是这样

字符串和富文本字符串的连接

SwiftRichString 允许你通过在 StringAttributedStringNSMutableAttributedString 的类型别名)和 Style 之间提供自定义 + 运算符来简化字符串连接。

这是一个例子

let body: Style = Style { ... }
let big: Style = Style { ... }
let attributed: AttributedString = "hello ".set(style: body)

// the following code produce an attributed string by
// concatenating an attributed string and two plain string
// (one styled and another plain).
let attStr = attributed + "\(username)!".set(style:big) + ". You are welcome!"

你还可以使用 + 运算符将样式添加到纯字符串或富文本字符串

// This produce an attributed string concatenating a plain
// string with an attributed string created via + operator
// between a plain string and a style
let attStr = "Hello" + ("\(username)" + big)

最后,你可以使用函数构建器连接字符串

let bold = Style { ... }
let italic = Style { ... }
        
let attributedString = AttributedString.composing {
  "hello".set(style: bold)
  "world".set(style: italic)
}

将样式应用于 StringAttributed String

StringAttributed String (又名 NSMutableAttributedString) 都有一些方便的方法,你可以使用它们通过代码轻松地创建和操作富文本。

字符串实例方法

一些例子

// apply a globally registered style named MyStyle to the entire string
let a1: AttributedString = "Hello world".set(style: "MyStyle")

// apply a style group to the entire string
// commonStyle will be applied to the entire string as base style
// styleH1 and styleH2 will be applied only for text inside that tags.
let styleH1: Style = ...
let styleH2: Style = ...
let StyleXML = StyleXML(base: commonStyle, ["h1" : styleH1, "h2" : styleH2])
let a2: AttributedString = "Hello <h1>world</h1>, <h2>welcome here</h2>".set(style: StyleXML)

// Apply a style defined via closure to a portion of the string
let a3 = "Hello Guys!".set(Style({ $0.font = SystemFonts.Helvetica_Bold.font(size: 20) }), range: NSMakeRange(0,4))

富文本字符串实例方法

类似的方法也适用于富文本字符串。

有三类方法

这些方法中的每一个都会改变富文本字符串的接收器实例,并且还会返回输出中的相同实例(因此允许链式调用)。

添加

设置

移除

例子

let a = "hello".set(style: styleA)
let b = "world!".set(style: styleB)
let ab = (a + b).add(styles: [coupondStyleA,coupondStyleB]).remove([.foregroundColor,.font])

Style 中的字体和颜色

所有您可以为 Style 设置的颜色和字体都由 FontConvertibleColorConvertible 协议包装。

SwiftRichString 显然为 UIColor/NSColor, UIFont/NSFont 实现了这些协议,也为 String 实现了这些协议。对于字体,这意味着您可以通过直接提供其 PostScript 名称来分配字体,它将自动转换为有效的实例

let firaLight: UIFont = "FiraCode-Light".font(ofSize: 14)
...
...
let style = Style {
	$0.font = "Jura-Bold"
	$0.size = 24
	...
}

在 UIKit 上,您还可以使用 SystemFonts 枚举从所有可用的 iOS 字体的一个类型安全的自动完成列表中进行选择

let font1 = SystemFonts.Helvetica_Light.font(size: 15)
let font2 = SystemFonts.Avenir_Black.font(size: 24)

对于颜色,这意味着您可以从 HEX 字符串创建有效的颜色实例

let color: UIColor = "#0433FF".color
...
...
let style = Style {
	$0.color = "#0433FF"
	...
}

显然,您仍然可以传递颜色/字体的实例。

派生一个 Style

有时您可能需要从现有的样式中推断出一个新样式的属性。在这种情况下,您可以使用 StylebyAdding() 函数来生成一个新的样式,该样式具有接收者的所有属性,并有机会配置其他/替换属性。

let initialStyle = Style {
	$0.font = SystemFonts.Helvetica_Light.font(size: 15)
	$0.alignment = right
}

// The following style contains all the attributes of initialStyle
// but also override the alignment and add a different foreground color.
let subStyle = bold.byAdding {
	$0.alignment = center
	$0.color = UIColor.red
}

符合 Dynamic Type

要支持你的字体/文本基于用户首选的内容大小进行动态缩放,你可以实现 style 的 dynamicText 属性。UIFontMetrics 属性被包装在 DynamicText 类中。

let style = Style {
	$0.font = UIFont.boldSystemFont(ofSize: 16.0)
	$0.dynamicText = DynamicText {
		$0.style = .body
		$0.maximumSize = 35.0
		$0.traitCollection = UITraitCollection(userInterfaceIdiom: .phone)
    }
}

渲染 XML/HTML 标记的字符串

SwiftRichString 也能够解析和渲染 xml 标记的字符串,以生成有效的 NSAttributedString 实例。 当您从远程服务接收动态字符串并且需要轻松生成渲染的字符串时,这尤其有用。

为了渲染 XML 字符串,您需要创建一个由您计划在单个 StyleXML 实例中渲染的所有样式组成的合成,并像您为单个 Style 所做的那样将其应用于您的源字符串。

例如

// The base style is applied to the entire string
let baseStyle = Style {
	$0.font = UIFont.boldSystemFont(ofSize: self.baseFontSize * 1.15)
	$0.lineSpacing = 1
	$0.kerning = Kerning.adobe(-20)
}

let boldStyle = Style {
	$0.font = UIFont.boldSystemFont(ofSize: self.baseFontSize)
    $0.dynamicText = DynamicText {
    $0.style = .body
    $0.maximumSize = 35.0
    $0.traitCollection = UITraitCollection(userInterfaceIdiom: .phone)
    }
}
		
let italicStyle = Style {
	$0.font = UIFont.italicSystemFont(ofSize: self.baseFontSize)
}

// A group container includes all the style defined.
let groupStyle = StyleXML.init(base: baseStyle, ["b" : boldStyle, "i": italicStyle])

// We can render our string
let bodyHTML = "Hello <b>world!</b>, my name is <i>Daniele</i>"
self.textView?.attributedText = bodyHTML.set(style: group)

自定义 XML 渲染:响应标签的属性和未知标签

您还可以将自定义属性添加到您的标签并按照您的偏好渲染它:您需要提供 XMLDynamicAttributesResolver 协议的一个具体实现并将其分配给 StyleXML.xmlAttributesResolver 属性。

该协议将接收两种类型的事件

以下示例用于覆盖任何已知标签使用的文本颜色

// First define our own resolver for attributes
open class MyXMLDynamicAttributesResolver: XMLDynamicAttributesResolver {
    
    public func applyDynamicAttributes(to attributedString: inout AttributedString, xmlStyle: XMLDynamicStyle) {
        let finalStyleToApply = Style()
        xmlStyle.enumerateAttributes { key, value  in
            switch key {
                case "color": // color support
                    finalStyleToApply.color = Color(hexString: value)
                
                default:
                    break
            }
        }
        
        attributedString.add(style: finalStyleToApply)
    }
}

// Then set it to our's StyleXML instance before rendering text.
let groupStyle = StyleXML.init(base: baseStyle, ["b" : boldStyle, "i": italicStyle])
groupStyle.xmlAttributesResolver = MyXMLDynamicAttributesResolver()

以下示例定义了一个名为 rainbow 的未知标签的行为。
具体来说,它通过为源字符串的每个字母设置自定义颜色来改变字符串。

open class MyXMLDynamicAttributesResolver: XMLDynamicAttributesResolver {

  public override func styleForUnknownXMLTag(_ tag: String, to attributedString: inout AttributedString, attributes: [String : String]?) {
        super.styleForUnknownXMLTag(tag, to: &attributedString, attributes: attributes)
        
        if tag == "rainbow" {
            let colors = UIColor.randomColors(attributedString.length)
            for i in 0..<attributedString.length {
                attributedString.add(style: Style({
                    $0.color = colors[i]
                }), range: NSMakeRange(i, 1))
            }
        }
        
    }
  }
}

您将在 attributes 参数中收到所有读取的标签属性。
您可以在 attributedString 属性中更改属性或作为 inout 参数接收的整个字符串。

该库还提供了一个默认的解析器,默认情况下使用:StandardXMLAttributesResolver。它将支持标签中的 color 属性和带有 url 链接的 <a href> 标签。

let sourceHTML = "My <b color=\"#db13f2\">webpage</b> is really <b>cool</b>. Take a look <a href=\"http://danielemargutti.com\">here</a>"
        
let styleBase = Style({
    $0.font = UIFont.boldSystemFont(ofSize: 15)
})
        
let styleBold = Style({
    $0.font = UIFont.boldSystemFont(ofSize: 20)
    $0.color = UIColor.blue
})
        
let groupStyle = StyleXML.init(base: styleBase, ["b" : styleBold])
self.textView?.attributedText = sourceHTML.set(style: groupStyle)

结果是这样

其中 b 标签的蓝色被 color 标签属性覆盖,并且 'here' 中的链接是可点击的。

自定义文本转换

有时您希望将自定义文本转换应用于您的字符串; 例如,您可能希望使具有给定样式的某些文本在使用当前语言环境时大写。
为了在 Style 实例中提供自定义文本转换,只需将一个或多个 TextTransform 设置为您的 Style.textTransforms 属性

let allRedAndUppercaseStyle = Style({
	$0.font = UIFont.boldSystemFont(ofSize: 16.0)
	$0.color = UIColor.red
	$0.textTransforms = [
		.uppercaseWithLocale(Locale.current)
	]
})

let text = "test".set(style: allRedAndUppercaseStyle) // will become red and uppercased (TEST)

虽然 TextTransform 是一个具有预定义转换集的枚举,但您也可以提供您自己的函数,该函数以 String 作为源,另一个 String 作为目标

let markdownBold = Style({
	$0.font = UIFont.boldSystemFont(ofSize: 16.0)
	$0.color = UIColor.red
	$0.textTransforms = [
		.custom({
			return "**\($0)**"
		})
	]
})

所有文本转换都以您在 textTransform 属性中设置的相同顺序应用。

文本中的本地和远程图像

SwiftRichString 支持本地和远程附加的图像以及带属性的文本。
您可以用一行代码创建一个带有图像的带属性的字符串

// You can specify the bounds of the image, both for size and the location respecting the base line of the text.
let localTextAndImage = AttributedString(image: UIImage(named: "rocket")!, bounds: CGRect(x: 0, y: -20, width: 25, height: 25))

// You can also load a remote image. If you not specify bounds size is the original size of the image.
let remoteTextAndImage = AttributedString(imageURL: "http://...")

// You can now compose it with other attributed or simple string
let finalString = "...".set(style: myStyle) + remoteTextAndImage + " some other text"

也可以通过使用 img 标签(本地资源使用 named 标签,远程 url 使用 url)渲染 XML 字符串来加载图像。
rect 参数是可选的,允许您指定资源的大小和位置。

let taggedText = """
  Some text and this image:
  <img named="rocket" rect="0,-50,30,30"/>
  
  This other is loaded from remote URL:
  <img url="https://www.macitynet.it/wp-content/uploads/2018/05/video_ApplePark_magg18.jpg"/>
"""

self.textView?.attributedText = taggedText.set(style: ...)

这是结果

有时您可能希望延迟提供这些图像。 为了做到这一点,只需在 StyleXML 实例中提供 imageProvider 回调的自定义实现

let xmlText = "- <img named=\"check\" background=\"#0000\"/> has done!"
        
let xmlStyle = StyleXML(base: {
  /// some attributes for base style
})

// This method is called when a new `img` tag is found. It's your chance to
// return a custom image. If you return `nil` (or you don't implement this method)
// image is searched inside any bundled `xcasset` file.
xmlStyle.imageProvider = { (imageName, attributes) in
	switch imageName {
		case "check":
		   // create & return your own image
		default:
		   // ...
	}
}
        
self.textView?.attributedText = xmlText.set(style: x)

StyleManager

注册全局可用的样式

您可以根据需要创建样式,也可以全局注册样式以便在需要时使用。 强烈建议使用第二种方法,因为它允许您根据需要设置应用程序的主题,并避免代码重复。

要全局注册一个 Style 或一个 StyleXML,您需要为其分配一个唯一的标识符,并通过 Styles 快捷方式(相当于调用 StylesManager.shared)调用 register() 函数。

为了使您的代码类型更安全,您可以使用一个不可实例化的结构来保存您的样式名称,然后使用它来注册样式

// Define a struct with your styles names
public struct StyleNames {
	public static let body: String = "body"
	public static let h1: String = "h1"
	public static let h2: String = "h2"
	
	private init { }
}

然后你可以

let bodyStyle: Style = ...
Styles.register(StyleNames.body, bodyStyle)

现在您可以在应用程序中的任何位置使用它;您可以仅使用其名称将其应用于文本

let text = "hello world".set(StyleNames.body)

或者您可以通过 Interface Builder 的可设计属性将 body 字符串分配给 styledText

按需延迟样式创建

有时您可能需要返回一个仅在应用程序的一小部分中使用的特定样式; 虽然您仍然可以直接设置它,但您也可以在 StylesManager 中延迟其创建。

通过实现 onDeferStyle() 回调,您可以选择在需要时创建一个新样式:您将收到该样式的标识符。

Styles.onDeferStyle = { name in
			
	if name == "MyStyle" {
		let normal = Style {
			$0.font = SystemFonts.Helvetica_Light.font(size: 15)
		}
				
		let bold = Style {
			$0.font = SystemFonts.Helvetica_Bold.font(size: 20)
			$0.color = UIColor.red
			$0.backColor = UIColor.yellow
		}
				
		let italic = normal.byAdding {
			$0.traitVariants = .italic
		}
				
		return (StyleXML(base: normal, ["bold": bold, "italic": italic]), true)
	}
			
	return (nil,false)
}

以下代码为 myStyle 标识符返回一个有效的样式并缓存它; 如果您不想缓存它,只需随样式实例一起返回 false

现在您可以使用您的样式来渲染,例如,将基于标签的文本渲染到 UILabel 中:只需设置要使用的样式的名称。

使用 Interface Builder 分配样式

SwiftRichString 也可以通过 Interface Builder 使用。

具有三个附加属性

分配的样式可以是 StyleStyleXMLStyleRegEx

通常,您将通过 IB 中的“样式名称”(styleName) 属性设置标签的样式,并通过设置 styledText 来更新控件的内容

// use `styleName` set value to update a text with the style
self.label?.styledText = "Another text to render" // text is rendered using specified `styleName` value.

否则,您可以手动设置值

// manually set the an attributed string
self.label?.attributedText = (self.label?.text ?? "").set(myStyle)

// manually set the style via instance
self.label?.style = myStyle
self.label?.styledText = "Updated text"

通过 Style 类可用的属性

以下属性可用

属性 类型 描述
size CGFloat 以磅为单位的字体大小
font FontConvertible 文本中使用的字体
color ColorConvertible 文本的前景色
backColor ColorConvertible 文本的背景颜色
shadow NSShadow 文本的阴影效果
underline (NSUnderlineStyle?,ColorConvertible?) 下划线样式和颜色(如果颜色为 nil,则使用前景色)
strikethrough (NSUnderlineStyle?,ColorConvertible?) 删除线样式和颜色(如果颜色为 nil,则使用前景色)
baselineOffset Float 字符与基线的偏移量,以点为单位
paragraph NSMutableParagraphStyle 段落属性
lineSpacing CGFloat 一个行片段的底部与下一个行片段的顶部之间的距离(以点为单位)
paragraphSpacingBefore CGFloat 段落顶部与其文本内容开头之间的距离
paragraphSpacingAfter CGFloat 添加到段落末尾的空间(以点为单位)
alignment NSTextAlignment 接收者的文本对齐方式
firstLineHeadIndent CGFloat 从文本容器的前导边距到段落第一行开头的距离(以点为单位)。
headIndent CGFloat 从文本容器的前导边距到除第一行之外的其他行的开头的距离(以点为单位)。
tailIndent CGFloat 该值是从前导边距开始的距离,如果为 0 或负数,则是从尾随边距开始的距离。
lineBreakMode LineBreak 用于换行的模式
minimumLineHeight CGFloat 接收器中任何行将占据的最小高度(以点为单位),而不管字体大小或任何附加图形的大小如何
maximumLineHeight CGFloat 接收器中任何行将占据的最大高度(以点为单位),而不管字体大小或任何附加图形的大小如何
baseWritingDirection NSWritingDirection 用于确定文本实际书写方向的初始书写方向
lineHeightMultiple CGFloat 接收者的自然行高乘以该因子(如果为正),然后再受到最小和最大行高的约束
hyphenationFactor Float 控制尝试连字符时的阈值
ligatures Ligatures 连字导致使用与这些字符对应的单个自定义字形来渲染特定的字符组合
speaksPunctuation Bool 启用文本中所有标点符号的朗读
speakingLanguage String 朗读字符串时要使用的语言(值为 BCP 47 语言代码字符串)。
speakingPitch Double 应用于朗读内容的音高
speakingPronunciation String
shouldQueueSpeechAnnouncement Bool 朗读的文本在现有朗读内容之后排队,或者中断现有朗读内容
headingLevel HeadingLevel 指定文本的标题级别
numberCase NumberCase “数字大小写(也称为“数字样式”)的配置”
numberSpacing NumberSpacing “数字间距(也称为“数字样式”)的配置”
fractions Fractions 显示分数的配置
superscript Bool 使用上标(superior)glpyh 变体,如 footnotes_。
subscript Bool 使用下标(inferior)字形变体:v_。
ordinals Bool 使用序数字形变体,如 4th 的常见排版。
scientificInferiors Bool 使用科学下标字形变体:H_O
smallCaps Set<SmallCaps> 配置小型大写字母的行为。
stylisticAlternates StylisticAlternates 可用于自定义字体的不同风格备选方案。
contextualAlternates ContextualAlternates 可用于自定义字体的不同上下文备选方案。
kerning Kerning 要应用的字距调整。
traitVariants TraitVariant 描述要应用于字体的特征变体

要求

安装

CocoaPods

CocoaPods 是 Cocoa 项目的依赖管理器。有关使用和安装说明,请访问其网站。要使用 CocoaPods 将 Alamofire 集成到您的 Xcode 项目中,请在您的 Podfile 中指定它

pod 'SwiftRichString'

Swift Package Manager

Swift Package Manager 是一种用于自动化 Swift 代码分发的工具,并集成到 swift 编译器中。 它还处于早期开发阶段,但 Alamofire 确实支持在支持的平台上使用它。

设置好 Swift 包后,将 Alamofire 添加为依赖项就像将其添加到 Package.swift 的 dependencies 值一样简单。

dependencies: [
    .package(url: "https://github.com/malcommac/SwiftRichString.git", from: "3.5.0")
]

Carthage

Carthage 是一个分散的依赖管理器,可构建您的依赖项并为您提供二进制框架。
要使用 Carthage 将 SwiftRichString 集成到您的 Xcode 项目中,请在您的 Cartfile 中指定它

github "malcommac/SwiftRichString"

运行 carthage 来构建框架,并将构建的 SwiftRichString.framework 拖到您的 Xcode 项目中。

贡献

欢迎提交问题和拉取请求!贡献者应遵守贡献者盟约行为准则

版权

SwiftRichString 在 MIT 许可证下可用。 有关更多信息,请参见 LICENSE 文件。

Daniele Margutti: hello@danielemargutti.com, @danielemargutti