Passing param value to selector - ios

I am declaring a selector and want to pass a label object as the parameter and not able to do so.
Below is my code:
public func animateLabelText(label: UILabel) {
caDisplayLink = CADisplayLink(target: self, selector: #selector(changeLabelText(label: label)))
}
#objc private func changeLabelText(label: UILabel) {
// Do something
}
I am seeing this error - Argument of '#selector' does not refer to an '#objc' method, property, or initializer.
How do I pass the parameter to selector?

The signature should be as following
public func animateLabelText(label: UILabel) {
caDisplayLink = CADisplayLink(target: self,
selector: #selector(changeLabelText(_:)))
}
#objc private func changeLabelText(_ label: UILabel) {
// Do something
}

Related

Must Selector method be in the same instances as where the observer is?

Normally, the code addObserver both the Selector method tag with #Objc are coded in the same instance (instantiated class).
It is possible to pass a Selector from different instance to the addObserver?
The reason for doing this is because Selector behavior as a callback most of the time. Some of the callback methods are commonly used and could well be coded into a CommonCallBack Class, an example of usage would be like this:
class SomeViewController{
override func viewDidLoad() {
...
let common = CommonCallback()
NotificationCenter.default.addObserver(
self,
selector: #selector(common.methodA),
name: "notificationName",
object: nil
)
}
}
class CommonCallback{
#Objc func methodA() {
// doing A
}
}
The issue is I keep getting unrecognized selector sent to instance
You can also achieve this by doing this way
class SomeViewController {
override func viewDidLoad() {
let common = CommonCallback()
common.enableObserver = true
}
}
class CommonCallback{
var enableObserver : Bool!
override func viewDidLoad() {
if enableObserver {
NotificationCenter.default.addObserver(
self,
selector: #selector(common.methodA),
name: "notificationName",
object: nil
)
}
}
#objc func methodA() {
// Your code here
}
}
I found out, where went wrong if the code. The observer argument must be in the same class as the Selector method, so instead of:
NotificationCenter.default.addObserver(
self,
selector: #selector(common.methodA),
name: "notificationName",
object: nil
)
it should be:
NotificationCenter.default.addObserver(
common,
selector: #selector(common.methodA),
name: "notificationName",
object: nil
)
This will call CommonCallback.methodA instead of SomeViewController.methodA

How to call a block of code in #selector? [duplicate]

