Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use UIPreviewParameters to specify a range of text as a highlighted preview for UIContextMenuInteraction without hiding the rest of the view?

Update for iOS 13.4 (March, 2020):

This also happens with UIPointerInteraction when hovering over the links.


I have a view that displays rich text and shows the iOS 13 context menu when the user long presses on a link. I want to be able to highlight just the link rather than the whole view when the user begins to long press.

To do this I provide a UITargetedPreview object that contains UIPreviewParameters with the CGRects of each line to be highlighted to the UIContextMenuInteractionDelegate of the view. This correctly highlights the link, but has the unwanted side effect of also hiding the rest of the view.

This image demonstrates the problem:

enter image description here

Notice that, while the link is highlighted correctly, the remainder of the view flashes in and out as the link is long pressed and then released.

Compare this to the behaviour in Apple's own Notes.app:

enter image description here

Notice that the rest of the view does not disappear when a link is long pressed. This also works as expected in Apple's other apps, too (e.g. Safari).


I provide UITargetedPreviews to the interaction delegate in the following way:

func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForHighlightingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
    guard let range = configuration.identifier as? NSRange else { return nil }        
    let lineRects: [NSValue] = // calculate appropriate rects for the range of text
    let parameters = UIPreviewParameters(textLineRects: lineRects)
    return UITargetedPreview(view: /* the rich text view */, parameters: parameters)
}

func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForDismissingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
    guard let range = configuration.identifier as? NSRange else { return nil }        
    let lineRects: [NSValue] = // calculate appropriate rects for the range of text
    let parameters = UIPreviewParameters(textLineRects: lineRects)
    return UITargetedPreview(view: /* the rich text view */, parameters: parameters)
}

I can't find anything in what documentation there is for UITargetedPreview and UIPreviewParameters, so does anyone know how this can be done?

like image 261
halleygen Avatar asked Jan 26 '20 09:01

halleygen


1 Answers

OK, I finally found out how to do it thanks to an implementation in WebKit. What I was doing wrong was not providing the targeted preview a UIPreviewTarget.

To highlight only a portion of a view you need to:

  1. Provide UIPreviewParameters stating the portion of the view to show in the preview, and set the background colour to the same as the view you want to preview

  2. Provide a UIPreviewTarget that establishes:

    • the whole view as the container of the animation
    • the centre of the portion being shown as the centre of the animation
  3. Create a snapshot of the portion of the view you want to show and designate it as view of the UITargetedPreview.

In code this looks like:

let view: UIView = // the view with the context menu interaction
let highlightedRect: CGRect = // the rect of the highlighted portion to show

// 1        
let previewParameters = UIPreviewParameters()
previewParameters.visiblePath = // path of highlighted portion of view to show, remember you can use UIPreviewParameters.init(textLineRects:) for text
previewParameters.backgroundColor = view.backgroundColor

// 2: Notice that we're passing the whole view in here
let previewTarget = UIPreviewTarget(container: view, center: CGPoint(x: highlightedRect.midX, y: highlightedRect.midY))

// 3: Notice that we're passing the snapshot view in here - not the whole view
let snapshot = view.resizableSnapshotView(from: highlightedRect, afterScreenUpdates: true, withCapInsets: .zero)!
return UITargetedPreview(view: snapshot, parameters: previewParameters, target: previewTarget)

like image 81
halleygen Avatar answered Nov 11 '22 12:11

halleygen