Accessing `shared` variable of `UIApplication` from inside `share extension` in Swift
Asked Answered
W

2

11

I need to execute my host app from inside an extension. In Objective-C I've used this:

// Get "UIApplication" class name through ASCII Character codes.
NSString *className = [[NSString alloc] initWithData:[NSData dataWithBytes:(unsigned char []){0x55, 0x49, 0x41, 0x70, 0x70, 0x6C, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E} length:13] encoding:NSASCIIStringEncoding];
if (NSClassFromString(className))
{
    //  A different way to call [UIApplication sharedApplication]
    id object = [NSClassFromString(className) performSelector: @selector(sharedApplication)];
    //  These lines invoke selector with 3 arguments
    SEL selector = @selector(openURL:options:completionHandler:);
    id  (*method)(id, SEL, id, id, id) = (void *)[object methodForSelector: selector];
    method(object, selector, myURL, nil, nil);
    //  Close extension
    [self.extensionContext completeRequestReturningItems: @[] completionHandler: nil];
}

But in Swift I have a couple of issues:

  1. UIApplication does not have sharedApplication method anymore. Instead it has a class property shared. So I can not perform a selector to get shared instance. I've tried to bypass this by writing an extension

    extension UIApplication { class func shared() -> UIApplication { return UIApplication.shared } }

but I get an error Function produces expected type 'UIApplication'; did you mean to call it with '()'?. Adding these braces will give me an infinite loop.

  1. Even if I get the instance somehow, I can't understand how to invoke open method.

    let selector = NSSelectorFromString("open(_:options:completionHandler:)") let method = object?.method(for: selector) method(destinationURL, String: Any, nil)

The last line gives me Cannot call value of non-function type 'IMP'. When pressing on the type in apple's documents, nothing happens. I can't find a description of IMP and how to work with it.

You might say: "just set Require only app-extension-safe api to NO in your extension's settings and call UIApplication.shared normally". To which I would reply that my builds get rejected by iTunes Connect with a message Compliance with export requirements is required or something along those lines (iTunes Connect is in Russian when my entire OS is in English).

So here are the questions:

  1. Is there a way to get UIApplication.shared using ASCII codes in Swift?

BTW I get class name like so

let codes = [CUnsignedChar](arrayLiteral: 0x55, 0x49, 0x41, 0x70, 0x70, 0x6C, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E)
let className = String(data: Data(bytes: UnsafeRawPointer(codes), count: 13), encoding: String.Encoding.ascii) ?? ""
  1. How do I invoke a method that has type IMP?

Thank you very much.

Wicklow answered 15/9, 2017 at 13:11 Comment(5)
Your extension works for me... Check your Swift versionCapps
@paper1111 If you are talking about Objective-C code: it is working. I have an issue with replication the same behaviour in Swift.Wicklow
"Export compliance" is about confirming that your app complies to the export laws (such as software encryption). I wonder how that is related to your problem stated here.Battleax
@Igor I'm talking about SwiftCapps
@all, I implemented shared extension in my app, In iOS 13, my share extension app can not open main app to receive shared file/image due to UIApplication.shared is unavailable now. Previous all version works fine. So, how could developer receive/import data from different sources ? Any workaround ?Optative
B
6

The problem is that you shouldn't try to access sharedApplication from your extension in the first place.

As per App Extension Programming Guide:

Some APIs Are Unavailable to App Extensions

Because of its focused role in the system, an app extension is ineligible to participate in certain activities. An app extension cannot:

  • Access a sharedApplication object, and so cannot use any of the methods on that object

So you may hack your way through it, but that will result in your application being rejected upon review.

However, to do what you're trying to do (open a URL) you don't need sharedApplication - you can simply do that using NSExtensionContexts open(_:completionHandler:)

extensionContext.open(myUrl, completionHandler: myCompletionHandler)
Bikol answered 15/9, 2017 at 13:29 Comment(1)
The problem is that extensionContext can't open host app.Wicklow
K
5

Although .shared property is not available in extensions directly, you can access it via #keyPath:

let application = UIApplication.value(forKeyPath: #keyPath(UIApplication.shared)) as! UIApplication

Kinsella answered 26/11, 2018 at 9:54 Comment(2)
I think that keyPath is like accessing to a private API, so Apple will decline your application during publishing procedure.Wangle
@AlessioCrestani, nope, keyPaths isn't like using private APIs.Kinsella

© 2022 - 2024 — McMap. All rights reserved.