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)? {
|
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)
|
||||||
|
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
|
var partialFraction: CGFloat = 0
|
||||||
let characterIndex = layoutManager.characterIndex(for: locationInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: &partialFraction)
|
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()
|
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 (link, range)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleLinkTapped(url: URL, text: String) {
|
func handleLinkTapped(url: URL, text: String) {
|
||||||
if let mention = getMention(for: url, text: text) {
|
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.
|
// Determine the line rects that the link takes up in the coordinate space of this view.
|
||||||
var rects = [CGRect]()
|
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
|
layoutManager.enumerateEnclosingRects(forGlyphRange: range, withinSelectedGlyphRange: NSRange(location: NSNotFound, length: 0), in: textContainer) { (rect, stop) in
|
||||||
rects.append(rect)
|
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.
|
||||||
// If a snapshot view cannot be created, we bail and use the system-provided preview.
|
// If a snapshot view cannot be created, we bail and use the system-provided preview.
|
||||||
|
|
Loading…
Reference in New Issue