Giter Club home page Giter Club logo

attributedstring's Introduction

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 async image attachment, you can load remote image to UITextView.
  • 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"

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

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

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: ASAttributedString = .init("lee", .font(.systemFont(ofSize: 13)))
// Interpolation
let b: ASAttributedString = "\("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)

// ASAttributedString.Attachment

textView.attributed.text = """

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

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

\(.attachment(NSTextAttachment()))

"""

Attachment Image: (Does not include watchOS)

// ASAttributedString.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 Async Image: (Only supports iOS: UITextView)

// ASAttributedString.AsyncImageAttachment

textView.attributed.text = """

\(.image(url, placeholder: xxxxx))

"""

Custom loader:

ASAttributedString.AsyncImageAttachment.Loader = AsyncImageAttachmentKingfisherLoader.self

Please read the demo AttachmentViewController.swift file for details.

Attachment View: (Only supports iOS: UITextView)

// ASAttributedString.ViewAttachment

textView.attributed.text = """

\(.view(xxxxView))

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

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

"""

Wrap:

let a: ASAttributedString = .init("123", .background(.blue))
let b: ASAttributedString = .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: ASAttributedString = .init("123", .background(.blue))
let b: ASAttributedString = .init("456", .background(.red))
let c: ASAttributedString = .init("789", .background(.gray))
textView.attributed.text = a + b
textView.attributed.text += c

Checking:

var string: ASAttributedString = .init("my phone number is +86 18611401994.", .background(.blue))
string.add(attributes: [.foreground(color)], checkings: [.phoneNumber])
textView.attributed.text = string
var string: ASAttributedString = .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: ASAttributedString = .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: ASAttributedString = .init("lee", .action({  }))
// Attachment (image)
let b: ASAttributedString = .init(.image(image), action: {
    // code
})

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

// More information. 
func clicked(_ result: ASAttributedString.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: ASAttributedString.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: ASAttributedString.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 = ASAttributedString.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

attributedstring's People

Contributors

dependabot[bot] avatar lixiang1994 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

attributedstring's Issues

富文本编辑插值问题

如果我想用来做富文本编辑器,插入图片的时候具体该怎么定位插入图片的位置呢?

Invalid line spacing for last line with button

Attached example:

Code for init:

let text = AttributedString(NSAttributedString(string: text), .font(textFont), .color(descriptionLabel.textColor))
        let button = AttributedString(NSAttributedString(string: button), .font(buttonFont), .color(buttonColor), .action([.color(buttonColor.withAlphaComponent(0.5))], action))
        descriptionLabel.attributed.text = text + " " + button

Code for layout

titleLabel.frame.origin = .zero
titleLabel.frame.set(width: frame.width)
titleLabel.sizeToFit()
        
descriptionLabel.frame = CGRect.init(x: 0, y: titleLabel.frame.bottomY + 4, width: frame.width, height: 0)
descriptionLabel.sizeToFit()

Number of lines set to zero.

SPM Warning

I think it happen because plist file inside source folder.
Screenshot 2021-12-16 at 11 38 57

AttributedString.swift文件中别名的问题

在AttributedString.swift中第14行至第24行代码中
#if os(macOS)
import AppKit
public typealias Image = NSImage
public typealias Color = NSColor
public typealias Font = NSFont
#else
import UIKit
public typealias Image = UIImage
public typealias Color = UIColor
public typealias Font = UIFont
#endif
上述代码中别名Image会与SwiftUI中的Image(uiImage:(UIImage(systemName: ""))!)起冲突,可以测试一下,如有此问题,可以修改别名,或者添加前缀如public typealias LXImage = NSImage

$100美刀 有偿征集解决方案 TextKit与UILabel内容同步问题


完美解决此问题, 以$100美刀作为酬谢, 支持 微信支付, 支付宝支付, PayPal支付.

  • 解决上述问题, 确保自测无误.
  • 提交PR或直接联系本人交付相关代码.
  • 本人验证通过测试.
  • 2小时内根据以上支付方式支付酬谢金.
  • Release记录 以示感谢🙏

描述:

本库对于 UILabel 的点击事件实现方案是使用 TextKit 构建一个与 UILabel 显示一致的内容, 通过TextKit计算出所点击的字符.

问题:

由于UILabel的特殊性, 导致TextKit无法构建出一个完全匹配的内容, 在某些情况下总会存在误差.

比如 富文本中 存在中英文不同字体混合的情况, 最终显示会不一致.

目前进展:

UILabel 真实的NSAttributedString对象的获取已解决, 可以肯定的是目前得到的NSAttributedString对象是准确无误的. 无需再考虑这方面的问题.

通过各种实践已知 TextKitUILabel的行高计算策略不同, 导致相同的NSAttributedString对象实际渲染后显示的不一样.

已经尝试使用Neat的方案解决, Neat本身存在很多问题, 比如富文本中包含NSTextAttachment Neat 就会出现问题, 幸运的是 这个问题我已经解决了.
其他问题目前还没有解决, 比如 numberOfLines会导致计算出错.
Neat的方案是否真正可靠还是个未知数, 但最起码它是目前最接近成功的一种方案, 你可以考虑继续使用Neat的方案走下去, 也可以另辟蹊径.

如何进行测试:

目前该分支内已经包含了调试代码.
CheckingViewController.swift文件中, 点击label后 会创建一个DebugView添加到UILabel上, DebugView上显示的内容由TextKit绘制, 如果所显示的内容完全重合 则表示通过, 如果出现不一致的情况 则表示不通过.

例子:

debug

通过:

success

不通过:

failure

分支地址

调试页面:
image

相关类:
image

点击事件崩溃

adjustsFontSizeToFitWidth = true 的时候 一行展示不下的时候 文字里面有点击部分点击会崩溃

Error

Xcode 13
let attr : ASAttributedString = "("lee", .action(clicked))"

error:

Cannot infer contextual base in reference to member 'action'
Generic parameter 'T' could not be inferred

UILabel.touchesBegan 会发生崩溃:'NSRangeException', reason: 'NSMutableRLEArray objectAtIndex:effectiveRange:: Out of bounds'

open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard
            isActionEnabled,
            let string = attributed.text,
            let touch = touches.first,
            let (range, action) = matching(touch.location(in: self)) else {
            super.touchesBegan(touches, with: event)
            return
        }
        // 设置触摸范围内容
        touched = (string, range, action)
        // 设置高亮样式
        var temp: [NSAttributedString.Key: Any] = [:]
        action.highlights.forEach { temp.merge($0.attributes, uniquingKeysWith: { $1 }) }
        attributedText = string.value.reset(range: range) { (attributes) in
            attributes.merge(temp, uniquingKeysWith: { $1 })
        }
    }

