TextKit 2, baby

This commit is contained in:
Shadowfacts 2022-06-28 23:05:52 -07:00
parent f9c3ad5921
commit cc10a13785
1 changed files with 49 additions and 9 deletions

View File

@ -195,15 +195,38 @@ class ContentTextView: LinkTextView, BaseEmojiLabel {
func getLinkAtPoint(_ point: CGPoint) -> (URL, NSRange)? { func getLinkAtPoint(_ point: CGPoint) -> (URL, NSRange)? {
let locationInTextContainer = CGPoint(x: point.x - textContainerInset.left, y: point.y - textContainerInset.top) let locationInTextContainer = CGPoint(x: point.x - textContainerInset.left, y: point.y - textContainerInset.top)
var partialFraction: CGFloat = 0 if #available(iOS 16.0, *),
let characterIndex = layoutManager.characterIndex(for: locationInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: &partialFraction) let textLayoutManager {
if characterIndex < textStorage.length && partialFraction < 1 { guard let fragment = textLayoutManager.textLayoutFragment(for: point),
var range = NSRange() let lineFragment = fragment.textLineFragments.first(where: { lineFragment in
if let link = textStorage.attribute(.link, at: characterIndex, longestEffectiveRange: &range, in: textStorage.fullRange) as? URL { lineFragment.typographicBounds.offsetBy(dx: fragment.layoutFragmentFrame.minX, dy: fragment.layoutFragmentFrame.minY).contains(point)
return (link, range) }) else {
return nil
} }
let charIndex = lineFragment.characterIndex(for: point)
var range = NSRange()
guard let link = lineFragment.attributedString.attribute(.link, at: charIndex, longestEffectiveRange: &range, in: lineFragment.attributedString.fullRange) as? URL else {
return nil
}
// lineFragment.attributedString is the NSTextLayoutFragment's string, and so range is in its index space
// but we need to return a range in our whole attributedString's space, so convert it
let textLayoutFragmentStart = textLayoutManager.offset(from: textLayoutManager.documentRange.location, to: fragment.rangeInElement.location)
let rangeInSelf = NSRange(location: range.location + textLayoutFragmentStart, length: range.length)
return (link, rangeInSelf)
} else {
var partialFraction: CGFloat = 0
let characterIndex = layoutManager.characterIndex(for: locationInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: &partialFraction)
guard characterIndex < textStorage.length && partialFraction < 1 else {
return nil
}
var range = NSRange()
guard let link = textStorage.attribute(.link, at: characterIndex, longestEffectiveRange: &range, in: textStorage.fullRange) as? URL else {
return nil
}
return (link, range)
} }
return nil
} }
func handleLinkTapped(url: URL, text: String) { func handleLinkTapped(url: URL, text: String) {
@ -297,8 +320,25 @@ extension ContentTextView: UIContextMenuInteractionDelegate {
// Determine the line rects that the link takes up in the coordinate space of this view. // Determine the line rects that the link takes up in the coordinate space of this view.
var rects = [CGRect]() var rects = [CGRect]()
layoutManager.enumerateEnclosingRects(forGlyphRange: range, withinSelectedGlyphRange: NSRange(location: NSNotFound, length: 0), in: textContainer) { (rect, stop) in if #available(iOS 16.0, *),
rects.append(rect) let textLayoutManager,
let contentManager = textLayoutManager.textContentManager {
// convert from NSRange to NSTextRange
// i have no idea under what circumstances any of these calls could fail
guard let startLoc = contentManager.location(contentManager.documentRange.location, offsetBy: range.location),
let endLoc = contentManager.location(startLoc, offsetBy: range.length),
let textRange = NSTextRange(location: startLoc, end: endLoc) else {
return nil
}
// .standard because i have no idea what the difference is
textLayoutManager.enumerateTextSegments(in: textRange, type: .standard, options: []) { range, rect, float, textContainer in
rects.append(rect)
return true
}
} else {
layoutManager.enumerateEnclosingRects(forGlyphRange: range, withinSelectedGlyphRange: NSRange(location: NSNotFound, length: 0), in: textContainer) { (rect, stop) in
rects.append(rect)
}
} }
// Try to create a snapshot view of this view to disply as the preview. // Try to create a snapshot view of this view to disply as the preview.