Swift iOS - 当 TextField、TextView 和背景被点击但丢失事件时删除视图

Posted

技术标签:

【中文标题】Swift iOS - 当 TextField、TextView 和背景被点击但丢失事件时删除视图【英文标题】:Swift iOS -Remove View When TextField, TextView, and Background Tapped But Losing Events 【发布时间】:2017-08-25 21:00:34 【问题描述】:

首先让我说我玩过程序化,但我目前是新手。

我混合了程序化视图和情节提要对象:

故事板对象:

    按钮 文本字段 文本视图

程序化视图:

    消息标签 viewForMessageLabel

当我按下按钮时,viewForMessageLabel 被添加。在viewDidLoad 中,我添加了一个点击手势以在点击背景时删除viewForMessageLabel。我还将相同的点击手势添加到 textField 以删除 viewForMessageLabel(如果存在)。我再次将相同的点击手势添加到 textField 以将其删除。

如果键盘存在,我会在 viewDidLoad 中向 textField 添加另一个轻击手势以将其关闭。我注意到事情很古怪,我失去了联系事件。

如果我在触摸背景时按下按钮添加标签,它不会被关闭。如果我按下 textField 它将关闭它并显示键盘。如果我再次按下按钮 textField 仍然处于启动状态,标签会出现,我再次按下 textField 并且没有任何反应。当我按回车键隐藏键盘时(我实现了该方法),键盘消失,按下按钮,viewForMessageLabel 出现,现在当我按下 textField 时,viewForMessageLabel 消失。基本上同样的事情发生在 textField 上。

我想要的是

    如果 viewForMessageLabel 存在并且我按下背景、textField 或 textView,它应该会消失。

    如果 textField 或 textView 的键盘存在并且我按下背景,则键盘也会消失。

我的代码:

