We can initialize a new UIButton this way:
// myAction is a var of type UIAction
let btn = UIButton(primaryAction: myAction)
So I'm thinking cool, I can subclass this and stop using target-action. But here's the problem, Xcode can't seem to recognize the primaryAction initializer.
class Button: UIButton {
init(type: String, action: UIAction) {
super.init(primaryAction: action) // Error "Must call a designated initializer of the superclass 'UIButton'"
}
}
So it's not a designated initializer. How can I access this primaryAction property inside a UIButton subclass?
Because you can't initialize a superclass within a custom initializer with one of its convenience initializers, you have to perform the tasks of that convenience init manually. So after you've initialized the superclass with one of its designated inits:
class Button: UIButton {
init(type: String, action: UIAction) {
super.init(frame: .zero)
// register the action for the primaryActionTriggered control event
// and set the title and image properties to the action’s title and image
}
}
And it's not as straightforward as changing the value of a single property.
You set up a control so that it sends an action message to a target
object by associating both target and action with one or more control
events. To do this, send addTarget(_:action:for:): to the control for
each Target-Action pair you want to specify.
https://developer.apple.com/documentation/uikit/uicontrol/event
I figured it out. We can use addAction instead of addTarget. Requires iOS 14+.
func addAction(_ action: UIAction, for controlEvents: UIControl.Event) is available on all subclass of UIControl. Meaning we no longer have to scatter targets and #objc func all over our code. Just add a UIAction to your control.
class Button: UIButton {
init(type: String, action: UIAction) {
super.init(frame: .zero)
addAction(action, for: .touchUpInside)
}
}
Related
I want to add a custom init to the UIButton class using an extension
extension UIButton {
init() {
backgroundColor = InterfaceDesign.button.backgroundColor
setTitleColor(InterfaceDesign.button.textColor, for: .normal)
}
}
The first problem is this error:
Designated initializer cannot be declared in an extension of 'UIButton'; did you mean this to be a convenience initializer?
Then if I click the red white dot to fix it, it inserts a convenience in front like so:
convenience init()
But then this error appers:
Initializer 'init()' with Objective-C selector 'init' conflicts with implicit initializer 'init()' with the same Objective-C selector
I already tried googling it but only found working code for subclasses of uibutton...
Thanks for your help
You can't do what you are trying to do. There are 2 interacting reasons for this.
You can't change a class' designated initializer from an extension.
You can create a convenience initializer in an extension, but a convenience initializer can't be an override of an existing initializer.
You are trying to redefine init(). You can't do that. You would need to create a convenience initializer that has different parameters than any existing initializer, and that convenience initializer needs to ultimately call the designated initializer.
struct Dummy { }
extension UIButton {
convenience init(dummy: Dummy) {
self.init()
backgroundColor = InterfaceDesign.button.backgroundColor
setTitleColor(InterfaceDesign.button.textColor, for: .normal)
}
}
UIButton(dummy: Dummy())
Even though the above code formally answers the question, consider doing this instead:
extension UIButton {
static var dummy: UIButton {
let button = UIButton()
button.backgroundColor = InterfaceDesign.button.backgroundColor
button.setTitleColor(InterfaceDesign.button.textColor, for: .normal)
return button
}
}
UIButton.dummy
I'm trying to create a custom class that creates a button. I'm having trouble adding a target to that button inside it's class. This is my code
class SelectButton{
var button:UIButton = UIButton()
init(button_frame: CGRect, button_title: String, connected: [UIButton]?){
self.button.frame = button_frame
self.button.setTitle(button_title, for: UIControlState.normal)
self.button.addTarget(self, action:#selector(self.buttonPressed), for: .touchUpInside)
}
func construct() -> UIButton {
return self.button
}
#objc func buttonPressed() {
print("Button Clicked")
}
}
The problem is that I can't connect an action on button click. This works if it's used outside my class but not inside.
Usage of the class
let test = SelectButton(button_frame: CGRect(x:50, y:50, width: 250, height:150), button_title: "Test button", connected: nil).construct()
self.view.addSubview(test)
When someone taps the button, usually you want something to happen somewhere else in your app (like in one of your view controllers or in some other UI element). The way the IBAction is set up right now, you have it so that something will trigger or happen within the button itself when someone taps on it. If you want to handle a button tap programmatically instead of ctrl dragging from the button into the view controller, you can do it this way if you prefer. First, add this code into the view controller:
#IBAction func buttonPressed(sender: UIButton) {
}
Then you can either add the selector programmatically by adding this method into your view controller:
myButton.addTarget(self, action:self.buttonPressed(sender), for: .touchUpInside)
Or by going to the connections inspector and dragging from the touch up inside over to the IBAction dot in your view controller code. Also, as someone else pointed out in the comments you should make your button inherit from UIButton by adding this to your class declaration:
class SelectButton: UIButton {
. . .
}
Nothing is holding a strong reference to your SelectButton instance, so as soon as the function that creates test exits, that instance is released.
The button itself is retained because you have added it as a subview. Therefore, it is still visible but there is no longer an object to respond to the action.
You either need to use an instance property rather than a local variable for test, or, preferably have SelectButton inherit directly from UIButton
I just want to write one or two lines of code on button click like
print("Button Clicked")
for this i dont want to create a seperate function and call via selector
as
action: #selector(BtnKlkFnc(_:))
I want to simplify like
action: { action in print("Button Clicked")}
I also tried
#selector({print("Button Clicked")})
Can anyone help me to simplify this
Am new to stackoverflow and do not have enough reputations yet, So kindly vote for my question up, so i can vote for your ans
Short answer: You can't do that. Button actions are part of the target/action mechanism built into Cocoa/Cocoa touch. It's based on selectors, and you must create a named method and use it's selector. You can't use a Swift closure as a button action.
EDIT:
Note that it is possible to create a custom subclass of UIButton that has a closure property and invokes that closure when the button is tapped. What you'd do is to make the button's init method set itself up as the target of a touchUpInside event and invoke a method of the button that in turn invokes your closure (after making sure the closure property isn't nil.)
EDIT #2:
Note that it is pretty straightforward to create a custom subclass of UIButton that sets itself up as the target for button presses and keeps a closure.
Here is a sample implementation:
class ClosureButton: UIButton {
var buttonClosure: ((UIButton) -> Void)?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addTarget(self, action: #selector(handleTap(_:)), for: .touchUpInside)
}
#objc func handleTap(_ sender: UIButton) {
if let buttonClosure = buttonClosure {
buttonClosure(sender)
} else {
print("No button closure defined")
return
}
}
}
And in your view controller:
override func viewDidLoad() {
super.viewDidLoad()
button.buttonClosure = { _ in
print("You tapped the button")
}
}
I want to subclass UITapGestureRecognizer as TapRecognizer so that navigation between pages within my app are handled in a standardised way:
Dragging a UITapGestureRecognizer onto any navigational elements in the storyboard, setting their class as TapRecognizer, and referencing them within the View Controller as an IBOutlet (#IBOutlet var heroTapRecognizer: TapRecognizer!)
Initialising them like so:
self.heroTapRecognizer = TapRecognizer.init(pageId: 1, pageType: PageType.CategoryPage)
Then in TapRecognizer.swift:
class TapRecognizer: UITapGestureRecognizer {
var pageId:Int!
var pageType:PageType!
convenience init(pageId: Int, pageType: PageType) {
self.init()
self.pageId = pageId
self.pageType = pageType
self.addTarget(self, action: #selector(TapRecognizer.handleTap(_:)))
}
func handleTap(sender: UITapGestureRecognizer) {
if sender.state == .Ended {
print("Handle navigation based on pageId + pageType")
}
}
}
But the above isn't working. I'm new to Swift and have only previously used UITapGestureRecognizer's programmatically.
Note: User interaction is enabled on the UIView that the recogniser is associated with.
Dragging a UITapGestureRecognizer onto any navigational elements in the storyboard, setting their class as TapRecognizer, and referencing them within the View Controller as an IBOutlet
Okay, but then your init will never be called. If you want something special to happen, implement awakeFromNib.
Alternatively, implement init(coder:). For some reason this is not documented, but it is the initializer that is actually called.
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(...).