I work in a project were I came across multiples cases like this,
class ViewController: UIViewController {
private let clearCacheButton: UIButton = {
let button = UIButton(type: .custom)
button.setTitle(AppMessages.General.Action.clearCache.description, for: .normal)
button.addTarget(self, action: #selector(clearCache), for: .touchUpInside)
return button
}()
}
self is being captured to add target for button, yet compiler does not complain about capturing self before initialisation, it works even without lazy var modifier...
Why is that?
Is this just a little convenience added by Apple at some point?
If I remember well it was not possible in the past
Thank you for the replies
The compiler has never complained about this. But it should (and I have filed a bug on this point), because the code you've written won't work: the button will not actually do anything when tapped. (The app might even crash, but then again it might not.)
The reason (for both phenomena) is that the compiler misinterprets the term self here to mean the class — which does exist before initialization of the instance.
The solution is to replace let by lazy var. That does work, because now the code will not actually be called until some later, when the instance does exist.
Related
This question already has an answer here:
Why is the #objc tag needed to use a selector?
(1 answer)
Closed 4 years ago.
Why does a button action need to be a function declared with objc? Im curious as to what the difference is between an objc func and a func and why a button can't simply reference it's action to a func.
Edit: I am using Swift. Thank you very much for your time.
var thisButton: UIButton = {
let button = UIButton(type: .system)
button.addTarget(self, action: #selector(thisFunction), for: .touchUpInside)
return button
}()
#objc func thisFunction() {
//stuff
}
Swift generates code that is only available to other Swift code, but
if you need to interact with the Objective-C runtime – all of UIKit,
for example – you need to tell Swift what to do.
That’s where the #objc attribute comes in: when you apply it to a
class or method it instructs Swift to make those things available to
Objective-C as well as Swift code. So, any time you want to call a
method from a UIBarButtonItem or a Timer, you’ll need to mark that
method using #objc so it’s exposed – both of those, and many others,
are Objective-C code.
Refer :
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithObjective-CAPIs.html
Not sure why I am getting an "Unrecognized selector sent to class" error when I try to use the button I've created in code.
Here is my code:
let sendButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Send", for: .normal)
button.setTitleColor(UIColor.white, for: .normal)
button.backgroundColor = #colorLiteral(red: 0.5098039216, green: 0.5215686275, blue: 0.8392156863, alpha: 1)
button.layer.cornerRadius = 5
button.isUserInteractionEnabled = true
button.addTarget(self, action: #selector(postComment), for: .touchUpInside)
return button
}()
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
I actually Just figured it out.
For some reason, replacing 'let' with 'lazy var' seems to do the trick. I can't explain why it works, but it does.
In sample code I have seen two different styles of declaring objects. What is the advantage of one over the other?
Both are declared as
var btn: UIButton!
Style 1:
btn = UIButton()
btn.translatesAutoresizingMaskIntoConstraints = false
btn.layer.borderColor = UIColor.blue.cgColor
btn.layer.borderWidth = 1
...
self.view.addSubview(btn)
Style 2:
btn = {
let b = UIButton()
b.translatesAutoresizingMaskIntoConstraints = false
b.layer.borderColor = UIColor.blue.cgColor
b.layer.borderWidth = 1
...
return b
}()
self.view.addSubview(btn)
The only advantage I currently see is that the second style makes code more legible when you have many objects. You can also collapse them in Xcode. Is there any other advantage? Doesn't the second version "cost" more resources at runtime?
Which is preferable?
Thanks
Closure initialization (your second example) has three big advantages.
Advantage one: Initializing let structs. Your example uses a UIButton, which is a class--a reference type. If we are initializing a struct into a let, we can not mutate it. We cannot change any of its setters, nor can we call any methods marked as mutating once we initialize it into a let declaration. The closure initialization allows us to do this set up before assigning into the let-declared variable.
Advantage two: Scope. The closure we initialize with gets its own scope. It can capture variables from the enclosing scope, but variables declared within the scope are not available outside it. This means we don't have collisions on variable names. It also means that ARC could do some clean-up as our initialization completes.
Advantage three: In-line initialization of class/struct member variables. The first two advantages I listed aren't always necessary and you can usually work around them. But without closure initialization, if you wanted to initialize your button at the point it is declared, you are stuck with something like this:
class MyViewController: UIViewController {
var button = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
// TODO: Set up button's properties
view.addSubview(button)
}
}
But with closure initialization, we can set those all set up at the point of declaration.
class MyViewController: UIViewController {
var button: UIButton = {
let button = UIButton()
// TODO: Set up button
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(button)
}
}
The differences are fairly trivial in small examples but may be more significant in larger, more complex software.
In the first example, the state of btn is temporarily invalid - until all of the property assignments are complete. In the second example, the button is completely built when assigned to btn. In addition, the code in the second is, in effect, a factory method that could be separated out into a separate class and parameterised. Useful if a lot of similar buttons are being created.
Separating the responsibility for building buttons and other controls into specialist classes is an excellent step towards reducing the size and complexity of view controllers in iOS apps.
Up until now I have had this code
if UIScreen.instancesRespondToSelector(Selector("scale")) {
UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.mainScreen().scale);
}else{...}
I didn't write this code, so I'm not sure what it's for, but it looks like they wanted to verify that UIScreen.mainScreen() in fact can have the variable .scale(?).
When looking at the .scale, it looks to me like this has been available since iOS 4.0. Since we support down to iOS 7, this shouldn't be necessary, right?
Anyway, this is not the current problem.
I am now having hundreds of warnings due to Xcode 7.3 towards Swift 3 with these new selector-instantiations or whatnot.
Xcode wants me to change this:
Selector("scale")
into
#selector(NSDecimalNumberBehaviors.scale)
Until now, all other selectors I have changed have been logical, like "change Selector("hello") into #selector(MyClass.hello), but this NSDecimal.. sounds a bit drastic. Can I trust Xcode to pick the right selector? I can't find NSDecimalNumberBehaviors anywhere connected to UIScreen.scale.. If I type #selector(UIScreen.scale) I get an error..
The only thing I know for sure is that if I CMD+click scale here: NSDecimalNumberBehaviors.scale and here: UIScreen.mainScreen().scale I end up in different places..
As noted in comments, this code is a vestigial remnant of attempts to support old iOS versions that not only are no longer relevant, but can't even be targeted when developing in Swift.
Just call UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.mainScreen().scale) directly — the scale property exists on all iOS versions that you can target with Swift, so there's no need to check for it.
In fact, in general it's not a good idea to test for API availability using selector checks. A selector may exist on a version below the one you're targeting, but be private API with different behavior — so your check will succeed, but your code will not behave correctly. This is why the #available and #available system was introduced in Swift 2.
(Another upside of version-based availability checking: when an OS version gets old enough for you to drop support, it's much easier to find all the sites in your code that you can clean up. You don't have to remember which version which method/property became universal in.)
If for some other reason you need to form a Selector for UIScreen.scale... you can't use the #selector expression to do that in Swift 2.2, because scale is a property, not a method. In Swift 2.2, #selector takes a function/reference, and there's no way to get a reference to the underlying getter or setter method of a property. You'd still need to construct that selector from a string. To get around the warning, stash the string literal in a temporary:
let scale = "scale"
let selector = Selector(scale)
Or do some other dance that passes a string, but doesn't directly pass a string literal, to the Selector initializer:
let selector = Selector({"scale"}())
In Swift 3 there'll be a special form of #selector for property getters/setters, but it hasn't landed yet.
my two cents:
1) swift has a new sntax for selectors
2) compiler can safely detect methods based on:
n. of params
type of every param
result type (ONLY in pure swift, so let's forget it now)
let see 4 cases:
A)
..
closeBtn.addTarget(self, action: #selector(doIt), for: .touchUpInside)
}
func doIt(_ sender: UIButton) {
print("touched")
}
func doIt() {
print("touched2 ")
}
Ambiguous use of 'doIt'
B)
closeBtn.addTarget(self, action: #selector(doIt), for: .touchUpInside)
}
// func doIt(_ sender: UIButton) {
// print("touched")
// }
func doIt() {
print("touched2 ")
}
it works, as compiler is able to detect the ONLY method that
can match the signature
C)
closeBtn.addTarget(self, action: #selector(doIt(_:)), for: .touchUpInside)
}
func doIt(_ sender: UIButton) {
print("touched")
}
func doIt() {
print("touched2 ")
}
it works, as compiler is able to detect the ONLY method that
can match the signature.
D)
closeBtn.addTarget(self, action: #selector(doIt), for: .touchUpInside)
}
func doIt(_ sender: UIButton) {
print("touched")
}
/*
func doIt() {
print("touched2 ")
}
*/
it works and:
compiler is able to detect the ONLY method that can match the
signature
in debugger You will get the correct button reference too, as the
UIButton CODE (in iOS libs) pushes the button address on the
stack, as compiler does create a stack frame.
I'm creating a view programatically, and adding a function so the action responds to the UIControlEvents.TouchUpInside event:
button.addTarget(self, action: action, forControlEvents:
UIControlEvents.TouchUpInside)
So, by going into the documentation I've added this action as a selector:
#selector(ViewController.onRegularClick)
XCode then complaints about:
Argument of #selector refers to a method that is not exposed to
Objective-C
So I have to set up the handler function with:
#objc func onRegularClick(sender: UIButton)
Can some one please put this noob on the right direction by guiding me to the documentation, or even give a short explanation, on:
why can't I no longer pass simply the function name String to the action?
how is the proper way to implement this following the Swift Way? Using the Selector class?
why do we need to pass the #objc keyword and how it affects the function?
Thank you!
why can't I no longer pass simply the function name String to the action?
Using strings for selectors has been deprecated, and you should now write #selector(methodName)instead of "methodName". If the methodName() method doesn't exist, you'll get a compile error – another whole class of bugs eliminated at compile time. This was not possible with strings.
how is the proper way to implement this following the Swift Way? Using the Selector class?
You did it the right way:
button.addTarget(self, action: #selector(ClassName.methodName(_:)), forControlEvents: UIControlEvents.TouchUpInside)
why do we need to pass the #objc keyword and how it affects the function?
In Swift the normal approach is to bind method's calls and method's bodies at compile time (like C and C++ do). Objective C does it at run time. So in Objective C you can do some things that are not possible in Swift - for example it is possible to exchange method's implementation at run time (it is called method swizzling). Cocoa was designed to work with Objective C approach and this is why you have to inform the compiler that your Swift method should be compiled in Objective-C-like style. If your class inherits NSObject it will be compiled ObjC-like style even without #objc keyword.
Well, it is called evolution
When there are some arguments in the method, you should declare the selector as:
let selector = #selector(YourClass.selector(_:))
You can type only #selector(selector(_:)) if the selector is in the same class of the caller. _: means that accept one parameter. So, if it accept more parameters, you should do something like: (_:, _:) and so on.
I found out that the #objc is needed only when the function is declared as private or the object doesn't inherit from NSObject
1: Currently you can, but it will create a deprecated warning. In Swift
3 this will be an error, so you should fix it soon. This is done
because just using a String can not be checked by the compiler if
the function really exists and if it is a valid Objective C function
which can be resolved dynamically during runtime.
2: Do it in this way:
button.addTarget(self, action: #selector(MyViewControllerClass.buttonPressed(_:)), forControlEvents: UIControlEvents.TouchUpInside)
3: Usually you not have to use the #objc attribute. I assume your class ViewController is (for any reason) not derived from UIViewController. If it derives from UIViewController is inherits also the needed ObjC behavior for calling selectors on functions.
For swift3.0 just do like below code :
yourButton.addTarget(self, action: #selector(yourButtonPressed), for: .touchUpInside)
and yourButtonPressed method
#IBAction func yourButtonPressed(sender:UIButton) {
// Do your code here
}
Everyones's answers are perfect but I have a better approach. Hope you gonna like it.
fileprivate extension Selector {
static let buttonTapped =
#selector(ViewController.buttonTapped(_:))
}
...
button.addTarget(self, action: .buttonTapped, for: .touchUpInside)
here in this file private will help to show buttonTapped only in file.
Programmatically
button.addTarget(self, action: #selector(returnAction), for: .touchUpInside)
// MARK: - Action
#objc private func returnAction(sender: UIButton) {
print(sender.tag)
}