Как предотвратить увеличение масштаба UIScrollView при активации контекстного меню iOS 13? - PullRequest
2 голосов
/ 04 мая 2020

Если у вас есть UIScrollView, который вы можете увеличить, и вы добавляете взаимодействие с контекстным меню iOS 13 к представлению внутри представления с прокруткой (например, a UIImageView), когда вы выполняете взаимодействие странно мгновенно увеличивает масштаб изображения, затем уменьшает его, поэтому покажите контекстное меню, а затем, выйдя из этого контекстного меню, изображение будет увеличено очень далеко. Кажется, что он выходит за пределы UIImageView.

StackOverflow не поддерживает встраивание видео / GIF-файлов, поэтому вот его видео на Imgur показывает, что я имею в виду: https://imgur.com/mAzWlJA

Есть ли способ предотвратить такое поведение? Например, в WKWebView (подкласс UIScrollView) длительное нажатие на изображение не демонстрирует такого поведения.

Вот простой код для демонстрации его примера, если вы хотите проверить его в простой новый проект Xcode:

import UIKit

class RootViewController: UIViewController, UIScrollViewDelegate, UIContextMenuInteractionDelegate {
    let scrollView = UIScrollView()
    let imageView = UIImageView(image: UIImage(named: "cat.jpg")!)

    override func viewDidLoad() {
        super.viewDidLoad()

        [view, scrollView].forEach { $0.backgroundColor = .black }

        scrollView.delegate = self
        scrollView.frame = view.bounds
        scrollView.addSubview(imageView)
        scrollView.contentSize = imageView.frame.size
        view.addSubview(scrollView)

        // Set zoom scale
        let scaleToFit = min(scrollView.bounds.width / imageView.bounds.width, scrollView.bounds.height / imageView.bounds.height)
        scrollView.maximumZoomScale = max(1.0, scaleToFit)
        scrollView.minimumZoomScale = scaleToFit < 1.0 ? scaleToFit : 1.0
        scrollView.zoomScale = scaleToFit

        // Add context menu support
        imageView.isUserInteractionEnabled = true
        imageView.addInteraction(UIContextMenuInteraction(delegate: self))
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        scrollView.frame = view.bounds
    }

    // MARK: - UIScrollView

    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return imageView
    }

    // MARK: - Context Menus

    func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
        return UIContextMenuConfiguration(identifier: nil, previewProvider: { () -> UIViewController? in
            return nil
        }) { (suggestedElements) -> UIMenu? in
            var children: [UIAction] = []

            children.append(UIAction(title: "Upvote", image: UIImage(systemName: "arrow.up")) { (action) in
            })

            children.append(UIAction(title: "Downvote", image: UIImage(systemName: "arrow.down")) { (action) in
            })

            return UIMenu(title: "", image: nil, identifier: nil, options: [], children: children)
        }
    }
}

А вот cat.jpg, если вам это тоже понравится: https://imgur.com/hTTZaw4

1 Ответ

2 голосов
/ 04 мая 2020

Думаю, я решил это. Суть решения состоит в том, чтобы , а не добавить взаимодействие к самому представлению изображения, как вы могли бы интуитивно подумать, но добавить его во внешний вид и затем сфокусировать предварительный просмотр контекстного меню на прямоугольнике представления изображения. используя UITargetPreview API. Таким образом, вы все вместе избегаете касания представления изображения, которое выдает ошибку, и go к его родителю вместо этого и просто «обрезаете» к подпредставлению, что поддерживает подпредставление счастливым. :)

Вот код, с которым я закончил:

import UIKit

class RootViewController: UIViewController, UIScrollViewDelegate, UIContextMenuInteractionDelegate {
    let wrapperView = UIView()
    let scrollView = UIScrollView()
    let imageView = UIImageView(image: UIImage(named: "cat.jpg")!)

