Swift selector method not being called - objective-c

I create an action class to store the selectors, then addTarget to the button, but when I click it, the method in the action class has not been called
class ItemCardAction {
static var shared: ItemCardAction = ItemCardAction()
private init() {
}
var action: Selector = #selector(ItemCardAction.shared.buttonPressed)
#objc func buttonPressed(_ sender: UIButton!) {
print("TEST")
AppEngine.shared.updateItem(tag: sender.tag)
AppEngine.shared.notigyAllObservers()
//self.updateUI()
}
}
add target
button.addTarget(self, action: ItemCardAction.shared.action, for: .touchDown)

What you need is
button.addTarget(ItemCardAction.shared, action: ItemCardAction.shared.action, for: .touchUpInside)
If you look at addTarget method carefully, the first argument is target, when button receives (in this specific case) a touch inside event, iOS will look for the specified selector (specified in action parameter) in target specified. In your case you specified the target as self and clearly selector is not available in self's scope hence nothing happens :)

Related

Is it possible to prevent property changes of an object from triggering didSet{} in Swift/Obj-c

I have an object bound to a controller as shown below:
// ViewController
#objc dynamic var objectX: SomeClass {
didSet {
print("something")
}
}
override func viewDidLoad() {
self.bind(NSBindingName(rawValue: "objectX"),
to: controller,
withKeyPath: "selectedObject",
options: nil)
}
An I expect didSet{} to be called if the controller has a different object selected. However if one of the properties of objectX gets changed then didSet{} is also getting called.
Surely this shouldn't be occurring - and if this is expected then is there any way to prevent property changes on the object itself from triggering the didSet{} or is it necessary to check in the didSet{} whether the object is a different one to the old one?
This is a bit odd because when I test in a Playground changing a property on the object does not trigger the didSet{} callback.

Recursive looking Swift extension func, actually isn't. Why not?

I am looking at the Swift code of the ThemeKit theming library.
In particular I would like to understand the following code in NSColor+ThemeKit.swift:
// ThemeKit.set() replacement to use theme-aware color
#objc public func themeKitSet() {
// call original .set() function
themeKitSet()
// check if the user provides an alternative color
if ThemeManager.shared.isEnabled && isThemeOverriden {
// call ThemeColor.set() function
ThemeColor.color(with: Selector(colorNameComponent)).set()
}
}
There is what appears to be an endless recursive call, but presumably can't be, since the code works fine. This is confirmed by setting a breakpoint on the call to themeKitSet(). It is not possible to step into the call and execution continues without recursion.
Earlier in the file there is the following call:
swizzleInstanceMethod(cls: NSClassFromString("NSDynamicSystemColor"), selector: #selector(set), withSelector: #selector(themeKitSet))
With the implementation in NSObject+ThemeKit.swift as follows:
/// Swizzle instance methods.
#objc internal class func swizzleInstanceMethod(cls: AnyClass?, selector originalSelector: Selector, withSelector swizzledSelector: Selector) {
guard cls != nil else {
print("Unable to swizzle \(originalSelector): dynamic system color override will not be available.")
return
}
// methods
let originalMethod = class_getInstanceMethod(cls, originalSelector)
let swizzledMethod = class_getInstanceMethod(cls, swizzledSelector)
// add new method
let didAddMethod = class_addMethod(cls, originalSelector, method_getImplementation(swizzledMethod!), method_getTypeEncoding(swizzledMethod!))
// switch implementations
if didAddMethod {
class_replaceMethod(cls, swizzledSelector, method_getImplementation(originalMethod!), method_getTypeEncoding(originalMethod!))
} else {
method_exchangeImplementations(originalMethod!, swizzledMethod!)
}
}
I suspect this is responsible for the magic, but my limited understanding of both Swift and Objective-C is letting me down.
What is happening here? Why is the apparently recursive call not actually recursive?
You correctly identified the magic bit: it's called method swizzling, and it's a way of wholesale replacing an existing method implementation.
You'll see this seemingly-recursive pattern a lot when method swizzling: that themeKitSet call actually runs the original implementation, as the comment says. It's because swizzling swaps the implementations of two methods, in this case themeKitSet and NSDynamicSystemColor.set.
Therefore, post-swizzle, NSDynamicSystemColor.set runs the code you see there, and themeKitSet has become the original implementation.

Value of type 'CustomButton' has no member 'touchDown'