I want to make a selector argument of my method refer to a closure property, both of them exist in the same scope. For example,
func backgroundChange() {
self.view.backgroundColor = UIColor.blackColor()
self.view.alpha = 0.55
let backToOriginalBackground = {
self.view.backgroundColor = UIColor.whiteColor()
self.view.alpha = 1.0
}
NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(backToOriginalBackground), userInfo: nil, repeats: false)
}
However, this shows an error: Argument of #selector cannot refer to a property.
Of course I can define a new, separate method and move the implementation of the closure to it, but I want to keep it frugal for such a small implementation.
Is it possible to set a closure to #selector argument?
Not directly, but some workarounds are possible. Take a look at the following example.
/// Target-Action helper.
final class Action: NSObject {
private let _action: () -> ()
init(action: #escaping () -> ()) {
_action = action
super.init()
}
#objc func action() {
_action()
}
}
let action1 = Action { print("action1 triggered") }
let button = UIButton()
button.addTarget(action1, action: #selector(action1.action), forControlEvents: .TouchUpInside)
I tried this for UIBarButtonItem at least:
private var actionKey: Void?
extension UIBarButtonItem {
private var _action: () -> () {
get {
return objc_getAssociatedObject(self, &actionKey) as! () -> ()
}
set {
objc_setAssociatedObject(self, &actionKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
convenience init(title: String?, style: UIBarButtonItemStyle, action: #escaping () -> ()) {
self.init(title: title, style: style, target: nil, action: #selector(pressed))
self.target = self
self._action = action
}
#objc private func pressed(sender: UIBarButtonItem) {
_action()
}
}
Then you can do this:
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Test", style: .plain, action: {
print("Hello World!")
})
As #gnasher729 notes, this is not possible because selectors are just names of methods, not methods themselves. In the general case, I'd use dispatch_after here, but in this particular case, the better tool IMO is UIView.animateWithDuration, because it's exactly what that function is for, and it's very easy to tweak the transition:
UIView.animateWithDuration(0, delay: 0.5, options: [], animations: {
self.view.backgroundColor = UIColor.whiteColor()
self.view.alpha = 1.0
}, completion: nil)
It is now possible. I've created a gist for block-based selectors in Swift 4.
https://gist.github.com/cprovatas/98ff940140c8744c4d1f3bcce7ba4543
Usage:
UIButton().addTarget(Selector, action: Selector { debugPrint("my code here") }, for: .touchUpInside)`
You can use ActionClosurable which support UIControl, UIButton, UIRefreshControl, UIGestureRecognizer and UIBarButtonItem.
https://github.com/takasek/ActionClosurable
Bellow show example of UIBarButtonItem
// UIBarButtonItem
let barButtonItem = UIBarButtonItem(title: "title", style: .plain) { _ in
print("barButtonItem title")
}
#werediver's answer is excellent. Here's an update that allows you to call it as a function.
import Foundation
public extension Selector {
/// Wraps a closure in a `Selector`.
/// - Note: Callable as a function.
final class Perform: NSObject {
public init(_ perform: #escaping () -> Void) {
self.perform = perform
super.init()
}
private let perform: () -> Void
}
}
//MARK: public
public extension Selector.Perform {
#objc func callAsFunction() { perform() }
var selector: Selector { #selector(callAsFunction) }
}
You need to manage strong references to Selector.Performs. One way to do that is to subclass UIKit classes that were designed to work with target-action:
/// A `UITapGestureRecognizer` that wraps a closure.
public final class TapGestureRecognizer: UITapGestureRecognizer {
public init(_ perform: #escaping () -> Void) {
self.perform = .init(perform)
super.init(target: self.perform, action: self.perform.selector)
}
public let perform: Selector.Perform
}
let tapRecognizer = TapGestureRecognizer { print("🍔🐈") }
tapRecognizer.perform() // "🍔🐈"
No, #selector refers to an Objective-C method.
You can do something much better though: Add an extension to NSTimer that lets you create a scheduled timer not with a target and selector, but with a closure.
If you change the scope of block to a class scope rather than function and hold a reference to closure there.
You could invoke that closure with a function. in the class. So that way you can invoke that closure as a selector.
Something like this:
class Test: NSObject {
let backToOriginalBackground = {
}
func backgroundChange() {
NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(test), userInfo: nil, repeats: false)
}
func test() {
self.backToOriginalBackground()
}
}
My solution was to create a class block variable like:
let completionBlock: () -> () = nil
Create a method which calls this completionBlock:
func completed(){
self.completionBlock!()
}
And inside where I want to put my selector like a block I did:
func myFunc(){
self.completionBlock = {//what I want to be done}
NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(Myclass.completed), userInfo: nil, repeats: false)
}
So my answer to having a selector be assigned to a closure in a swift like manner is similar to some of the answers already, but I thought I would share a real life example of how I did it within a UIViewController extension.
fileprivate class BarButtonItem: UIBarButtonItem {
var actionCallback: ( () -> Void )?
func buttonAction() {
actionCallback?()
}
}
fileprivate extension Selector {
static let onBarButtonAction = #selector(BarButtonItem.buttonAction)
}
extension UIViewController {
func createBarButtonItem(title: String, action: #escaping () -> Void ) -> UIBarButtonItem {
let button = BarButtonItem(title: title, style: .plain, target nil, action: nil)
button.actionCallback = action
button.action = .onBarButtonAction
return button
}
}
// Example where button is inside a method of a UIViewController
// and added to the navigationItem of the UINavigationController
let button = createBarButtonItem(title: "Done"){
print("Do something when done")
}
navigationItem.setLeftbarButtonItems([button], animated: false)
Swift 5.2.x
First of all, you need to declare an "easy to use" typealias for your block:
typealias Completion = () -> ()
Then, you must declare private var to use "as a gate" for your function:
private var action: Completion?
After that, you should create a function that can be called by your Selector (it accept only string format) and to call private completion:
#objc func didAction() {
self.action?()
}
Finally you can re-write your function (using the new swift syntax) like:
Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(didAction), userInfo: nil, repeats: false)
self.action = backToOriginalBackground
P.S.: Remember that your variable (or parameter if you embed it to a function) must be of the same of type declared to your typeAlias so, in our case:
var backToOriginalBackground: () -> ()
or also:
var backToOriginalBackground: Completion
It has been several years since this question was asked, and it is worth noting that in those years, Apple has added variants of many selector-using methods that take closures instead.
The original question asks about NSTimer.scheduledTimerWithTimeInterval. That method is now spelled Timer.scheduledTimer and has a version that takes a closure. So the function in the original question can be rewritten thus:
extension UIViewController {
func changeBackground() {
self.view.backgroundColor = .black
self.view.alpha = 0.55
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
self.view.backgroundColor = .white
self.view.alpha = 1.0
}
}
}
Here are some other common cases where, as of May 2016, a selector was required, but which can now use a closure:
UIControl now has an addAction method that takes a UIAction, and UIAction takes a closure. Subclasses of UIControl include UIButton, UISwitch, and UITextField.
UIBarButtonItem has an initializer that takes a UIAction.
NotificationCenter now has an addObserver method that takes a closure. It also supports Combine (the publisher method) and async/await (the notifications method).
RunLoop now has a perform method that takes a closure.

Swift 3 protocol extension using selector error

I have what I thought to be a very simple protocol extension for my UIViewControllers providing the capability to dismiss a keyboard through a tap gesture. Here's my code:
#objc protocol KeyboardDismissing {
func on(tap: UITapGestureRecognizer)
}
extension KeyboardDismissing where Self: UIViewController {
func addDismissalGesture() {
let tap = UITapGestureRecognizer(target: self, action: #selector(Self.on(tap:)))
view.addGestureRecognizer(tap)
}
func on(tap: UITapGestureRecognizer) {
dismissKeyboard()
}
func dismissKeyboard() {
view.endEditing(true)
}
}
The problem is that the above code throws a compile error on this line:
let tap = UITapGestureRecognizer(target: self, action: #selector(Self.on(tap:)))
With the error message:
Argument of '#selector' refers to instance method 'on(tap:)' that is not exposed to Objective-C
with the suggestion to "fix it" by adding #objc before func on(tap: UITapGestureRecognizer)
Ok fine, I add the tag:
#objc func on(tap: UITapGestureRecognizer) {
dismissKeyboard()
}
But then, it throws a different compile error on this newly added #objc tag with the error message:
#objc can only be used with members of classes, #objc protocols, and concrete extensions of classes
with the suggestion to "fix it" by removing the exact same tag I was just told to add.
I originally thought adding #objc before my protocol definition would solve any #selector problems but apparently that's not the case, and these cyclical error messages/suggestions aren't helping in the slightest. I've gone down a wild goose chase of adding/removing #objc tags everywhere, marking methods as optional, putting methods in the protocol's definition, etc.
It also doesn't matter what I put in the protocol definition Leaving the extension the same, the following example does not work nor does any combination of the declared methods in the protocol's definition:
#objc protocol KeyboardDismissing {
func on(tap: UITapGestureRecognizer)
}
This tricks me into thinking it works by compiling as a stand alone protocol, but the second I try to add it to a view controller:
class ViewController: UIViewController, KeyboardDismissing {}
it spits back the original error.
Can someone explain what I'm doing wrong and how I can compile this?
Note:
I've looked at this question but it is for Swift 2.2 not Swift 3 nor does the answer compile as soon as you create a view controller class that inherits from the protocol defined in the example.
I've also looked at this question but the answer uses NotificationCenter which is not what I'm after.
If there are any other seemingly duplicate questions, please let me know.
Matt's answer is correct. However, I would just add that, if you are dealing with #selector to use from a NotificationCenter notification, you could try to avoid #selector by using the closure version.
Example:
Instead of writing:
extension KeyboardHandler where Self: UIViewController {
func startObservingKeyboardChanges() {
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillShow(_:)),
// !!!!!
// compile error: cannot be included in a Swift protocol
name: .UIKeyboardWillShow,
object: nil
)
}
func keyboardWillShow(_ notification: Notification) {
// do stuff
}
}
you could write:
extension KeyboardHandler where Self: UIViewController {
func startObservingKeyboardChanges() {
// NotificationCenter observers
NotificationCenter.default.addObserver(forName: .UIKeyboardWillShow, object: nil, queue: nil) { [weak self] notification in
self?.keyboardWillShow(notification)
}
}
func keyboardWillShow(_ notification: Notification) {
// do stuff
}
}
This is a Swift protocol extension. Swift protocol extensions are invisible to Objective-C, no matter what; it knows nothing of them. But #selector is about Objective-C seeing and calling your function. That is not going to happen because your on(tap:) function is defined only in the protocol extension. Thus the compiler rightly stops you.
This question is one of a large class of questions where people think they are going to be clever with protocol extensions in dealing with Cocoa by trying to inject Objective-C-callable functionality (selector, delegate method, whatever) into a class via a protocol extension. It's an appealing notion but it's just not going to work.
As Matt said, you can't implement #objc methods in a protocol. Frédéric's answer covers Notifications, but what can you do about standard Selectors?
Let's say you have a protocol & extension, like so
protocol KeyboardHandler {
func setupToolbar()
}
extension KeyboardHandler {
func setupToolbar() {
let toolbar = UIToolbar()
let doneButton = UIBarButtonItem(title: "Done",
style: .done,
target: self,
action: #selector(self.donePressed))
}
#objc func donePressed() {
self.endEditing(true)
}
}
This will generate an error, as we know. What we can do, is take advantage of callbacks.
protocol KeyboardHandler {
func setupToolbar(callback: (_ doneButton: UIBarButtonItem) -> Void))
}
extension KeyboardHandler {
func setupToolbar(callback: (_ doneButton: UIBarButtonItem) -> Void)) {
let toolbar = UIToolbar()
let doneButton = UIBarButtonItem(title: "Done",
style: .done,
target: self,
action: nil
callback(doneButton)
}
}
Then, add an extension for the class you want to implement your protocol
extension ViewController: KeyboardHandler {
func addToolbar(textField: UITextField) {
addToolbar(textField: textField) { doneButton in
doneButton.action = #selector(self.donePressed)
}
}
#objc func donePressed() {
self.view.endEditing(true)
}
}
Instead of setting the action on creation, set it just after creation in the callback.
This way, you still get your desired functionality and can call the function in your class (ex. ViewController) without even seeing the callbacks!
I have made another attempt, from another point of view. I use in many of my developments, a protocol to handle the style of UINavigationBar in a global way, from each of the UIViewController contained in it.
One of the biggest problems of doing this is the standard behavior to return to the previous UIViewController (pop) and dismiss a UIViewController shown in a modal way. Let's look at some code:
public protocol NavigationControllerCustomizable {
}
extension NavigationControllerCustomizable where Self: UIViewController {
public func setCustomBackButton(on navigationItem: UINavigationItem) {
let backButton = UIButton()
backButton.setImage(UIImage(named: "navigationBackIcon"), for: .normal)
backButton.tintColor = navigationController?.navigationBar.tintColor
backButton.addTarget(self, action: #selector(defaultPop), for: .touchUpInside)
let barButton = UIBarButtonItem(customView: backButton)
navigationItem.leftBarButtonItem = barButton
}
}
This is a very simplified (and slightly modified) version of the original protocol, although it will be worth explaining the example.
As you can see, a #selector is being set within a protocol extension. As we know, protocol extensions are not exposed to Objective-C and therefore this will generate an error.
My solution is to wrap the methods that handle the standard behaviors of all my UIViewController (pop and dismiss) in another protocol and extend UIViewController to it. Viewing this in code:
public protocol NavigationControllerDefaultNavigable {
func defaultDismiss()
func defaultPop()
}
extension UIViewController: NavigationControllerDefaultNavigable {
public func defaultDismiss() {
dismiss(animated: true, completion: nil)
}
public func defaultPop() {
navigationController?.popViewController(animated: true)
}
}
With this workaround, all UIViewController implementing the NavigationControllerCustomizable will immediately have the methods defined in NavigationControllerDefaultNavigable, with their default implementation, and therefore be accessible from Objective-C to create expressions of type #selector, without any type of error.
I hope this explanation can help someone.
Here's my idea: avoid mixing swift protocol & objc protocol.
#Frédéric Adda answer have the downside that you are responsible to unregister your observer, because it uses the block based way of adding an observer. In iOS 9 and later, the 'normal' way of adding an observer, will hold a weak reference to the observer and therefore the developer doesn't have to unregister the observer.
The following way will use the 'normal' way of adding an observer through protocol extensions. It uses a bridging class that will hold the selector.
Pro's:
You do not have the manually remove the observer
Typesafe way of using the NotificationCenter
Con's:
You have to call register manually. Do this once after self is fully initialized.
Code:
/// Not really the user info from the notification center, but this is what we want 99% of the cases anyway.
public typealias NotificationCenterUserInfo = [String: Any]
/// The generic object that will be used for sending and retrieving objects through the notification center.
public protocol NotificationCenterUserInfoMapper {
static func mapFrom(userInfo: NotificationCenterUserInfo) -> Self
func map() -> NotificationCenterUserInfo
}
/// The object that will be used to listen for notification center incoming posts.
public protocol NotificationCenterObserver: class {
/// The generic object for sending and retrieving objects through the notification center.
associatedtype T: NotificationCenterUserInfoMapper
/// For type safety, only one notification name is allowed.
/// Best way is to implement this as a let constant.
static var notificationName: Notification.Name { get }
/// The selector executor that will be used as a bridge for Objc - C compability.
var selectorExecutor: NotificationCenterSelectorExecutor! { get set }
/// Required implementing method when the notification did send a message.
func retrieved(observer: T)
}
public extension NotificationCenterObserver {
/// This has to be called exactly once. Best practise: right after 'self' is fully initialized.
func register() {
assert(selectorExecutor == nil, "You called twice the register method. This is illegal.")
selectorExecutor = NotificationCenterSelectorExecutor(execute: retrieved)
NotificationCenter.default.addObserver(selectorExecutor, selector: #selector(selectorExecutor.hit), name: Self.notificationName, object: nil)
}
/// Retrieved non type safe information from the notification center.
/// Making a type safe object from the user info.
func retrieved(userInfo: NotificationCenterUserInfo) {
retrieved(observer: T.mapFrom(userInfo: userInfo))
}
/// Post the observer to the notification center.
func post(observer: T) {
NotificationCenter.default.post(name: Self.notificationName, object: nil, userInfo: observer.map())
}
}
/// Bridge for using Objc - C methods inside a protocol extension.
public class NotificationCenterSelectorExecutor {
/// The method that will be called when the notification center did send a message.
private let execute: ((_ userInfo: NotificationCenterUserInfo) -> ())
public init(execute: #escaping ((_ userInfo: NotificationCenterUserInfo) -> ())) {
self.execute = execute
}
/// The notification did send a message. Forwarding to the protocol method again.
#objc fileprivate func hit(_ notification: Notification) {
execute(notification.userInfo! as! NotificationCenterUserInfo)
}
}
From my GitHub (you can't use the code through Cocoapods): https://github.com/Jasperav/JVGenericNotificationCenter
Here is a similar use-case, you can call a method through a selector without using #objc as in swift by using the dynamic keyword. By doing so, you are instructing the compiler to use dynamic dispatch implicitly.
import UIKit
protocol Refreshable: class {
dynamic func refreshTableData()
var tableView: UITableView! {get set}
}
extension Refreshable where Self: UIViewController {
func addRefreshControl() {
tableView.insertSubview(refreshControl, at: 0)
}
var refreshControl: UIRefreshControl {
get {
let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
if let control = _refreshControl[tmpAddress] as? UIRefreshControl {
return control
} else {
let control = UIRefreshControl()
control.addTarget(self, action: Selector(("refreshTableData")), for: .valueChanged)
_refreshControl[tmpAddress] = control
return control
}
}
}
}
fileprivate var _refreshControl = [String: AnyObject]()
class ViewController: UIViewController: Refreshable {
#IBOutlet weak var tableView: UITableView! {
didSet {
addRefreshControl()
}
}
func refreshTableData() {
// Perform some stuff
}
}

Handle the selector parameter in swift

Let's say I declare a function in my CustomTimer class:
class CustomTimer {
class func scheduledTimerWithSelector(aSelector: Selector) -> CustomTimer {
// aSelector ??
}
}
How can I handle this aSelector parameter?
Like the NSTimer.scheduledTimerWithTimeInterval method, how dose it work?
You should check the Selector structure.
From the apple docs:
In Swift, Objective-C selectors are represented by the Selector
structure. You can construct a selector with a string literal, such as
let mySelector: Selector = "tappedButton:". Because string literals
can be automatically converted to selectors, you can pass a string
literal to any method that accepts a selector.
Selector function with Swift :
func selectorFunc(aSel:Selector){
if self.respondsToSelector(aSel){
NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: aSel, userInfo: nil, repeats: false)
}
}
func gooleIt(){
println("Hello")
}
Function Call :
self.selectorFunc(Selector(gooleIt()))
Hope it help you.
You can declare a method with a selector like this and you can call it by creating a UIControl because performSelector methods are not available in Swift:
func methodWithSelector(sel:Selector) {
var control = UIControl()
control.sendAction(sel, to: self, forEvent: nil)
}
If you do not need to call it on the main thread another option is like this:
func methodWithSelector(sel:Selector) {
NSThread.detachNewThreadSelector(sel, toTarget: self, withObject: nil)
}
You will call it like this:
methodWithSelector(Selector("methodCall"))
or like this
methodWithSelector("methodCall")
then you must have a method with the name of the selector
func methodCall() {
println("methodCall")
}

How to implement callback/selector with performSelector in swift?

I am trying to create an equivalent of below method signature (Objective-C) in swift language. I couldn't get an answer on how to get the right equivalent for this. Any help is highly appreciated.
- (void)myMethod:(MyObject*)firstParam
setCallbackObject:(id)obj
withMySelector:(SEL)selector {
[obj performSelector:selector withObject:nil afterDelay:0]
}
First:
NOTE
The performSelector: method and related selector-invoking methods are not imported in Swift because they are inherently unsafe.
If you still want to implement it that way, read below.
You could use NSTimer:
var myTimer: NSTimer = NSTimer.scheduledTimerWithTimeInterval(0.0, target: self, selector: "selectorMethod", userInfo: nil, repeats: false)
String can be used where Selector is needed. It will automatically be converted (autoboxing).
The delay can be of course higher: 0.1 is then equal to 1 tenth of a second.
To call a method like:
func selectorMethod() {
...
}
We need to check before using the selector on the class. But the respondsToSelector: is in the NSObject protocol, so you have to derive at least from that (or one that subclasses from it).
To make it clear, here is the example.
Code:
class Test {
func myMethod(firstParam: String, setCallbackObject obj: AnyObject, withMySelector selector: Selector) {
if obj.respondsToSelector(selector) {
var myTimer: NSTimer = NSTimer.scheduledTimerWithTimeInterval(0.0, target: obj, selector: selector, userInfo: nil, repeats: false)
myTimer.fire()
} else {
println("Warning: does not respond to given selector")
}
}
}
class Test2: NSObject {
func selectorMethod() {
print("worked")
}
}
var test: Test = Test()
var callBackObj: Test2 = Test2()
test.myMethod("thisfirstis", setCallbackObject: callBackObj, withMySelector: Selector("selectorMethod"))
Output:
workedProgram ended with exit code: 0

Resources