Swiftpack.co - Package - lixiang1994/AttributedString
Swiftpack.co is a collection of thousands of indexed Swift packages. Search packages.

Logo

AttributedString - 基于Swift插值方式优雅的构建富文本

License  Swift  Platform  Swift Package Manager  Carthage  Cocoapods

🇨🇳天朝子民

Features

  • ☑ Constructing rich text using interpolation, Smooth coding, Elegant and natural style.
  • ☑ More control extension support.
  • ☑ Support for multi-level rich text cascading and provide other style priority strategies.
  • ☑ Support for all NSAttributedString.Key functions.
  • ☑ Support iOS & macOS & watchOS & tvOS.
  • ☑ Support text and attachment click or press event callback, support highlight style.
  • ☑ Support view attachment, you can add custom view to UITextView.
  • ☑ Continue to add more new features.

Screenshot

Simple Coding
All Font
Kern Stroke

Installation

CocoaPods - Podfile

pod 'AttributedString'

Carthage - Cartfile

github "lixiang1994/AttributedString"

Swift Package Manager for Apple platforms

Select Xcode menu File > Swift Packages > Add Package Dependency and enter repository URL with GUI.

Repository: https://github.com/lixiang1994/AttributedString

Swift Package Manager

Add the following to the dependencies of your Package.swift:

.package(url: "https://github.com/lixiang1994/AttributedString.git", from: "version")

Usage

First make sure to import the framework:

import AttributedString

How to initialize:

// Normal
let a: AttributedString = .init("lee", .font(.systemFont(ofSize: 13)))
// Interpolation
let b: AttributedString = "\("lee", .font(.systemFont(ofSize: 13)))"

Here are some usage examples. All devices are also available as simulators:

Font:

textView.attributed.text = """

\("fontSize: 13", .font(.systemFont(ofSize: 13)))

\("fontSize: 20", .font(.systemFont(ofSize: 20)))

\("fontSize: 22 weight: semibold", .font(.systemFont(ofSize: 22, weight: .semibold)))

"""

ForegroundColor:

textView.attributed.text = """

\("foregroundColor", .foreground(.white))

\("foregroundColor", .foreground(.red))

"""

Strikethrough:

textView.attributed.text = """

\("strikethrough: single", .strikethrough(.single))

\("strikethrough: double color: .red", .strikethrough(.double, color: .red))

"""

Attachment: (Does not include watchOS)

// AttributedString.Attachment

textView.attributed.text = """

\(.data(xxxx, type: "zip"))

\(.file(try!.init(url: .init(fileURLWithPath: "xxxxx"), options: [])))

\(.attachment(NSTextAttachment()))

"""

Attachment Image: (Does not include watchOS)

// AttributedString.ImageAttachment

textView.attributed.text = """

\(.image(UIImage(named: "xxxx")))

\(.image(UIImage(named: "xxxx"), .custom(size: .init(width: 200, height: 200))))

\(.image(UIImage(named: "xxxx"), .proposed(.center))).

"""

Attachment View: (Only supports iOS: UITextView)

// AttributedString.ViewAttachment

textView.attributed.text = """

\(.view(xxxxView))

\(.view(xxxxView, .custom(size: .init(width: 200, height: 200))))

\(.view(xxxxView, .proposed(.center))).

"""

Wrap:

let a: AttributedString = .init("123", .background(.blue))
let b: AttributedString = .init("456", .background(.red))
textView.attributed.text = "\(wrap: a) \(wrap: b, .paragraph(.alignment(.center)))"

// Defalut embedding mode, Nested internal styles take precedence over external styles
textView.attributed.text = "\(wrap: a, .paragraph(.alignment(.center)))"
textView.attributed.text = "\(wrap: .embedding(a), .paragraph(.alignment(.center)))"
// Override mode, Nested outer style takes precedence over inner style
textView.attributed.text = "\(wrap: .override(a), .paragraph(.alignment(.center)))"

Append:

let a: AttributedString = .init("123", .background(.blue))
let b: AttributedString = .init("456", .background(.red))
let c: AttributedString = .init("789", .background(.gray))
textView.attributed.text = a + b
textView.attributed.text += c

Checking:

