In iOS 14 how do you tap a button programmatically? - ios

In iOS 14 I have configured a button to display a menu of questions. On its own the button is working perfectly. Here’s how it’s configured:
let button = UIButton()
button.setImage(UIImage(systemName: "chevron.down"), for: .normal)
button.showsMenuAsPrimaryAction = true
button.menu = self.menu
I have it set on a text field’s rightView. This is how it looks when the menu is displayed.
When a menu item is tapped, that question appears in a label just above the text field. It works great. Except….
I also want the menu to appear when the user taps in the text field. I don’t want them to have to tap on the button specifically. In the old days of target-action this was easy: button.sendActions(for: .touchUpInside). I tried that and .menuActionTriggered and .primaryActionTriggered but none of those work. I think sendActions() is looking for the old selector based actions.
But there’s a new sendActions(for: UIControl.Event) method. Here’s what I’m trying in textFieldShouldBeginEditing():
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
let tempAction = UIAction { action in
print("sent action")
}
menuButton.sendAction(tempAction)
print("textField tapped")
return false
}
Sure enough, “sent action” and “textField tapped” appear in the console when the text field is tapped. But I have no idea what UIAction I could send that means “display your menu”. I wish there was this method: send(_: UIControl.Event).
Any ideas on how to get the button to display its menu when the text field is tapped?
ps. yes, textFieldShouldBeginEditing will need to know if a question has been selected and in that case will need to allow editing. That part is easy.

From what I read, you cannot programmatically trigger UIContextMenuInteraction as interactions seem to be handled internally by itself unlike send(_: UIControl.Event)
I see this mentioned on this SO post here
Also in the docs it seems that we don't have access to interaction management Apple needs to decide 3D touch is available or default back to long tap
From the docs
A context menu interaction object tracks Force Touch gestures on devices that support 3D Touch, and long-press gestures on devices that don't support it.
Workaround
I can propose the following workaround for your use case. My example was created using frame instead of auto layout as faster and easier to demo the concept this way, however you will need to make adjustments using autolayout
1. Create the UI elements
// I assume you have a question label, answer text field and drop down button
// Set up should be adjusted in case of autolayout
let questionLabel = UILabel(frame: CGRect(x: 10, y: 100, width: 250, height: 20))
let answerTextField = UITextField(frame: CGRect(x: 10, y: 130, width: 250, height: 50))
let dropDownButton = UIButton()
2. Regular setup of the label and the text view first
// Just regular set up
private func configureQuestionLabel()
{
questionLabel.textColor = .white
view.addSubview(questionLabel)
}
// Just regular set up
private func configureAnswerTextField()
{
let placeholderAttributes = [NSAttributedString.Key.foregroundColor :
UIColor.lightGray]
answerTextField.backgroundColor = .white
answerTextField.attributedPlaceholder = NSAttributedString(string: "Tap to select a question",
attributes: placeholderAttributes)
answerTextField.textColor = .black
view.addSubview(answerTextField)
}
3. Add the button to the text view
// Here we have something interesting
private func configureDropDownButton()
{
// Regular set up
dropDownButton.setImage(UIImage(systemName: "chevron.down"), for: .normal)
dropDownButton.showsMenuAsPrimaryAction = true
// 1. Create the menu options
// 2. When an option is selected update the question label
// 3. When an option is selected, update the button frame
// Update button width function explained later
dropDownButton.menu = UIMenu(title: "Select a question", children: [
UIAction(title: "Favorite movie") { [weak self] action in
self?.questionLabel.text = action.title
self?.answerTextField.placeholder = "Add your answer"
self?.answerTextField.text = ""
self?.updateButtonWidthIfRequired()
},
UIAction(title: "Car make") { [weak self] action in
self?.questionLabel.text = action.title
self?.answerTextField.placeholder = "Add your answer"
self?.answerTextField.text = ""
self?.updateButtonWidthIfRequired()
},
])
// I right align the content and set some insets to get some padding from
// the right of the text field
dropDownButton.contentHorizontalAlignment = .right
dropDownButton.contentEdgeInsets = UIEdgeInsets(top: 0.0,
left: 0.0,
bottom: 0.0,
right: 20.0)
// The button initially will stretch across the whole text field hence we
// right aligned the content above
dropDownButton.frame = answerTextField.bounds
answerTextField.addSubview(dropDownButton)
}
// Update the button width if a menu option was selected or not
func updateButtonWidthIfRequired()
{
// Button takes the text field's width if the question isn't selected
if let questionText = questionLabel.text, questionText.isEmpty
{
dropDownButton.frame = answerTextField.bounds
return
}
// Reduce button width to right edge as a question was selected
dropDownButton.frame = CGRect(x: answerTextField.frame.width - 50.0,
y: answerTextField.bounds.origin.y,
width: 50,
height: answerTextField.frame.height)
}
4. End Result
Start with a similar view to yours
Then I tap in the middle of the text field
It displays the menu as intended
After selecting an option, the question shows in the label and the placeholder updates
Now I can start typing my answer using the text field and the button is only active on the right side since it was resized
And the button is still active
Final thoughts
Could be better to put this into a UIView / UITextField subclass
This was an example using frames with random values, adjustments need to made for autolayout
Edits from OP (too long for a comment):
I had tried setting contentEdgeInsets so the button was way over to the left but it covered up the placeholder text.
Your idea of simply adding the button as a subview of the text field was the key, but...
After selecting a question and resizing the button, if the text field was the first responder, tapping the button had no effect. The button was in the view hierarchy but the text field got the tap.
So, when a question is selected, I remove the button from its superview (the textfield) and add it to the textField's rightView. Then it would accept a tap even if the textField was the first responder.
And, as you suspected, I had to pin the button to the textField with constraints.

