ビューコントローラーに応じてボタンの完了を設定する必要がある汎用コントロールクラスがあります。そのため、setLeftButtonActionWithClosure関数は、ボタンとしてのアクションとして設定されるクロージャーをパラメーターとして受け取る必要があります。 Swiftアクション名:パラメータに関数名を文字列として渡す必要があるため。
func setLeftButtonActionWithClosure(completion: () -> Void)
{
self.leftButton.addTarget(<#target: AnyObject?#>, action: <#Selector#>, forControlEvents: <#UIControlEvents#>)
}
注:@EthanHuangが言ったように、「2つ以上のインスタンスがある場合、このソリューションは機能しません。すべてのアクションは、最後の割り当てによって上書きされます。」もうすぐ別のソリューション。
クロージャーをターゲットとしてUIButton
に追加する場合は、UIButton
を使用して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)
}
}
旧
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)
}
}
そして呼び出し:
let button = UIButton()
button.actionHandle(controlEvents: UIControlEvents.TouchUpInside,
ForAction:{() -> Void in
print("Touch")
})
すでにリストされているものと同様のソリューションですが、おそらくより軽量です:
class ClosureSleeve {
let closure: ()->()
init (_ closure: @escaping ()->()) {
self.closure = closure
}
@objc func invoke () {
closure()
}
}
extension UIControl {
func addAction(for controlEvents: UIControl.Event, _ closure: @escaping ()->()) {
let sleeve = ClosureSleeve(closure)
addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents)
objc_setAssociatedObject(self, "[\(arc4random())]", sleeve, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
使用法:
button.addAction(for: .touchUpInside) {
print("Hello, Closure!")
}
または、保持ループを回避する場合:
self.button.addAction(for: .touchUpInside) { [unowned self] in
self.doStuff()
}
ユーザーMH175は、「コントロールのallTargetsプロパティ:static Set_unnconditionallyBridgeFromObjectiveC(_ :) –」を使用すると、実行時例外が発生することに言及しています。 ClosureSleeveをNSObjectから拡張すると、問題が解決します。
@objc class ClosureSleeve: NSObject {
let closure: ()->()
init (_ closure: @escaping ()->()) {
self.closure = closure
super.init()
}
@objc func invoke () {
closure()
}
}
UIButtonをサブクラス化することで、これを効果的に実現できます。
class ActionButton: UIButton {
var touchDown: ((button: UIButton) -> ())?
var touchExit: ((button: UIButton) -> ())?
var touchUp: ((button: UIButton) -> ())?
required init?(coder aDecoder: NSCoder) { fatalError("init(coder:)") }
override init(frame: CGRect) {
super.init(frame: frame)
setupButton()
}
func setupButton() {
//this is my most common setup, but you can customize to your liking
addTarget(self, action: #selector(touchDown(_:)), forControlEvents: [.TouchDown, .TouchDragEnter])
addTarget(self, action: #selector(touchExit(_:)), forControlEvents: [.TouchCancel, .TouchDragExit])
addTarget(self, action: #selector(touchUp(_:)), forControlEvents: [.TouchUpInside])
}
//actions
func touchDown(sender: UIButton) {
touchDown?(button: sender)
}
func touchExit(sender: UIButton) {
touchExit?(button: sender)
}
func touchUp(sender: UIButton) {
touchUp?(button: sender)
}
}
使用する:
let button = ActionButton(frame: buttonRect)
button.touchDown = { button in
print("Touch Down")
}
button.touchExit = { button in
print("Touch Exit")
}
button.touchUp = { button in
print("Touch Up")
}
これは基本的に Armanoide's の回答ですが、私にとって便利ないくつかのわずかな変更があります:
UIButton
引数を取ることができ、self
を渡すことができます関数と引数の名前は、たとえばSwiftクロージャーをUIButton
アクションから区別することによって)何が起こっているかを明確にするように名前が変更されます。
private func setOrTriggerClosure(closure:((button:UIButton) -> Void)? = nil) {
//struct to keep track of current closure
struct __ {
static var closure :((button:UIButton) -> Void)?
}
//if closure has been passed in, set the struct to use it
if closure != nil {
__.closure = closure
} else {
//otherwise trigger the closure
__. closure?(button: self)
}
}
@objc private func triggerActionClosure() {
self.setOrTriggerClosure()
}
func setActionTo(closure:(UIButton) -> Void, forEvents :UIControlEvents) {
self.setOrTriggerClosure(closure)
self.addTarget(self, action:
#selector(UIButton.triggerActionClosure),
forControlEvents: forEvents)
}
ここでいくつかの強力な魔法のためにアルマノイデに多くの小道具。
私はArmanoideの回答を使用し始めました。これは、2番目の割り当てによってオーバーライドされるという事実を無視しています。主に、最初は重要ではない特定の場所で必要だったためです。しかし、それはバラバラになり始めました。
AssicatedObjectsを使用して新しい実装を思い付きました。この制限はありません。よりスマートな構文を持っていると思いますが、完全な解決策ではありません。
ここにあります:
typealias ButtonAction = () -> Void
fileprivate struct AssociatedKeys {
static var touchUp = "touchUp"
}
fileprivate class ClosureWrapper {
var closure: ButtonAction?
init(_ closure: ButtonAction?) {
self.closure = closure
}
}
extension UIControl {
@objc private func performTouchUp() {
guard let action = touchUp else {
return
}
action()
}
var touchUp: ButtonAction? {
get {
let closure = objc_getAssociatedObject(self, &AssociatedKeys.touchUp)
guard let action = closure as? ClosureWrapper else{
return nil
}
return action.closure
}
set {
if let action = newValue {
let closure = ClosureWrapper(action)
objc_setAssociatedObject(
self,
&AssociatedKeys.touchUp,
closure as ClosureWrapper,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
self.addTarget(self, action: #selector(performTouchUp), for: .touchUpInside)
} else {
self.removeTarget(self, action: #selector(performTouchUp), for: .touchUpInside)
}
}
}
}
ご覧のとおり、touchUpInside
専用のケースを作成することにしました。コントロールにはこれよりも多くのイベントがありますが、だれが冗談でしょうか?それらのすべてに対してアクションが必要ですか?!この方法ははるかに簡単です。
使用例:
okBtn.touchUp = {
print("OK")
}
いずれにせよ、この答えを拡張したい場合は、すべてのイベントタイプに対してアクションのSet
を作成するか、他のイベントにイベントのプロパティを追加することができます。これは比較的簡単です。
乾杯、M。
UIControlおよびUIGestureRecognizerのSwift 4.2、およびSwift拡張ストアドプロパティパラダイムによるターゲットの削除。
セレクターのラッパークラス
class Target {
private let t: () -> ()
init(target t: @escaping () -> ()) { self.t = t }
@objc private func s() { t() }
public var action: Selector {
return #selector(s)
}
}
非表示にできるassociatedtype
sを持つプロトコルobjc_
コード
protocol PropertyProvider {
associatedtype PropertyType: Any
static var property: PropertyType { get set }
}
protocol ExtensionPropertyStorable: class {
associatedtype Property: PropertyProvider
}
プロパティをデフォルトで使用可能にする拡張機能
extension ExtensionPropertyStorable {
typealias Storable = Property.PropertyType
var property: Storable {
get { return objc_getAssociatedObject(self, String(describing: type(of: Storable.self))) as? Storable ?? Property.property }
set { return objc_setAssociatedObject(self, String(describing: type(of: Storable.self)), newValue, .OBJC_ASSOCIATION_RETAIN) }
}
}
魔法をかけましょう
extension UIControl: ExtensionPropertyStorable {
class Property: PropertyProvider {
static var property = [String: Target]()
}
func addTarget(for controlEvent: UIControl.Event = .touchUpInside, target: @escaping () ->()) {
let key = String(describing: controlEvent)
let target = Target(target: target)
addTarget(target, action: target.action, for: controlEvent)
property[key] = target
}
func removeTarget(for controlEvent: UIControl.Event = .touchUpInside) {
let key = String(describing: controlEvent)
let target = property[key]
removeTarget(target, action: target?.action, for: controlEvent)
property[key] = nil
}
}
そしてジェスチャーに
extension UIGestureRecognizer: ExtensionPropertyStorable {
class Property: PropertyProvider {
static var property: Target?
}
func addTarget(target: @escaping () -> ()) {
let target = Target(target: target)
addTarget(target, action: target.action)
property = target
}
func removeTarget() {
let target = property
removeTarget(target, action: target?.action)
property = nil
}
}
使用例:
button.addTarget {
print("touch up inside")
}
button.addTarget { [weak self] in
print("this will only happen once")
self?.button.removeTarget()
}
button.addTarget(for: .touchDown) {
print("touch down")
}
slider.addTarget(for: .valueChanged) {
print("value changed")
}
textView.addTarget(for: .allEditingEvents) { [weak self] in
self?.editingEvent()
}
gesture.addTarget { [weak self] in
self?.gestureEvent()
self?.otherGestureEvent()
self?.gesture.removeTarget()
}
既にリストされているものと同様のソリューションですが、おそらくより軽量で、ランダム性に依存せずに一意のIDを生成します。
class ClosureSleeve {
let closure: ()->()
init (_ closure: @escaping ()->()) {
self.closure = closure
}
@objc func invoke () {
closure()
}
}
extension UIControl {
func add (for controlEvents: UIControlEvents, _ closure: @escaping ()->()) {
let sleeve = ClosureSleeve(closure)
addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents)
objc_setAssociatedObject(self, String(ObjectIdentifier(self).hashValue) + String(controlEvents.rawValue), sleeve,
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
使用法:
button.add(for: .touchUpInside) {
print("Hello, Closure!")
}
私の解決策。
typealias UIAction = () -> Void;
class Button: UIButton {
public var touchUp :UIAction? {
didSet {
self.setup()
}
}
func setup() -> Void {
self.addTarget(self, action: #selector(touchInside), for: .touchUpInside)
}
@objc private func touchInside() -> Void {
self.touchUp!()
}
}
Swift
すべてのソリューションを試した後、再利用可能なテーブルビューセルのボタンが
import UIKit
typealias UIButtonTargetClosure = UIButton -> ()
class ClosureWrapper: NSObject {
let closure: UIButtonTargetClosure
init(_ closure: UIButtonTargetClosure) {
self.closure = closure
}
}
extension UIButton {
private struct AssociatedKeys {
static var targetClosure = "targetClosure"
}
private var targetClosure: UIButtonTargetClosure? {
get {
guard let closureWrapper = objc_getAssociatedObject(self, &AssociatedKeys.targetClosure) as? ClosureWrapper else { return nil }
return closureWrapper.closure
}
set(newValue) {
guard let newValue = newValue else { return }
objc_setAssociatedObject(self, &AssociatedKeys.targetClosure, ClosureWrapper(newValue), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
func addTargetClosure(closure: UIButtonTargetClosure) {
targetClosure = closure
addTarget(self, action: #selector(UIButton.closureAction), forControlEvents: .TouchUpInside)
}
func closureAction() {
guard let targetClosure = targetClosure else { return }
targetClosure(self)
}
}
そして、次のように呼び出します:
loginButton.addTargetClosure { _ in
// login logics
}
もう1つの最適化(多くの場所で使用し、objc_setAssociatedObject
への呼び出しを複製したくない場合に便利です)。これにより、objc_setAssociatedObject
のダーティーな部分を心配せずに、ClosureSleeve
のコンストラクター内に保持できます。
class ClosureSleeve {
let closure: () -> Void
init(
for object: AnyObject,
_ closure: @escaping () -> Void
) {
self.closure = closure
objc_setAssociatedObject(
object,
String(format: "[%d]", arc4random()),
self,
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
)
}
@objc func invoke () {
closure()
}
}
そのため、拡張機能はごくわずかに見えます。
extension UIControl {
func add(
for controlEvents: UIControlEvents,
_ closure: @escaping ()->()
) {
let sleeve = ClosureSleeve(
for: self,
closure
)
addTarget(
sleeve,
action: #selector(ClosureSleeve.invoke),
for: controlEvents
)
}
}
class ViewController : UIViewController {
var aButton: UIButton!
var assignedClosure: (() -> Void)? = nil
override func loadView() {
let view = UIView()
view.backgroundColor = .white
aButton = UIButton()
aButton.frame = CGRect(x: 95, y: 200, width: 200, height: 20)
aButton.backgroundColor = UIColor.red
aButton.addTarget(self, action: .buttonTapped, for: .touchUpInside)
view.addSubview(aButton)
self.view = view
}
func fizzleButtonOn(events: UIControlEvents, with: @escaping (() -> Void)) {
assignedClosure = with
aButton.removeTarget(self, action: .buttonTapped, for: .allEvents)
aButton.addTarget(self, action: .buttonTapped, for: events)
}
@objc func buttonTapped() {
guard let closure = assignedClosure else {
debugPrint("original tap")
return
}
closure()
}
}
fileprivate extension Selector {
static let buttonTapped = #selector(ViewController.buttonTapped)
}
次に、アプリのライフサイクルのある時点で、インスタンスのクロージャを変更します。ここに例があります
fizzleButtonOn(events: .touchUpInside, with: { debugPrint("a new tap action") })