This question already has an answer here:
Why does Xcode line-out autocomplete methods for selector?
(1 answer)
Closed 2 years ago.
I have a UIButton within a UITableViewCell. When I call addTarget on the button and attempt to put a function in, the autocomplete has the function crossed out with a white line (see first image). Xcode still allows me to put the function in and run the app; however, when the button is tapped the function isn't called.
Button initialization:
private let infoButton: UIButton = {
let button = UIButton()
button.backgroundColor = .clear
button.adjustsImageWhenHighlighted = false
button.setImage(Images.sizeGuide, for: .normal)
button.addTarget(self, action: #selector(infoButtonPressed(_:)), for: .touchUpInside)
return button
}()
Function (infoButtonPressed):
#objc private func infoButtonPressed(_ sender: UIButton) {
print("Button Pressed")
}
Because I reuse this cell multiple times and only one of these cells needs to have this button, I have a variable that dictates whether or not to show the button:
var hasInfoButton: Bool = false {
didSet {
if hasInfoButton {
setupInfoButton()
}
}
}
The function that is called above simply sets up the button using autoLayout. Something to mention: when I tried calling addTarget in this function, the app crashed with Unrecognized selector sent to instance...
The tableView in which this is embedded in is only static and displays data. Therefore, allowSelection and allowsMultipleSelection are both set to false.
Does anyone have a solution to this?
You shouldn't need (_ sender: UIButton) the method should just be:
#objc func infoButtonPressed() {
print("button pressed")
}
EDIT:
The button initializer is also a little strange. The way I generally go about this kind of thing is like this:
let button = UIButton()
private func configureButton() {
button.backgroundColor = .clear
button.adjustsImageWhenHighlighted = false
button.setImage(Images.sizeGuide, for: .normal)
button.addTarget(self, action: #selector(infoButtonPressed(_:)), for: .touchUpInside)
}
Then call configureButton() in viewDidLoad()
Related
When setting the action with the "addTarget" method on a button in Swift, is there a way for me to pass a parameter to the function I want to trigger?
Say I had a simple scenario like this:
let button = UIButton()
button.addTarget(self, action: #selector(didPressButton), for: .touchUpInside)
#objc func didPressButton() {
// do something
}
Obviously the above code works fine, but say I wanted the 'didPressButton' function to take in a parameter:
#objc func didPressButton(myParam: String) {
// do something with myParam
}
Is there a way I can pass a parameter into the function in the 'addTarget' method?
Something like this:
let button = UIButton()
button.addTarget(self, action: #selector(didPressButton(myParam: "Test")), for: .touchUpInside)
#objc func didPressButton(myParam: String) {
// do something with myParam
}
I'm coming from a JavaScript background and in JavaScript it's pretty simple to achieve this behavior by simply passing an anonymous function that would then call the 'didPressButton' function. However, I can't quite figure how to achieve this with swift. Can a similar technique be used by using a closure? Or does the '#selector' keyword prevent me from doing something like that?
Thank you to anyone who can help!
The short answer is no.
The target selector mechanism only sends the target in the parameters. If the string was a part of a subclass of UIbutton then you could grab it from there.
class SomeButton: UIButton {
var someString: String
}
#objc func didPressButton(_ button: SomeButton) {
// use button.someString
}
It is not possible to do that in iOS. You can get the View in the selector in your case it is a button.
button.addTarget(self, action: #selector(buttonClick(_:)), for: .touchUpInside)
#objc func buttonClick(_ view: UIButton) {
switch view.titleLabel?.text {
case "Button":
break
default:
break
}
}
I created a UIButton within a for-loop because the number of these button depend on a certain number in the database. However, I have to change the image of this button after it is clicked, which doesn't work.
What I have done is I tried holding an array of those buttons and trying to access the buttons like that, which I don't think is not working.
let buttonArray: [UIButton] = [UIButton]()
...extra code
for (index, num) in numberOfButtonsNeeded.enumerated() {
let button = UIButton()
button.tag = index
button.addTarget(self, action: #selector(self.tap(sender:)), for: .touchUpInside)
buttonArray.append(button)
}
...
#objc func tap(sender: UIButton) {
DispatchQueue.main.asnyc{
buttonArray[sender.tag].setImage(UIImage(named:"Somename"))
}
}
I can access the tap gesture but this button's image won't be reset. Is there a way I can access the button? I do not think this array works.
This is suspect:
#objc func tap(sender: UIButton) {
DispatchQueue.main.asnyc{
buttonArray[sender.tag].setImage(UIImage(named:"Somename"))
}
You are already on the main queue, and the sender is the button that was tapped. So you don't need the array.
#objc func tap(sender: UIButton) {
sender.setImage(UIImage(named:"Somename"))
}
I was changing file from one module to another, doing so I start getting this error in one of my tests. While earlier it was working absolutely fine.
[Assert] UIApp is nil which means we cannot dispatch control actions to their targets. If this assert is hit, we probably got here without UIApplicationMain() being executed, which likely means this code is not running in an app (perhaps a unit test being run without a host app) and will not work as expected.
In code add button in viewDidLoad()
private lazy var button: ABCTypeButton = {
let button = ABCTypeButton(title: viewModel.title, buttonType: .Payment).withAutoLayout()
button.accessibilityLabel = viewModel.title
button.accessibilityIdentifier = "paymentButton"
button.resetTintColor()
button.addTarget(self, action: #selector(ABCViewController.action1), for: .touchUpInside)
button.addTarget(self, action: #selector(ABCViewController.action2), for: .touchDown)
button.addTarget(self, action: #selector(ABCViewController.action3), for: [.touchUpOutside, .touchDragExit])
return button
}()
#objc private func action1() {
// code
}
public class ABCTypeButton: UIControl {
let iconImageView = UIImageView()
let buttonTitleLabel = UILabel()
private let chevronImageView = UIImageView(image: Icon.navigateNext.image)
private let stackView = UIStackView().withAutoLayout()
public init(title buttonTitle: String,
buttonType: FeeButtonType,
height: CGFloat = Spacing.four) {
super.init(frame: CGRect.zero)
setupViews(buttonTitle, buttonType: buttonType)
setupConstraints(height: height)
}
}
Trying to tap button from tests.
func test() {
let viewController = ViewController(viewModel: viewModel)
let button = viewController.view.findViewByIdentifier("paymentButton") as! ABCTypeButton
// I Checked that button is not nil
button.sendActions(for: .touchUpInside)
XCTAssertEqual(viewController.value, button.accessibilityIdentifier)
}
Target method action1() is not getting called
i just ran into this, and made this rough extension for the touchUpInside event. can obviously be refactored to take in whatever events you'd like to call.
extension UIButton {
public func touchUpInside(forTarget target: UIViewController) {
guard let action = actions(forTarget: target, forControlEvent: .touchUpInside)?.first else {
assertionFailure("could not find touchUpInside action for target")
return
}
target.perform(Selector(action))
}
}
I know that you asked this question 3 years ago, but maybe my answer will be helpful for somebody.
So, I did exactly what was said in the message perhaps a unit test being run without a host app.
To change this you need to go Test_Target -> General -> Host Application
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();