如果你有一个 UIScrollView
你可以放大图像,而你在滚动视图内的视图中添加一个iOS 13上下文菜单交互(例如:a UIImageView
),当你执行该交互时,它会奇怪地瞬间放大图像,然后将其放大,以便显示上下文菜单,然后在退出该上下文菜单时,它将图像放大得很远。它似乎是要离开UIImageView的边界。
StackOverflow似乎不支持嵌入视频GIF,所以这里有一段Imgur上的视频,显示我的意思。https:/imgur.commAzWlJA
有什么办法可以防止这种行为吗?在 WKWebView
(a 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.comhTTZaw4。
我想我解决了。解决方案的要点是 不 将交互添加到图像视图本身,就像你直觉认为的那样,但将其添加到外部视图中,然后将上下文菜单预览聚焦到图像视图的矩形上,使用 UITargetPreview
APIs。这样一来,你就一起避免了触碰图片视图的bug,而是去它的父视图,只需 "裁剪 "到子视图,这样就能让子视图保持快乐的状态了:)
这是我最后的代码。
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)
}
}
一些说明:
UIScrollView
子类,试图斥责iOS级别的变化,但是是的,我已经花了差不多的时间在这上面了,所以我只是将scrollView的zoomScale重置为完全放大,一旦它要求上下文菜单(注意你必须在这里做,在上下文菜单的 willPresent
上下文菜单API就太晚了)。) 它并不坏,完全解决了这个问题,只是重置用户的缩放级别有点烦人。但如果我收到支持邮件,我就把他们链接到这个帖子上。wrapperView
内的视图控制器视图。这可能是针对我的用例,在你的用例中可能没有必要。本质上,你可以把它附加到 scrollView
本身,但我的有一些自定义的镶边,让它始终在缺口iPhone内居中,关于安全区域镶边,如果我用滚动视图来做交互目标预览,就会把它跳来跳去,这看起来并不好。你也不想直接使用视图控制器的视图作为交互,因为它在做动画的时候会把它遮挡住,这样媒体查看器scroll视图的黑色背景就完全消失了,看起来不是很好。所以在顶层设置一个包装器视图就很好的防止了这两点。