Weird behavior when I try to use a closure as the target of a UIButton - swift

I know I need to replace the let keyword with lazy var for accessing the property otherwise I cannot access the 'self'.
But I found that the button.addTarget can build successfully as below,
Why? Normally if you try to access the property from a closure that needs to be a lazy variable, am I right?
For comparison, The testProperty shows red error message:
Cannot convert value of type (testController) -> () -> testController to specified type UITabBarController
import UIKit
class testController: UIViewController {
let actionButton: UIButton = {
let button = UIButton(type: .system)
button.addTarget(self, action: #selector(actionButtonTapped), for: .touchUpInside)
return button
}()
let testProperty: UIViewController = {
let obj: UIViewController = self
return obj
}()
#objc func actionButtonTapped() {
}
}

If you check the addTarget signature, you will see the following:
open func addTarget(_ target: Any?, action: Selector, for controlEvents: UIControl.Event)
First parameter target is Any?, so passing (Function) as target compiles fine. It even will work but can lead to weird issues, like opening keyboard will stop the button from calling the action.

You currently think that self refers to the instance.
But no, for an NSObject, it refers to partial application of this method. Actually running ViewController.self() before one has been instantiated, e.g. within the context of this closure, will crash your app, but self() used to still present itself as being available prior to Xcode 13.3.
As of now, Swift cannot cope with referring to this method without generating a warning. The warning tells you to use ViewController.self, which Swift can only interpret as a metatype, not a method. It doesn't understand what's going on, but at least it informs you that what you're doing is incorrect—the method is not actually the target.
Regardless of the warning, I don't know Objective-C well enough to tell you why a message sent to the method will trickle down to an instance of the related type. But don't do it.

Related

The type of self in Swift and its use with respect to two-phase initialization

Consider the following code, which adds a gesture recognizer to a view.
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
let gesture = UITapGestureRecognizer(target: self, action: #selector(handleGesture(gesture:)))
let test1 = self
#objc func handleGesture(gesture: UITapGestureRecognizer) {
// some code
print("hello")
}
override func viewDidLoad() {
let test2 = self
super.viewDidLoad()
imageView.addGestureRecognizer(gesture)
}
}
As per this question, the above code does not work because I'm trying to use self (in the gesture recognizer's initializer) when not fully initialized, and this is so because of Swift's two-phase initialization.
I'm not interested in the easy fix to make this work, but this triggers a couple of questions:
1) Why does the compiler allow us to use self here if self is not ready to be used? Shouldn't I get a compiler error if I'm trying to use self too soon?
2) We can't directly inspect the type of self with alt+click in XCode. However, we can inspect the types of my ad hoc variables test1 and test2. While test2's type is ViewController, as expected, test1's type is (ViewController) -> () -> ViewController (i.e., a closure that takes a ViewController and returns a closure that takes nothing and returns a ViewController). What is that and why does self have two different types within the same class?
1)
Shouldn't I get a compiler error if I'm trying to use self too soon?
I do agree. You may send a bug report to swift.org.
Why does the compiler allow us to use self here if self is not ready to be used?
Unfortunately, there's another self in the descendants of NSObject, the method self() of NSObject.
2)
What is that and why does self have two different types within the same class?
The current Swift interprets the initial value expression in the class context, not in the instance context.
You know method names can be used as closures in Swift:
class ViewController: UIViewController {
//..
func aMethod() {
//...
}
func anInstanceMethod() {
let meth = aMethod // () -> ()
}
}
Swift can also refer to an instance method in the class context, which generates a so-called unapplied method reference (see SE-0042), which currently returns a curried function:
class ViewController: UIViewController {
//...
func aMethod() {
//...
}
class func aClassMethod() {
let meth = aMethod // (ViewController) -> () -> ()
}
}
The method self() as well.
Generally we do not need self() method and this behavior should be changed, I think.
This is interesting behaviour that works for Objective-C objects. Let's take these three examples:
class Object: NSObject {
let test = self // compiles
}
class NonNSObject {
// let test = self // errors
lazy var lazyTest = self // compiles
}
struct NonClass {
// let test = self // errors
lazy var lazyTest = self // errors
}
NonNSObject exhibits what you'd escape:
The object cannot reference itself until it is fully initialized, and let bindings must all be initialized before full initialization, so this failed.
However, NSObject happens to have an Objective-C method - (instancetype)self; which returns self. We can model this on NonNSObject as so:
func returnSelf() -> NonNSObject {
return self
}
This is where we start to see the answer to 2).
If we reference this method returnSelf on the Class we get the signature (NonNSObject) -> () -> NonNSObject. You can do this with any instance method as so:
let test = NonNSObject.returnSelf
The signature makes sense in this context:
The argument is the object we actually want to call the method on
Then we "apply" the function (with no arguments, in this case)
And we finally get our return value
let curriedFunction = NonNSObject.returnSelf // (Self) -> () -> Self
let readyToCall = curriedFunction(NonNSObject()) // () -> Self
let finallyApplied = readyToCall() // Self
Putting all the pieces together, we can see that in the case of ViewController (which inherits from UIViewController which way up the chain inherits from NSObject) there is an instance method self which the compiler is assuming you meant, so it uses that instead of the instance itself (as that would be an error). Its signature is thus a natural consequence of using an instance method on the class itself—it needs an instance, which is the first argument.
In summary:
1) Instead of assuming you made an error, the Swift compiler finds a function self on NSObject and returns the curried form.
2) This is the curried form of a function, in particular, an instance method which returns its own type.
2.5) It's still highlighted in pink because Swift-ObjC interop is mildly hacky, and self is both a method and, well, self.
As a bonus, the struct cannot reference itself at all, even lazily.

