SwiftLocation

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

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

主要特性

特性亮点
🦄 使用简洁的声明式语法轻松进行样式和排版管理
🏞 在文本中附加本地图像(懒加载/静态)和远程图像
🧬 快速且高度可定制的 XML/HTML 标签字符串渲染
🌟 在样式中应用文本转换
📐 原生支持 iOS 11 动态类型
🖇 支持 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))

AttributedString 实例方法

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

有三种方法类别

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

添加

设置

移除

示例

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

符合 动态类型

为了支持您的字体/文本根据用户首选内容大小动态缩放,您可以实现样式的 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"

也可以通过渲染 XML 字符串来加载图像,方法是使用 img 标签(本地资源使用 named 标签,远程 url 使用 url 标签)。
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

注册全局可用的样式

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

要全局注册 StyleStyleXML,您需要为其分配唯一的标识符,并通过 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 中通过 Style Name (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 使用上标(上角)字形变体,如 footnotes_。
subscript Bool 使用下标(下角)字形变体:v_。
序数 Bool 序数字形变体被使用,例如在常见的“4th”排版中。
科学下标 Bool 科学下标字形变体被使用:H_O
小型大写字母 Set<小型大写字母> 配置小型大写字母的行为。
风格替代 风格替代 可使用不同的风格替代来定制字体。
上下文替代 上下文替代 可使用不同的上下文替代来定制字体。
字距调整 字距调整 要应用的字距。
特征变体 特征变体 描述要应用于字体的特征变体

要求

安装

CocoaPods

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

pod 'SwiftRichString'

Swift Package Manager

The 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