var string: AttributedString = .init("my phone number is +86 18611401994.", .background(.blue))
string.add(attributes: [.foreground(color)], checkings: [.phoneNumber])
textView.attributed.text = string
var string: AttributedString = .init("open https://www.apple.com and https://github.com/lixiang1994/AttributedString", .background(.blue))
string.add(attributes: [.foreground(color)], checkings: [.link])
textView.attributed.text = string
var string: AttributedString = .init("123456789", .background(.blue))
string.add(attributes: [.foreground(color)], checkings: [.regex("[0-6]")])
textView.attributed.text = string

Action: (Only supports iOS: UILabel / UITextView & macOS: NSTextField)

For complex styles, it is recommended to use UITextView.

UITextview needs to set isEditable and isSelectable to false.

Click:
// Text
let a: AttributedString = .init("lee", .action({  }))
// Attachment (image)
let b: AttributedString = .init(.image(image), action: {
    // code
})

// It is recommended to use functions as parameters.
func clicked() {
    // code
}
// Normal
let c: AttributedString = .init("lee", .action(clicked))
let d: AttributedString = .init(.image(image), action: clicked)
// Interpolation
let e: AttributedString = "\("lee", .action(clicked))"
let f: AttributedString = "\(.image(image), action: clicked)"

// More information. 
func clicked(_ result: AttributedString.Action.Result) {
    switch result.content {
    case .string(let value):
       	print("Currently clicked text: \(value) range: \(result.range)")
				
    case .attachment(let value):
        print("Currently clicked attachment: \(value) range: \(result.range)")
    }
}

label.attributed.text = "This is \("Label", .font(.systemFont(ofSize: 20)), .action(clicked))"
textView.attributed.text = "This is a picture \(.image(image, .custom(size: .init(width: 100, height: 100))), action: clicked) Displayed in custom size."
Press:
func pressed(_ result: AttributedString.Action.Result) {
    switch result.content {
    case .string(let value):
        print("Currently pressed text: \(value) range: \(result.range)")
                
    case .attachment(let value):
        print("Currently pressed attachment: \(value) range: \(result.range)")
    }
}

label.attributed.text = "This is \("Long Press", .font(.systemFont(ofSize: 20)), .action(.press, pressed))"
textView.attributed.text = "This is a picture \(.image(image, .custom(size: .init(width: 100, height: 100))), trigger: .press, action: pressed) Displayed in custom size."
Highlight style:
func clicked(_ result: AttributedString.Action.Result) {
    switch result.content {
    case .string(let value):
        print("Currently clicked text: \(value) range: \(result.range)")
                
    case .attachment(let value):
        print("Currently clicked attachment: \(value) range: \(result.range)")
    }
}

label.attributed.text = "This is \("Label", .font(.systemFont(ofSize: 20)), .action([.foreground(.blue)], clicked))"
Custom:
let custom = AttributedString.Action(.press, highlights: [.background(.blue), .foreground(.white)]) { (result) in
    switch result.content {
    case .string(let value):
        print("Currently pressed text: \(value) range: \(result.range)")
        
    case .attachment(let value):
        print("Currently pressed attachment: \(value) range: \(result.range)")
    }
}

label.attributed.text = "This is \("Custom", .font(.systemFont(ofSize: 20)), .action(custom))"
textView.attributed.text = "This is a picture \(.image(image, .original(.center)), action: custom) Displayed in original size."

Observe: (Only supports iOS: UILabel / UITextView & macOS: NSTextField)

label.attributed.observe([.phoneNumber], highlights: [.foreground(.blue)]) { (result) in
    print("Currently clicked \(result)")
}

textView.attributed.observe([.link], highlights: [.foreground(.blue)]) { (result) in
    print("Currently clicked \(result)")
}

For more examples, see the sample application.

Properties available via Attribute class

The following properties are available:

