Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
389 views
in Technique[技术] by (71.8m points)

ios - Hooking up UIButton to closure? (Swift, target-action)

I want to hook up a UIButton to a piece of code –?from what I have found, the preferred method to do this in Swift is still to use the addTarget(target: AnyObject?, action: Selector, forControlEvents: UIControlEvents) function. This uses the Selector construct presumably for backwards compatibility with Obj-C libraries. I think I understand the reason for @selector in Obj-C –?being able to refer to a method since in Obj-C methods are not first-class values.

In Swift though, functions are first-class values. Is there a way to connect a UIButton to a closure, something similar to this:

// -- Some code here that sets up an object X

let buttonForObjectX = UIButton() 

// -- configure properties here of the button in regards to object
// -- for example title

buttonForObjectX.addAction(action: {() in 

  // this button is bound to object X, so do stuff relevant to X

}, forControlEvents: UIControlEvents.TouchUpOutside)

To my knowledge, the above is currently not possible. Considering that Swift looks like it's aiming to be a quite functional, why is this? The two options could clearly co-exist for backwards compatibility. Why doesn't this work more like onClick() in JS? It seems that the only way to hook up a UIButton to a target-action pair is to use something that exists solely for backwards compatibility reasons (Selector).

My use case is to create UIButtons in a loop for different objects, and then hook each up to a closure. (Setting a tag / looking up in a dictionary / subclassing UIButton are dirty semi-solutions, but I'm interested in how to do this functionally, ie this closure approach)

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You can replace target-action with a closure by adding a helper closure wrapper (ClosureSleeve) and adding it as an associated object to the control so it gets retained.

This is a similar solution to the one in n13's answer. But I find it simpler and more elegant. The closure is invoked more directly and the wrapper is automatically retained (added as an associated object).

Swift 3 and 4

class ClosureSleeve {
    let closure: () -> ()

    init(attachTo: AnyObject, closure: @escaping () -> ()) {
        self.closure = closure
        objc_setAssociatedObject(attachTo, "[(arc4random())]", self, .OBJC_ASSOCIATION_RETAIN)
    }

    @objc func invoke() {
        closure()
    }
}

extension UIControl {
    func addAction(for controlEvents: UIControlEvents = .primaryActionTriggered, action: @escaping () -> ()) {
        let sleeve = ClosureSleeve(attachTo: self, closure: action)
        addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents)
    }
}

Usage:

button.addAction {
    print("Hello")
}

It automatically hooks to the .primaryActionTriggered event which equals to .touchUpInside for UIButton.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...