iOS API Function UIImageWriteToSavedPhotosAlbum takes a selector as one argment:
func UIImageWriteToSavedPhotosAlbum(_ image: UIImage,
_ completionTarget: Any?,
_ completionSelector: Selector?,
_ contextInfo: UnsafeMutableRawPointer?)
https://developer.apple.com/documentation/uikit/1619125-uiimagewritetosavedphotosalbum
However, in swift, when I call this function, the selector never gets recognized:
class Base {
func save_image(img:UIImage) {
UIImageWriteToSavedPhotosAlbum(img, self, Selector("image:didFinishSavingWithError:contextInfo:"), nil)
// I also tried this:
// UIImageWriteToSavedPhotosAlbum(img, self, #selector(image(_:didFinishSavingWithError:contextInfo:))
}
#objc func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
print("Photo Saved Successfully")
}
}
class Child:Base {
}
// This is how I call the save_image function:
let child = Child()
child.save_image()
As you can see, I tried constructing the selector from the signature, and from a string, but neither works. I always get this error in runtime:
'XXX.Child' does not implement methodSignatureForSelector: -- trouble ahead
Unrecognized selector ......
What is happening here? I am wondering if this is because swift doesn't see the method from the Child class, as the method is inherited from Base class?
How can I pass the selector successfully?
Relevant question I have read:
#selector() in Swift?
methodSignatureForSelector is the NSObject's method.
So, You need to inherit the NSObject class.
class Base: NSObject {
...
}
Provide some guidance to your selector to help it find the right function:
class Base {
func save_image(img:UIImage) {
UIImageWriteToSavedPhotosAlbum(img, self, #selector(Base.image(_:didFinishSavingWithError:contextInfo:)), nil)
}
#objc func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
print("Photo Saved Successfully")
}
}
class Child:Base {
}
// This is how I call the save_image function:
let child = Child()
child.save_image()
After all I have tried, I find that the crucial things are:
method signature must match(a lot of answers have covered that)
do not pass the parameters to the wrong positions, one of my failures is, I passed the target to the contextInfo
maybe in Swift only, the class that implements the the callback function, must be inherited from NSObject
Related
I am having trouble calling a function in a swift file from an objective-c file where there is a closure in the swift function.
This is the Swift function
//In Utilities class
static func getString(query: NSString, completion: #escaping (_ response: NSString) -> Void) {
completion("hello")
}
This is how I try to call it in the objective-c class:
[Utilities getString:#"hi there" completion:^(NSString* response) {
NSLog(response);
}];
I'm getting the error 'No known class method for selector 'getString:completion:'
What is wrong with above?
Note: I am able to call a simpler method without the closure/completion bloc.
in swift class
static func myTest () {
print("function called")
}
called from objective-c class with:
[Utilities myTest];
SO the problem seems to relate to the closure syntax.
Surround the class with
#objcMembers class Utilities:NSObject {
or the function
#objc class func getString(query: NSString, completion: #escaping (_ response: NSString) -> Void) {
[Utilities getStringWithQuery:#"hi there" completion:^(NSString* response) {
NSLog(response);
}];
As the title states, for some reason, the following (simplified) code is not working:
extension InputView: {
func updateTable(text: String) {
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(loadPlaces(text:)), object: nil)
//NSObject.cancelPreviousPerformRequests(withTarget: self)
self.perform(#selector(loadPlaces(text:)), with: text, afterDelay: 0.5)
prevSearch = inputField.text!;
}
//Private wrapper function
#objc private func loadPlaces(text: String) {
print("loading results for: \(text)")
// locator?.searchTextHasChanged(text: text)
}
}
I call updateTable every time a user edits a UITextField, which calls localPlaces which calls a function that queries google's online places API (commented out). Unfortunately, the print line in loadPlaces is called after every single call to updateTable. From my visual inspection, it seems there is in fact a delay to the print statements, however, the old calls do not cancel. I've tried looking on a lot of StackOverflow threads but I couldn't find anything updated for Swift 3. Am I calling something incorrectly?
PS. If I instead use the commented out, single-argument, cancelPreviousPerformRequests. It works for some reason.
Edit: I have been able to replicate this error in a separate project. So I'm 100% sure that the above code is wrong. If you would like to replicate this error, open up a new iOS project and paste the following code into the default ViewController:
class InputView: UIView {
func updateTable(text: String) {
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(loadPlaces(text:)), object: nil)
self.perform(#selector(loadPlaces(text:)), with: text, afterDelay: 0.5)
}
//Private wrapper function
#objc private func loadPlaces(text: String) {
print("loading results for: \(text)")
// locator?.searchTextHasChanged(text: text)
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let input = InputView()
for i in 0..<200 {
input.updateTable(text: "Call \(i)")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The explanation in Duncan C's answer is not appropriate for this case.
In the reference of cancelPreviousPerformRequests(withTarget:selector:object:):
Discussion
All perform requests are canceled that have the same target as aTarget, argument as anArgument, and selector as
aSelector.
So, when you have a line like:
<aTarget>.perform(<aSelector>, with: <anArgument>, afterDelay: someDelay)
You can cancel it with:
NSObject.cancelPreviousPerformRequests(withTarget: <aTarget>, selector: <aSelector>, object: <anArgument>)
only when all 3 things aTarget, aSelector and anArgument match.
Please try something like this and check what you see:
class InputView: UIView {
var lastPerformArgument: NSString? = nil
func updateTable(text: String) {
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(loadPlaces(text:)), object: lastPerformArgument)
lastPerformArgument = text as NSString
self.perform(#selector(loadPlaces(text:)), with: lastPerformArgument, afterDelay: 0.5)
}
//Private wrapper function
#objc private func loadPlaces(text: String) {
print("loading results for: \(text)")
// locator?.searchTextHasChanged(text: text)
}
}
EDIT:
The first part of this answer is wrong. See the edit at the bottom for updated information. I'm leaving the original answer since the discussion might be useful.
It looks to me like there is a bug in the way NSObject maps Swift function names to selectors that is preventing this from working correctly. The only way I was able to get the cancelPreviousPerformRequests function to actually cancel the pending perform() is if the function does not have any parameters. If the function takes a single anonymous parameter or a named parameter then the cancelPreviousPerformRequests function does not cancel the pending perform(_:with:afterDelay:).
Another bug I've found: If you use a function with an anonymous parameter, e.g.:
func foo(_ value: String) {
print("In function \(#function)")
}
Then the result you see in the print statement is:
In function foo
You'll see the same thing if the function has 2, 3, or more anonymous parameters.
If you have a function with no parameters, you get a different result:
func foo() {
print("In function \(#function)")
}
That code will display the message:
In function foo()
(Note the parentheses after the function name.)
EDIT
Note that it seems I was wrong. Apparently the object parameter to cancelPreviousPerformRequests must match what was passed in. You can only pass object:nil to cancelPreviousPerformRequests if the selector was invoked with a nil argument.
To quote the docs:
The argument for requests previously registered with the
perform(:with:afterDelay:) instance method. Argument equality is
determined using isEqual(:), so the value need not be the same object
that was passed originally. Pass nil to match a request for nil that
was originally passed as the argument.
Consider the private C function _UICreateScreenUIImage, which returns a UIImage snapshot of the current device screen:
OBJC_EXTERN UIImage *_UICreateScreenUIImage(void) NS_RETURNS_RETAINED;
I can put this in a bridging header and access it in Swift like so:
MyApp-Bridging-Header.h
#import UIKit;
UIImage *_UICreateScreenUIImage(void) NS_RETURNS_RETAINED;
MyClass.swift
let image = _UICreateScreenUIImage()
print(image) // <UIImage: 0x7fc4ba6081c0>, {375, 667}
Is there a way I can access _UICreateScreenUIImage in pure Swift without using a bridging header?
An initial thought was to create an extension on UIImage, but the extension is expecting me to declare the body of the function in the extension:
extension UIImage {
public func _UICreateScreenUIImage(_: Void) -> UIImage // "Expected '{' in body of function declaration"
}
This implementation is flawed anyways, as _UICreateScreenUIImage isn't a method on UIImage.
Is exposing and accessing this method possible in pure Swift?
People seem to be confusing my question with "How do I take a screenshot?" That's not what I'm asking. I'm asking how do I access methods like UIImage *_UICreateScreenUIImage(void); in Swift. It could be any private method, such as +(UIImage *)_deviceSpecificImageNamed:(NSString *)name inBundle:(NSBundle *)bundle; or +(UIImage *)_pu_PhotosUIImageNamed:(NSString *)name;
.
It's a lot easier than you would expect:
#asmname("_UICreateScreenUIImage")
func _UICreateScreenUIImage() -> UIImage
// That's it – go ahead and call it:
_UICreateScreenUIImage()
As it happens, #asmname has actually just been changed in the 2.3 builds to #_silgen_name, so be ready to adjust accordingly:
#_silgen_name("_UICreateScreenUIImage")
func _UICreateScreenUIImage() -> UIImage
To my knowledge, #_silgen_name does not provide resolution of Objective-C methods. For this, there is the evenmore powerful Objective-C runtime API:
let invokeImageNamed: (String, NSTimeInterval) -> UIImage? = {
// The Objective-C selector for the method.
let selector: Selector = "animatedImageNamed:duration:"
guard case let method = class_getClassMethod(UIImage.self, selector)
where method != nil else { fatalError("Failed to look up \(selector)") }
// Recreation of the method's implementation function.
typealias Prototype = #convention(c) (AnyClass, Selector, NSString, NSTimeInterval) -> UIImage?
let opaqueIMP = method_getImplementation(method)
let function = unsafeBitCast(opaqueIMP, Prototype.self)
// Capture the implemenation data in a closure that can be invoked at any time.
return { name, interval in function(UIImage.self, selector, name, interval) }
}()
extension UIImage {
// Convenience method for calling the closure from the class.
class func imageNamed(name: String, interval: NSTimeInterval) -> UIImage? {
return invokeImageNamed(name, interval)
}
}
UIImage.imageNamed("test", interval: 0)
As far as handling NS_RETURNS_RETAINED, this won't be generated for you. Instead, you can use a return type of Unmanaged, and wrap that in a function to your convenience:
#_silgen_name("_UICreateScreenUIImage")
func _UICreateScreenUIImage() -> Unmanaged<UIImage>
func UICreateScreenUIImage() -> UIImage {
return _UICreateScreenUIImage().takeRetainedValue()
}
Since updating to Xcode 6.3 I've run into this on one of my extensions... for WKWebView.. Specifically on these functions.
import UIKit
import WebKit
extension WKWebView: MyWebViewProvider {
// Method 'URL()' with Objective-C selector 'URL' conflicts with getter for 'URL' with the same Objective-C Selector
func URL() -> NSURL? {
return self.URL
}
// Method 'canGoBack()' with Objective-C selector 'canGoBack' conflicts with getter for 'canGoBack' with the same Objective-C Selector
func canGoBack() -> Bool {
return self.canGoBack
}
// Method 'canGoForward()' with Objective-C selector 'canGoForward' conflicts with getter for 'canGoFoard' with the same Objective-C Selector
func canGoForward() -> Bool {
return self.canGoForward
}
// Method 'evaluateJavaScript(_:completionHandler:)' with Objective-C selector 'evaluateJavaScript:completionHandler:' conflicts with previous declaration with the same Objective-C selector
func evaluateJavaScript(javascriptString: String!, completionHandler: (AnyObject, NSError) -> ()) {
self.evaluateJavaScript(javascriptString, completionHandler: { (AnyObject, NSError) -> Void in
})
}
}
How can I fix these? It was fine in 6.2.. bit of a pain.
You'll have to change the function name, or override those functions if that's what you want.
By the way, name conflicting should always be avoided. I suggest using something like myURL, myCanGoBack().
If I observe a property using KVO, if the observer is a generic class then I receive the following error:
An -observeValueForKeyPath:ofObject:change:context: message was
received but not handled.
The following setup demonstrates the problem succinctly. Define some simple classes:
var context = "SomeContextString"
class Publisher : NSObject {
dynamic var observeMeString:String = "Initially this value"
}
class Subscriber<T> : NSObject {
override func observeValueForKeyPath(keyPath: String,
ofObject object: AnyObject,
change: [NSObject : AnyObject],
context: UnsafeMutablePointer<Void>) {
println("Hey I saw something change")
}
}
Instantiate them and try to observe the publisher with the subscriber, like so (done here inside a UIViewController subclass of a blank project):
var pub = Publisher()
var sub = Subscriber<String>()
override func viewDidLoad() {
super.viewDidLoad()
pub.addObserver(sub, forKeyPath: "observeMeString", options: .New, context: &context)
pub.observeMeString = "Now this value"
}
If I remove the generic type T from the class definition then everything works fine, but otherwise I get the "received but not handled error". Am I missing something obvious here? Is there something else I need to do, or are generics not supposed to work with KVO?
Explanation
There are two reasons, in general, that can prevent a particular Swift class or method from being used in Objective-C.
The first is that a pure Swift class uses C++-style vtable dispatch, which is not understood by Objective-C. This can be overcome in most cases by using the dynamic keyword, as you obviously understand.
The second is that as soon as generics are introduced, Objective-C looses the ability to see any methods of the generic class until it reaches a point in the inheritance hierarchy where an ancestor is not generic. This includes new methods introduced as well as overrides.
class Watusi : NSObject {
dynamic func watusi() {
println("watusi")
}
}
class Nguni<T> : Watusi {
override func watusi() {
println("nguni")
}
}
var nguni = Nguni<Int>();
When passed to Objective-C, it regards our nguni variable effectively as an instance of Watusi, not an instance of Nguni<Int>, which it does not understand at all. Passed an nguni, Objective-C will print "watusi" (instead of "nguni") when the watusi method is called. (I say "effectively" because if you try this and print the name of the class in Obj-C, it shows _TtC7Divided5Nguni00007FB5E2419A20, where Divided is the name of my Swift module. So ObjC is certainly "aware" that this is not a Watusi.)
Workaround
A workaround is to use a thunk that hides the generic type parameter. My implementation differs from yours in that the generic parameter represents the class being observed, not the type of the key. This should be regarded as one step above pseudocode and is not well fleshed out (or well thought out) beyond what's needed to get you the gist. (However, I did test it.)
class Subscriber : NSObject {
private let _observe : (String, AnyObject, [NSObject: AnyObject], UnsafeMutablePointer<Void>) -> Void
required init<T: NSObject>(obj: T, observe: ((T, String) -> Void)) {
_observe = { keyPath, obj, changes, context in
observe(obj as T, keyPath)
}
}
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
_observe(keyPath, object, change, context)
}
}
class Publisher: NSObject {
dynamic var string: String = nil
}
let publisher = Publisher()
let subscriber = Subscriber(publisher) { _, _ in println("Something changed!") }
publisher.addObserver(subscriber, forKeyPath: "string", options: .New, context: nil)
publisher.string = "Something else!"
This works because Subscriber itself is not generic, only its init method. Closures are used to "hide" the generic type parameter from Objective-C.