2

如果您有一个UIScrollView可以放大的,并且您将 iOS 13 上下文菜单交互添加到滚动视图内的视图(例如: a UIImageView),当您执行交互时,它会奇怪地暂时放大图像,然后将其缩小显示上下文菜单,然后在退出此上下文菜单时,它会使图像放大得很远。它似乎超出了 UIImageView 的范围。

StackOverflow 似乎不支持嵌入视频/GIF,所以这是 Imgur 上的一段视频,显示了我的意思:https ://imgur.com/mAzWlJA

有没有办法防止这种行为?例如,在WKWebViewUIScrollView子类)中,长按图像不会表现出这种行为。

如果您想在一个简单的新 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

4

1 回答 1

2

想我解决了。该解决方案的要点是不要UITargetPreview像您直观地认为的那样将交互添加到图像视图本身,而是将其添加到外部视图,然后使用API将上下文菜单预览聚焦到图像视图的矩形上。这样你就可以一起避免接触出错的图像视图,而是转到它的父视图,只是“裁剪”到子视图,这让子视图保持快乐。:)

这是我最终得到的代码:

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 级别更改的子类来对抗它,但是是的,我已经花了尽可能多的时间来解决这个问题,所以我只是将scrollView的zoomScale重置为在它要求上下文菜单时完全缩小(注意你必须在这里做,在willPresent上下文菜单 API 为时已晚)。它并没有那么糟糕并且完全解决了它,只是有点烦人地重置了用户的缩放级别。但是,如果我收到一封支持电子邮件,我会将它们链接到这篇文章。
  • 角半径 13.0 与 iOS 默认值相匹配。唯一要注意的是,这不会像 iOS 那样将圆角半径从 0 动画到圆角半径,它有点跳跃,但几乎不引人注意。我确信有办法解决这个问题,上下文菜单 API 的标题以某种方式提到了动画,但是文档真的很缺乏,我不想花大量时间试图弄清楚如何。
  • 在此示例中,我wrapperView在视图控制器视图中使用。这可能特定于我的用例,在您的用例中可能不是必需的。基本上你可以将它附加到它scrollView本身,但我的有一些自定义插入,以保持它在安全区域插入方面始终居中在带缺口的 iPhone 中,如果我使用滚动视图进行交互/目标预览,它会稍微跳跃一下,这看起来不太好。您也不想直接使用视图控制器的视图作为交互,因为它在执行动画时将其屏蔽,因此媒体查看器/滚动视图的黑色背景完全消失,看起来不太好。所以顶层的包装视图很好地防止了这两种情况。
于 2020-05-04T17:55:14.433 回答