TextKit类结构介绍
TextKit 是 iOS7推出的,面向对象的文本处理框架;
借用一张图来介绍一下:
图中真正的引用关系是实线的引用,反方向的虚线是弱引用
具体解读
1>NSTextStorage 负责存储属性文本;
2>NSLayoutManager 用来布局的,负责绘制 3个方法: 绘制字形、绘制背景(比如有些要设置底色的)、布局字形(知道用户点到哪里,点到第几个字符了);
3>NSTextContainer 一般不怎么用,它只是用来指定区域,在 layoutSubviews 设置关联就可以,就是来设定文本显示区域的
TextKit作用:
实现效果:点击到网址部分,文字高亮,跳转到下一个页面
实现思路:
1.使用 TextKit 接管 Label 的底层实现 - ‘绘制’ textStorage 的文本内容
2.使用正则表达式过滤 URL
3.交互
代码实现:
class XZLabel: UILabel {
// MARK: - 重写属性 - 进一步体会 TextKit 接管底层的实现
// 一旦内容变化,需要让 textStorage 响应变化!
override var text: String? {
didSet { // 重写准备文本内容
prepareTextContent()
}
}
override var attributedText: NSAttributedString? {
didSet { // 重写准备文本内容
prepareTextContent()
}
}
override var font: UIFont! {
didSet { // 重写准备文本内容
prepareTextContent()
}
}
override var textColor: UIColor! {
didSet { // 重写准备文本内容
prepareTextContent()
}
}
// MARK: - 构造函数
override init(frame: CGRect) {
super.init(frame: frame)
prepareTextSystem()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
prepareTextSystem()
}
// MARK: - 和 URL 的文本交互
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// 1.获取用户点击的位置
guard let location = touches.first?.location(in: self) else {
return
}
// 2.获取当前点中字符的索引
let idx = layoutManager.glyphIndex(for: location, in: textContainer)
print("点我了 \(idx)")
// 3.判断 idx 是否在 urls 的 ranges 范围内,如果在,就高亮
for rang in urlRanges ?? []{
// 返回 bool 值
if NSLocationInRange(idx, rang) {
// 如果在范围内,需要高亮
// 修改文本的字体属性
textStorage.addAttributes([NSAttributedStringKey.foregroundColor : linkTextColor], range: rang)
// 需要调用 setNeedsDisplay 函数重绘,但是不是 drawRect
setNeedsDisplay()
}else {
print("没戳中")
}
}
}
/// 绘制文本
/**
- 在 iOS 中绘制工作是类似于‘油画’似的,后绘制的内容,会把之前绘制的内容覆盖!
- 尽量避免使用带透明度的颜色,会严重影响性能!
- UILabel 默认不能实现垂直顶部对齐,使用 TextKit 的当前方法可以实现
*/
override func drawText(in rect: CGRect) {
let range = NSRange(location: 0, length: textStorage.length)
// 绘制背景 - 注意:绘制背景要放在绘制'字形'前面,否则会覆盖 '字形'
layoutManager.drawBackground(forGlyphRange: range, at: CGPoint())
// 绘制 Glyphs 字形
layoutManager.drawGlyphs(forGlyphRange: range, at: CGPoint())
}
override func layoutSubviews() {
super.layoutSubviews()
// 指定文本绘制的区域
textContainer.size = bounds.size
}
/// 链接颜色
public var linkTextColor = UIColor.blue
/// 选中背景色
public var selectedBackgroundColor = UIColor.init(white: 0.8, alpha: 1.0)
// MARK: - TextKit 的核心对象
/// 属性文本存储
private lazy var textStorage = NSTextStorage()
/// 负责文本'字形'布局
private lazy var layoutManager = NSLayoutManager()
/// 设置文本绘制的范围
private lazy var textContainer = NSTextContainer()
}
// MARK: - 正则表达式函数
private extension XZLabel {
/// 返回 textStorage 中的 URL range 数组
var urlRanges : [NSRange]? {
// 1. url 的正则表达式
let pattern = "[a-zA-Z]*://[a-zA-Z0-9/\\.]*"
guard let regx = try? NSRegularExpression(pattern: pattern, options: []) else {
return nil
}
// 2.多重匹配
let matches = regx.matches(in: textStorage.string, options: [], range: NSRange(location: 0, length: textStorage.length))
// 3.遍历数组,生成 range 的数组
var ranges = [NSRange]()
for m in matches {
ranges.append(m.range(at: 0))
}
return ranges
}
}
// MARK: - 设置 TextKit 核心对象
private extension XZLabel {
/// 准备文本系统
func prepareTextSystem() {
// 0.开启用户交互
isUserInteractionEnabled = true
// 1. 准备文本内容
prepareTextContent()
// 2.设置对象的关系
textStorage.addLayoutManager(layoutManager)
layoutManager.addTextContainer(textContainer)
}
/// 准备文本内容 - 使用 textStorage 接管 label 的内容
func prepareTextContent() {
if let attributedText = attributedText {
textStorage.setAttributedString(attributedText)
}else if let text = text {
textStorage.setAttributedString(NSAttributedString(string: text))
}else {
textStorage.setAttributedString(NSAttributedString(string: ""))
}
// 范围是 - Optional([{3, 20}])
print("范围是 - \(urlRanges)")
// 遍历范围数组,设置 URL 文字属性,URL 特殊显示
for rang in urlRanges ?? [] {
textStorage.addAttributes(
[
NSAttributedStringKey.foregroundColor : UIColor.red,
NSAttributedStringKey.backgroundColor : selectedBackgroundColor
],
range: rang)
}
}