Triggering function with parameters upon UIButton press

I'm trying to call a function with parameters on a button press, from what I understand #selector just checks that a function is there and then runs it. I've seen other answers where the button can be sent to the function but sadly I don't think that will solve my problem.
If I run this code (func is any function and a: 14 is just an example of a parameter being given):
myButton.addTarget(self, action: #selector(func(a: 14)), for: .touchUpInside)
I get an error saying 'Argument of #selector does not refer to an #objc method, property, or initializer
A workaround that I've been using:
myButton.addTarget(target: self, action: #selector(myFunc), for: .touchUpInside)
func myFunc() {
someOtherFunc(args)
}
The problem with this is that unless the argument that was going to be passed is global or class wide and known, you wont be able to use it.
Main Question:
Is there a way to have it run a function with parameters when a button is clicked without setting a class wide variable and assessing that instead of using a parameter?
My Solution:
Simplified, and buttons aren't setup and such...
var personName:String!
func scrollViewClicked(name:String) {
personName = name
myButton.addTarget(target: self, action: #selector(myFunc), for: .touchUpInside)
}
func myFunc() {
do something with personName
}
So pretty much I have a way of 'solving' the problem but it feels like a bit of a hack/improper way. Just trying to figure out if there is a 'real' way to do this or if it isn't meant to happen.
No, there's no way to have a UIButton run a function with arbitrary parameters.
But there is a way to have it run with some parameters, which may be useful to you.
The documentation for addTarget says that it takes a selector, which is essentially just a reference to a method. If you pass it a method with the right set of arguments, it will call it and pass whatever it's designed to pass. If you send a method with other arguments, you'll get an "unrecognized selector" error.
UIControl's addTarget understands three kinds of selectors:
func myFunc()
func myFunc(sender: UIButton)
func myFunc(sender: UIButton, forEvent event: UIEvent)
So you can set it to run a function with parameters, but the only parameters it knows how to send are the button that was pressed and the event it generated.
This is still potentially useful though, if you can use information about the button and/or the event to determine your action. For example you can set up your handler:
func myFunc(sender: UIButton, forEvent event: UIEvent) {
switch(sender) {
case myButton:
print("myButton was pressed")
default:
print("Something else was pressed.")
}
}
Depending on your use case, you could make use of the button's storyboard restoration ID, its title or other identifier, or you could even subclass UIButton and give it an instance variable to hold your parameter, like this:
class MyButtonClass: UIButton {
var argument:String = ""
}
Then when you're setting up your button you specify the argument:
myButton.argument = "Some argument"
And you can access it from your handler like this:
func myFunc(sender: UIButton, forEvent event: UIEvent) {
if let button = sender as? MyButtonClass {
print(button.argument)
}
}
It's still not as neat as just specifying your parameter in the selector, but as far as I know that's not possible.

UIView, CMDeviceMotionHandler : unowned may only be applied to class and class-bound protocol types

I'm creating a UIView that listens to CMDeviceMotion Events:
class MyView: UIView{
private var motionManager = CMMotionManager()
let motionQueue = NSOperationQueue()
override func awakeFromNib() {
self.setupView()
}
func setupView(){
self.motionManager.deviceMotionUpdateInterval = 0.5
self.motionManager.startDeviceMotionUpdatesUsingReferenceFrame(.XArbitraryZVertical, toQueue: self.motionQueue, withHandler: self.motionHandler)
}
// MARK: - CMDeviceMotionHandler
let motionHandler : CMDeviceMotionHandler = {
[unowned self] (motion,error) in
}
}
I'd like to declare my CMDeviceMotionHandler closure as a member variable however I get the error:
'unowned' may only be applied to class and class-bound protocol types,
not 'MyView -> () -> MyView'
MyView is a UIView which in turn is a class so I don't get why it's complaining that unowned can not be applied.
I've searched for other questions with the same issue but most of them dealt with lazily computed variables. How do I resolve this error for my scenario?
The line of code you're on is actually run during the init function. self is not available until after all stored properties are initialized. You're in the middle of the process.
The error message is quite confusing and useless, because self in the context of property initializers is not an instance of a MyView, but a tricky meta-type: a class-member function that is unbound to its instance, but becomes bound and usable once the instance is passed in as the first argument. It's to do with member functions being implemented in Swift with currying, and is rather academic unless you love type calculus.
You have two options:
Declare it indeed as lazy var instead of let, so the code is not run during init but in fact at first use.
Declare it without initialization as an Optional. Depending on your design constraints this is either cumbersome or elegant. No way to know. Anyway, before it is needed, initialize it to a non-nil value. An easy place to do this, if this UIView is used strictly within Storyboard, is to initialize it within awakeFromNib().

swift + OS X sandboxing: treat 'NSVBOpenPanel' as a 'NSOpenPanel' :: because I need to get the sender in the delegate method

Im using swift and I show a NSOpenPanel. In the delegate I need to look at the sender's prompt to distinguish which action to take:
e.g.
func show() {
...
panel.delegate = self
panel.prompt = "xy"
panel.run ....
}
func show2() {
...
panel.delegate = self
panel.prompt = "abc"
panel.run ....
}
//delegate
func panel(sender: AnyObject, shouldEnableURL url: NSURL) -> Bool {
let panelPrompt = (sender as! NSOpenPanel).prompt ...
}
without sandbox = WORKS fine
the sender of the delegate is a NSOpenPanel indeed
with sandbox = Cast fails, crash
the sender of the delegate is NOT a NSOpenPanel but a NSVBOpenPanel. Apple's private class that remotely speaks to the outside world and allows the user to choose files NORMALLY not in your sandbox. (for details I refer to apple's sandboxing guide)
So the question is how do I do use this in swift without crashing?
Is there a nice way or is it just a bug/ugly idk behavior
Do I have to revert to use performSelector?
===
Addition: extensions to NSOpenPanel don't work either!
Instead of casting the sender to NSOpenPanel (which fails because the
sender is an instance of the private NSVBOpenPanel class),
or some performSelector magic, you can use the fact that
arbitrary methods and properties can be accessed on AnyObject
without casting, and the call behaves like an implicitly
unwrapped optional:
func panel(sender: AnyObject, shouldEnableURL url: NSURL) -> Bool {
let panelPrompt = sender.prompt ?? ""
// ...
return true
}
This gives the prompt for any sender object which has a prompt
property, and the empty string as a fallback. In my test it worked well
in a sandboxed environment.
See The strange behaviour of Swift's AnyObject for more details, examples, and references to the
documentation.
This is how it would work with performSelector. It is quite ugly though:
let panelPromptUnmanaged = (sender as! NSObject).performSelector(NSSelectorFromString("prompt"))
let panelPrompt = panelPromptUnmanaged != nil ? panelPromptUnmanaged.takeRetainedValue() as! String : ""

Using protocol extension to dismiss keyboard on outside tap

In my project I have few view controllers which are subclasses of UITableViewController, UIViewController, on each I want to implement this behavior:
When user taps outside of a text field it should dismiss the keyboard which was visible when user tapped inside it.
I can easily implement it by defining a tap gesture recognizer and associating a selector to dismiss the keyboard:
class MyViewController {
override func viewDidLoad() {
super.viewDidLoad()
configureToDismissKeyboard()
}
private func configureToDismissKeyboard() {
let tapGesture = UITapGestureRecognizer(target: self, action: "hideKeyboard")
tapGesture.cancelsTouchesInView = true
form.addGestureRecognizer(tapGesture)
}
func hideKeyboard() {
form.endEditing(true)
}
}
Since I have to implement same behavior in multiple view controllers, I am trying to identify a way to avoid using repetitive code in multiple classes.
One option for me is to define a BaseViewController, which is subclass of UIViewController, with all above methods defined within it and then subclass each of my view controller to BaseViewController. The problem with this approach is that I need to define two BaseViewControllers one for UIViewController and one for UITableViewController since I am using subclasses of both.
The other option which I am trying to use is - Protocol-Oriented Programming. So I defined a protocol:
protocol DismissKeyboardOnOutsideTap {
var backgroundView: UIView! { get }
func configureToDismissKeyboard()
func hideKeyboard()
}
Then defined its extension:
extension DismissKeyboardOnOutsideTap {
func configureToDismissKeyboard() {
if let this = self as? AnyObject {
let tapGesture = UITapGestureRecognizer(target: this, action: "hideKeyboard")
tapGesture.cancelsTouchesInView = true
backgroundView.addGestureRecognizer(tapGesture)
}
}
func hideKeyboard() {
backgroundView.endEditing(true)
}
}
In my view controller I confirmed to the protocol:
class MyViewController: UITableViewController, DismissKeyboardOnOutsideTap {
var backgroundView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// configuring background view to dismiss keyboard on outside tap
backgroundView = self.tableView
configureToDismissKeyboard()
}
}
Problem is - above code is crashing with exception:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MyProject.MyViewController hideKeyboard]: unrecognized selector sent to instance 0x7f88c1e5d700'
To avoid this crash I need to redefine hideKeyboard function within MyViewControllerclass, which is defeating my purpose of avoiding repetitive code :(
Please suggest if I am doing any thing wrong over here or is there any better way to implement my requirement.
I think there are two possible problems: casting Self to AnyObject, and not using the new #selector syntax.
Instead of casting Self to AnyObject, define the protocol as a class-only protocol:
protocol DismissKeyboardOnOutsideTap: class {
// protocol definitions...
}
Then use type constraints to apply your extension to only subclasses of UIViewController, and use Self directly in your code, rather than casting to AnyObject:
extension DismissKeyboardOnOutsideTap where Self: UIViewController {
func configureToDismissKeyboard() {
let gesture = UITapGestureRecognizer(target: self,
action: #selector(Self.hideKeyboard()))
gesture.cancelsTouchesInView = true
backgroundView.addGestureRecognizer(gesture)
}
}
Edit: I remembered the other problem I ran into when doing this. The action argument for UITapGestureRecognizer is an Objective-C selector, but Swift extensions to classes aren't Objective-C. So I changed the protocol to an #objc protocol, but that was a problem because my protocol included some Swift optionals, and it also introduced new crashes when I tried to implement the protocol in my VC.
Ultimately, I discovered an alternative method that didn't require an Objective-C selector as an argument; in my case, I was setting an NSNotification center observer.
In your case you might be better off simply extending UIViewController, as UITableViewController is a subclass, and subclasses inherit extensions (I think).
As pointed out by the user ConfusedByCode, even though a protocol oriented approach starts out as a nice one, it becomes un-Swifty as the compiler forces you to use the keyword #objc.
Therefore extending UIViewController is a better approach; at least in my opinion.
In order to maintain a clean project structure, create a file named UIViewController+DismissKeyboard.swift and paste the following content inside:
import UIKit
extension UIViewController {
func configureKeyboardDismissOnTap() {
let keyboardDismissGesture = UITapGestureRecognizer(target: self,
action: #selector(self.dismissKeyboard))
view.addGestureRecognizer(keyboardDismissGesture)
}
func dismissKeyboard() {
// to be implemented inside your view controller(s) wanting to be able to dismiss the keyboard via tap gesture
}
}
Afterwards, any one of your view controllers or other base classes from Apple inheriting from UIViewController such as UITableViewController, etc. for that matter, will have access to the method configureKeyboardDismissOnSwipeDown().
Therefore, merely calling configureKeyboardDismissOnSwipeDown() inside viewDidLoad in each of your view controllers will be automatically injecting a swipe down gesture to dismiss the keyboard.
One caveat still remaining is that, every view controller will be in need to call configureKeyboardDismissOnSwipeDown() separately. Unfortunately, this is a bummer as you can't simply override viewDidLoad() in your extension. Moreover, it's still a mystery to me as to why Apple haven't implemented this directly into the keyboard so that us developers would not need to code around it.
Anyways, this issue can be solved by a technique called Method Swizzling. Basically, it's overriding methods given by Apple so that their behaviour change at runtime. I won't go into any more detail about method swizzling any more than saying that it can be highly dangerous to play around as you would be modifying battle-tested, solid code provided by Apple and used by the system.
Afterwards, when you implement the above provided dismissKeyboard() method in a view controller where you want to be able to dismiss the keyboard, you'll be able to do so.
TapGestureDismissable.swift
#objc protocol TapGestureDismissable where Self: UIViewController {
func hideKeyboard()
}
extension TapGestureDismissable {
func configureTapGestureToDismissKeyboard() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(hideKeyboard))
tapGesture.cancelsTouchesInView = true
view.addGestureRecognizer(tapGesture)
}
}
Inside your ViewController
extension myViewController: TapGestureDismissable {
func hideKeyboard() {
view.endEditing(true)
}
}