其中这里会发生崩溃:'NSRangeException', reason: 'NSMutableRLEArray objectAtIndex:effectiveRange:: Out of bounds'

        attributedText = string.value.reset(range: range) { (attributes) in
            attributes.merge(temp, uniquingKeysWith: { $1 })
        }

image

image

对于textview的点击相应的说明

在textview相应对应的点击 长按的事件的时候,必须设置textView.isEditable = false
textView.isSelectable = false,才能相应对应的点击事件,可以在readme事件中标注一下

多语言怎么支持呢

目前本文有点击事件,因为内容是拼接的,导致多语言识别起来困难。

iOS 15 ,Xcode 13

'AttributedString' is ambiguous for type lookup in this context

自定义构造 Attribute

我的需求是希望能自定义构造字符串差值过程中使用自定义的 attributes

// 构造自定义的 Attribute
extension ASAttributedString.Attribute {
    
    public static func fontStyle(_ value:  UIFont.TextStyle) -> Self {
        let font = UIFont.preferredFont(forTextStyle: value)
        
        let style = NSMutableParagraphStyle()
        style.minimumLineHeight = 20
        style.maximumLineHeight = 20
        
        let baselineOffset = (20 - font.lineHeight) * 0.5

        return .init(attributes: [
            .font: font,
            .paragraphStyle: style,
            .baselineOffset: baselineOffset
        ])
    }
}
// 用法如下:
 """
        \("fontStyle: .sbuheadline", .fontStyle(.subheadline), .background(UIColor.systemBackground), .foreground(UIColor.label))
"""

但是目前由于 Attribute 的 attributes 是 internal 的,导致无法构造自定义的 attributes.

iOS11以下崩溃

在iOS11以下的系统,kvo的监听会崩溃,An instance 0x7f9395d49750 of class SXLabel was deallocated while key value observers were still registered with it. Current observation info:
使用了textView的attachment
attributed.text = .init("\(.view(customLabel, .original(.center))) \(string)", .paragraph(.lineSpacing(7)))

XCode14打包在iOS12设备上崩溃问题

XCode14打的Release包,在触发UILabel的点击事件方法时会造成崩溃.

