Access Private UIKit Function Without Using Bridging Header - ios

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()
}

Related

Call Swift function with multiple arguments form Objective-C syntax

From Objective-C, to call a a Swift function with one argument you have to use bracket syntax and do something I've always found confusing: Add the word "With" before the argument and capitalize the argument label as in:
//swift extension
#objc public func getImageCalled(name: String?) -> UIImage? {
}
//Called from Objective-C
ImageClass *imgObject = [[ImageClass alloc] init];
UIImage * img = [imgObject getImageCalledWithName:myname];
How do you do this with two arguments as in:
#objc public func getImageCalled(name: String?,width:Int?) -> UIImage? {
}
[imgObject getImageCalledWithName:myname WithWidth:30]; is not working for me.
You can try
[imgObject getImageCalledWithName:myname width:30];

Syntax to Call Asynchronous Shared Instance Method from Within Closure in Swift

I am having trouble with syntax to call a shared instance method from within a closure:
Here is my code:
func getContactImage (contact:Contacts, completion:#escaping (_ myimage: UIImage)->()){//open 1 method
var animg = UIImage(named:"default.png")!
let surl = "https://~/contactimage.png"
Utilities.shared.downloadImage(surl: surl as NSString, completion: image as UIImage ->Void in animg = img)
completion(animg)
}
The line Utilities.shared.downloadImage gives several errors including:
Cannot convert value of type 'UIAccessibilityTraits' (aka 'UInt64') to type 'UIImage' in coercion
The shared instance method looks like this:
#objc func downloadImage(surl: NSString, completion : #escaping (UIImage) -> Void ) {
//download image
}
What is the correct syntax to call the shared instance method from within closure?
Use
Utilities.shared.downloadImage(surl) { (img) in
// use img here
}
Also change function
#objc func downloadImage(_ surl: String, completion : #escaping (UIImage) -> Void ) { }
func getContactImage (contact:Contacts, completion:#escaping (_ myimage: UIImage)->()){//open 1 method
var animg = UIImage(named:"default.png")!
let surl = "https://~/contactimage.png"
Utilities.shared.downloadImage(surl) { (img) in
completion(img)
}
}
BTW encourage using SDWebImage

Trouble with Swift Protocols, associatedtypes, Self and default implementations

I am trying to get some functionality through default implementations that I can't nail. Consider the following code, which is a simplification of what I'm trying to do, but captures the problem as simply as possible.
//protocol definition
protocol Configurable {
associatedtype Data
func configure(data: Data)
static func generateObject() -> Self
}
//default implementation for any UIView
extension Configurable where Self: UIView {
static func generateObject() -> Self {
return Self()
}
}
//implement protocol for UILabels
extension UILabel: Configurable {
typealias Data = Int
func configure(data: Int) {
label.text = "\(data)"
}
}
//use the protocol
let label = UILabel.generateObject()
label.configure(data: 5)
print(label.text!) //5
I have a protocol, a default implementation for some methods for UIView, and the a specific implementation for UILabel.
My issue is the last part... the actual use of all this functionality
let label = UILabel.generateObject()
label.configure(data: 5)
print(label.text!) //5
I find myself doing generateObject() followed by configure(data: <something>) constantly. So I tried doing the following:
Add static func generateObjectAndConfigure(data: Data) -> Self to the protocol. The issue comes when I try to make a default implementation for UIView for this method. I get the following error
Method 'generateObjectAndConfigure(data:)' in non-final class 'UILabel' cannot be implemented in a protocol extension because it returnsSelfand has associated type requirements
Basically, I can't have a method that returns Self and uses an associated type. It feels really nasty for me to always call the two methods in a row. I want to only declare configure(Data) for each class and get generateObjectAndConfigure(Data) for free.
Any suggestions?
You're overcomplicating this a bit, by using Self.
All you need to do is declare an initialiser in your Configurable protocol that accepts your Data associatedtype as an argument, and has a non-static configure function:
protocol Configurable {
associatedtype Data
init(data: Data)
func configure(data: Data)
}
Provide a default implementation of that initializer in an extension for the Configurable protocol (for UIView and its subclasses):
extension Configurable where Self: UIView {
init(data: Data) {
self.init(frame: CGRect.zero)
self.configure(data: data)
}
}
Finally, add conformance to the protocol via an extension to any UIView subclasses you're interested in. All you need to do here is to implement the typealias and configure method:
extension UILabel: Configurable {
typealias Data = Int
func configure(data: Data) {
text = "\(data)"
}
}
extension UIImageView: Configurable {
typealias Data = String
func configure(data: Data) {
image = UIImage(named: data)
}
}
This implementation has the added bonus that you're using an initializer to create your views (the standard Swift pattern for instantiating an object), rather than a static method:
let label = UILabel(data: 10)
let imageView = UIImageView(data: "screenshot")
It's not exactly clear to me why the compiler doesn't like your version. I would have thought that subclasses of UILabel would inherit the typealias meaning that the compiler shouldn't have a problem inferring both Self and Data, but apparently this isn't supported yet.
Edit: #Cristik makes a good point about UICollectionView in the comments.
This problem can be solved by adding a protocol extension for Configurable where the Self is UICollectionView, using the appropriate initializer:
extension Configurable where Self: UICollectionView {
init(data: Data) {
self.init(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout())
self.configure(data: data)
}
}
Then, when adding conformance to Configurable for UICollectionView, we make the Data typealias a UICollectionViewLayout:
extension UICollectionView: Configurable {
typealias Data = UICollectionViewLayout
func configure(data: Data) {
collectionViewLayout = data
}
}
Personally, I think this is a reasonable approach for classes where the init(frame:) initializer isn't appropriate.

