What is the proper way to use UIPreviewParameters(textLineRects:) for context menu configurations on a text view? I'm trying to replicate the behavior in Safari, where long-pressing/3d-touching on a link shows the preview during the context menu animation only for the text of the link being pressed. I've gotten so far as calculating the text rects for a link itself, and passing them to the preview parameters, but the actual preview displayed doesn't look quite correct.
I'm doing the following things in the context menu interaction delegate callbacks of my UITextView subclass.
Firstly, in the contextMenuInteraction(_:configurationForMenuAtLocation:) method, I call NSLayoutManager.enumerateEnclosingRects(forGlyphRange:withinSelectedGlyphRange:in:using:) to get the actual rects occupied by the pressed link (these are in the coordinate space of the UITextView, if I understand correctly). I store these rects along with the range of the link in an instance of my custom subclass of UIContextMenuConfiguration I return from this method.
Then, in the contextMenuInteraction(_:previewForHighlightingMenuWithConfiguration:) method, I pull the aforementioned information out of the context menu configuration. I create a UIPreviewParameters object using the rects retrieved above. Then I create a UIPreviewTarget targeting self with the center as the calculated center of the text line rects. For the UITargetedPreview, I create a new UITextView whose frame is my text view's bounds. The new text view's attributed text is a copy of my text view's attributed text with the color of everything outside the link range set to clear.
This results in a context menu preview that seems to be the correct shape and size, but the duplicated text view used for the preview itself doesn't have the the text in the same position as the original one. In most instances, the text is correctly horizontally aligned, it's just positioned in the copy about 10pts below where it is in the original. Occasionally, though, the text position changes entirely (e.g., if the link starts at the very end of a line originally, it might start on the beginning of the next line in the copied text view).
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
		if let (link, range) = getLinkAtPoint(location) {
				var rects = [CGRect]()
				layoutManager.enumerateEnclosingRects(forGlyphRange: range, withinSelectedGlyphRange: NSRange(location: NSNotFound, length: 0), in: textContainer) { (rect, stop) in
						rects.append(rect)
				}
				let configuration = CustomContextMenuConfiguration(identifier: nil, previewProvider: /* omitted */, actionProvider: /* omitted */)
				configuration.textLineRects = rects
				configuration.linkRange = range
				return configuration
		} else {
				return nil
		}
}
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForHighlightingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
		if let configuration = configuration as? MyConfiguration,
				let rects = configuration.textLineRects,
				let linkRange = configuration.linkRange {
				
				var minX: CGFloat = .greatestFiniteMagnitude, maxX: CGFloat = .leastNonzeroMagnitude, minY: CGFloat = .greatestFiniteMagnitude, maxY: CGFloat = .leastNonzeroMagnitude
				for rect in rects {
						minX = min(rect.minX, minX)
						maxX = max(rect.maxX, maxX)
						minY = min(rect.minY, minY)
						maxY = max(rect.maxY, maxY)
				}
				let rectsCenter = CGPoint(x: (minX + maxX) / 2 - frame.minX, y: (minY + maxY) / 2)
				
				let copy = UITextView(frame: self.bounds)
				let mut = NSMutableAttributedString(attributedString: self.attributedText)
				mut.setAttributes([.foregroundColor: UIColor.clear, .font: self.defaultfont], range: NSRange(location: 0, length: linkRange.location))
				mut.setAttributes([.foregroundColor: UIColor.clear, .font: self.defaultFont], range: NSRange(location: linkRange.upperBound, length: mut.length - linkRange.upperBound))
				copy.attributedText = mut
				
				let parameters = UIPreviewParameters(textLineRects: rects as [NSValue])
				let target = UIPreviewTarget(container: self, center: rectsCenter)
				return UITargetedPreview(view: copy, parameters: parameters, target: target)
		} else {
				return nil
		}
}
Post
Replies
Boosts
Views
Activity
Is there any sample code available for the new column-style of UISplitViewController that was introduced in iOS 14?