Swift использовать аргумент селектора, как закрытие

Мне просто интересно, можно ли передать функцию на действие кнопки (обычно это селектор).

Например, обычно я бы сказал:

UIBarButtonItem(title: "Press", style: .Done, target: self, action: "functionToCall")

func functionToCall() {
    // Do something
}

Но мне было интересно, можно ли сделать что-то вроде:

UIBarButtonItem(title: "Press", style: .Done, target: self, action: {
    // Do Something
})

Причина, по которой я хочу это сделать, состоит в том, что моя функция супер проста, и похоже, что она была бы более аккуратной и более быстрой, чем с акцентом, который они ставят на закрытие.

Ответы

Ответ 1

Здесь обновленное решение для Swift 3.

class BlockBarButtonItem: UIBarButtonItem {
  private var actionHandler: ((Void) -> Void)?

  convenience init(title: String?, style: UIBarButtonItemStyle, actionHandler: ((Void) -> Void)?) {
    self.init(title: title, style: style, target: nil, action: #selector(barButtonItemPressed))
    self.target = self
    self.actionHandler = actionHandler
  }

  convenience init(image: UIImage?, style: UIBarButtonItemStyle, actionHandler: ((Void) -> Void)?) {
    self.init(image: image, style: style, target: nil, action: #selector(barButtonItemPressed))
    self.target = self
    self.actionHandler = actionHandler
  }

  func barButtonItemPressed(sender: UIBarButtonItem) {
    actionHandler?()
  }
}

Ответ 2

Это альтернативное решение без подкласса:

extension UIBarButtonItem {
    private struct AssociatedObject {
        static var key = "action_closure_key"
    }

    var actionClosure: (()->Void)? {
        get {
            return objc_getAssociatedObject(self, &AssociatedObject.key) as? ()->Void
        }
        set {
            objc_setAssociatedObject(self, &AssociatedObject.key, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            target = self
            action = #selector(didTapButton(sender:))
        }
    }

    @objc func didTapButton(sender: Any) {
        actionClosure?()
    }
}

Он использует связанные объекты из среды выполнения Objective-C, чтобы добавить свойство закрытия/блокировки.

При установке он изменяет цель на себя и указывает селектор на новую функцию, которая вызывает замыкание, если она существует.

С этим в любой момент вы можете просто установить actionClosure любого UIBarButtonItem и ожидать, что все будет работать, вот пример:

let button = UIBarButtonItem(
    barButtonSystemItem: .action,
    target: nil, action: nil
)
button.actionClosure = {
    print("hello")
}

Ответ 3

Сообщение о Reddit объясняет решение для этого с помощью настраиваемого компонента - https://www.reddit.com/r/swift/comments/3fjzap/creating_button_action_programatically_using

Чтобы использовать его, хотя я должен был добавить кнопку программно, а не через раскадровку. Вот как я это сделал.

let tempVariableIWantToReference = "Classy"

navigationTitle.leftBarButtonItem = BlockBarButtonItem.init(
   title: "< Back", style: UIBarButtonItemStyle.Plain, 
   actionHandler: { () -> Void in
       print("Hey I'm in a closure")
       print("I can even reference temporary variables. \(self.tempVariableIWantToReference)!!!")
   })

Ответ 4

К сожалению, Apple не может использовать инициализаторы. То, как это работает в фоновом режиме, - это отражение, и обеспечение закрытия - это нечто совершенно другое, которое в настоящее время не поддерживается.

Возможно, вы сможете создать собственное решение с некоторыми хаками или Apple может представить это в будущем.

Ответ 5

  • Обновлено для Swift 5.
  • Реализует все удобные инициализаторы, которые имеют цели.
  • Добавляет typealias для сигнатуры функции для очистки синтаксиса.
  • UIBarButtonItem в обработчик действия в соответствии с соглашениями Cocoa.
import UIKit

class SwiftBarButtonItem: UIBarButtonItem {
    typealias ActionHandler = (UIBarButtonItem) -> Void

    private var actionHandler: ActionHandler?

    convenience init(image: UIImage?, style: UIBarButtonItem.Style, actionHandler: ActionHandler?) {
        self.init(image: image, style: style, target: nil, action: #selector(barButtonItemPressed(sender:)))
        target = self
        self.actionHandler = actionHandler
    }

    convenience init(title: String?, style: UIBarButtonItem.Style, actionHandler: ActionHandler?) {
        self.init(title: title, style: style, target: nil, action: #selector(barButtonItemPressed(sender:)))
        target = self
        self.actionHandler = actionHandler
    }

    convenience init(barButtonSystemItem systemItem: UIBarButtonItem.SystemItem, actionHandler: ActionHandler?) {
        self.init(barButtonSystemItem: systemItem, target: nil, action: #selector(barButtonItemPressed(sender:)))
        target = self
        self.actionHandler = actionHandler
    }

    @objc func barButtonItemPressed(sender: UIBarButtonItem) {
        actionHandler?(sender)
    }
}