Passing closures to Private API's

I'm trying to fetch all the available airplay devices from the private API MPAVRoutingController. I'm using a third party perform selector library for swift called performSelector-Swift. The method I am trying to call is fetchAvailableRoutesWithCompletionHandler. This takes one parameter, an objective-c block. - (void)fetchAvailableRoutesWithCompletionHandler:(id /* block */)arg1; When I try and pass in a closure I get a compile error and if I don't pass anything in my app crashes. I'm not releasing this app and thats why I'm using the priv API.
let MPAVRoutingController = NSClassFromString("MPAVRoutingController")! as! NSObject.Type
let routingController = MPAVRoutingController.init()
if let availableRoutes = routingController.swift_performSelector("fetchAvailableRoutesWithCompletionHandler:", withObject: {
object in
}) {
print(availableRoutes)
}
First.. How I found the correct completion block signature: http://i.imgur.com/UGVayPE.png
That shows that it allocates an NSMutableArray as the parameter to the completion block when it invokes it. That's the only parameter. You don't have to do this (disassemble it). Upon an exception being thrown, you can print the signature. Sometimes it will also tell you which kind of block is expected.
Next, my opinion on invoking selectors dynamically..
Your best option is to not perform selectors.. It's a pain especially when the call contains MULTIPLE parameters..
What you can do is invocation through interface/extension pointers.. I do this in C++ (Idea from the Pimpl idiom.. COMM interfaces do this too) all the time and it works with Swift, Objective-C, Java.. etc..
Create a protocol that has the same interface as the object. Create an extension that inherits that protocol. Then cast the object instance to that extension/interface/protocol.
Call whatever function you want via the interface/extension/protocol pointer.
import UIKit
import MediaPlayer
#objc
protocol MPAProtocol { //Functions must be optional. That way you don't implement their body when you create the extension.
optional func availableRoutes() -> NSArray
optional func discoveryMode() -> Int
optional func fetchAvailableRoutesWithCompletionHandler(completion: (routes: NSArray) -> Void)
optional func name() -> NSString
}
extension NSObject : MPAProtocol { //Needed otherwise casting will fail!
//Do NOT implement the body of the functions from the protocol.
}
Usage:
let MPAVRoutingControllerClass: NSObject.Type = NSClassFromString("MPAVRoutingController") as! NSObject.Type
let MPAVRoutingController: MPAProtocol = MPAVRoutingControllerClass.init() as MPAProtocol
MPAVRoutingController.fetchAvailableRoutesWithCompletionHandler! { (routes) in
print(routes);
}
If you were to do it with a Bridging header instead of creating the extension + protocol, you'd just do a single Objective-C category:
#import <Foundation/Foundation.h>
#interface NSObject (MPAVRoutingControllerProtocol)
- (void)fetchAvailableRoutesWithCompletionHandler:(void(^)(NSArray *routes))completion;
#end
#implementation NSObject (MPAVRoutingControllerProtocol)
#end
Then:
let MPAVRoutingControllerClass: NSObject.Type = NSClassFromString("MPAVRoutingController") as! NSObject.Type
let MPAVRoutingController = MPAVRoutingControllerClass.init()
MPAVRoutingController.fetchAvailableRoutesWithCompletionHandler! { (routes) in
print(routes);
}
Finally, if you can use protocol injection, you can do this much easier:
func classFromString(cls: String, interface: Protocol?) -> NSObject.Type? {
guard let interface = interface else {
return NSClassFromString(cls) as? NSObject.Type
}
if let cls = NSClassFromString(cls) {
if class_conformsToProtocol(cls, interface) {
return cls as? NSObject.Type
}
if class_addProtocol(cls, interface) {
return cls as? NSObject.Type
}
}
return nil
}
func instanceFromString<T>(cls: String, interface: Protocol?) -> T? {
return classFromString(cls, interface: interface)?.init() as? T
}
#objc
protocol MPAProtocol {
optional func availableRoutes() -> NSArray
optional func discoveryMode() -> Int
optional func fetchAvailableRoutesWithCompletionHandler(completion: (routes: NSArray) -> Void)
optional func name() -> NSString
}
let MPAVRoutingController: MPAProtocol = instanceFromString("MPAVRoutingController", interface: MPAProtocol.self)!
MPAVRoutingController.fetchAvailableRoutesWithCompletionHandler! { (routes) in
print(routes);
}

