Swift programmatically create function for button with a closure
Create your own UIButton
subclass to do this:
class MyButton: UIButton {
var action: (() -> Void)?
func whenButtonIsClicked(action: @escaping () -> Void) {
self.action = action
self.addTarget(self, action: #selector(MyButton.clicked), for: .touchUpInside)
}
// Button Event Handler:
// I have not marked this as @IBAction because it is not intended to
// be hooked up to Interface Builder
@objc func clicked() {
action?()
}
}
Substitute MyButton
for UIButton
when you create buttons programmatically and then call whenButtonIsClicked
to set up its functionality.
You can also use this with UIButton
s in a Storyboard (just change their class to MyButton
) and then call whenButtonIsClicked
in viewDidLoad
.
@IBOutlet weak var theButton: MyButton!
var count = 0
override func viewDidLoad() {
super.viewDidLoad()
// be sure to declare [unowned self] if you access
// properties or methods of the class so that you
// don't create a strong reference cycle
theButton.whenButtonIsClicked { [unowned self] in
self.count += 1
print("count = \(self.count)")
}
A much more capable implementation
Recognizing the fact that programmers might want to handle more events than just .touchUpInside
, I wrote this more capable version which supports multiple closures per UIButton
and multiple closures per event type.
class ClosureButton: UIButton {
private var actions = [UInt : [((UIControl.Event) -> Void)]]()
private let funcDict: [UInt : Selector] = [
UIControl.Event.touchCancel.rawValue: #selector(eventTouchCancel),
UIControl.Event.touchDown.rawValue: #selector(eventTouchDown),
UIControl.Event.touchDownRepeat.rawValue: #selector(eventTouchDownRepeat),
UIControl.Event.touchUpInside.rawValue: #selector(eventTouchUpInside),
UIControl.Event.touchUpOutside.rawValue: #selector(eventTouchUpOutside),
UIControl.Event.touchDragEnter.rawValue: #selector(eventTouchDragEnter),
UIControl.Event.touchDragExit.rawValue: #selector(eventTouchDragExit),
UIControl.Event.touchDragInside.rawValue: #selector(eventTouchDragInside),
UIControl.Event.touchDragOutside.rawValue: #selector(eventTouchDragOutside)
]
func handle(events: [UIControl.Event], action: @escaping (UIControl.Event) -> Void) {
for event in events {
if var closures = actions[event.rawValue] {
closures.append(action)
actions[event.rawValue] = closures
} else {
guard let sel = funcDict[event.rawValue] else { continue }
self.addTarget(self, action: sel, for: event)
actions[event.rawValue] = [action]
}
}
}
private func callActions(for event: UIControl.Event) {
guard let actions = actions[event.rawValue] else { return }
for action in actions {
action(event)
}
}
@objc private func eventTouchCancel() { callActions(for: .touchCancel) }
@objc private func eventTouchDown() { callActions(for: .touchDown) }
@objc private func eventTouchDownRepeat() { callActions(for: .touchDownRepeat) }
@objc private func eventTouchUpInside() { callActions(for: .touchUpInside) }
@objc private func eventTouchUpOutside() { callActions(for: .touchUpOutside) }
@objc private func eventTouchDragEnter() { callActions(for: .touchDragEnter) }
@objc private func eventTouchDragExit() { callActions(for: .touchDragExit) }
@objc private func eventTouchDragInside() { callActions(for: .touchDragInside) }
@objc private func eventTouchDragOutside() { callActions(for: .touchDragOutside) }
}
Demo
class ViewController: UIViewController {
var count = 0
override func viewDidLoad() {
super.viewDidLoad()
let button = ClosureButton(frame: CGRect(x: 50, y: 100, width: 60, height: 40))
button.setTitle("press me", for: .normal)
button.setTitleColor(.blue, for: .normal)
// Demonstration of handling a single UIControl.Event type.
// If your closure accesses self, be sure to declare [unowned self]
// to prevent a strong reference cycle
button.handle(events: [.touchUpInside]) { [unowned self] _ in
self.count += 1
print("count = \(self.count)")
}
// Define a second handler for touchUpInside:
button.handle(events: [.touchUpInside]) { _ in
print("I'll be called on touchUpInside too")
}
let manyEvents: [UIControl.Event] = [.touchCancel, .touchUpInside, .touchDown, .touchDownRepeat, .touchUpOutside, .touchDragEnter,
.touchDragExit, .touchDragInside, .touchDragOutside]
// Demonstration of handling multiple events
button.handle(events: manyEvents) { event in
switch event {
case .touchCancel:
print("touchCancel")
case .touchDown:
print("touchDown")
case .touchDownRepeat:
print("touchDownRepeat")
case .touchUpInside:
print("touchUpInside")
case .touchUpOutside:
print("touchUpOutside")
case .touchDragEnter:
print("touchDragEnter")
case .touchDragExit:
print("touchDragExit")
case .touchDragInside:
print("touchDragInside")
case .touchDragOutside:
print("touchDragOutside")
default:
break
}
}
self.view.addSubview(button)
}
}
Adding a closure as target to a UIButton
Do Not Use This Answer, See Note Below
NOTE:
like @EthanHuang said
"This solution doesn't work if you have more than two instances. All actions will be overwrite by the last assignment."
Keep in mind this when you develop, i will post another solution soon.
If you want to add a closure as target to a UIButton
, you must add a function to UIButton
class by using extension
Swift 5
import UIKit
extension UIButton {
private func actionHandler(action:(() -> Void)? = nil) {
struct __ { static var action :(() -> Void)? }
if action != nil { __.action = action }
else { __.action?() }
}
@objc private func triggerActionHandler() {
self.actionHandler()
}
func actionHandler(controlEvents control :UIControl.Event, ForAction action:@escaping () -> Void) {
self.actionHandler(action: action)
self.addTarget(self, action: #selector(triggerActionHandler), for: control)
}
}
Older
import UIKit
extension UIButton {
private func actionHandleBlock(action:(() -> Void)? = nil) {
struct __ {
static var action :(() -> Void)?
}
if action != nil {
__.action = action
} else {
__.action?()
}
}
@objc private func triggerActionHandleBlock() {
self.actionHandleBlock()
}
func actionHandle(controlEvents control :UIControlEvents, ForAction action:() -> Void) {
self.actionHandleBlock(action)
self.addTarget(self, action: "triggerActionHandleBlock", forControlEvents: control)
}
}
and the call:
let button = UIButton()
button.actionHandle(controlEvents: .touchUpInside,
ForAction:{() -> Void in
print("Touch")
})
Make a UIButton programmatically in Swift
You're just missing the colon at the end of the selector name. Since pressed takes a parameter the colon must be there. Also your pressed function shouldn't be nested inside viewDidLoad.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let myFirstLabel = UILabel()
let myFirstButton = UIButton()
myFirstLabel.text = "I made a label on the screen #toogood4you"
myFirstLabel.font = UIFont(name: "MarkerFelt-Thin", size: 45)
myFirstLabel.textColor = .red
myFirstLabel.textAlignment = .center
myFirstLabel.numberOfLines = 5
myFirstLabel.frame = CGRect(x: 15, y: 54, width: 300, height: 500)
myFirstButton.setTitle("✸", for: .normal)
myFirstButton.setTitleColor(.blue, for: .normal)
myFirstButton.frame = CGRect(x: 15, y: -50, width: 300, height: 500)
myFirstButton.addTarget(self, action: #selector(pressed), for: .touchUpInside)
}
@objc func pressed() {
var alertView = UIAlertView()
alertView.addButtonWithTitle("Ok")
alertView.title = "title"
alertView.message = "message"
alertView.show()
}
EDIT: Updated to reflect best practices in Swift 2.2. #selector() should be used rather than a literal string which is deprecated.
Execute code when pressing button created programmatically in function
What you are looking for are closures:
var some_code_block = {
print("my awesome code block!")
}
Put this at a higher / global scope, then you can run / change the block at will, simply by adding '()' at the end of the closure (variable name)
func addCode() {
// Wont execute:
some_code_block = { print("new code inserted") }
}
func buttonPressed() {
// Executes:
some_code_block()
}
Personally, I use arrays / dictionaries to store / update code blocks on the fly.. then you can iterate through / match indices/keys to get the code block you want.
https://developer.apple.com/library/mac/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html
If your problem is actually making different buttons, then you can store your newly created instance of a UI button into an array or dictionary
[UIButton]
An array / dictionary of buttons, matching with an array / dictionary of code blocks can certainly work... You just have to make sure they are in the right scope / and synchronize indices / keys.
Or, you could create a class / struct that holds a UIButton instance, and a code block, then put the class / struct into an array / dictionary
// Make your own initializer for your purposes
struct ButtonStuff {
var button: UIButton?
var code_block: ()?
}
// Make empty array that holds type ButtonStuff (global / outer scope)
var buttons: [ButtonStuff] = []
func addButton(pram1:Pram1, pram2:Pram2) {
// Make a new button (called locally inside of a func)
var new_button = ButtonStuff()
// Put stuff in here...
// new_button.button = pram1
// new_button.code_block = pram2
// Add our locally made new_button to the Global buttons array
buttons.append(new_buttons)
}
NOTE: The pram1, pram2 can be whatever you want, but to store to the code block (without running it) you want type ()
. If you want to automatically run the code block, you would use type ()->()
There are many ways of doing the above (you don't have to use optionals); just a quick example. You would then just have to make some logic / algo to find the right button, display it, then call the code block.
If you have any questions, need me to edit my answer, please leave a message in the comments with @fluidity
Custom anonymous closure navigation back button inside of UiView()
First add a callback function in the CustomView
. Then call this callback
closure from goToBack()
method.
class CustomView: UIView {
var backButtonTapped: (() -> Void)?
lazy var backButton: UIButton = {
let button = UIButton(type: .system)
let image = UIImage(systemName: "chevron.left")
button.setImage(image, for: UIControl.State())
button.tintColor = .white
button.isHidden = true
button.addTarget(self, action: #selector(goToBack), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
@objc func goToBack() {
backButtonTapped?()
}
}
In UIViewController
where you initialise this CustomView
, give the action of the closure.
let view = CustomView()
view.backButtonTapped = { [weak self] in
self?.navigationController?.popViewController(animated: true)
}
Adding a target to a button programmatically throws an error unrecognized selector sent to class
It's actually all a matter of timing. You are declaring an instance property along with a define-and-call function that initializes that property, and you proceed to say button.addTarget(self...
in that function. The question then is: when will that function's code run? Incredibly, that matters a lot, so much so that it actually changes the meaning of self
.
When you say let
to make this declaration, the code runs at a time when the instance does not yet exist. The instance is exactly what we are in the process of forming at that moment; it isn't "cooked" yet. In fact, unbeknownst to you, self
at that moment means the UIButton class! Therefore the resulting target-action is invalid; you have no class method postComment
; it's an instance method (rightly). And so later on when you tap the button and we try to say postComment
to the class, we crash. "Unrecognized selector sent to class", exactly as you say.
On the other hand, when you say lazy var
, the code is not called until after the instance self
does exist, self
means the instance, and all is well.
It's terribly confusing to beginners, and to not-so-beginners; even after pointing out the problem, I've proceeded to make exactly the same mistake myself. In my opinion the compiler should catch this and stop you, but it doesn't. I've filed a bug: http://bugs.swift.org/browse/SR-4865
Related Topics
How to Add Documentation to Enum Associated Values in Swift
Swift Codable with Different Array Types
How to Parse Firestore Fieldvalue to Date in Swift
How to Pass Binding to Child View in the New Navigationstack.Navigationdestination
How to Calculate the Energy Per Bin in a Dft
Swift: How to Fix Infinite Loop When Adding a Value to a Firebase Variable
Crop/Mask Circular Image Node in Sprite Kit Gives Jagged Edges
How to Avoid Duplicate Key Error in Swift When Iterating Over a Dictionary
How to Find the Time Interval Remaining from Nstimer
How to Get Rid of Array Brackets While Printing
How to Delete Item from Collection View
Get Nil When Looking for File in Subdirectory of Main Bundle
How to Open to a Specific View Using Home Quick Actions
Why Does My @Lazy Property Crash, But If I Make It Non Lazy It Works
Animate an Skspritenode with Textures That Have a Size Different from the Original
Inline Kvo of a Property in Another View Controller
Swift MAC App, Run Terminal Command Without Knowing the Path (So It Looks in Every Path in $Path)
How to Add a Left Bar Button Without Overriding the Natural Back Button