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

Swift iOS -Remove View When TextField, TextView, and Background Tapped But Losing Events

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

我混合了编程视图和故事板对象:

故事板对象:

  1. 按钮
  2. 文本字段
  3. TextView

程序化视图:

  1. 消息标签
  2. viewForMessageLabel

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

如果键盘存在,我会在 viewDidLoad 中向 textField 添加另一个点击手势以关闭它。我发现事情很古怪,我失去了触摸事件。

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

我要的是

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

  2. 如果 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
    }
}
  1. If the textField's or textView's keyboard is present and I press the background the keyboard should disappear also.

您将其呈现为以当前是否显示键盘为条件,但您的代码并未反映(也不应该)。您可以根据需要多次调用 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()
}

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

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

它将退出 textField 添加到功能中,这非常有帮助。

我还在 viewDidLoad 中添加了:

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

关键是使用 .touchDown 而不是 .editingDidBegin。这样我就可以在 textField 和 textView 之间来回切换,键盘将响应两者。我不得不再添加 1 个东西 - 一个 toolBar 到 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)
    }
}