PROPERTY TYPE DESCRIPTION
font UIFont font
color UIColor foreground color
background UIColor background color
paragraph ParagraphStyle paragraph attributes
ligature Bool Ligatures cause specific character combinations to be rendered using a single custom glyph that corresponds to those characters
kern CGFloat kerning
strikethrough NSUnderlineStyle . UIColor strikethrough style and color (if color is nil foreground is used)
underline NSUnderlineStyle , UIColor underline style and color (if color is nil foreground is used)
link String / URL URL
baselineOffset CGFloat character’s offset from the baseline, in point
shadow NSShadow shadow effect of the text
stroke CGFloat, UIColor stroke width and color
textEffect NSAttributedString.TextEffectStyle text effect
obliqueness CGFloat text obliqueness
expansion CGFloat expansion / shrink
writingDirection WritingDirection / [Int] initial writing direction used to determine the actual writing direction for text
verticalGlyphForm Bool vertical glyph (Currently on iOS, it's always horizontal.)

Cases available via Attribute.Checking enumerated

CASE DESCRIPTION
range(NSRange) custom range
regex(String) regular expression
action action
date date (Based on NSDataDetector)
link link (Based on NSDataDetector)
address address (Based on NSDataDetector)
phoneNumber phone number (Based on NSDataDetector)
transitInformation transit Information (Based on NSDataDetector)

Contributing

If you have the need for a specific feature that you want implemented or if you experienced a bug, please open an issue. If you extended the functionality of AttributedString yourself and want others to use it too, please submit a pull request.

License

AttributedString is under MIT license. See the LICENSE file for more info.


欢迎入群交流

QQ

Github

link
Stars: 272

Releases

优化UILabel点击计算, 修复换行符问题 - 2020-09-02T11:01:31

修复视图附件小尺寸无效问题 - 2020-08-27T09:56:45

修复当视图附件高度小于行高时无效的问题.

优化段落样式API - 2020-08-18T04:52:23

API

Changed:

AttributedString.Attribute:

old:

public static func paragraph(_ value: [ParagraphStyle]) -> Self

new:

public static func paragraph(with value: [ParagraphStyle]) -> Self

优化Pod配置 - 2020-08-14T10:14:41

优化Pod配置 - 2020-08-14T10:02:27

继续加强UILabel点击的精准性 - 2020-08-14T09:11:48

New Version ヾ(@^▽^@)ノ 💥

加强了UILabel点击的精准性, 完善了UILabelbaselineAdjustment属性对齐处理, 优化了单行时的对齐处理.

已知问题:

使TextKitUILabel 达到完全一致的排版布局是一件非常困难的事情, 经过几周的不断尝试和努力, 已经解决了大部分情况的问题, 但是依旧存在一些问题, 由于无法得知UILabel内部真实的排版逻辑, 所以这件事变得异常艰辛与困难, 目前只能通过大量测试来尽可能达到一致, 为此我构建了一个专门测试UILabel的Debug页面 (详见Demo中).

以下是目前还未解决的问题, 如果你知道如何解决 欢迎提交PR🙏.

  • UILabellineBreakModebyCharWrapping 时 会导致无法准确定位点击位置.
  • 当富文本中包含多个换行\n\n\n时 可能会导致点击位置不正确.
  • 当富文本中包含段落样式时, 同时在触发文本截断时或者文本字号缩放时, 会导致无法准确定位点击位置.
  • 其他一些极端情况 主要和段落样式有关.

建议: 如果需要使用富文本点击相关的特性, 优先建议使用UITextView.

特别鸣谢MPITextKit作者wanhmr提供的帮助.

重构UILabel的文本特殊处理 完美兼容adjustsFontSizeToFitWidth特性 - 2020-07-22T09:48:24

New Version ヾ(@^▽^@)ノ 💥

重构了UILabel内部的富文本特殊处理, 现在可以完美兼容adjustsFontSizeToFitWidth特性了, 无论如何缩小可以精准定位touch位置. 有兴趣的同学可以查看源码来一探究竟 哈哈.

AttributedString.Checking增加attachment类型, 强化func observe - 2020-07-16T07:37:20

New Version ヾ(@^▽^@)ノ 💥

AttributedString.Checkingcase link 优先筛选NSAttributedString.Key.link属性类型.

API

Changed:

AttributedString.Checking.Result:

old:

case action(AttributedString.Action.Result)

new:

case action(AttributedString.Action.Result.Content)

Add:

AttributedString.Checking:

case attachment

AttributedString.Checking.Result:

case attachment(NSTextAttachment)

UILabel / UITextView / NSTextField

func observe(_ checkings: [Checking] = .defalut, highlights: [Highlight] = .defalut, with callback: @escaping (NSRange, Checking.Result) -> Void)

iOS: UITextView增加视图附件(ViewAttachment)特性, 其他优化. - 2020-07-10T08:13:35

New Version ヾ(@^▽^@)ノ 💥

特性:

ViewAttachment 支持将自定义视图添加到富文本中 并在UITextView中显示, 支持更改视图大小.

注意: 不要擅自修改自定义视图的centertransform属性.

优化:

附件垂直居中对齐 (应该是全网最准确的居中方案 😂不是无脑-4)

AttributedString.ViewAttachment: (Only supports iOS: UITextView)

textView.attributed.text = """

\(.view(xxxxView))

\(.view(xxxxView, .custom(size: .init(width: 200, height: 200))))

\(.view(xxxxView, .proposed(.center))).

"""
WX20200710-160829@2x

修复使用非系统字体导致的Label点击无效的问题. - 2020-07-09T03:35:41

Fix some fonts causing invalid clicks. https://github.com/lixiang1994/AttributedString/issues/9

修复监听点击事件问题 - 2020-07-07T05:01:20

增加文本检查特性, 控件增加监听不同类型的点击事件支持, 优化API - 2020-07-04T06:58:49

New Version ヾ(@^▽^@)ノ 💥

特性:

增加文本检查特性, 可过滤匹配指定类型的内容, 例如: Action类型, 正则表达式类型, NSDataDetector自带的类型等.

  • UILabel & UITextView & NSTextField 原支持Action的控件 增加了监听方法, 可监听某一个或多个类型的点击事件.

  • 为某一个或多个类型 添加新的属性 或 为某一个或多个类型 覆盖原有属性.

应用:

  • 例如为整段文本的电话号码类型增加点击事件获取 跳转拨号.

  • 例如在View的tintAdjustmentMode.dimmed时, 设置Action类型的颜色转为灰色.

  • 例如为整段文本添加一个正则表达式类型 为符合表达式的内容增加高亮的颜色.

更多细节可参考README 和 Demo, 如果你有更好的想法 可以Issues留言.


API

Changed:

AttributedString:

old:

init(_: String)

new:

init(string: String)

AttributedString.Attribute:

old:

func color(_: Color)

new:

func foreground(_: Color)

Add:

Class:

AttributedString.Checking:

extension AttributedString {
        
    public enum Checking: Hashable {
        /// 自定义范围
        case range(NSRange)
        /// 正则表达式
        case regex(String)
        #if os(iOS) || os(macOS)
        case action
        #endif
        ///
        case date
        case link
        case address
        case phoneNumber
        case transitInformation
    }
}

AttributedString.Checking.Result:

extension AttributedString.Checking {
    
    public enum Result {
        /// 自定义范围
        case range(NSAttributedString)
        /// 正则表达式
        case regex(NSAttributedString)
        #if os(iOS) || os(macOS)
        case action(AttributedString.Action.Result)
        #endif
        
        case date(Date)
        case link(URL)
        case address(Address)
        case phoneNumber(String)
        case transitInformation(TransitInformation)
    }
}

Founction:

AttributedString:

init(string: String, _: Attribute...)

init(string: String, with: [Attribute])
func isContentEqual(_: AttributedString?)
mutating func add(attributes: [Attribute], range: NSRange)

mutating func set(attributes: [Attribute], range: NSRange)
mutating func add(attributes: [Attribute], checkings: [Checking])

mutating func set(attributes: [Attribute], checkings: [Checking])

UILabel & UITextView & NSTextField:

func observe(_ checking: Checking, highlights: [Highlight], with: @escaping (Checking.Result) -> Void)

func observe(_ checkings: [Checking], highlights: [Highlight], with: @escaping (Checking.Result) -> Void)
func remove(checking: Checking)

func remove(checkings: [Checking])

Remove:

AttributedString:

init<T>(_ value: T)

修复UILabel 点击范围计算错误的问题 - 2020-06-22T06:59:59

Action 增加触发方式, 高亮样式设置, 支持单击或按住 - 2020-06-20T06:10:59

  • 修复 UILabel 点击位置错误问题
  • Action 增加触发方式, 高亮样式设置, 支持单击或按住
点击:
// 文本
let a: AttributedString = .init("lee", .action({  }))
// 附件 (图片)
let b: AttributedString = .init(.image(image), action: {
    // code
})

// 建议使用函数作为参数 语法上比直接使用闭包更加整洁.
func clicked() {
    // code
}
// 正常初始化
let c: AttributedString = .init("lee", .action(clicked))
let d: AttributedString = .init(.image(image), action: clicked)
// 字面量初始化
let e: AttributedString = "\("lee", .action(clicked))"
let f: AttributedString = "\(.image(image), action: clicked)"

// 获取更多信息 
func clicked(_ result: AttributedString.Action.Result) {
    switch result.content {
    case .string(let value):
        print("点击了文本: \(value) range: \(result.range)")
                
    case .attachment(let value):
        print("点击了附件: \(value) range: \(result.range)")
    }
}

label.attributed.text = "This is \("Label", .font(.systemFont(ofSize: 20)), .action(clicked))"
textView.attributed.text = "This is a picture \(.image(image, .custom(size: .init(width: 100, height: 100))), action: clicked) Displayed in custom size."
按住:
func pressed(_ result: AttributedString.Action.Result) {
    switch result.content {
    case .string(let value):
        print("按住了文本: \(value) range: \(result.range)")
                
    case .attachment(let value):
        print("按住了附件: \(value) range: \(result.range)")
    }
}

label.attributed.text = "This is \("Long Press", .font(.systemFont(ofSize: 20)), .action(.press, pressed))"
textView.attributed.text = "This is a picture \(.image(image, .custom(size: .init(width: 100, height: 100))), trigger: .press, action: pressed) Displayed in custom size."
高亮样式:
func clicked(_ result: AttributedString.Action.Result) {
    switch result.content {
    case .string(let value):
        print("点击了文本: \(value) range: \(result.range)")
                
    case .attachment(let value):
        print("点击了附件: \(value) range: \(result.range)")
    }
}

label.attributed.text = "This is \("Label", .font(.systemFont(ofSize: 20)), .action([.color(.blue)], clicked))"
自定义:
// 触发方式为 按住, 高亮样式为 蓝色背景色和白色文字
let custom = AttributedString.Action(.press, highlights: [.background(.blue), .color(.white)]) { (result) in
    switch result.content {
    case .string(let value):
        print("按住了文本: \(value) range: \(result.range)")
        
    case .attachment(let value):
        print("按住了附件: \(value) range: \(result.range)")
    }
}

label.attributed.text = "This is \("Custom", .font(.systemFont(ofSize: 20)), .action(custom))"
textView.attributed.text = "This is a picture \(.image(image, .original(.center)), action: custom) Displayed in original size."

增加SPM支持 - 2020-06-15T04:41:01

Swift Package Manager for Apple platforms

Select Xcode menu File > Swift Packages > Add Package Dependency and enter repository URL with GUI.

Repository: https://github.com/lixiang1994/AttributedString

Swift Package Manager

Add the following to the dependencies of your Package.swift:

.package(url: "https://github.com/lixiang1994/AttributedString.git", from: "version")

macOS NSTextField 增加action支持 - 2020-06-12T05:41:13

完善+运算符扩展 - 2020-06-09T03:46:52

增加点击事件支持 - 2020-06-05T09:14:20

点击事件回调 仅支持iOS - UILabel / UITextView. 优化初始化方法.

支持 macOS, tvOS, watchOS - 2020-04-10T12:49:56

暂定兼容版本 iOS >= 9.0 macOS >= 10.13 tvOS >= 10.0 watchOS >= 5.0

完善扩展 - 2020-03-06T07:52:22

补充了对String,NSAttributedString, AttributedString.Style + += 的支持

增加 + 运算符扩展 - 2019-11-21T04:46:31

let a: AttributedString = .init("123", .background(.blue))
let b: AttributedString = .init("456", .background(.blue))
textView.attributed.text = a + b
        
textView.attributed.text += "test"

update - 2019-11-20T12:46:48

release - 2019-11-20T10:56:56

init - 2019-11-18T03:06:52

init - 2019-11-18T03:02:35