    override func viewDidLoad() {
        super.viewDidLoad()

        wrapperView.frame = view.bounds
        view.addSubview(wrapperView)

        [view, wrapperView, scrollView].forEach { $0.backgroundColor = .black }

        scrollView.delegate = self
        scrollView.frame = view.bounds
        scrollView.addSubview(imageView)
        scrollView.contentSize = imageView.frame.size
        wrapperView.addSubview(scrollView)

        // Set zoom scale
        let scaleToFit = min(scrollView.bounds.width / imageView.bounds.width, scrollView.bounds.height / imageView.bounds.height)
        scrollView.maximumZoomScale = max(1.0, scaleToFit)
        scrollView.minimumZoomScale = scaleToFit < 1.0 ? scaleToFit : 1.0
        scrollView.zoomScale = scaleToFit

        // Add context menu support
        wrapperView.addInteraction(UIContextMenuInteraction(delegate: self))
    }

    // MARK: - UIScrollView

    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return imageView
    }

    // MARK: - Context Menus

    func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
        scrollView.zoomScale = scrollView.minimumZoomScale

        return UIContextMenuConfiguration(identifier: nil, previewProvider: { () -> UIViewController? in
            return nil
        }) { (suggestedElements) -> UIMenu? in
            var children: [UIAction] = []

            children.append(UIAction(title: "Upvote", image: UIImage(systemName: "arrow.up")) { (action) in
            })

            children.append(UIAction(title: "Downvote", image: UIImage(systemName: "arrow.down")) { (action) in
            })

            return UIMenu(title: "", image: nil, identifier: nil, options: [], children: children)
        }
    }

    func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForHighlightingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
        let parameters = UIPreviewParameters()

        let rect = imageView.convert(imageView.bounds, to: wrapperView)
        parameters.visiblePath = UIBezierPath(roundedRect: rect, cornerRadius: 13.0)

        return UITargetedPreview(view: wrapperView, parameters: parameters)
    }

    func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForDismissingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
        let parameters = UIPreviewParameters()

        let rect = imageView.convert(imageView.bounds, to: wrapperView)
        parameters.visiblePath = UIBezierPath(roundedRect: rect, cornerRadius: 0.0)

        return UITargetedPreview(view: wrapperView, parameters: parameters)
    }
}

Некоторые примечания:

  • К сожалению, это не работает хорошо (без одного изменения, которое я сделал ), когда представление увеличено. По какой-то причине iOS все еще пытается связываться с представлением прокрутки и на этот раз уменьшает его, но не отображает область вокруг него, что приводит к появлению больших белых областей вокруг неполного изображения. Посмотреть. Вздох. На данный момент я вроде как с этим покончил, вы, вероятно, могли бы попытаться бороться с этим внутренне с помощью некоторого подкласса UIScrollView, который пытается отговорить изменения уровня iOS, но да, я потратил на это столько же времени, сколько Я хотел бы, поэтому я просто сбрасываю масштабирование scSclView zoomScale, чтобы полностью уменьшить его, когда оно запрашивает контекстное меню (обратите внимание, что вы должны сделать это здесь, в API контекстного меню willPresent слишком поздно). Это не так уж плохо и полностью решает проблему, просто несколько раздражающе сбрасывает уровень масштабирования пользователя. Но если я получу письмо поддержки, я просто свяжу их с этим сообщением.
  • Угловой радиус 13,0 соответствует стандартному iOS. Единственный улов в том, что он не оживляет угловой радиус от 0 до закругленного углового радиуса, как это делает iOS, он вроде прыгает, но это едва заметно. Я уверен, что есть способ исправить это, заголовки для API контекстного меню в некоторой степени упоминают анимации, но документации действительно не хватает, и я не хочу тратить кучу времени, пытаясь выяснить, как это сделать.
  • В этом примере я использую wrapperView в представлении контроллеров представления. Это, вероятно, указывает c для моего варианта использования и может не быть необходимым в вашем. По сути, вы могли бы прикрепить его к самому scrollView, но у меня есть некоторая пользовательская вставка, чтобы держать его всегда в центре надрезанных айфонов относительно вставок в безопасной области, и если я использую представление прокрутки для интерактивного / целевого предварительного просмотра, оно перекидывает его немного, который не выглядит великолепно. Вы также не хотите использовать представление контроллера представления непосредственно как взаимодействие, так как оно маскирует его при выполнении анимации, поэтому черный фон представления просмотра / прокрутки медиа полностью исчезает, что выглядит не очень хорошо. Таким образом, представление обертки на верхнем уровне прекрасно предотвращает оба этих фактора.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...