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 秒)。

通常,我们会在视图的框架中获取触摸位置并查看它是否包含在边界内。

不过,在这种情况下,因为视图的框架已经更改 - 即使我们还没有看到它 - 我们必须补偿新位置。