forked from shadowfacts/Tusker
TextKit 2, baby
This commit is contained in:
parent
f9c3ad5921
commit
cc10a13785
|
@ -195,16 +195,39 @@ class ContentTextView: LinkTextView, BaseEmojiLabel {
|
|||
|
||||
func getLinkAtPoint(_ point: CGPoint) -> (URL, NSRange)? {
|
||||
let locationInTextContainer = CGPoint(x: point.x - textContainerInset.left, y: point.y - textContainerInset.top)
|
||||
if #available(iOS 16.0, *),
|
||||
let textLayoutManager {
|
||||
guard let fragment = textLayoutManager.textLayoutFragment(for: point),
|
||||
let lineFragment = fragment.textLineFragments.first(where: { lineFragment in
|
||||
lineFragment.typographicBounds.offsetBy(dx: fragment.layoutFragmentFrame.minX, dy: fragment.layoutFragmentFrame.minY).contains(point)
|
||||
}) 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)
|
||||
if characterIndex < textStorage.length && partialFraction < 1 {
|
||||
guard characterIndex < textStorage.length && partialFraction < 1 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var range = NSRange()
|
||||
if let link = textStorage.attribute(.link, at: characterIndex, longestEffectiveRange: &range, in: textStorage.fullRange) as? URL {
|
||||
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) {
|
||||
if let mention = getMention(for: url, text: text) {
|
||||
|
@ -297,9 +320,26 @@ extension ContentTextView: UIContextMenuInteractionDelegate {
|
|||
|
||||
// Determine the line rects that the link takes up in the coordinate space of this view.
|
||||
var rects = [CGRect]()
|
||||
if #available(iOS 16.0, *),
|
||||
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.
|
||||
// If a snapshot view cannot be created, we bail and use the system-provided preview.
|
||||
|
|
Loading…
Reference in New Issue