class ViewController: UIViewController, UITextFieldDelegate, UITextViewDelegate 

    //MARK:- Outlets
    @IBOutlet weak var textField: UITextField!
    @IBOutlet weak var textView: UITextView!
    @IBOutlet weak var button: UIButton!

    let messagelabel: UILabel = 
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "Pizza Pizza Pizza Pizza Pizza"
        label.font = UIFont(name: "Helvetica-Regular", size: 17)
        label.sizeToFit()
        label.numberOfLines = 0
        label.textAlignment = .center
        label.textColor = UIColor.white
        label.backgroundColor = UIColor.clear
        return label
    ()

    let viewForMessageLabel: UIView = 
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = UIColor.red
        return view
    ()

    //View Controller Lifecycle
    override func viewDidLoad() 
        super.viewDidLoad()

        textField.delegate = self
        textView.delegate = self

        // 0. hide viewForMessageLabel is background is tapped
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(removeViewForMessageLabel))
        view.addGestureRecognizer(tapGesture)

        // 1. hide viewForMessageLabel if textView is tapped
        textView.addGestureRecognizer(tapGesture)

        // 2. hide keyboard if background if tapped
        let hideKeyboard = UITapGestureRecognizer(target: self, action: #selector(hideKeyboardWhenBackGroundTapped))
        view.addGestureRecognizer(hideKeyboard)

        // 3. hide keyboard if textView is tapped
        textView.addGestureRecognizer(hideKeyboard)

        // 4. hide viewForMessageLabel for textField if background is tapped
        textField.addTarget(self, action: #selector(removeViewForMessageLabel), for: .editingDidBegin)

    

    //MARK:- Button
    @IBAction func buttonPressed(_ sender: UIButton) 
        view.addSubview(viewForMessageLabel)
        setViewForMessageLabelAnchors()
        setMessageLabelAnchors()
    

    //MARK:- Functions
    func setViewForMessageLabelAnchors()
        viewForMessageLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 44).isActive = true
        viewForMessageLabel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
        viewForMessageLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
        viewForMessageLabel.addSubview(messagelabel)
    

    func setMessageLabelAnchors()
        messagelabel.topAnchor.constraint(equalTo: viewForMessageLabel.topAnchor, constant: 0).isActive = true
        messagelabel.widthAnchor.constraint(equalTo: viewForMessageLabel.widthAnchor).isActive = true
        viewForMessageLabel.bottomAnchor.constraint(equalTo: messagelabel.bottomAnchor, constant: 0).isActive = true
    

    func removeViewForMessageLabel()
        viewForMessageLabel.removeFromSuperview()
    

    func hideKeyboardWhenBackGroundTapped()
        textField.resignFirstResponder()
    

    //MARK:- TextField Delegate
    func textFieldShouldReturn(_ textField: UITextField) -> Bool 
        view.endEditing(true)
        return true
    

    func textViewDidBeginEditing(_ textView: UITextView) 
        removeViewForMessageLabel()
    

    //MARK:- TextView Delegate
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool 
        if(text == "\n") 
            textView.resignFirstResponder()
            return false
        
        return true
    

【问题讨论】:

你有很多重叠的点击手势识别器。我不确定水龙头是否默认通过。看起来你有点过于复杂了。 @toddg 谢谢。我刚刚读了一篇文章,说我可以在同一个水龙头上添加另一个动作。我要试试。你为什么说我把事情复杂化了? 【参考方案1】:
    如果 textField 或 textView 的键盘存在并且我按下背景,则键盘也应该消失。

您呈现此内容的前提是当前是否显示了键盘,但您的代码并未反映这一点(也不应该)。您可以根据需要多次拨打resignFirstResponder,不会发生任何不好的事情。你也可以在已经被移除的视图上调用removeFromSuperview(见here)。

因此,我认为您可以将一个动作附加到单击手势识别器:

var tapGesture: UITapGestureRecognizer?

override func viewDidLoad() 
    super.viewDidLoad()

    textField.delegate = self
    textView.delegate = self

    // 0. hide viewForMessageLabel is background is tapped
    tapGesture = UITapGestureRecognizer(target: self, action: #selector(removeLabelAndHideKeyboard))
    view.addGestureRecognizer(tapGesture)

    // 1. hide viewForMessageLabel if textView is tapped
    textView.addGestureRecognizer(tapGesture)


func removeLabelAndHideKeyboard() 
    viewForMessageLabel.removeFromSuperview()
    textField.resignFirstResponder()

【讨论】:

感谢您的帮助!我会在几个小时后回家尝试一下?? 告诉我结果如何。 我试过了,但它不起作用。首先我在其中键入委托方法,然后我将它们注释掉(然后取消注释它们)。当键盘存在并且我点击背景时,没有任何反应。当键盘和 viewForMessageLabel 都存在并且我点击 bg 时没有任何反应。在两者仍然存在的情况下,我点击 textField 并且视图仍然存在。唯一删除视图的是 textView。我 c+p 你写的内容并在 viewDidLoad 中注释掉了其他所有内容 我认为这可能是因为UITextField 已经有手势识别器。现在我的问题是,当用户点击文本字段时,为什么要隐藏键盘 大声笑你的权利我看过了,smh。我只是在玩它。如果我使用: tapGesture = ... 和 func removeLabelAndHideKeyboard() viewForMessageLabel.removeFromSuperview() textField.resignFirstResponder() 我可以点击背景或 textView 并且视图将消失。如果选择了 textField 并且我点击了 bg,则键盘将消失。现在这里有 2 个问题: 1. 如果我点击 textView 并触摸背景,键盘会停留。 2.如果视图存在并且我点击文本字段视图不会消失【参考方案2】:

这并不能完全回答问题,但我找到了解决方法。如果我使用@Toddg 建议的方法:

func removeLabelAndHideKeyboard() 
    viewForMessageLabel.removeFromSuperview()
    textField.resignFirstResponder()

它为函数添加了对文本字段的签名,这对我们有很大帮助。

我还在 viewDidLoad 里面添加了:

textField.addTarget(self, action: #selector(removeViewForMessageLabel), for: .touchDown)

关键是使用 .touchDown 而不是 .editingDidBegin。这样我就可以在 textField 和 textView 之间来回切换,键盘会同时响应两者。我必须再添加 1 个东西 - 一个工具栏到 textView 的键盘上,它上面有完成按钮来关闭 textView:

    func addDoneButtonOnKeyboard()
        let toolBar = UIToolbar()
        toolBar.sizeToFit()
        doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissKeyboard))
        toolBar.setItems([doneButton!], animated: true)
        textView.inputAccessoryView = toolBar
    

    @objc func dismissTextViewKeyboard()
        view.endEditing(true)
    

这样,当 textView 存在时,我可以将其关闭。

在所有情况下,如果我按下 textField、background 或 textView 并且 viewForMessageLabel 存在,它将消失。

如果 textField 是第一响应者并且它的键盘存在并且我按下背景它将消失。

除了其他所有内容之外,我还没有弄清楚如何在触摸背景时关闭 textView,因此我在工具栏上实现了一个完成按钮。如果我按下它并且 textView 的键盘在调用我添加的dismissTextViewKeyboard() 函数时将被关闭。两者都在底部,其他所有内容都在 viewDidLoad 中。

如果有人有更好的答案,我会投赞成票。

class ViewController: UIViewController, UITextFieldDelegate, UITextViewDelegate 


    //MARK:- Outlets
    @IBOutlet weak var textField: UITextField!
    @IBOutlet weak var textView: UITextView!
    @IBOutlet weak var button: UIButton!

    let messagelabel: UILabel = 
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "Pizza Pizza Pizza Pizza Pizza"
        label.font = UIFont(name: "Helvetica-Regular", size: 17)
        label.sizeToFit()
        label.numberOfLines = 0
        label.textAlignment = .center
        label.textColor = UIColor.white
        label.backgroundColor = UIColor.clear
        return label
    ()

    let viewForMessageLabel: UIView = 
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = UIColor.red
        return view
    ()

    fileprivate var doneButton: UIBarButtonItem?

    //View Controller Lifecycle
    override func viewDidLoad() 
        super.viewDidLoad()

        textField.delegate = self
        textView.delegate = self

        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(removeViewForMessageLabel))
        view.addGestureRecognizer(tapGesture)

        textField.addTarget(self, action: #selector(removeViewForMessageLabel), for: .touchDown)

        addDoneButtonOnKeyboard()
    

    //MARK:- Button
    @IBAction func buttonPressed(_ sender: UIButton) 
        //removeMessage()
        view.addSubview(viewForMessageLabel)
        setBackgroundAnchors()
        setMessageAndLabelAnchors()
    

    //MARK:- Functions
    func setBackgroundAnchors()
        viewForMessageLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 44).isActive = true
        viewForMessageLabel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
        viewForMessageLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
        viewForMessageLabel.addSubview(messagelabel)
    

    func setMessageAndLabelAnchors()

        messagelabel.topAnchor.constraint(equalTo: viewForMessageLabel.topAnchor, constant: 0).isActive = true
        messagelabel.widthAnchor.constraint(equalTo: viewForMessageLabel.widthAnchor).isActive = true
        viewForMessageLabel.bottomAnchor.constraint(equalTo: messagelabel.bottomAnchor, constant: 0).isActive = true
    

    func removeViewForMessageLabel()
        viewForMessageLabel.removeFromSuperview()
        textField.resignFirstResponder()
    

    //MARK:- TextField Delegate
    func textFieldShouldReturn(_ textField: UITextField) -> Bool 
        view.endEditing(true)
        return true
    

    func textViewDidBeginEditing(_ textView: UITextView) 
        removeViewForMessageLabel()
    

    //MARK:- TextView Delegate
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool 
        if(text == "\n") 
            textView.resignFirstResponder()
            return false
        
        return true
    

    //MARK:- Additional Functions
    //add a done button to the keyboard when the textView is first responder
    fileprivate func addDoneButtonOnKeyboard()
        let toolBar = UIToolbar()
        toolBar.sizeToFit()
        doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissTextViewKeyboard))
        toolBar.setItems([doneButton!], animated: true)
        textView.inputAccessoryView = toolBar
    

    //dismiss the keyboard when the Done button is tapped
    @objc func dismissTextViewKeyboard()
        view.endEditing(true)
    

【讨论】:

以上是关于Swift iOS - 当 TextField、TextView 和背景被点击但丢失事件时删除视图的主要内容,如果未能解决你的问题,请参考以下文章

Swift:仅当键盘隐藏 TextField 或 Button 时滚动视图

Swift iOS - TextField 初始宽度错误的底部边框

损坏的内联 datePicker 作为 TextField Swift iOS 14 的 InputView

获取 Button 和 Textfield 数据,当它们在 swift 5 中以编程方式添加时

如何使用 Swift 保存从 tableView 上的 textField 收到的用户答案

Textfield 甚至是空的,但在 Swift 中不认为是空的