Draw strikethrough and underline manually on iOS 18

This commit is contained in:
Isaac 2024-09-27 20:26:44 +08:00
parent e22b2b6b5a
commit d70a0cf0e0

View File

@ -112,13 +112,14 @@ private final class InteractiveTextNodeLine {
let isTruncated: Bool let isTruncated: Bool
let isRTL: Bool let isRTL: Bool
var strikethroughs: [InteractiveTextNodeStrikethrough] var strikethroughs: [InteractiveTextNodeStrikethrough]
var underlines: [InteractiveTextNodeStrikethrough]
var spoilers: [InteractiveTextNodeSpoiler] var spoilers: [InteractiveTextNodeSpoiler]
var spoilerWords: [InteractiveTextNodeSpoiler] var spoilerWords: [InteractiveTextNodeSpoiler]
var embeddedItems: [InteractiveTextNodeEmbeddedItem] var embeddedItems: [InteractiveTextNodeEmbeddedItem]
var attachments: [InteractiveTextNodeAttachment] var attachments: [InteractiveTextNodeAttachment]
let additionalTrailingLine: (CTLine, Double)? let additionalTrailingLine: (CTLine, Double)?
init(line: CTLine, constrainedWidth: CGFloat, frame: CGRect, intrinsicWidth: CGFloat, ascent: CGFloat, descent: CGFloat, range: NSRange?, isTruncated: Bool, isRTL: Bool, strikethroughs: [InteractiveTextNodeStrikethrough], spoilers: [InteractiveTextNodeSpoiler], spoilerWords: [InteractiveTextNodeSpoiler], embeddedItems: [InteractiveTextNodeEmbeddedItem], attachments: [InteractiveTextNodeAttachment], additionalTrailingLine: (CTLine, Double)?) { init(line: CTLine, constrainedWidth: CGFloat, frame: CGRect, intrinsicWidth: CGFloat, ascent: CGFloat, descent: CGFloat, range: NSRange?, isTruncated: Bool, isRTL: Bool, strikethroughs: [InteractiveTextNodeStrikethrough], underlines: [InteractiveTextNodeStrikethrough], spoilers: [InteractiveTextNodeSpoiler], spoilerWords: [InteractiveTextNodeSpoiler], embeddedItems: [InteractiveTextNodeEmbeddedItem], attachments: [InteractiveTextNodeAttachment], additionalTrailingLine: (CTLine, Double)?) {
self.line = line self.line = line
self.constrainedWidth = constrainedWidth self.constrainedWidth = constrainedWidth
self.frame = frame self.frame = frame
@ -129,6 +130,7 @@ private final class InteractiveTextNodeLine {
self.isTruncated = isTruncated self.isTruncated = isTruncated
self.isRTL = isRTL self.isRTL = isRTL
self.strikethroughs = strikethroughs self.strikethroughs = strikethroughs
self.underlines = underlines
self.spoilers = spoilers self.spoilers = spoilers
self.spoilerWords = spoilerWords self.spoilerWords = spoilerWords
self.embeddedItems = embeddedItems self.embeddedItems = embeddedItems
@ -1452,6 +1454,7 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
isTruncated: false, isTruncated: false,
isRTL: false, isRTL: false,
strikethroughs: [], strikethroughs: [],
underlines: [],
spoilers: [], spoilers: [],
spoilerWords: [], spoilerWords: [],
embeddedItems: [], embeddedItems: [],
@ -1493,6 +1496,7 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
isTruncated: false, isTruncated: false,
isRTL: isRTL && segment.blockQuote == nil, isRTL: isRTL && segment.blockQuote == nil,
strikethroughs: [], strikethroughs: [],
underlines: [],
spoilers: [], spoilers: [],
spoilerWords: [], spoilerWords: [],
embeddedItems: [], embeddedItems: [],
@ -1551,6 +1555,7 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
isTruncated: true, isTruncated: true,
isRTL: lastLine.isRTL, isRTL: lastLine.isRTL,
strikethroughs: [], strikethroughs: [],
underlines: [],
spoilers: [], spoilers: [],
spoilerWords: [], spoilerWords: [],
embeddedItems: [], embeddedItems: [],
@ -1605,6 +1610,7 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
isTruncated: true, isTruncated: true,
isRTL: lastLine.isRTL, isRTL: lastLine.isRTL,
strikethroughs: [], strikethroughs: [],
underlines: [],
spoilers: [], spoilers: [],
spoilerWords: [], spoilerWords: [],
embeddedItems: [], embeddedItems: [],
@ -1736,6 +1742,11 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
let upperX = ceil(CTLineGetOffsetForStringIndex(line.line, range.location + range.length, nil)) let upperX = ceil(CTLineGetOffsetForStringIndex(line.line, range.location + range.length, nil))
let x = lowerX < upperX ? lowerX : upperX let x = lowerX < upperX ? lowerX : upperX
line.strikethroughs.append(InteractiveTextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: line.frame.height))) line.strikethroughs.append(InteractiveTextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: line.frame.height)))
} else if let _ = attributes[NSAttributedString.Key.underlineStyle] {
let lowerX = floor(CTLineGetOffsetForStringIndex(line.line, range.location, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(line.line, range.location + range.length, nil))
let x = lowerX < upperX ? lowerX : upperX
line.underlines.append(InteractiveTextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: line.frame.height)))
} }
if let embeddedItem = (attributes[NSAttributedString.Key(rawValue: "TelegramEmbeddedItem")] as? AnyHashable ?? attributes[NSAttributedString.Key(rawValue: "Attribute__EmbeddedItem")] as? AnyHashable) { if let embeddedItem = (attributes[NSAttributedString.Key(rawValue: "TelegramEmbeddedItem")] as? AnyHashable ?? attributes[NSAttributedString.Key(rawValue: "Attribute__EmbeddedItem")] as? AnyHashable) {
@ -2090,6 +2101,14 @@ final class TextContentItem {
} }
} }
private let drawUnderlinesManually: Bool = {
if #available(iOS 18.0, *) {
return true
} else {
return false
}
}()
final class TextContentItemLayer: SimpleLayer { final class TextContentItemLayer: SimpleLayer {
final class Params { final class Params {
let item: TextContentItem let item: TextContentItem
@ -2322,6 +2341,46 @@ final class TextContentItemLayer: SimpleLayer {
} }
} }
if drawUnderlinesManually {
if !line.strikethroughs.isEmpty {
for strikethrough in line.strikethroughs {
guard let lineRange = line.range else {
continue
}
var textColor: UIColor?
params.item.attributedString?.enumerateAttributes(in: NSMakeRange(lineRange.location, lineRange.length), options: []) { attributes, range, _ in
if range == strikethrough.range, let color = attributes[NSAttributedString.Key.foregroundColor] as? UIColor {
textColor = color
}
}
if let textColor = textColor {
context.setFillColor(textColor.cgColor)
}
let frame = strikethrough.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)
context.fill(CGRect(x: frame.minX, y: frame.midY, width: frame.width, height: 1.0))
}
}
if !line.underlines.isEmpty {
for strikethrough in line.underlines {
guard let lineRange = line.range else {
continue
}
var textColor: UIColor?
params.item.attributedString?.enumerateAttributes(in: NSMakeRange(lineRange.location, lineRange.length), options: []) { attributes, range, _ in
if range == strikethrough.range, let color = attributes[NSAttributedString.Key.foregroundColor] as? UIColor {
textColor = color
}
}
if let textColor = textColor {
context.setFillColor(textColor.cgColor)
}
let frame = strikethrough.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)
context.fill(CGRect(x: frame.minX, y: frame.maxY - 2.0, width: frame.width, height: 1.0))
}
}
}
if let (additionalTrailingLine, _) = line.additionalTrailingLine { if let (additionalTrailingLine, _) = line.additionalTrailingLine {
context.textPosition = CGPoint(x: lineFrame.minX + line.intrinsicWidth, y: lineFrame.maxY - line.descent) context.textPosition = CGPoint(x: lineFrame.minX + line.intrinsicWidth, y: lineFrame.maxY - line.descent)