Can I add multiple target actions on a UIButton for the same event like below?
[button addTarget:self action:#selector(xxx) forControlEvents:UIControlEventTouchUpInside];
[button addTarget:object action:#selector(yyy) forControlEvents:UIControlEventTouchUpInside];
I made a quick app to test it out and it carries out both the actions on button press.
I want to know if it's good practice to do so, and also will the order of execution always remain the same?
Thanks in advance.
Edit: I did find this post which states it's called in reverse order of addition, i.e., the most recently added target is called first. But it's not confirmed
Yes it is possible to add multiple actions to a button.
personally i would prefer a Delegate to subscribe to the button.
Let the object you want to add as target subscribe on the delegate's method so it can receive events when you press the button.
or
A single action that forwards the event to other methods to be fully in control
A simple test in swift
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let button = UIButton(frame: CGRect(x: 50, y: 50, width: 300, height: 30))
button.backgroundColor = .orange
button.addTarget(self, action: #selector(action1), for: .touchUpInside)
button.addTarget(self, action: #selector(action2), for: .touchUpInside)
button.addTarget(self, action: #selector(actionHandler), for: .touchUpInside)
self.view.addSubview(button)
}
#objc func actionHandler(_ sender: UIButton){
print("actionHandler")
action1(sender)
action2(sender)
}
#objc func action1(_ sender: UIButton) {
print("action1")
}
#objc func action2(_ sender: UIButton) {
print("action2 \n")
}
}
Output after one click:
action1
action2
actionHandler
action1
action2
Can you confirm on the order of execution when normally adding the actions
Yes it is executed in the order you set the targets.
Related
I wish to get the sender's tag of a programmatically set button.
i have set many buttons with tags programmatically and when the app is running and one of the buttons is tapped i wish to get the sender's tag back.
An example in my project. This #IBAction is connected with 7 buttons, from Monday to Sunday.
#IBAction func OnBtnWeekDay(_ sender: UIButton) {
let day = sender.tag - 1
//....
}
Main point is the parameter of function: (_ sender: UIBUtton)
This is simple. Make a button programmatically e.g in viewdidload and set it's tag and add action to it using the following code
//make a button programmatically e.g in viewdidload
let button = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
button.setTitle("Test Button", for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
//set tag to any integer value
button.tag = 1
//add to view controllers view or where ever you want
self.view.addSubview(button)
#objc func buttonAction(sender: UIButton!) {
print("Button tapped")
print("Buttons Tag is \(sender.tag)")
}
Tested and verified this solution my self. see the screen shot
My view (in the Main.storyboard) contains another UIView which takes only about 80% of the screen's height (set with constraints).
In this second view (I call it the viewContainer) I want to display different views, which works with
viewContainer.bringSubview(toFront: someView)
I created a new group in my project which contains 3 UIViewController classes and 3 xib files which I want to display in my viewContainer.
For each of those xib files I changed the background color to something unique so I can tell if it's working. And it does so far.
Now I tried adding a UIButton to the first UIViewController class and added an #IBAction for it. That just prints text to the console.
When I run the app I can switch between my 3 classes (3 different background colors) and I can also see and click the button I added to the first class when I select it.
But the code is never executed and my console is empty. Is that because my 3 other Views are not shown in my Main.storyboard?
SimpleVC1.swift
import UIKit
class SimpleVC1: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func onButtonPressed(_ sender: Any) {
print("test")
}
}
Try using a target instead (and programmatically create the button), it might be easier:
import UIKit
class SimpleVC1: UIViewController {
var button = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(button)
button.frame.size = CGSize(width: 100, height: 100)
button.center = CGPoint(x: 100, y: 100)
button.setTitle("Control 1", for: .normal)
button.addTarget(self, action: #selector(control1), for: .touchUpInside)
button.backgroundColor = UIColor.blue
button.setTitleColor(UIColor.white, for: .normal)
}
#objc func control1() {
//Add your code for when the button is pressed here
button.backgroundColor = UIColor.red
}
}
addTarget
So I know that you can use addTarget on a button
like so:
import UIKit
class ViewController: UIViewController {
func calledMethod(_ sender: UIButton!) {
print("Clicked")
}
override func viewDidLoad() {
super.viewDidLoad()
let btn = UIButton(frame: CGRect(x: 30, y: 30, width: 60, height: 30))
btn.backgroundColor = UIColor.darkGray
btn.addTarget(self, action: #selector(self.calledMethod(_:)), for: UIControlEvents.touchUpInside)
self.view.addSubview(btn)
}
}
This works absolutely fine but I was just wondering is there another way to detect when something is clicked and run some code when that happens.
You can add button in the storyboard and create an outlet for it's action.
#IBAction func buttonAction(_ sender: UIButton) {
//implement your code here
}
A button is a subclass of UIControl. A UIControl is designed to use target/action to specify code to run when the specified action occurs. If you don't want to use that mechanism, you have to create an equivalent.
You could attach a tap gesture recognizer to some other object and set it's userInteractionEnabled flag to true, but gesture recognizers ALSO use the target/action pattern.
My suggestion is to "stop worrying and learn to love the button." Just learn to use target/action.
Another possibility: Create a custom subclass of UIButton that sets itself up as it's own target at init time (specifically in init(coder:) and init(frame:), the 2 init methods you need to implement for view objects.) This button would include an array of closures that its action method would execute if the button was tapped. It would have methods to add or remove closures. You'd then put such a button in your view controller and call the method to add the closure you want.
You can also have a look at RxSwift/RxCocoa or similar. All the changes and actions are added automatically and you can decide if you want to observe them or not.
The code would look something like this:
let btn = UIButton(frame: CGRect(x: 30, y: 30, width: 60, height: 30))
btn.backgroundColor = UIColor.darkGray
btn
.rx
.tap
.subscribe(onNext: {
print("Clicked")
}).disposed(by: disposeBag)
view.addSubview(btn)
I have a function, setupGame(). When I press the play again button, the setupGame() function should be called. Where should I add this function?
let playAgain: UIButton = UIButton(frame: CGRectMake(-10, 400, 400, 150))
func setupGame() {
score = 0
physicsWorld.gravity = CGVectorMake(5, 5)
}
func buttonPressed(sender: UIButton) {
}
playAgain.setTitle("Play Again", forState: UIControlState.Normal)
playAgain.titleLabel!.font = UIFont(name: "Helvetica", size: 50)
playAgain.addTarget(self, action: "buttonPressed:", forControlEvents: .TouchUpInside)
playAgain.tag = 1
self.view!.addSubview(playAgain)
If you want to use your storyboard you should add a button and then drag into your code (Outlet). Check this link of how to create an outlet connection.
Or you could create a button programmatically as you have done and call setupGame.
playAgain.addTarget(self, action: "setupGame", forControlEvents: .TouchUpInside)
Simply replace "buttonPressed:" with "setupGame", and remove the buttonPressed function altogether.
You should make a IBAction instead of
func buttonPressed(sender: UIButton) {
}
but yes this is where the setupGame() function should be called
iBAction buttonPressed(sender:UIButton) {
setupGame()
}
and then just make sure you hook up your button to this function so it can detect the tapped interaction.
To call a function when a button is pressed, you should use UIButton.addTarget, which it looks like you already have. The problem is that you have the wrong action specified.
playAgain.addTarget(
self,
action: "buttonPressed:", // This should be "setupGame"
forControlEvents: .TouchUpInside
)
The action parameter of the .addTarget function essentially points to the function that should be called. The little colon after the name is to indicate that the function should accept the sender of the action as an argument.
Assuming this is being added to a button, helloWorld: corresponds to func helloWorld(sender: UIButton), and helloWorld (notice the missing colon) corresponds to func helloWorld()
So, you should use
func setupGame() {
score = 0
physicsWorld.gravity = CGVectorMake(5, 5)
}
//button code
// Notice how the function above has no arguments,
// so there is no colon in the action parameter of
// this call
playAgain.addTarget(
self, // the function to be called is in this class instance (self)
action: "setupGame", // corresponds to the above setupGame function
forControlEvents: .TouchUpInside
)
//other code
When I first run my app, I retrieve a number from my server and display it for my UIButton label. Think of this as a notification number displayed on a red UIButton.
When I remove a notification within the app, I want my UIButton label decrement by 1. I am able to get the decremented number from the server after I delete a notification, but I can't display this new number on the UIButton. The button always displays the number when the app is first fired.
I call makeButtonView() method after I remove a notification to update the UIButton
func makeButtonView(){
var button = makeButton()
view.addSubView(button)
button.tag = 2
if (view.viewWithTag(2) != nil) {
view.viewWithTag(2)?.removeFromSuperview()
var updatedButton = makeButton()
view.addSubview(updatedButton)
}else{
println("No button found with tag 2")
}
}
func makeButton() -> UIButton{
let button = UIButton(frame: CGRectMake(50, 5, 60, 40))
button.setBackgroundImage(UIImage(named: "redBubbleButton"), forState: .Normal)
API.getNotificationCount(userID) {
data, error in
button.setTitle("\(data)", forState: UIControlState.Normal)
}
button.addTarget(self, action: "targetController:", forControlEvents: UIControlEvents.TouchUpInside)
return button
}
Use this code for Swift 4 or 5
button.setTitle("Click Me", for: .normal)
I need more information to give you a proper code. But this approach should work:
lazy var button : UIButton = {
let button = UIButton(frame: CGRectMake(50, 5, 60, 40))
button.setBackgroundImage(UIImage(named: "redBubbleButton"), forState: .Normal)
button.addTarget(self, action: "targetController:", forControlEvents: UIControlEvents.TouchUpInside)
return button
}()
func makeButtonView(){
// This should be called just once!!
// Likely you should call this method from viewDidLoad()
self.view.addSubview(button)
}
func updateButton(){
API.getNotificationCount(userID) {
data, error in
// be sure this is call in the main thread!!
button.setTitle("\(data)", forState: UIControlState.Normal)
}
}
There have been some updates since Swift 4. This works for me:
self.button.setTitle("Button Title", for: UIControl.State.init(rawValue: 0))
Replace button with your IBOutlet name. You can also use a variable or array in place of the quoted text.
It's fairly simple ...
import UIKit
class ViewController: UIViewController {
#IBOutlet var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
button.setTitle("hello world", forState: UIControlState.Normal)
}
}
I believe if you set the state to normal, the value will propagate by default to other states so long as you haven't explicitly set a title for those states.
Said differently, if you set it for normal, it should also display this title when the button enters additional states
UIControlState.allZeros
UIControlState.Application
UIControlState.Disabled
UIControlState.Highlighted
UIControlState.Reserved
UIControlState.Selected
Lastly, here's Apple's documentation in case you have other questions.
Since your API call should be running on a background thread you need to dispatch your UI update back to the main thread like this:
DispatchQueue.main.async {
button.setTitle(“new value”, forState: .normal)
}
After setting the title, just a simple redraw of the button will do:
button.setNeedsDisplay();