UIGestureRecognizer 在 UIView 边界之外而不是在内部工作
UIGestureRecognizer works outside of UIView borders instead of inside
我在我的应用程序中实现了弹出通知,以向用户显示数据是否已成功更新。
这个弹出窗口只是一个从 .xib 实例化的 UIView。这是 xib 文件中元素层次结构的屏幕截图:CLICK
另外,如果用户不想看到它在屏幕上显示的完整时间,我还希望能够滑出弹出窗口。为此,我实现了 UIGestureRecognizer:
private func configureTapGesture() {
guard let window = UIApplication.shared.windows.first(where: {[=10=].isKeyWindow}) else { return }
let swipe = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipes(_:)))
swipe.direction = .up
window.addGestureRecognizer(swipe)
}
@objc private func handleSwipes(_ sender:UISwipeGestureRecognizer) {
if sender.direction == .up {
UIView.animate(withDuration: 0.15, delay: 0.0, options: .curveLinear) {
self.popupView.center.y -= 50
} completion: { _ in
self.popupView.removeFromSuperview()
}
self.isRemovedByTap = true
}
}
当我 运行 应用程序时,它只有在我向外滑动但不在 popupView 内的任何地方时才有效。请检查 GIF:CLICK
如果我将目标从 self
(Popup class)替换为 popupView
(UIVisualEffectView,这是您在 GIF 上看到的圆角矩形),那么我会收到一个错误 unrecognized selector sent to instance
这是我的完整自定义弹出窗口 class,我在其中初始化视图、配置、设置动画并显示它:
import UIKit
class PopupView: UIView {
@IBOutlet weak var popupView: UIVisualEffectView!
@IBOutlet weak var symbol: UIImageView!
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var descriptionLabel: UILabel!
private var isRemovedByTap = false
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
private func configure() {
if let views = Bundle.main.loadNibNamed("PopupView", owner: self) {
guard let view = views.first as? UIView else { return }
view.frame = bounds
addSubview(view)
}
}
private func configurePopup() {
guard let window = UIApplication.shared.windows.first(where: {[=11=].isKeyWindow}) else { return }
popupView.layer.cornerRadius = 20
popupView.clipsToBounds = true
popupView.center.x = window.frame.midX
popupView.translatesAutoresizingMaskIntoConstraints = true
window.addSubview(popupView)
}
private func animatePopup() {
UIView.animate(withDuration: 0.15, delay: 0.0, options: .curveLinear) {
self.popupView.center.y += 34
} completion: { _ in
UIView.animate(withDuration: 0.15, delay: 10.0, options: .curveLinear) {
self.popupView.center.y -= 50
} completion: { _ in
if !self.isRemovedByTap {
self.popupView.removeFromSuperview()
}
}
}
}
private func configureTapGesture() {
guard let window = UIApplication.shared.windows.first(where: {[=11=].isKeyWindow}) else { return }
let swipe = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipes(_:)))
swipe.direction = .up
window.addGestureRecognizer(swipe)
}
@objc private func handleSwipes(_ sender:UISwipeGestureRecognizer) {
if sender.direction == .up {
UIView.animate(withDuration: 0.15, delay: 0.0, options: .curveLinear) {
self.popupView.center.y -= 50
} completion: { _ in
self.popupView.removeFromSuperview()
}
self.isRemovedByTap = true
}
}
func showPopup(title: String, message: String, symbol: UIImage) {
titleLabel.text = title
descriptionLabel.text = message
self.symbol.image = symbol
configurePopup()
animatePopup()
configureTapGesture()
}
}
要改成只有在popupView的圆角矩形内才能滑出吗?
private func configureTapGesture() {
guard let window = UIApplication.shared.windows.first(where: {[=10=].isKeyWindow}) else { return }
let swipe = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipes(_:)))
swipe.direction = .up
window.addGestureRecognizer(swipe)
// you also must add this and test it
self.addGestureRecognizer(swipe)
}
几个问题 - 没有看到您的 xib 视图层次结构,我无法完全测试它,但它应该可以帮助您。
您正在将滑动手势添加到 window ...
您无法在内部 弹出视图滑动的原因是弹出视图捕获触摸手势。如果您设置 .isUserInteractionEnabled = false
,您应该可以在视图内滑动。
如果您只想在视图内滑动,请检查滑动位置并查看视图是否包含该点。
按照这些思路:
@objc private func handleSwipes(_ sender:UISwipeGestureRecognizer) {
// may need to convert touch and frame
let loc = sender.location(in: self)
let r = popupView.frame
if sender.direction == .up, r.contains(loc) {
UIView.animate(withDuration: 0.15, delay: 0.0, options: .curveLinear) {
self.popupView.center.y -= 50
} completion: { _ in
self.popupView.removeFromSuperview()
}
self.isRemovedByTap = true
}
}
您需要调试框架和手势坐标以确保您检查的是正确的区域。
编辑
要检查滑动是否在 popupView 框架内,我们必须做几件事...
添加一个新的 属性——我称之为 yAdjustment
——然后在你的 animatePopup()
函数中,将其设置为 popupView 的 y 原点:
var yAdjustment: CGFloat = 0
private func animatePopup() {
UIView.animate(withDuration: 0.15, delay: 0.0, options: .curveLinear) {
self.popupView.center.y += 34
// save the top of the popupView frame
self.yAdjustment = self.popupView.frame.origin.y
} completion: { _ in
UIView.animate(withDuration: 0.15, delay: 10.0, options: .curveLinear) {
self.popupView.center.y -= 50
} completion: { _ in
if !self.isRemovedByTap {
self.popupView.removeFromSuperview()
}
}
}
}
然后,将您的滑动区域测试更改为:
@objc private func handleSwipes(_ sender:UISwipeGestureRecognizer) {
// get location of swipe
var loc = sender.location(in: popupView)
// adjust the touch y
loc.y -= (yAdjustment - popupView.frame.origin.y)
// get the view bounds
let r = popupView.bounds
// only do this if
// swipe direct is up
// AND
// swipe location is inside popupView
if sender.direction == .up, r.contains(loc) {
UIView.animate(withDuration: 0.15, delay: 0.0, options: .curveLinear) {
self.popupView.center.y -= 50
} completion: { _ in
self.popupView.removeFromSuperview()
}
self.isRemovedByTap = true
}
}
我们必须“纠正”框架的原因...
当你写那个链式动画块时,这一行:
self.popupView.center.y -= 50
立即执行——动画只是延迟(在本例中为 10 秒)。
通常,我们会在视图的框架中获取触摸位置并查看它是否包含在边界内。
不过,在这种情况下,因为视图的框架已经更改 - 即使我们还没有看到它 - 我们必须补偿新位置。
我在我的应用程序中实现了弹出通知,以向用户显示数据是否已成功更新。
这个弹出窗口只是一个从 .xib 实例化的 UIView。这是 xib 文件中元素层次结构的屏幕截图:CLICK
另外,如果用户不想看到它在屏幕上显示的完整时间,我还希望能够滑出弹出窗口。为此,我实现了 UIGestureRecognizer:
private func configureTapGesture() {
guard let window = UIApplication.shared.windows.first(where: {[=10=].isKeyWindow}) else { return }
let swipe = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipes(_:)))
swipe.direction = .up
window.addGestureRecognizer(swipe)
}
@objc private func handleSwipes(_ sender:UISwipeGestureRecognizer) {
if sender.direction == .up {
UIView.animate(withDuration: 0.15, delay: 0.0, options: .curveLinear) {
self.popupView.center.y -= 50
} completion: { _ in
self.popupView.removeFromSuperview()
}
self.isRemovedByTap = true
}
}
当我 运行 应用程序时,它只有在我向外滑动但不在 popupView 内的任何地方时才有效。请检查 GIF:CLICK
如果我将目标从 self
(Popup class)替换为 popupView
(UIVisualEffectView,这是您在 GIF 上看到的圆角矩形),那么我会收到一个错误 unrecognized selector sent to instance
这是我的完整自定义弹出窗口 class,我在其中初始化视图、配置、设置动画并显示它:
import UIKit
class PopupView: UIView {
@IBOutlet weak var popupView: UIVisualEffectView!
@IBOutlet weak var symbol: UIImageView!
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var descriptionLabel: UILabel!
private var isRemovedByTap = false
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
private func configure() {
if let views = Bundle.main.loadNibNamed("PopupView", owner: self) {
guard let view = views.first as? UIView else { return }
view.frame = bounds
addSubview(view)
}
}
private func configurePopup() {
guard let window = UIApplication.shared.windows.first(where: {[=11=].isKeyWindow}) else { return }
popupView.layer.cornerRadius = 20
popupView.clipsToBounds = true
popupView.center.x = window.frame.midX
popupView.translatesAutoresizingMaskIntoConstraints = true
window.addSubview(popupView)
}
private func animatePopup() {
UIView.animate(withDuration: 0.15, delay: 0.0, options: .curveLinear) {
self.popupView.center.y += 34
} completion: { _ in
UIView.animate(withDuration: 0.15, delay: 10.0, options: .curveLinear) {
self.popupView.center.y -= 50
} completion: { _ in
if !self.isRemovedByTap {
self.popupView.removeFromSuperview()
}
}
}
}
private func configureTapGesture() {
guard let window = UIApplication.shared.windows.first(where: {[=11=].isKeyWindow}) else { return }
let swipe = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipes(_:)))
swipe.direction = .up
window.addGestureRecognizer(swipe)
}
@objc private func handleSwipes(_ sender:UISwipeGestureRecognizer) {
if sender.direction == .up {
UIView.animate(withDuration: 0.15, delay: 0.0, options: .curveLinear) {
self.popupView.center.y -= 50
} completion: { _ in
self.popupView.removeFromSuperview()
}
self.isRemovedByTap = true
}
}
func showPopup(title: String, message: String, symbol: UIImage) {
titleLabel.text = title
descriptionLabel.text = message
self.symbol.image = symbol
configurePopup()
animatePopup()
configureTapGesture()
}
}
要改成只有在popupView的圆角矩形内才能滑出吗?
private func configureTapGesture() {
guard let window = UIApplication.shared.windows.first(where: {[=10=].isKeyWindow}) else { return }
let swipe = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipes(_:)))
swipe.direction = .up
window.addGestureRecognizer(swipe)
// you also must add this and test it
self.addGestureRecognizer(swipe)
}
几个问题 - 没有看到您的 xib 视图层次结构,我无法完全测试它,但它应该可以帮助您。
您正在将滑动手势添加到 window ...
您无法在内部 弹出视图滑动的原因是弹出视图捕获触摸手势。如果您设置 .isUserInteractionEnabled = false
,您应该可以在视图内滑动。
如果您只想在视图内滑动,请检查滑动位置并查看视图是否包含该点。
按照这些思路:
@objc private func handleSwipes(_ sender:UISwipeGestureRecognizer) {
// may need to convert touch and frame
let loc = sender.location(in: self)
let r = popupView.frame
if sender.direction == .up, r.contains(loc) {
UIView.animate(withDuration: 0.15, delay: 0.0, options: .curveLinear) {
self.popupView.center.y -= 50
} completion: { _ in
self.popupView.removeFromSuperview()
}
self.isRemovedByTap = true
}
}
您需要调试框架和手势坐标以确保您检查的是正确的区域。
编辑
要检查滑动是否在 popupView 框架内,我们必须做几件事...
添加一个新的 属性——我称之为 yAdjustment
——然后在你的 animatePopup()
函数中,将其设置为 popupView 的 y 原点:
var yAdjustment: CGFloat = 0
private func animatePopup() {
UIView.animate(withDuration: 0.15, delay: 0.0, options: .curveLinear) {
self.popupView.center.y += 34
// save the top of the popupView frame
self.yAdjustment = self.popupView.frame.origin.y
} completion: { _ in
UIView.animate(withDuration: 0.15, delay: 10.0, options: .curveLinear) {
self.popupView.center.y -= 50
} completion: { _ in
if !self.isRemovedByTap {
self.popupView.removeFromSuperview()
}
}
}
}
然后,将您的滑动区域测试更改为:
@objc private func handleSwipes(_ sender:UISwipeGestureRecognizer) {
// get location of swipe
var loc = sender.location(in: popupView)
// adjust the touch y
loc.y -= (yAdjustment - popupView.frame.origin.y)
// get the view bounds
let r = popupView.bounds
// only do this if
// swipe direct is up
// AND
// swipe location is inside popupView
if sender.direction == .up, r.contains(loc) {
UIView.animate(withDuration: 0.15, delay: 0.0, options: .curveLinear) {
self.popupView.center.y -= 50
} completion: { _ in
self.popupView.removeFromSuperview()
}
self.isRemovedByTap = true
}
}
我们必须“纠正”框架的原因...
当你写那个链式动画块时,这一行:
self.popupView.center.y -= 50
立即执行——动画只是延迟(在本例中为 10 秒)。
通常,我们会在视图的框架中获取触摸位置并查看它是否包含在边界内。
不过,在这种情况下,因为视图的框架已经更改 - 即使我们还没有看到它 - 我们必须补偿新位置。