Related

UITextField is not editable, has only Touch Events, no Editing Events

I have a UITextField above a Google Maps View (GMSView). When I tap on the Textfield it does not become the first responder, neither do any of the delegate or action events work (Delegate Event like textFieldShouldBeginEditing and Action Events like .allEditingEvents).
The only way the Textfield is becoming the first responder is by hardcoding it in with
tf.becomeFirstResponder()
But when I tap on it nothing happens.
The Action Touch events like .touchUpInside etc. do work normally on the TextField, so I know the frame is ok.
I am creating and adding the TextView like this:
tf = UITextField(frame: CGRect(x: 100, y: 200, width: 300, height: 50))
tf.backgroundColor = .white
tf.placeholder = "sdfdsf"
tf.translatesAutoresizingMaskIntoConstraints = false
tf.addTarget(self, action: #selector(self.tapped), for: .touchUpInside)
self.mapView.addSubview(tf)
tf.centerXAnchor.constraint(equalTo: mapView.centerXAnchor).isActive = true
tf.centerYAnchor.constraint(equalTo: mapView.centerYAnchor).isActive = true
tf.widthAnchor.constraint(equalToConstant: 300).isActive = true
tf.heightAnchor.constraint(equalToConstant: 50).isActive = true
If you have any idea why this happens (or in this case does not happen) please leave a comment or an answer. I have worked on this for over 2 days straight now.
UPDATE:
I actually added the textfield to the Mapview itself. The Mapview
kinda does something strange with the layering of the view and messes
up the view of the TextView.
The solution was to add the TextView to the main view and not to the
MapView subview.
So it's (from back to front layer):
ViewController
(Main) View
Google Maps view
View
TextField
This works
Please try this:
self.mapView.bringSubviewToFront(tf) // Add this line below "self.mapView.addSubview(tf)"
This will ensure that the tf(TextField is on Top and Tappable)
Did you assigne delegate to text field like :-
tf. delegate = self
If you want to bring a subview to the front, you can use:
self.view.bringSubviewToFront(tf)

How to dynamically change the display text of a button in xcode using tags

Im trying to create an email us button in my app where the text displayed changes depending on what version of the app youre using. Currently the text is "Email: " and i want to add an email address to that text.
The button is in a table view cell, and i want to change this text using tags, how would i go about this or is there another option?
P.S The reason i want to do it with tags is when i try to hook the button up to an outlook the build will fail
let sendEmailButton = cell.viewWithTag(666)!
var rect = sendEmailButton.frame
rect.origin.x = 19
sendEmailButton.frame = rect
rect.origin.y = 75
sendEmailButton.frame = rect
change text here to be add email address
In this code i'm moving the button into its position, and then i want to change its text. Again, there no outlet as that caused fails due to it being in a table view. I realise this screen should not be in a table view, but its the way my company does it and i have to work with it
cast your button as UIButton like this:
guard let sendEmailButton = cell.viewWithTag(666) as? UIButton else { return }
and than just set title
sendEmailButton.setTitle("here goes your text", for: .normal)
For your code it will be work.
if let button = cell.viewWithTag(666) as? UIButton {
button.setTitle("Set Your Text here", for: .normal)
}

How to programmaticaly add button in iOS game app swift 3?

Can someone explain the code for a programatically added button in an iOS game application for swift 3 Xcode 8? All the other threads on this topic we're in single view and didn't work for me. I couldn't figure out how to add buttons to the game app Main.storyboard, so I'm trying to make a programattically added button. This is the code I'm trying to use now but doesn't work:
var playAgain = UIButton()
playAgain.setTitle("Play Again", for: .normal)
playAgain.setTitleColor(UIColor.black, for: .normal)
playAgain.backgroundColor = SKColor.green
playAgain.layer.borderWidth = 2
playAgain.layer.cornerRadius = 18
playAgain.frame = CGRect(x: (self.frame.width)/2, y: (self.frame.height)/2, width: 100, height: 36)
self.view?.addSubview(playAgain)
Why would the buttons in single view be different in game apps? Also, when(and if) this is created, how would I modify the Touches ended method to know when the button was touched?
Your code is adding the button to the .view, but using the coordinate system of the SKScene. So, your button is there, just not in view.
Assuming you want the button to be centered on the screen (at least, for now), change the placement to:
playAgain.frame = CGRect(x: 0, y: 0, width: 100, height: 36)
playAgain.center = (self.view?.center)!
self.view?.addSubview(playAgain)
This will put the button above the game scene (z-layer, that is), so you can use normal button tap without needing to deal with touches. So, right after you add the button:
self.view?.addSubview(playAgain)
playAgain.addTarget(self, action: #selector(playAgainTapped(_:)), for: .touchUpInside)
and then elsewhere in your class:
func playAgainTapped(_ sender: Any?) -> Void {
print("Play again was Tapped!")
// take whatever action you want here
}
There is another way to create a button programmatically. You can create an empty UIView and override touch method. Also you are able to process touch event on this view and simulate buttons action. I think this is a fastest way for you.

Swift - Set label to be on top of other view items

I'm creating a basic sketching app using swift 1.2 and Xcode 6.4. The feature I'm working on is adding text to the image for future manipulation.
So far I have the text the user enters adding to the drawing via label, but it is being placed (I assume) behind the UIImage that is to be drawn upon. This is preventing the associated tap gesture from being recognised. The label is also obscured when drawn over.
How do I place the label on top of everything else to prevent it being drawn over? The end goal is to allow the user to move the label.
If I was using my usual web stack I'd set the z-index of the element to a high value via CSS.
Edit: Using view.bringSubviewToFront(label) prevents the text from being drawn over, but does not allow the tap gesture to be picked up.
Heres my code:
func printText(){
println(textString)
var label = UILabel(frame: CGRectMake(5, 100, 200, 50 ))
label.textAlignment = NSTextAlignment.Center
label.text = textString;
let tap = UITapGestureRecognizer(target: self, action: Selector("handleTap"))
label.addGestureRecognizer(tap)
self.view.addSubview(label);
}
func handleTap() {
print("tap working")
}
Any help much appreciated.
If you want z-index use:
label.layer.zPosition = 1;
Also you could play with bringToFront method on the parent view:
self.view.bringSubviewToFront(label);
You can check that function here :
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/
Also, you should add the following:
label.userInteractionEnabled = true
to enable touches.
Try using those in the parent view:
view.bringSubviewToFront(label)
view.sendSubviewToBack(imageView)
It all depends on what you can, or want to do.
You can call label to front by
self.view.bringSubviewToFront(label)
or
You can send image view to the back by
self.view.sendSubviewToBack(imageView)

Subclassing UINavigationBar with Swift

I'm writing an app that gives a user tokens to spend and I want to display the user's current number of tokens in a UINavigationBar. What I would like to have is a label with the number of tokens, and an image of a coin in the top right corner of my navigation bar.
I've been searching for ways to customize the UINavigationBar, and have found plenty of posts related to adding an image to cover the entire bar, and changing the title. However, I can't find a simple way to do what I want.
I think I need to subclass UINavigationBar and add the text/image myself, but being new to iOS development and Swift, I was hoping that someone could point me in the right direction.
You can create a custom view contains subviews UILabel and UIImageView to show the token number and token image. Add it to right bar button item of the navigation controller.
It will look like:
Below code will create the custom view. Here you can observe that it is a local variable. However, you can manage global variable for custom view or create a whole new class and manage it independently for real-time updates to show token number.
// Custom to hold token number and image
let tokenView = UIView(frame: CGRect(x:0, y:0, width:100, height:44))
tokenView.backgroundColor = UIColor.yellow
// Label to show token number
let tokenLabel = UILabel(frame: CGRect(x:0, y:0, width:60, height:44))
tokenLabel.text = "1234"
tokenLabel.textAlignment = NSTextAlignment.right
let imageHeight = CGFloat(30)
let marginY = CGFloat((tokenView.frame.size.height / 2) - (imageHeight / 2))
// ImageView to display token image
let tokenImage = UIImageView(image: UIImage(named: "coin"))
tokenImage.frame = CGRect(x:70, y:marginY, width:30, height:30)
tokenView.addSubview(tokenLabel)
tokenView.addSubview(tokenImage)
// Add custom view as a right bar button item
let barButtonItem = UIBarButtonItem(customView: tokenView)
self.navigationItem.rightBarButtonItem = barButtonItem
To display the current number of tokens you can just set the title. In your view controller:
navigationItem.title = '\(numberOfCoins) coins"
If you want to do anything more fancy you can set your own title view instead of the standard UILabel:
navigationItem.titleView = UIView(...) // your custom view
To show the coin in the top right corner you'd set the rightBarButtonItem:
navigationItem.rightBarButtonItem = UIBarButtonItem(
image: UIImage(named:"Coin"),
style: .Plain,
target:self,
action:"tappedCoinButton:")

Resources