I am facing this problem: I would like to assign an action for a button created runtime, I'm using this custom class:
import UIKit
class SubViewManager: NSObject {
var button = UIButton()
Then I have the function to add the button in the view:
frame.addSubview(button)
And the function for assign the action:
func setButtonAction(sender: UIButton!, buttonAction: Selector) {
button.addTarget(self, action: buttonAction, forControlEvents: UIControlEvents.TouchUpInside)
}
Now in the ViewController Class I call the SubViewManager object like this:
var newSubView:SubViewManager!
Then in a procedure I'm doing:
newSubView.addButton(...//Dimension and details//...)
newSubView.setButtonAction(newSubView.button, buttonAction: "save")
And here I have the problem, this error appair in the console:
unrecognized selector sent to instance 0x7f92f9694140
What I am doing wrong?
EDIT: I noticed that if I initialize the object directly in the function where I call the .setButtonAction it works, but I can't declare in the same function because I need it in multiple functions!
RE-EDIT: When i do the .addTarget method I would like to run a ViewController Class's procedure but it runs the SubViewManager procedure which don't exist, that's the why of the error, but I don't know how to run the ViewController Class procedure while calling the .addTarget in an external class.
its crashing because in your code the target that you set to handle the button's action event is the SubViewManager instance ('self' inside setButtonAction method)
func setButtonAction(sender: UIButton!, buttonAction: Selector) {
button.addTarget(self, action: buttonAction, forControlEvents: UIControlEvents.TouchUpInside)
}
But the actually object where you implemented the save function is ViewController class.
You can try modify the parameter of the setButtonAction method to pass the target that will implemented the button action instead of unused sender : UIButton! because you already call addButton method and has button property to point to it already.
Try something like this
func setButtonAction(target: AnyObject! , buttonAction: Selector) {
button.addTarget(target, action: buttonAction, forControlEvents: UIControlEvents.TouchUpInside)
}
Then pass in the viewController when you calling the setButtonAction method
newSubView.setButtonAction(self, buttonAction: "save")
where self is instance of the ViewController class that implemented the save function.
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
}
}
now I practice applying the MVC pattern in my Swift project.
I have one View Controller (VC) file and one UIView file.
In the VC file, I added the UIView file like below.
class MyViewController: UIViewController {
private var myView = MyView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(myView)
myView.frame = CGRect(origin: .zero, size: view.bounds.size)
// and other codes...
}
#objc func showDeleteAlert() {
print("showDelete is pressed")
}
}
Then, inside the UIView file, I added some views and buttons (I just copy the button part).
class MyView: UIView {
lazy var deleteButton: UIButton = {
let button = UIButton()
button.setTitle("delete", for: .normal)
button.addTarget(target: MyViewController, action: #selector(showDeleteAlert), for: UIControl.Event.touchUpInside) // -> I get error in here saying "Cannot find 'showDeleteAlert' in scope"
return button
}()
// and more codes...
}
What I want to do here is how to set the target to showDeleteAlert function when the deleteButton is pressed?
I saw tutorials add "self" as a target argument, but in my case, I separated view and controller so not really sure how to access the function in MyViewController.
Thank you...
You have two options here:
move addTarget into view controller viewDidLoad:
myView.deleteButton.addTarget(self, action: #selector(showDeleteAlert), for: UIControl.Event.touchUpInside)
If you wanna make button private(which is a good practice), you can add proxy func to your MyView
func addDeleteButtonTarget(_ target: Any?, action: Selector) {
deleteButton.addTarget(target, action: action, for: .touchUpInside)
}
And call it:
myView.addDeleteButtonTarget(self, action: #selector(showDeleteAlert))
In any case, you cant call button.addTarget(target: MyViewController, ...), because you need to pass an instance of the MyViewController, not just a class name.
I'm adding a target to a button but instead of having the target action reference a predefined function I want it to reference a closure.
typealias AlertAction = (title: String, handler: () -> ())
class ErrorView: UIView {
func addAction(_ action: AlertAction) {
let button = UIButton()
...
let selector = #selector(action.handler) //error happens here
button.addTarget(self, action: selector, for: .touchUpInside)
}
}
I'm getting an error on this line:
let selector = #selector(action.handler)
which is "Argument of '#selector' does not refer to an '#objc' method, property, or initializer"
This makes sense because usually you have to add #objc to your func declaration, but I'm wondering if there's a way to make my closure refer to an #objc method after the fact perhaps by wrapping it in another function.
Is this possible? I don't know how to define an #objc marked closure so I'm not sure.
#selector() is based on Objective-C bridging, since swift closures are non-objective-c, you can't use them.
One alternative solution is wrap your code inside an Objective-C function.
class AlertAction:NSObject {
var title:String?
#objc
func getHandler(sender:Any){
print("hi")
}
}
and use it like:
// Instance of a class
let alertAction = AlertAction()
// Usage
let button = UIButton()
let selector1 = #selector(AlertAction.getHandler)
button.addTarget(alertAction, action: selector1, for: .touchUpInside)
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(...).
How do you addTarget to a UIButton in an NSObject class? Or will that class not work?
class Support: NSObject {
func helpButton(viewController: ProfileVC) {
let helpButton = viewController.helpButton
helpButton.frame.size = CGSizeMake(35.0, 35.0)
helpButton.layer.cornerRadius = helpButton.frame.height/2
helpButton.layer.borderWidth = 0.5
helpButton.layer.borderColor = lightColoredFont.CGColor
helpButton.setTitle("?", forState: .Normal)
helpButton.setTitleColor(lightColoredFont, forState: .Normal)
helpButton.titleLabel?.font = fontSmaller
helpButton.addTarget(self, action: Selector("showOptions"), forControlEvents: .TouchUpInside)
helpButton.center.y = viewController.logoutButton.center.y
helpButton.frame.origin.x = (viewController.view.bounds.width - viewController.logoutButton.frame.maxX)
viewController.view.addSubview(helpButton)
}
func showOptions() {
print("showing")
}
}
The print is not showing. Even if I feed an instantiated support class into the target for the button it will not work. What is the proper way to do this?
In short, no.
NSObject does not inherit from anything in UIKit. Your inheritance should be the other way around. Perhaps you could make a UIButton that has a property of type NSObject to carry some accompanying information?
Look at the UIControl API
func addTarget(_ target: AnyObject?,
action action: Selector,
forControlEvents controlEvents: UIControlEvents)
But what are you trying to do? Normally you would add a button in interface builder and a reference (IBOutlet) to that button from some controller class like UIViewController.
edit
Ah, now I see the problem. Don't use Selector in swift.
This should work.
helpButton.addTarget(self, action: "showOptions", forControlEvents: .TouchUpInside)