How to handle Protocol Delegate when converting Objective-C to Swift

I'm trying to convert a speech recognition code to Swift, Protocol defined in ViewController.h as:
#interface ViewController : UIViewController<SpeechRecognitionProtocol>
{
NSMutableString* textOnScreen;
DataRecognitionClient* dataClient;
MicrophoneRecognitionClient* micClient;
SpeechRecognitionMode recoMode;
bool isMicrophoneReco;
bool isIntent;
int waitSeconds;
}
I got stuck converting below function at ViewController.h:
micClient = [SpeechRecognitionServiceFactory createMicrophoneClient:(recoMode)
withLanguage:(language)
withKey:(primaryOrSecondaryKey)
withProtocol:(self)];
This function is defined in the SpeechSDK.framework as:
#interface SpeechRecognitionServiceFactory : NSObject
/*
#param delegate The protocol used to perform the callbacks/events upon during speech recognition.
*/
+(MicrophoneRecognitionClient*)createMicrophoneClient:(SpeechRecognitionMode)speechRecognitionMode
withLanguage:(NSString*)language
withKey:(NSString*)primaryOrSecondaryKey
withProtocol:(id<SpeechRecognitionProtocol>)delegate;
#end
this protocol looks like this in my converted ViewController.Swift:
import UIKit
protocol SpeechRecognitionProtocol {
func onIntentReceived(result: IntentResult)
func onPartialResponseReceived(response: String)
func onFinalResponseReceived(response: RecognitionResult)
func onError(errorMessage: String, withErrorCode errorCode: Int)
func onMicrophoneStatus(recording: DarwinBoolean)
func initializeRecoClient()
}
class ViewController: UIViewController, SpeechRecognitionProtocol {
var myDelegate: SpeechRecognitionProtocol?
finally I am calling this function inside ViewController.swift. I am getting following error after withProtocol: cannot convert value of type 'SpeechRecognitionProtocol.Protocol' to expected argument type 'SpeechRecognitionProtocol!' :
func initializeRecoClient() {
let language: String = "en-us"
let path: String = NSBundle.mainBundle().pathForResource("settings", ofType: "plist")!
let settings = NSDictionary(contentsOfFile: path)
let primaryOrSecondaryKey = settings?.objectForKey("primaryKey") as! String
micClient = SpeechRecognitionServiceFactory.createMicrophoneClient(recoMode!,
withLanguage: language,
withKey: primaryOrSecondaryKey,
withProtocol: SpeechRecognitionProtocol)
}
You shouldn't declare the SpeechRecognitionProtocol yourself (not sure you added this just for demonstration purposes or whether your actually have that in your code). SpeechRecognitionProtocol is already declared in SpeechRecognitionService.h and available to Swift - this is the one you need to use.
The object implementing that protocol is ViewController. Assuming your initializeRecoClient is a method of that class, the call would need to look like:
micClient = SpeechRecognitionServiceFactory
.createMicrophoneClient(recoMode!,
withLanguage: language,
withKey: primaryOrSecondaryKey,
withProtocol: self)
The SpeechSDK API didn't choose a particularly good name for that factory method.
The withProtocol argument doesn't take the protocol object itself (as the name suggests), but the object implementing the protocol (obviously).
P.S.: Not sure what SpeechAPI version you use, I had to implement those Swift methods to make ViewController conform to SpeechRecognitionProtocol:
func onPartialResponseReceived(response: String!) {}
func onFinalResponseReceived (response: RecognitionResult) {}
func onError (errorMessage: String!,
withErrorCode errorCode: Int32) {}
func onMicrophoneStatus (recording: Bool) {}
func onIntentReceived (result: IntentResult) {}

Resources