First let me say I play around with programmatic but I'm currently a novice at it.
I have mix of programmatic views and storyboard objects:
StoryBoard Objects:
Programmatic Views:
When I press the button the viewForMessageLabel
is added. In viewDidLoad
I add a tap gesture to remove the viewForMessageLabel
when the background is tapped. I also add the same tap gesture to the textField to remove the viewForMessageLabel
if it's present. I again add the same tap gesture to the textField to remove it also.
If the keyboard is present I add another tap gesture to in viewDidLoad
to the textField to dismiss it also. I notice things are wacky and I lose touch events.
If I press the button to add the label when I touch the background it doesn't get dismissed. If I press the textField it will dismiss it and show the keyboard. While the textField is still up if I press the button again, the label appears, I press the textField again and nothing happens. When I press return to hide the keyboard (I implemented the method), the keyboard disappears, press the button, the viewForMessageLabel
appears, and I now when I press the textField the viewForMessageLabel
disappears. Basically the same thing is happening with the textField.
What I want is
If the viewForMessageLabel
is present and I press either the background, textField, or textView it should disappear.
If the textField's or textView's keyboard is present and I press the background the keyboard should disappear also.
My code:
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
}
}
This doesn't exactly answer the question but I found a work around. If I use the method @Toddg suggested:
func removeLabelAndHideKeyboard() {
viewForMessageLabel.removeFromSuperview()
textField.resignFirstResponder()
}
It adds resigning the textField to the function which helped tremendously.
Also inside viewDidLoad I added:
textField.addTarget(self, action: #selector(removeViewForMessageLabel), for: .touchDown)
The key there is to use .touchDown and NOT .editingDidBegin. This way I can go back and forth in between the textField and the textView and the keyboard will respond to both. I had to add 1 more thing -a toolBar to the textView's keyboard which has Done button on it to dismiss the 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)
}
This way when the textView is present I can dismiss it.
In all situations if I press the textField, background, or textView and the viewForMessageLabel is present it will disappear.
If the textField is first responder and it's keyboard is present and I press the background it will disappear.
I have not figured out how to also dismiss the textView when the background is touched in addition to everything else so I implemented a Done button on the toolBar instead. If I press it and the textView's keyboard is present will get dismissed when it calls the dismissTextViewKeyboard()
function I added in. Both are at the bottom and everything else is in viewDidLoad.
If anyone has a better answer I'll up vote it.
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)
}
}