Thread 0 Crashed:
0   libswiftCore.dylib                  0x000000020ae4ab00 specialized _fatalErrorMessage(_:_:file:line:flags:) + [ : 296] 
1   libswiftCore.dylib                  0x000000020ae4ab00 specialized _fatalErrorMessage(_:_:file:line:flags:) + [ : 296] 
2   libswiftCore.dylib                  0x000000020acc1750 $ss17__CocoaDictionaryV8IteratorC4nextyXl3key_yXl5valuetSgyF + [ : 156] 
3   libswiftFoundation.dylib            0x000000020b063a80 $ss30_dictionaryDownCastConditionalySDyq0_q1_GSgSDyxq_GSHRzSHR0_r2_lFSDySo8NSObjectCyXlGAcFRszyXlRs_SHR0_r2_lIetgo_Tp5 + [ : 916] 
4   libswiftFoundation.dylib            0x000000020b0636ac static Dictionary._conditionallyBridgeFromObjectiveC(_:result:) + [ : 376] 
5   libswiftCore.dylib                  0x000000020aecb480 _dynamicCastClassToValueViaObjCBridgeable(swift::OpaqueValue*, swift::OpaqueValue*, swift::TargetMetadata<swift::InProcess> const*, swift::TargetMetadata<swift::InProcess> const*, (anonymous namespace)::_ObjectiveCBridgeableWitnessTable const*, swift::DynamicCastFlags) + [ : 456] 
6   libswiftCore.dylib                  0x000000020aecb6ac _dynamicCastFromExistential(swift::OpaqueValue*, swift::OpaqueValue*, swift::TargetExistentialTypeMetadata<swift::InProcess> const*, swift::TargetMetadata<swift::InProcess> const*, swift::DynamicCastFlags) + [ : 312] 
7   AttributedString                    0x0000000103b981c8 specialized AssociatedWrapper<A>.get(_:) + [ : 136] 
8   AttributedString                    0x0000000103b99288 ASAttributedStringWrapper<A>.setupGestureRecognizers() + [UILabelExtension.swift : 215] 
9   AttributedString                    0x0000000103b9894c ASAttributedStringWrapper<A>.setter.setter + [<compiler-generated> : 0] 

图片不展示

let img = UIImage.init(named: "icon_apple")! textLabel.attributed.text = """ \(.image(img)) """
开发工具为AppCode

检查样式add这个方法没有

var string: AttributedString = """
我的名字叫李响,我的手机号码是18611401994,我的电子邮件地址是[email protected],现在是2020/06/28 20:30。我的GitHub主页是https://github.com/lixiang1994。欢迎来Star! ("点击联系我", .action(clicked))
"""
string.add(attributes: [.foreground(#colorLiteral(red: 0.9529411793, green: 0.6862745285, blue: 0.1333333403, alpha: 1)), .font(.systemFont(ofSize: 20, weight: .medium))], checkings: [.phoneNumber])
string.add(attributes: [.foreground(#colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1)), .font(.systemFont(ofSize: 20, weight: .medium))], checkings: [.link])
string.add(attributes: [.foreground(#colorLiteral(red: 0.1764705926, green: 0.01176470611, blue: 0.5607843399, alpha: 1)), .font(.systemFont(ofSize: 20, weight: .medium))], checkings: [.date])
string.add(attributes: [.font(.systemFont(ofSize: 20, weight: .medium))], checkings: [.action])
container.label.attributed.text = string

点击事件识别不灵敏

        let d: AttributedString = .init("《隐私协议》", .font(.font12), .foreground(.red_F84417), .action(.click, {[weak self] in
            
        }))

响应不及时,按一秒以上才能响应

自定义视图控件后,无法设置段落样式吗?

textView.attributed.text = .init(
"""
(.view(customLabel, .original(.center)))(" 两市涨跌不一,医疗器械集体走强两市涨跌不一,医疗器械集体走强", .font(.systemFont(ofSize: 17, weight: .medium)), .paragraph(.lineSpacing(5)))
("美国三大股指涨跌不一,道指下1.39%,纳指上涨0.53%,标普500下跌0.56%...", .paragraph(.lineSpacing(5)))
""",
.font(.systemFont(ofSize: 15))
)
设置自定义视图控件之后,段落样式无法生效,如果不添加自定义视图控件则能正常显示

TextView 图片偏移显示不全

给 textView 设置 contentInset 后,比如 UIEdgeInsets(top: 16, left: 20, bottom: 16, right: 20) ,图片会比预期的更靠右,如果是一个带圆角的图片就能很明显的看得出来,右边的圆角没有显示完全。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.