Skip to content

Commit 7b680a2

Browse files
committed
优化Label缩小的同步处理
1 parent ed5c861 commit 7b680a2

2 files changed

Lines changed: 99 additions & 24 deletions

File tree

Sources/Extension/UIKit/UILabel/UILabelExtension.swift

Lines changed: 82 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Sources/Extension/UIKit/UILabel/UILabelLayoutManagerDelegate.swift

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@ import UIKit
1717

1818
class UILabelLayoutManagerDelegate: NSObject, NSLayoutManagerDelegate {
1919

20-
static let shared = UILabelLayoutManagerDelegate()
20+
// 当Label发生Scalet时 最大行数为1时 基线偏移不会改变
21+
let scaledMetrics: UILabel.ScaledMetrics?
22+
23+
init(_ scaledMetrics: UILabel.ScaledMetrics?) {
24+
self.scaledMetrics = scaledMetrics
25+
super.init()
26+
}
2127

2228
func layoutManager(_ layoutManager: NSLayoutManager,
2329
shouldSetLineFragmentRect lineFragmentRect: UnsafeMutablePointer<CGRect>,
@@ -62,6 +68,16 @@ class UILabelLayoutManagerDelegate: NSObject, NSLayoutManagerDelegate {
6268
rect.size.height = temp + maximum.lineSpacing + paragraphSpacing + paragraphSpacingBefore
6369
used.size.height = textContainer.maximumNumberOfLines == 1 ? temp : rect.height
6470

71+
// 当Label发生Scaled时 最大行数为1时 基线偏移不会改变
72+
if let scaledMetrics = scaledMetrics, textContainer.maximumNumberOfLines == 1 {
73+
var baseline = baselineOffset.pointee
74+
let cha = CGFloat(scaledMetrics.baselineOffset - scaledMetrics.baselineOffset * scaledMetrics.actualScaleFactor)
75+
baseline += cha
76+
baselineOffset.pointee = baseline
77+
rect.size.height += cha
78+
used.size.height += cha
79+
}
80+
6581
// 重新赋值最终结果
6682
lineFragmentRect.pointee = rect
6783
lineFragmentUsedRect.pointee = used

0 commit comments

Comments
 (0)