I'm relatively new to Swift programming and just learned from this article on Stack Overflow that I can't change UIButton text via the 'sender' argument in #IBAction code, but must setup an #IBOutlet from the button and use methods on the outlet variable. How broad of a rule is that? What methods are OK to apply to the 'sender' argument, and which are not?
I don't know what they're referring to in that post you've linked to, as it's not correct. You don't need an #IBOutlet for anything specifically. It doesn't grant any special powers to the button, it's just a handy pointer to the object you can use.
You of course need a pointer to the object if you want to call it's methods, but the sender attribute to an #IBAction is just as good for that IBAction's code.
You do have to make sure the sender is the right type.
For example:
#IBAction func buttonPressed(sender: UIButton) {
sender.setTitle("New Title", forState: .Normal)
}
That will work fine and change the button tapped. Doesn't matter if there's an #IBOutlet pointing to it anywhere. The sender variable is all you need.
In some cases, you'll want to use an AnyObject type for sender, in which case you'll need to check the type first:
#IBAction func buttonPressed(sender: AnyObject) {
if let button = sender as? UIButton {
button.setTitle("New Title", forState: .Normal)
}
}
Now, if you wanted to change a different button's title, then an #IBOutlet can make it easier.
#IBOutlet weak var someOtherButton: UIButton!
#IBAction func buttonPressed(sender: UIButton) {
someOtherButton.setTitle("New Title", forState: .Normal)
}
But again, an #IBOutlet isn't required (it is recommended). To show #IBOutlet doesn't have any special powers, you could set a tag value (e.g. 100) on the button in Interface Builder and use code like this:
#IBAction func buttonPressed(sender: AnyObject) {
if let button = view.viewWithTag(100) as? UIButton {
button.setTitle("New Title", forState: .Normal)
}
}
The 100 number I've used here is arbitrary and could be any number so long as you only use the number for one item in the view.
#IBAction func buttonClicked(sender : AnyObject) {
println("Button was clicked", sender)
}
here sender means your button's reference. you can perform different on button on click by using sender like sender.backgroundColor etc.
Hope this will help :)
The sender argument you mention is part of the Target-Action Mechanism.
The sender parameter usually identifies the control sending the action message (although it can be another object substituted by the actual sender). The idea behind this is similar to a return address on a postcard. The target can query the sender for more information if it needs to. If the actual sending object substitutes another object as sender, you should treat that object in the same way. For example, say you have a text field and when the user enters text, the action method nameEntered: is invoked in the target:
As the sender could be substituded, there is no guarantee that someone could use another sender in the parameter if calling it manually, while if you own a reference to your button in your class, via an IBOutlet, then it could sometimes be wiser to use it for any method you expect to call as you are sure that it refers to the button.
However, if you are sure the method is only linked with the correct sender, you could potentially call any method.
The question would not as much be "what action can I call" but "am I really sure sender really is the relevent button"
Related
When I want a tap response on my main View in my ViewController
A. I could create an IBOutlet as below
class ViewController: UIViewController {
#IBOutlet var tapGesture: UITapGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
tapGesture.addTarget(self, action: #selector(tapped))
}
#objc private func tapped(_: UITapGestureRecognizer) {
print("Log is here")
}
}
Or
B. I could an IBAction on the TapGesture such as below
class ViewController: UIViewController {
#IBAction func tapGestureAction(_ sender: Any) {
print("Log is here")
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
Is there a preferred approach of one above the other? If not, which situation should we use A approach, and which we should use B approach?
Option B, i.e. just having the #IBAction outlet would be preferred when you already created your UITapGestureRecognizer in the storyboard, as this encapsulates as much logic as possible in the storyboard, reducing the overhead of reading unnecessary code and potential regressions if/when the code is refactored (but the storyboard remains unchanged).
You can still mark the #IBAction private (as it's effectively the same as using an #objc attribute). Also, if you need to access the gesture recognizer itself, you can have a regular #IBOutlet with a didSet to modify it, or change sender: Any to sender: UITapGestureRecognizer to access it in the action.
It is an interesting question, from my perspective this depends on how much from your application is in the storyboard or you want it explicitly written in the code.
My recommendation will be if you are doing something small and it should be done fast to use your storyboard. But if you have a big project with a big team then it will be better to have it in the code.
The other thing that can be a key factor for these approaches will be who is the owner of the reference and do you want to have some interactions of the gesture. For example, I have a gesture that should be enabled in specific cases and for others, it should be disabled. For this, you need to have a reference in the code.
What I'm trying to explain is that you should think for criteria like how and when you can use this gesture. And based on this to decide if you need less code or reference to the gesture or whatever you need
I am just learning the swift basics and thought it would be a good idea that I try using my skills and a problem appeared. I have tried everything I know can someone help. My image below.
First of all please post text, not images
You have to use the (IBOutlet) instance number rather than the type UIButton and you have to use the proper API
number.setTitle(String(score), for: .normal)
But in an IBAction I'd declare the method with the static sender type (rather than unspecified Any) and use that
#IBAction func touched(_ sender : UIButton) {
score += 1
sender.setTitle(String(score), for: .normal)
}
If you want to change the title of a button, you need to do this:
button_Outlet_Name.setTitle(title: String?, for: UIControlState)
or
button_Outlet_Name.title.text = "New Title"
Remember to do this on your button OUTLET, not on the UIButton class
You cannot change the your button's title that way, what you have done there by writing
UIButton.title = String(score)
This means you are calling a static method of UIButton class and the name of the method is title.
If you want to change the button's tite you can do that the below way:
Step 1: Take a reference of your button by ctrl+drag.
Step 2: Inside your IBAction you need to write:
yourButton.setTitle("\(score)", for: .normal)
Here you are accessing the static method of the UIButton class. If you want to set the title, you need to do so on the instance.
Based on what you have, within the IBAction, you can cast the sender as a UIButton and then set the title. You’d also do that for a specific state since the titles are closely related to the the state for a UIButton.
if let btn = sender as? UIButton {
btn.setTitle(“\(score)”, forState: .normal)
}
You could have also used the IBOutlet reference instead.
number.setTitle(“\(score)”, forState: .normal)
Whenever you use \(variable) within a string, it uses the string value of the variable to be displayed in the string.
I'm not sure what are u trying to do but if you want to change the title of the clicked button u can do like this:
#IBAction func touched(_ sender: Any) {
score += 1
// check if the sender is UIButton
if let button = sender as? UIButton {
//change your button title
button.setTitle("\(scroe)", for: UIControlState.normal)
}
}
I currently have three separate sliders on my viewcontroller.swift that I would like to all perform the same function, which is to round the slider value to the nearest position. I have already completed the first one, as such:
#IBAction func changePos(_ sender: UISlider) {
slider.value = roundf(slider.value)
}
I have tried many different ways to add the same function to different UISliders by using slider2, changePos2, etc, but my app either crashes or doesn't perform the function.
How do I make separate UISliders perform the same function within the same viewcontroller?
You can hook all 3 sliders up to the same #IBAction. Just use the sender parameter instead of your slider output in the implementation.
#IBAction func changePos(_ sender: UISlider) {
sender.value = sender.value.rounded()
}
The good way to do this is using IBOutletCollections like below.
#IBOutlet var sliders: [UISlider]!
and then using nice swift syntax below:
sliders.forEach { (slider) in
slider.addTarget(self, action: #selector(sliderValueDidChange(sender:)), for: .valueChanged)
}
This way you will have same selector get called for each of the sliders.
I try figure out why self point to the GameViewController instead of Answer
GameViewController.swift
class GameViewController: UIViewController {
var gameplay = QuestionsController(colors: colors)
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(gameplay.answersController.answers[0].button)
}
func didPressAnswerButton(sender: UIButton!) {
sender.setTitle("Im from GameViewController class", forState: .Normal)
}
}
QuestionsController.swift
class QuestionsController {
var question: Question
var answersController: AnswersController
}
AnswersController.swift
class AnswersController {
var answers = [Answer]()
func prepareAnswers() {
let answer = Answer()
answers.append(answer)
}
}
Answer.swift
class Answer{
let button: UIButton
func prepareButton() {
let answerButton = AnswerButton(type: .System)
answerButton.addTarget(self, action: "didPressAnswerButton:", forControlEvents: .TouchUpInside)
button = answerButton
}
func didPressAnswerButton(sender: UIButton!) {
sender.setTitle("Im from Answer class", forState: .Normal)
}
}
addTarget:action:forControlEvents: tells the control (answerButton in this case) what method to call, and what object to call it on, when the user taps the button. Looking at your code in more detail:
answerButton.addTarget(self, action: "didPressAnswerButton:", forControlEvents: .TouchUpInside)
When the user taps a button, the TouchUpInside event fires on the answerButton, and when that happens we want to invoke a method didPressAnswerButton: on an Answer object
So, we need to tell answerButton what do do when this TouchUpEvent fires. You do this calling the addTarget:action:forControlEvents method on the answerButton
The self argument tells the answerButton what object to notify about the event: it is the target. In this context, self is an Answer object.
The "didPressAnswerButton:" argument indicates what method the answerButton should call in response to the tap event: this is the action
This is the target-action mechanism of Objective-C/Cocoa. It's a very common pattern, it's worth it to read the linked documentation to learn a bit more about how it works. The key is that this is based on Objective-C* message passing: in the code above, "didPressAnswerButton:" indicates a selector, which when paired with a target (self), tells the answerButton how to send a "message" to the target when the user taps the button.
Also, note that when you are editing a storyboard and ctrl-drag from a button to your view controller and select a method, you are also setting up a target/action using this same mechanism. You select the target object by dragging to the view controller icon (or some other icon), and then you pick the action/selector when clicking on a method name in the popup.
* Target-Action was originally designed for Objective-C, but for the common case of implementing a view controller, you can assume Swift works the same way. Just note when reading documentation that Swift uses simple strings for actions, whereas Objective-C uses #selector(...).
I'm writing some unit tests and, because of the nature of this particular app, it's important that I get as high up the UI chain as possible. So, what I'd like to do is programmatically trigger a button-press, as if the user had pressed the button in the GUI.
(Yes, yes -- I could just call the IBAction selector but, again, the nature of this particular app makes it important that I fake the actual button press, such that the IBAction be called from the button, itself.)
What's the preferred method of doing this?
It turns out that
[buttonObj sendActionsForControlEvents:UIControlEventTouchUpInside];
got me exactly what I needed, in this case.
EDIT: Don't forget to do this in the main thread, to get results similar to a user-press.
For Swift 3:
buttonObj.sendActions(for: .touchUpInside)
An update to this answer for Swift
buttonObj.sendActionsForControlEvents(.TouchUpInside)
EDIT: Updated for Swift 3
buttonObj.sendActions(for: .touchUpInside)
Swift 3:
self.btn.sendActions(for: .touchUpInside)
If you want to do this kind of testing, you’ll love the UI Automation support in iOS 4. You can write JavaScript to simulate button presses, etc. fairly easily, though the documentation (especially the getting-started part) is a bit sparse.
In this case, UIButton is derived from UIControl. This works for object derived from UIControl.
I wanted to reuse "UIBarButtonItem" action on specific use case. Here, UIBarButtonItem doesn't offer method sendActionsForControlEvents:
But luckily, UIBarButtonItem has properties for target & action.
if(notHappy){
SEL exit = self.navigationItem.rightBarButtonItem.action;
id world = self.navigationItem.rightBarButtonItem.target;
[world performSelector:exit];
}
Here, rightBarButtonItem is of type UIBarButtonItem.
For Xamarin iOS
btnObj.SendActionForControlEvents(UIControlEvent.TouchUpInside);
Reference
Swift 5:
class ViewController: UIViewController {
#IBOutlet weak var theTextfield: UITextField!
#IBOutlet weak var someButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
theTextfield.text = "Pwd"
someButton.sendActions(for: .touchUpInside)
}
#IBAction func someButtonTap(_ sender: UIButton) {
print("button tapped")
}
}
It's handy for people who write Unit Tests without UI Tests ;-)
Swift 5 way to solve it for UIBarButtonItem, which does not have sendAction method like UIButton etc.
extension UIBarButtonItem {
func sendAction() {
guard let myTarget = target else { return }
guard let myAction = action else { return }
let control: UIControl = UIControl()
control.sendAction(myAction, to: myTarget, for: nil)
}
}
And now you can simply:
let action = UIBarButtonItem(title: "title", style: .done, target: self, action: #selector(doSomething))
action.sendAction()
Swift 4:
self .yourButton(self)