在弹出视图之外的任何地方点击时关闭弹出视图,包括按钮、文本字段等
Close a popup view when tapping anywhere outside it, including buttons, textfields, etc
问题已编辑 因为人们似乎很困惑...
查看下面我的代码并观看所附的“视频”,了解正在发生的事情。弹窗关闭:
- 当用户点击弹出窗口中的按钮选择时
- 当用户在 父视图 视图的任意位置点击弹出窗口外部时(从而关闭弹出窗口而不从弹出窗口中进行选择)
这就是我想要的行为。但是,如果用户点击 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
}
}
这是实际效果。请注意,当您在其中点击进行选择时,弹出窗口会关闭。当您在其外部点击时它也会关闭,但当您在文本字段中点击或点击“按钮”时不会关闭。我希望当您点击弹出窗口外的任何地方时关闭弹出窗口,即使是在文本字段或“按钮”内。如果您通过点击文本字段内部或“按钮”关闭它,他们应该会像往常一样继续响应。
修改后的答案。这使您可以在 pop window 之外点击。我测试过并且有效。
将此添加到您的 viewDidLoad。
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
view.addGestureRecognizer(tap)
let pop = YourPopup()
在执行某些操作后使弹出窗口可见。
添加以下方法:
@objc func handleTap(_ sender: UITapGestureRecognizer? = nil) {
pop.removeFromSuperview()
}
看来我只需要添加以下内容:
@objc func buttonTapped(_ button: UIButton) {
popup?.isHidden = true
label.text = "Button tapped!"
}
func textFieldDidBeginEditing(_ textField: UITextField) {
popup?.isHidden = true
}
如果父视图中假设有数百个文本字段和按钮(以及其他 UI 启用了用户交互的元素),我必须对它们中的每一个都执行相同的操作。我希望有一个更通用、更优雅的解决方案。
经过多次摆弄,我终于找到了一种方法,可以随时随地 close/hide 弹出窗口,并且仍然让任何 UI 元素出现在屏幕上的任何位置(包括弹出窗口本身)让水龙头发挥作用。如果点击 在 弹出窗口内,则选择弹出窗口并关闭弹出窗口。如果点击位于父视图弹出窗口 外部 的任何位置,弹出窗口将关闭。如果点击屏幕上的任何其他 UI 元素,即使屏幕上有一千个元素,弹出窗口也会关闭,但 UI 元素仍会像往常一样响应点击。
我们需要设置 UITapGestureRecognizer,但我们让 gestureRecognizer(_:shouldReceive:)
完成工作而不是 #selector 函数,所以我们设置 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
}
}
问题已编辑 因为人们似乎很困惑...
查看下面我的代码并观看所附的“视频”,了解正在发生的事情。弹窗关闭:
- 当用户点击弹出窗口中的按钮选择时
- 当用户在 父视图 视图的任意位置点击弹出窗口外部时(从而关闭弹出窗口而不从弹出窗口中进行选择)
这就是我想要的行为。但是,如果用户点击 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
}
}
这是实际效果。请注意,当您在其中点击进行选择时,弹出窗口会关闭。当您在其外部点击时它也会关闭,但当您在文本字段中点击或点击“按钮”时不会关闭。我希望当您点击弹出窗口外的任何地方时关闭弹出窗口,即使是在文本字段或“按钮”内。如果您通过点击文本字段内部或“按钮”关闭它,他们应该会像往常一样继续响应。
修改后的答案。这使您可以在 pop window 之外点击。我测试过并且有效。
将此添加到您的 viewDidLoad。
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:))) view.addGestureRecognizer(tap) let pop = YourPopup()
在执行某些操作后使弹出窗口可见。
添加以下方法:
@objc func handleTap(_ sender: UITapGestureRecognizer? = nil) { pop.removeFromSuperview() }
看来我只需要添加以下内容:
@objc func buttonTapped(_ button: UIButton) {
popup?.isHidden = true
label.text = "Button tapped!"
}
func textFieldDidBeginEditing(_ textField: UITextField) {
popup?.isHidden = true
}
如果父视图中假设有数百个文本字段和按钮(以及其他 UI 启用了用户交互的元素),我必须对它们中的每一个都执行相同的操作。我希望有一个更通用、更优雅的解决方案。
经过多次摆弄,我终于找到了一种方法,可以随时随地 close/hide 弹出窗口,并且仍然让任何 UI 元素出现在屏幕上的任何位置(包括弹出窗口本身)让水龙头发挥作用。如果点击 在 弹出窗口内,则选择弹出窗口并关闭弹出窗口。如果点击位于父视图弹出窗口 外部 的任何位置,弹出窗口将关闭。如果点击屏幕上的任何其他 UI 元素,即使屏幕上有一千个元素,弹出窗口也会关闭,但 UI 元素仍会像往常一样响应点击。
我们需要设置 UITapGestureRecognizer,但我们让 gestureRecognizer(_:shouldReceive:)
完成工作而不是 #selector 函数,所以我们设置 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
}
}