Как использовать UIPreviewParameters, чтобы указать диапазон текста в качестве выделенного предварительного просмотра для UIContextMenuInteraction, не скрывая остальную часть представления?

Обновление для iOS 13.4 (март 2020 г.):

Это также происходит с UIPointerInteraction при наведении курсора на ссылки.


У меня есть представление, которое отображает форматированный текст и показывает контекстное меню iOS 13, когда пользователь долго нажимает на ссылку. Я хочу иметь возможность выделять только ссылку, а не все представление, когда пользователь начинает долгое нажатие.

Для этого я предоставляю UITargetedPreview объект, содержащий UIPreviewParameters с CGRects каждой строки, которую нужно выделить UIContextMenuInteractionDelegateзрения. Это правильно выделяет ссылку, но имеет нежелательный побочный эффект, заключающийся в скрытии остальной части представления.

Это изображение демонстрирует проблему:

Обратите внимание на то, что, хотя ссылка выделена правильно, остальная часть представления мигает и гаснет, когда ссылка нажимается и затем отпускается.

Сравните это с поведением в Notes.app от Apple:

Обратите внимание, что остальная часть представления не исчезает при длительном нажатии на ссылку. Это также работает должным образом и в других приложениях Apple (например, Safari).


Я предоставляю UITargetedPreviews делегату взаимодействия следующим образом:

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)
}

Я ничего не могу найти в документации по UITargetedPreview а также UIPreviewParameters, так кто-нибудь знает, как это можно сделать?

1 ответ

Решение

Хорошо, я наконец узнал, как это сделать, благодаря реализации в WebKit. Что я делал неправильно, так это не предоставлял целевой предварительный просмотрUIPreviewTarget.

Чтобы выделить только часть представления, вам необходимо:

  1. Предоставьте UIPreviewParameters, указав, какая часть представления будет отображаться в предварительном просмотре, и установите цвет фона так же, как и у представления, которое вы хотите просмотреть.

  2. Предоставьте UIPreviewTarget, который устанавливает:

    • весь вид как контейнер анимации
    • центр части, показанной как центр анимации
  3. Создайте снимок той части представления, которую вы хотите показать, и назначьте ее представлением UITargetedPreview.

В коде это выглядит так:

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)

Ваше решение очень полезно. Я использовал hitTest, чтобы определить выделенное представление, и я рад результатам, поэтому просто хотел поделиться.

         - (UITargetedPreview *)contextMenuInteraction:(UIContextMenuInteraction *)interaction previewForHighlightingMenuWithConfiguration:(UIContextMenuConfiguration *)configuration {    
    UIView *view = interaction.view;
    CGPoint location = [interaction locationInView:view];
    // Detecting the hittable subview at the given location
    UIView *subView = [view hitTest:location withEvent:nil];
    if (subView) {
        CGRect highlightedRect = subView.frame;
        UIPreviewParameters *previewParameters = [UIPreviewParameters new];
        previewParameters.backgroundColor = subView.backgroundColor;
        previewParameters.visiblePath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, highlightedRect.size.width, highlightedRect.size.height)];

        return [[UITargetedPreview alloc] initWithView:subView parameters:previewParameters];
    }
    return nil;
}

Другие вопросы по тегам