@@ -254,6 +254,91 @@ extension UILabel {
254254 }
255255}
256256
257+ fileprivate extension UILabel {
258+
259+ @objc
260+ func attributedAction( _ sender: UIGestureRecognizer ) {
261+ guard isActionEnabled else { return }
262+ guard let action = touched? . 2 else { return }
263+ guard action. trigger. matching ( sender) else { return }
264+ // 点击 回调
265+ action. handle ? ( )
266+ }
267+
268+ func matching( _ point: CGPoint ) -> ( NSRange , Action ) ? {
269+ let text = adaptation ( scaledAttributedText ?? synthesizedAttributedText ?? attributedText, with: numberOfLines)
270+ guard let attributedString = AttributedString ( text) else { return nil }
271+
272+ // 构建同步Label的TextKit
273+ // 注: 目前还剩一个截断处理没解决 比如 "a\n\n\nb" numberOfLines=2
274+ let delegate = UILabelLayoutManagerDelegate ( scaledMetrics)
275+ let textStorage = NSTextStorage ( )
276+ let textContainer = NSTextContainer ( size: bounds. size)
277+ let layoutManager = NSLayoutManager ( )
278+ layoutManager. delegate = delegate // 重新计算行高确保TextKit与UILabel显示同步
279+ textContainer. lineBreakMode = lineBreakMode
280+ textContainer. lineFragmentPadding = 0.0
281+ textContainer. maximumNumberOfLines = numberOfLines
282+ layoutManager. usesFontLeading = false // UILabel没有使用FontLeading排版
283+ layoutManager. addTextContainer ( textContainer)
284+ textStorage. addLayoutManager ( layoutManager)
285+ textStorage. setAttributedString ( attributedString. value) // 放在最后添加富文本 TextKit的坑
286+
287+ // 确保布局
288+ layoutManager. ensureLayout ( for: textContainer)
289+
290+ // 获取文本所占高度
291+ let height = layoutManager. usedRect ( for: textContainer) . height
292+
293+ // 获取点击坐标 并排除各种偏移
294+ var point = point
295+ point. y -= ( bounds. height - height) / 2
296+
297+ // Debug
298+ subviews. filter ( { $0 is DebugView } ) . forEach ( { $0. removeFromSuperview ( ) } )
299+ let view = DebugView ( frame: . init( x: 0 , y: ( bounds. height - height) / 2 , width: bounds. width, height: height) )
300+ view. draw = { layoutManager. drawGlyphs ( forGlyphRange: . init( location: 0 , length: textStorage. length) , at: . zero) }
301+ addSubview ( view)
302+
303+ // 获取字形下标
304+ var fraction : CGFloat = 0
305+ let glyphIndex = layoutManager. glyphIndex ( for: point, in: textContainer, fractionOfDistanceThroughGlyph: & fraction)
306+ // 获取字符下标
307+ let index = layoutManager. characterIndexForGlyph ( at: glyphIndex)
308+ // 通过字形距离判断是否在字形范围内
309+ guard fraction > 0 , fraction < 1 else {
310+ return nil
311+ }
312+ // 获取点击的字符串范围和回调事件
313+ guard
314+ let range = actions. keys. first ( where: { $0. contains ( index) } ) ,
315+ let action = actions [ range] else {
316+ return nil
317+ }
318+ return ( range, action)
319+ }
320+ }
321+
322+ class DebugView : UIView {
323+
324+ var draw : ( ( ) -> Void ) ?
325+
326+ override init ( frame: CGRect ) {
327+ super. init ( frame: frame)
328+ isUserInteractionEnabled = false
329+ backgroundColor = #colorLiteral( red: 1 , green: 1 , blue: 1 , alpha: 0.2983732877 )
330+ }
331+
332+ required init ? ( coder: NSCoder ) {
333+ fatalError ( " init(coder:) has not been implemented " )
334+ }
335+
336+ override func draw( _ rect: CGRect ) {
337+ super. draw ( rect)
338+ self . draw ? ( )
339+ }
340+ }
341+
257342private extension String {
258343
259344 func reversedBase64Decoder( ) -> String ? {
@@ -349,9 +434,9 @@ extension UILabel {
349434
350435 private func adaptation( _ string: NSAttributedString ? , with numberOfLines: Int ) -> NSAttributedString ? {
351436 /**
352- 由于富文本中的lineBreakMode对于UILabel和TextKit的行为是不一致的, UILabel默认的.byTruncatingTail在TextKit中则无法正确显示.
353- 所以将富文本中的lineBreakMode全部替换为TextKit默认的.byWordWrapping, 以解决多行显示和不一致的问题.
354- 注意: 经测试 最大行数为1行时 换行模式表现与byCharWrapping一致.
437+ 由于富文本中的lineBreakMode对于UILabel和TextKit的行为是不一致的, UILabel默认的.byTruncatingTail在TextKit中则无法正确显示.
438+ 所以将富文本中的lineBreakMode全部替换为TextKit默认的.byWordWrapping, 以解决多行显示和不一致的问题.
439+ 注意: 经测试 最大行数为1行时 换行模式表现与byCharWrapping一致.
355440 */
356441 guard let string = string else {
357442 return nil
@@ -375,89 +460,5 @@ extension UILabel {
375460 }
376461}
377462
378- fileprivate extension UILabel {
379-
380- @objc
381- func attributedAction( _ sender: UIGestureRecognizer ) {
382- guard isActionEnabled else { return }
383- guard let action = touched? . 2 else { return }
384- guard action. trigger. matching ( sender) else { return }
385- // 点击 回调
386- action. handle ? ( )
387- }
388-
389- func matching( _ point: CGPoint ) -> ( NSRange , Action ) ? {
390- let text = adaptation ( scaledAttributedText ?? synthesizedAttributedText ?? attributedText, with: numberOfLines)
391- guard let attributedString = AttributedString ( text) else { return nil }
392-
393- // 构建同步Label设置的TextKit
394- let delegate = UILabelLayoutManagerDelegate ( scaledMetrics)
395- let textStorage = NSTextStorage ( )
396- let textContainer = NSTextContainer ( size: . init( bounds. size. width, bounds. size. height) )
397- let layoutManager = NSLayoutManager ( )
398- layoutManager. delegate = delegate // 重新计算行高确保TextKit与UILabel显示同步
399- textContainer. lineBreakMode = lineBreakMode
400- textContainer. lineFragmentPadding = 0.0
401- textContainer. maximumNumberOfLines = numberOfLines
402- layoutManager. usesFontLeading = false // UILabel没有使用FontLeading排版
403- layoutManager. addTextContainer ( textContainer)
404- textStorage. addLayoutManager ( layoutManager)
405- textStorage. setAttributedString ( attributedString. value) // 放在最后添加富文本 TextKit的坑
406-
407- // 确保布局
408- layoutManager. ensureLayout ( for: textContainer)
409-
410- // 获取文本所占高度
411- let height = layoutManager. usedRect ( for: textContainer) . height
412-
413- // 获取点击坐标 并排除各种偏移
414- var point = point
415- point. y -= ( bounds. height - height) / 2
416-
417- // Debug
418- subviews. filter ( { $0 is DebugView } ) . forEach ( { $0. removeFromSuperview ( ) } )
419- let view = DebugView ( frame: . init( x: 0 , y: ( bounds. height - height) / 2 , width: bounds. width, height: height) )
420- view. draw = { layoutManager. drawGlyphs ( forGlyphRange: . init( location: 0 , length: textStorage. length) , at: . zero) }
421- addSubview ( view)
422-
423- // 获取字形下标
424- var fraction : CGFloat = 0
425- let glyphIndex = layoutManager. glyphIndex ( for: point, in: textContainer, fractionOfDistanceThroughGlyph: & fraction)
426- // 获取字符下标
427- let index = layoutManager. characterIndexForGlyph ( at: glyphIndex)
428- // 通过字形距离判断是否在字形范围内
429- guard fraction > 0 , fraction < 1 else {
430- return nil
431- }
432- // 获取点击的字符串范围和回调事件
433- guard
434- let range = actions. keys. first ( where: { $0. contains ( index) } ) ,
435- let action = actions [ range] else {
436- return nil
437- }
438- return ( range, action)
439- }
440- }
441-
442- class DebugView : UIView {
443- var draw : ( ( ) -> Void ) ?
444-
445- override init ( frame: CGRect ) {
446- super. init ( frame: frame)
447- isUserInteractionEnabled = false
448- // backgroundColor = .clear
449- backgroundColor = #colorLiteral( red: 1 , green: 1 , blue: 1 , alpha: 0.2983732877 )
450- }
451-
452- required init ? ( coder: NSCoder ) {
453- fatalError ( " init(coder:) has not been implemented " )
454- }
455-
456- override func draw( _ rect: CGRect ) {
457- super. draw ( rect)
458- self . draw ? ( )
459- }
460- }
461-
462463#endif
463464#endif
0 commit comments