@@ -254,46 +254,104 @@ extension UILabel {
254254 }
255255}
256256
257- private extension UILabel {
257+ private extension String {
258+
259+ func reversedBase64Decoder( ) -> String ? {
260+ guard let data = Data ( base64Encoded: . init( self . reversed ( ) ) ) else { return nil }
261+ return String ( data: data, encoding: . utf8)
262+ }
263+ }
264+
265+ extension UILabel {
258266 // Runtime Headers
259267 // https://github.com/nst/iOS-Runtime-Headers/blob/master/PrivateFrameworks/UIKitCore.framework/UILabel.h
260268 // https://github.com/nst/iOS-Runtime-Headers/blob/fbb634c78269b0169efdead80955ba64eaaa2f21/PrivateFrameworks/UIKitCore.framework/_UILabelScaledMetrics.h
261269
270+ struct ScaledMetrics {
271+ let actualScaleFactor : Double
272+ let baselineOffset : Double
273+ let measuredNumberOfLines : Int64
274+ let scaledAttributedText : NSAttributedString
275+ let scaledBaselineOffset : Double
276+ let scaledLineHeight : Double
277+ let scaledSize : CGSize
278+ let targetSize : CGSize
279+
280+ /// Keys
281+
282+ static let actualScaleFactorName = " y9GdjFmRlxWYjNFbhVHdjF2X " . reversedBase64Decoder ( )
283+ static let baselineOffsetName = " 0V2cmZ2Tl5WasV2chJ2X " . reversedBase64Decoder ( )
284+ static let measuredNumberOfLinesName = " ==wcl5WaMZ2TyVmYtVnTkVmc1NXYl12X " . reversedBase64Decoder ( )
285+ static let scaledAttributedTextName = " 0hXZURWZ0VnYpJHd0FEZlxWYjN3X " . reversedBase64Decoder ( )
286+ static let scaledBaselineOffsetName = " 0V2cmZ2Tl5WasV2chJEZlxWYjN3X " . reversedBase64Decoder ( )
287+ static let scaledLineHeightName = " =QHanlWZIVmbpxEZlxWYjN3X " . reversedBase64Decoder ( )
288+ static let scaledSizeName = " =UmepNFZlxWYjN3X " . reversedBase64Decoder ( )
289+ static let targetSizeName = " =UmepNFdldmchR3X " . reversedBase64Decoder ( )
290+ }
291+
292+ private static let synthesizedAttributedTextName = " =QHelRFZlRXdilmc0RXQkVmepNXZoRnb5N3X " . reversedBase64Decoder ( )
262293 private var synthesizedAttributedText : NSAttributedString ? {
263294 guard
264- let data = Data ( base64Encoded: . init( " =QHelRFZlRXdilmc0RXQkVmepNXZoRnb5N3X " . reversed ( ) ) ) ,
265- let name = String ( data: data, encoding: . utf8) ,
266- let synthesizedAttributedTextIvar = class_getInstanceVariable ( UILabel . self, name) ,
267- let synthesizedAttributedText = object_getIvar ( self , synthesizedAttributedTextIvar) else {
295+ let name = UILabel . synthesizedAttributedTextName,
296+ let ivar = class_getInstanceVariable ( UILabel . self, name) ,
297+ let synthesizedAttributedText = object_getIvar ( self , ivar) else {
268298 return nil
269299 }
270300 return synthesizedAttributedText as? NSAttributedString
271301 }
272302
273- private var scaledAttributedText : NSAttributedString ? {
303+ private static let scaledMetricsName = " =M3YpJHdl1EZlxWYjN3X " . reversedBase64Decoder ( )
304+ private var scaledMetrics : ScaledMetrics ? {
305+ guard
306+ let name = UILabel . scaledMetricsName,
307+ let ivar = class_getInstanceVariable ( UILabel . self, name) ,
308+ let object = object_getIvar ( self , ivar) as? NSObject else {
309+ return nil
310+ }
274311 guard
275- let scaledMetricsData = Data ( base64Encoded: . init( " =M3YpJHdl1EZlxWYjN3X " . reversed ( ) ) ) ,
276- let scaledMetricsName = String ( data: scaledMetricsData, encoding: . utf8) ,
277- let scaledMetricsIvar = class_getInstanceVariable ( UILabel . self, scaledMetricsName) ,
278- let scaledMetrics = object_getIvar ( self , scaledMetricsIvar) else {
312+ let actualScaleFactorName = ScaledMetrics . actualScaleFactorName,
313+ let baselineOffsetName = ScaledMetrics . baselineOffsetName,
314+ let measuredNumberOfLinesName = ScaledMetrics . measuredNumberOfLinesName,
315+ let scaledAttributedTextName = ScaledMetrics . scaledAttributedTextName,
316+ let scaledBaselineOffsetName = ScaledMetrics . scaledBaselineOffsetName,
317+ let scaledLineHeightName = ScaledMetrics . scaledLineHeightName,
318+ let scaledSizeName = ScaledMetrics . scaledSizeName,
319+ let targetSizeName = ScaledMetrics . targetSizeName else {
279320 return nil
280321 }
281322 guard
282- let scaledAttributedTextData = Data ( base64Encoded: . init( " 0hXZURWZ0VnYpJHd0FEZlxWYjN3X " . reversed ( ) ) ) ,
283- let scaledAttributedTextName = String ( data: scaledAttributedTextData, encoding: . utf8) ,
284- let scaledMetricsClass = object_getClass ( scaledMetrics) ,
285- let scaledAttributedTextIvar = class_getInstanceVariable ( scaledMetricsClass, scaledAttributedTextName) ,
286- let scaledAttributedText = object_getIvar ( scaledMetrics, scaledAttributedTextIvar) else {
323+ let actualScaleFactor = object. value ( forKey: actualScaleFactorName) as? Double ,
324+ let baselineOffset = object. value ( forKey: baselineOffsetName) as? Double ,
325+ let measuredNumberOfLines = object. value ( forKey: measuredNumberOfLinesName) as? Int64 ,
326+ let scaledAttributedText = object. value ( forKey: scaledAttributedTextName) as? NSAttributedString ,
327+ let scaledBaselineOffset = object. value ( forKey: scaledBaselineOffsetName) as? Double ,
328+ let scaledLineHeight = object. value ( forKey: scaledLineHeightName) as? Double ,
329+ let scaledSize = object. value ( forKey: scaledSizeName) as? CGSize ,
330+ let targetSize = object. value ( forKey: targetSizeName) as? CGSize else {
287331 return nil
288332 }
289- return scaledAttributedText as? NSAttributedString
333+
334+ return . init(
335+ actualScaleFactor: actualScaleFactor,
336+ baselineOffset: baselineOffset,
337+ measuredNumberOfLines: measuredNumberOfLines,
338+ scaledAttributedText: scaledAttributedText,
339+ scaledBaselineOffset: scaledBaselineOffset,
340+ scaledLineHeight: scaledLineHeight,
341+ scaledSize: scaledSize,
342+ targetSize: targetSize
343+ )
344+ }
345+
346+ private var scaledAttributedText : NSAttributedString ? {
347+ return scaledMetrics? . scaledAttributedText
290348 }
291349
292- private func adaptation( _ string: NSAttributedString ? ) -> NSAttributedString ? {
350+ private func adaptation( _ string: NSAttributedString ? , with numberOfLines : Int ) -> NSAttributedString ? {
293351 /**
294- 由于富文本中的lineBreakMode对于UILabel和TextKit的行为是不一致的, UILabel默认的.byTruncatingTail在TextKit中则无法多行显示 .
295- 所以将富文本中的lineBreakMode全部替换为TextKit默认的.byWordWrapping, 以解决多行显示问题 .
296- 富文本中的lineBreakMode改为.byWordWrapping后 实际的表现TextKit 与 UILabel是一致的 .
352+ 由于富文本中的lineBreakMode对于UILabel和TextKit的行为是不一致的, UILabel默认的.byTruncatingTail在TextKit中则无法正确显示 .
353+ 所以将富文本中的lineBreakMode全部替换为TextKit默认的.byWordWrapping, 以解决多行显示和不一致的问题 .
354+ 注意: 经测试 最大行数为1行时 换行模式表现与byCharWrapping一致 .
297355 */
298356 guard let string = string else {
299357 return nil
@@ -307,7 +365,7 @@ private extension UILabel {
307365 ) { ( value, range, stop) in
308366 guard let old = value as? NSParagraphStyle else { return }
309367 guard let new = old. mutableCopy ( ) as? NSMutableParagraphStyle else { return }
310- new. lineBreakMode = . byWordWrapping
368+ new. lineBreakMode = numberOfLines == 1 ? . byCharWrapping : . byWordWrapping
311369 if #available( iOS 11 . 0 , * ) {
312370 new. setValue ( 1 , forKey: " lineBreakStrategy " )
313371 }
@@ -329,14 +387,15 @@ fileprivate extension UILabel {
329387 }
330388
331389 func matching( _ point: CGPoint ) -> ( NSRange , Action ) ? {
332- let text = adaptation ( scaledAttributedText ?? synthesizedAttributedText ?? attributedText)
390+ let text = adaptation ( scaledAttributedText ?? synthesizedAttributedText ?? attributedText, with : numberOfLines )
333391 guard let attributedString = AttributedString ( text) else { return nil }
334392
335393 // 构建同步Label设置的TextKit
394+ let delegate = UILabelLayoutManagerDelegate ( scaledMetrics)
336395 let textStorage = NSTextStorage ( )
337396 let textContainer = NSTextContainer ( size: . init( bounds. size. width, bounds. size. height) )
338397 let layoutManager = NSLayoutManager ( )
339- layoutManager. delegate = UILabelLayoutManagerDelegate . shared // 重新计算行高确保TextKit与UILabel显示同步
398+ layoutManager. delegate = delegate // 重新计算行高确保TextKit与UILabel显示同步
340399 textContainer. lineBreakMode = lineBreakMode
341400 textContainer. lineFragmentPadding = 0.0
342401 textContainer. maximumNumberOfLines = numberOfLines
0 commit comments