在弹出视图之外的任何地方点击时关闭弹出视图,包括按钮、文本字段等

Posted

技术标签:

【中文标题】在弹出视图之外的任何地方点击时关闭弹出视图,包括按钮、文本字段等【英文标题】:Close a popup view when tapping anywhere outside it, including buttons, textfields, etc 【发布时间】:2021-09-14 01:32:56 【问题描述】:

问题已编辑,因为人们似乎很困惑......

请参阅下面的代码并观看随附的“视频”,了解正在发生的事情。弹出窗口关闭:

当用户点击弹出窗口中的按钮选择时 当用户在弹出窗口外点击时,父视图上的任意位置(从而关闭弹出窗口而不从弹出窗口中进行选择)

这是我想要的行为。但是,如果用户点击 parent 视图中的按钮或文本字段,弹出窗口不会关闭。因此,在这种情况下,弹出窗口仍然会弹出。

如何在弹出窗口之外的任何位置检测点击手势,包括按钮、文本字段和任何其他已经拥有自己的点击处理程序的 UI 元素,以便我可以关闭弹出窗口而不劫持这些点击处理程序的行为?

import UIKit

class ViewController: UIViewController, UIGestureRecognizerDelegate, UITextFieldDelegate 
    
    var popup: UIView!
    var label: UILabel!

    override func viewDidLoad() 
        super.viewDidLoad()
        view.backgroundColor = .darkGray
        
        let textfield = UITextField()
        textfield.backgroundColor = .white
        textfield.translatesAutoresizingMaskIntoConstraints = false
        textfield.placeholder = "some text"
        view.addSubview(textfield)
        
        let button1 = UIButton()
        button1.setTitle("Button", for: .normal)
        button1.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        button1.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(button1)
        
        let button2 = UIButton()
        button2.setTitle("Show Popup", for: .normal)
        button2.addTarget(self, action: #selector(popupButtonTapped), for: .touchUpInside)
        button2.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(button2)
        
        label = UILabel()
        label.textColor = .yellow
        label.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(label)

        NSLayoutConstraint.activate([
            textfield.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            textfield.topAnchor.constraint(equalTo: view.topAnchor, constant: 160),
            textfield.widthAnchor.constraint(equalToConstant: 299),
            textfield.heightAnchor.constraint(equalToConstant: 30),
            button1.topAnchor.constraint(equalTo: textfield.bottomAnchor, constant: 40),
            button1.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button2.topAnchor.constraint(equalTo: button1.bottomAnchor, constant: 40),
            button2.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            //label.topAnchor.constraint(equalTo: button2.bottomAnchor),
            label.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -260),
            label.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        ])
        
        textfield.delegate = self
        let viewTapGesture = UITapGestureRecognizer(target: self, action: #selector(viewTapped))
        viewTapGesture.delegate = self
        view.addGestureRecognizer(viewTapGesture)
    
    
    @objc func viewTapped(gestureRecognizer: UITapGestureRecognizer) 
        popup?.isHidden = true
    

    @objc func buttonTapped(_ button: UIButton) 
        label.text = "Button tapped!"
    
    
    @objc func popupButtonTapped(_ button: UIButton) 
        if popup == nil 
            popup = UIView()
            popup.backgroundColor = #colorLiteral(red: 1, green: 0.9175537825, blue: 0.79708004, alpha: 1)
            popup.layer.borderWidth = 1
            popup.layer.borderColor = UIColor.black.cgColor
            popup.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(popup)
            
            let stackview = UIStackView()
            stackview.axis = .vertical
            stackview.alignment = .fill
            stackview.distribution = .fillEqually
            stackview.translatesAutoresizingMaskIntoConstraints = false
            popup.addSubview(stackview)
            
            for i in 1...5 
                let button = UIButton()
                button.setTitle("Selection \(i)", for: .normal)
                button.setTitleColor(.black, for: .normal)
                button.widthAnchor.constraint(equalToConstant: 120).isActive = true
                button.addTarget(self, action: #selector(popupItemTapped), for: .touchUpInside)
                stackview.addArrangedSubview(button)
            
            
            NSLayoutConstraint.activate([
                stackview.topAnchor.constraint(equalTo: popup.topAnchor),
                stackview.leadingAnchor.constraint(equalTo: popup.leadingAnchor),
                stackview.trailingAnchor.constraint(equalTo: popup.trailingAnchor),
                stackview.bottomAnchor.constraint(equalTo: popup.bottomAnchor),
                popup.topAnchor.constraint(equalTo: button.topAnchor),
                popup.leadingAnchor.constraint(equalTo: button.leadingAnchor),
            ])
         else 
            popup.isHidden = false
        
    
    
    @objc func popupItemTapped(_ button: UIButton) 
        label.text = "\(button.currentTitle!) tapped!"
        popup.isHidden = true
    
    
    func textFieldShouldReturn(_ textField: UITextField) -> Bool 
        textField.resignFirstResponder()
        label.text = "You typed:   \(textField.text!)"
        return true
    



这就是它在行动中的样子。请注意,当您在其中点击以进行选择时,弹出窗口将关闭。当您在外部点击时,它也会关闭,但在您点击文本字段或点击“按钮”时不会关闭。我希望当您点击弹出窗口外部的任何位置时关闭弹出窗口,即使在文本字段或“按钮”内也是如此。如果您通过在文本字段内或“按钮”上点击来关闭它,它们应该会继续像往常一样响应。

【问题讨论】:

"当在其中点击文本字段时弹出的视图(如工具提示)" 您在代码中的什么位置?甚至没有人知道它来自哪里以及你用什么类来展示它。 @ElTomato 我通过包含代码和演示来编辑我的问题。 提示:removeFromSuperview(). @ElTomato 对不起,我不关注。我在删除弹出窗口时没有问题。其实我只是在隐瞒。我的问题是如何检测弹出窗口之外的任何地方的点击,包括文本字段、按钮等,以便我可以隐藏弹出窗口。 我明白了。对于那个很抱歉。然后尝试在文本字段中添加手势。 【参考方案1】:

看来我只需要添加以下内容:

    @objc func buttonTapped(_ button: UIButton) 
        popup?.isHidden = true 
        label.text = "Button tapped!"
    

    func textFieldDidBeginEditing(_ textField: UITextField) 
        popup?.isHidden = true
    

如果父视图中有数百个文本字段和按钮(以及其他启用了用户交互的 UI 元素),我必须对它们中的每一个都执行相同的操作。我希望有一个更通用、更优雅的解决方案。

【讨论】:

【参考方案2】:

修改后的答案。这使您可以在弹出窗口之外点击。我测试过,它可以工作。

    将此添加到您的 viewDidLoad。

         let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
         view.addGestureRecognizer(tap)
         let pop = YourPopup()
    

    做某事后使弹出可见。

    添加如下方法:

    @objc func handleTap(_ sender: UITapGestureRecognizer? = nil) pop.removeFromSuperview()

【讨论】:

对不起,我不明白。你想解决什么问题?删除弹出窗口没有问题。我的问题是如何检测弹出窗口外的点击,包括点击文本字段、按钮等,以便我可以关闭/隐藏弹出窗口。请重新阅读整个问题并观看随附的“视频”。 我明白了。如果你点击里面的任何地方,我的解决方案就可以工作:) 我用这样的东西来捕捉外面的敲击,但它需要激活某些控制。所以以此类推,我会在弹出窗口下为整个屏幕创建一个视图并使其可点击。 @IBAction func menuButtonClicked(_ sender: UIButton) if (isPopupPresented == false) //显示弹窗 isPopupPresented = true else isPopupPresented = false pop.animateOut() 【参考方案3】:

经过多次摆弄,我终于找到了一种方法,可以随时随地关闭/隐藏弹出窗口,并且仍然让屏幕上任何位置的任何 UI 元素(包括弹出窗口本身)都可以进行点击操作。如果点击在弹出窗口内部,则选择弹出窗口并关闭弹出窗口。如果点击在父视图上弹出窗口外部的任何位置,弹出窗口将关闭。如果点击屏幕上的任何其他 UI 元素,即使屏幕上有上千个,弹出窗口也会关闭,但 UI 元素仍会像往常一样响应点击。

我们需要设置 UITapGestureRecognizer,但是我们没有使用#selector 函数,而是让gestureRecognizer(_:shouldReceive:) 来完成这项工作,所以我们设置了action: nil

// Setup TapGestureRecognizer. The ACTION PARAMETER IS NIL since we do not need a
// selector function. We'll let gestureRecognizer(_:shouldReceive:) do the work.
// But we MUST at least register the gesture recognizer's delegate with the view!
let viewTapGesture = UITapGestureRecognizer(target: self, action: nil)
viewTapGesture.delegate = self
view.addGestureRecognizer(viewTapGesture)

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool 
    if !popup.isHidden 
        popup.isHidden = true
    
    return false

这是完整的(修改后的)代码:

import UIKit

class ViewController: UIViewController, UIGestureRecognizerDelegate, UITextFieldDelegate 
    
    var popup: UIView!
    var label: UILabel!

    override func viewDidLoad() 
        super.viewDidLoad()
        view.backgroundColor = .darkGray
        
        let textfield = UITextField()
        textfield.backgroundColor = .white
        textfield.translatesAutoresizingMaskIntoConstraints = false
        textfield.placeholder = "some text"
        view.addSubview(textfield)
        
        let button1 = UIButton()
        button1.setTitle("Button", for: .normal)
        button1.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        button1.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(button1)
        
        let button2 = UIButton()
        button2.setTitle("Show Popup", for: .normal)
        button2.addTarget(self, action: #selector(popupButtonTapped), for: .touchUpInside)
        button2.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(button2)
        
        label = UILabel()
        label.textColor = .yellow
        label.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(label)

        NSLayoutConstraint.activate([
            textfield.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            textfield.topAnchor.constraint(equalTo: view.topAnchor, constant: 160),
            textfield.widthAnchor.constraint(equalToConstant: 299),
            textfield.heightAnchor.constraint(equalToConstant: 30),
            button1.topAnchor.constraint(equalTo: textfield.bottomAnchor, constant: 40),
            button1.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button2.topAnchor.constraint(equalTo: button1.bottomAnchor, constant: 40),
            button2.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            label.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -260),
            label.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        ])
        
        popup = UIView()
        popup.backgroundColor = #colorLiteral(red: 1, green: 0.9175537825, blue: 0.79708004, alpha: 1)
        popup.layer.borderWidth = 1
        popup.layer.borderColor = UIColor.black.cgColor
        popup.translatesAutoresizingMaskIntoConstraints = false
        popup.isHidden = true
        view.addSubview(popup)

        let stackview = UIStackView()
        stackview.axis = .vertical
        stackview.alignment = .fill
        stackview.distribution = .fillEqually
        stackview.translatesAutoresizingMaskIntoConstraints = false
        popup.addSubview(stackview)

        for i in 1...5 
            let button = UIButton()
            button.setTitle("Selection \(i)", for: .normal)
            button.setTitleColor(.black, for: .normal)
            button.widthAnchor.constraint(equalToConstant: 120).isActive = true
            button.addTarget(self, action: #selector(popupItemTapped), for: .touchUpInside)
            stackview.addArrangedSubview(button)
        

        NSLayoutConstraint.activate([
            stackview.topAnchor.constraint(equalTo: popup.topAnchor),
            stackview.leadingAnchor.constraint(equalTo: popup.leadingAnchor),
            stackview.trailingAnchor.constraint(equalTo: popup.trailingAnchor),
            stackview.bottomAnchor.constraint(equalTo: popup.bottomAnchor),
            popup.topAnchor.constraint(equalTo: button2.topAnchor),
            popup.leadingAnchor.constraint(equalTo: button2.leadingAnchor),
        ])
        
        textfield.delegate = self
        
        // Setup TapGestureRecognizer. The ACTION PARAMETER IS NIL since we do not need a
        // selector function. We'll let gestureRecognizer(_:shouldReceive:) do the work.
        // But we MUST at least register the gesture recognizer's delegate with the view!
        let viewTapGesture = UITapGestureRecognizer(target: self, action: nil)
        viewTapGesture.delegate = self
        view.addGestureRecognizer(viewTapGesture)

    
    
    @objc func buttonTapped(_ button: UIButton) 
        label.text = "Button tapped!"
    
    
    @objc func popupButtonTapped(_ button: UIButton) 
        popup.isHidden = false
    
    
    @objc func popupItemTapped(_ button: UIButton) 
        label.text = "\(button.currentTitle!) tapped!"
    
    
    func textFieldShouldReturn(_ textField: UITextField) -> Bool 
        label.text = "You typed:   \(textField.text!)"
        return true
    
    
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool 
        if !popup.isHidden 
            popup.isHidden = true
        
        return false
    

【讨论】:

以上是关于在弹出视图之外的任何地方点击时关闭弹出视图,包括按钮、文本字段等的主要内容,如果未能解决你的问题,请参考以下文章

如何设置 Popover 视图以正确关闭

当用户在弹出窗口之外点击时,防止 JQuery Mobile 关闭弹出窗口

iOS 在弹出窗口中显示视图控制器

iOS 弹出框在关闭后不会消失

在弹出窗口中显示多个图像

尝试关闭弹出视图控制器 Swift 时没有任何反应