I have created custom button class and override all touches methods. It works fine in swift 2 and Xcode 7.3.1. But when I open same app in Xcode 8.0, it will show errors :
Value of type 'CustomButton' has no member 'touchDown'
Value of type 'CustomButton' has no member 'touchUpInside'
Value of type 'CustomButton' has no member 'touchDragExit'
Value of type 'CustomButton' has no member 'touchDragEnter'
Value of type 'CustomButton' has no member 'touchCancel'
Here is my code :
import UIKit
#IBDesignable
#objc public class CustomButton: UIButton {
private func addTargets() {
//------ add target -------
self.addTarget(self, action: #selector(self.touchDown(_:)), for: UIControlEvents.TouchDown)
self.addTarget(self, action: #selector(self.touchUpInside(_:)), for: UIControlEvents.TouchUpInside)
self.addTarget(self, action: #selector(self.touchDragExit(_:)), for: UIControlEvents.TouchDragExit)
self.addTarget(self, action: #selector(self.touchDragEnter(_:)), for: UIControlEvents.TouchDragEnter)
self.addTarget(self, action: #selector(self.touchCancel(_:)), for: UIControlEvents.TouchCancel)
}
func touchDown(sender: CustomButton) {
self.layer.opacity = 0.4
}
func touchUpInside(sender: CustomButton) {
self.layer.opacity = 1.0
}
func touchDragExit(sender: CustomButton) {
self.layer.opacity = 1.0
}
func touchDragEnter(sender: CustomButton) {
self.layer.opacity = 0.4
}
func touchCancel(sender: CustomButton) {
self.layer.opacity = 1.0
}
}
If anyone have any solution, please let me know.
If you want to keep your method headers as are in your code, you need to change the selector references to #selector(touchDown(sender:)), and so on.
(Generally, you have no need to prefix self..)
Remember all functions and methods now have consistent label treatment for their first parameters. SE-0046
(You may find many good articles, searching with "swift3 selector".)
If you want to keep the selector references, you need to change the methods like:
func touchDown(_ sender: CustomButton) {
For addition, #selector(touchDown) would work, if your class has only one touchDown(...) method.
I have found solution as #OOPer suggested, but there also need to change UIControlEvents in small case. In Xcode 7.3.1 it was UIControlEvents.TouchDown but now it has to be UIControlEvents.touchDown.
It will be like :
self.addTarget(self, action: #selector(touchDown(sender:)), for: UIControlEvents.touchDown)
self.addTarget(self, action: #selector(touchUpInside(sender:)), for: UIControlEvents.touchUpInside)
self.addTarget(self, action: #selector(touchDragExit(sender:)), for: UIControlEvents.touchDragExit)
self.addTarget(self, action: #selector(touchDragEnter(sender:)), for: UIControlEvents.touchDragEnter)
self.addTarget(self, action: #selector(touchCancel(sender:)), for: UIControlEvents.touchCancel)

Having a function argument of type "Selector" in a Swift class will render the function unavailable to Objective-C

When trying to use the Menu class below from my Objective-C code, I'm only able to access foobar (or any other function that does not have the Selector type in the argument list), while the addItemWithName function is unavailable (I have checked the autogenerated swift to objective-c file and the function is not listed in the Menu interface there). How can I make this work?
public class Menu : UIView {
public func foobar() {
// implementation
}
public func addItemWithName(itemName: String, target: AnyObject?, action: Selector?) {
// implementation
}
}
The problem type isn't Selector, it's Selector?. You can't express Optional<Selector> in ObjC because SEL is not an object type.

What is the method signature when it uses an external param name in Swift

I'm using an NSTimer object in my Swift code, which requires a method signature to be passed to its 'selector' parameter in order to recurrently perform said method. When the method signature does not have an external parameter name i.e.
func timerMethod(internal: String) { ... }
I can pass the timer object this signature as so:
var timer = NSTimer.scheduledTimerWithTimeInterval(1.0,
target: self,
selector: Selector("timerMethod:"),
userInfo: userInfo,
repeats: true)
However, if I give the method a signature with an external parameter name, such as:
func timerMethod(external internal: String) { ... }
I can't figure out how to call the method. I attempted to log it using:
println("\(__FUNCTION__)")
Which logs the following:
timerMethod(external:)
But whenever I try this or any of the following, I receive 'unrecognized selector' exceptions:
timerMethod:
timerMethod:external
timerMethod:external:
timerMethod:(external)
timerMethod:(external:)
timerMethod(external):
Stumped for now. Anybody running into something similar?
It is timerMethodWithExternal: you can test that with object_getClass(t).instancesRespondToSelector(Selector("timerMethodWithExternal:"))
i used following code to introspect
func with(t: Test, inout count : CUnsignedInt) -> UnsafePointer<Method> {
var mc : CUnsignedInt = 0
return class_copyMethodList(object_getClass(t), &count)
}
var i=0
var mc : CUnsignedInt = 0
var t = Test()
var mlist = with(t,&mc)
var n : Int = Int(mc)
for (i=0; i<n;i++) {
println(sel_getName(method_getName(mlist[i])))
}
Although the signature for your method doesn't look correct, in Swift you pass a selector simply as the string name. The Apple documentation:
Because string literals can be automatically converted to selectors,
you can pass a string literal to any method that accepts a selector.
As for the signature of NSTimer.scheduledTimerWithTimeInterval, the Apple documentation states (See NSTimer documentation, Swift info):
The selector should have the following signature: timerFireMethod:
(including a colon to indicate that the method takes an argument). The
timer passes itself as the argument, thus the method would adopt the
following pattern:
func timerFireMethod(timer: NSTimer) { }
When you define your own method as:
func timerMethod(external internal: String) { ... }
then you are not meeting the required signature. The expected call is:
someMethod(timer: ...)
but your call would be:
someMethod(external: ...)
Besides, the argument should be of type NSTimer, not String. You were probably lucky that your first attempt worked.
FWIW, I ran into the same problem and was also able to confirm the proper selector name by inspecting the Xcode-generated header file in the derived data folder for my project.
Just search for your method's name ~/Library/Developer/Xcode/DerivedData and for the original poster's example you would find that the selector to be used is timerMethodWithExternal: (from its Objective-C definition).
I believe this header is generated even for pure Swift projects, the project I tested on had some Objective-C in it though.