Share Extension to open containing app - ios

I want to create an Android Style share feature for my app.
I created a share extension which gets called when you select pictures inside the stock photo app and press share.
Now I want those pictures to be sent to the main app and get handled over there.
My question is now:
Can iOS open my app after a button is pressed on the share extension window?
How do I get the picture files inside my main app?

Swift 4+ (tested on iOS 13)
#objc should be added to the declaration of openURL, that is,
#objc func openURL(_ url: URL) -> Bool {
// Code below.
}
Without it one would see this compiler error:
Argument of '#selector' refers to instance method 'openURL' that is not exposed to Objective-C
Working solution in Swift 3.1 (tested in iOS10):
You need to create your own URL Scheme, then add this function to your ViewController and call it with openURL("myScheme://myIdentifier")
// Function must be named exactly like this so a selector can be found by the compiler!
// Anyway - it's another selector in another instance that would be "performed" instead.
func openURL(_ url: URL) -> Bool {
var responder: UIResponder? = self
while responder != nil {
if let application = responder as? UIApplication {
return application.perform(#selector(openURL(_:)), with: url) != nil
}
responder = responder?.next
}
return false
}
Edit: Notes for clarification:
openURL is a method of UIApplication - since your ShareExtension is not derived from UIApplication I added my own openURL with the same definition as the one from UIApplication to keep the compiler happy (so that #selector(openURL(_:) can be found).
Then I go through the responders until I find one that is really derived from UIApplication and call openURL on that.
More stripped-down-example-code which copies files in a ShareExtension to a local directory, serializing filenames and calling openURL on another app:
//
// ShareViewController.swift
//
import UIKit
import Social
import MobileCoreServices
class ShareViewController: UIViewController {
var docPath = ""
override func viewDidLoad() {
super.viewDidLoad()
let containerURL = FileManager().containerURL(forSecurityApplicationGroupIdentifier: "group.com.my-domain")!
docPath = "\(containerURL.path)/share"
// Create directory if not exists
do {
try FileManager.default.createDirectory(atPath: docPath, withIntermediateDirectories: true, attributes: nil)
} catch let error as NSError {
print("Could not create the directory \(error)")
} catch {
fatalError()
}
// removing previous stored files
let files = try! FileManager.default.contentsOfDirectory(atPath: docPath)
for file in files {
try? FileManager.default.removeItem(at: URL(fileURLWithPath: "\(docPath)/\(file)"))
}
}
override func viewDidAppear(_ animated: Bool) {
let alertView = UIAlertController(title: "Export", message: " ", preferredStyle: .alert)
self.present(alertView, animated: true, completion: {
let group = DispatchGroup()
NSLog("inputItems: \(self.extensionContext!.inputItems.count)")
for item: Any in self.extensionContext!.inputItems {
let inputItem = item as! NSExtensionItem
for provider: Any in inputItem.attachments! {
let itemProvider = provider as! NSItemProvider
group.enter()
itemProvider.loadItem(forTypeIdentifier: kUTTypeData as String, options: nil) { data, error in
if error == nil {
// Note: "data" may be another type (e.g. Data or UIImage). Casting to URL may fail. Better use switch-statement for other types.
// "screenshot-tool" from iOS11 will give you an UIImage here
let url = data as! URL
let path = "\(self.docPath)/\(url.pathComponents.last ?? "")"
print(">>> sharepath: \(String(describing: url.path))")
try? FileManager.default.copyItem(at: url, to: URL(fileURLWithPath: path))
} else {
NSLog("\(error)")
}
group.leave()
}
}
}
group.notify(queue: DispatchQueue.main) {
NSLog("done")
let files = try! FileManager.default.contentsOfDirectory(atPath: self.docPath)
NSLog("directory: \(files)")
// Serialize filenames, call openURL:
do {
let jsonData : Data = try JSONSerialization.data(
withJSONObject: [
"action" : "incoming-files"
],
options: JSONSerialization.WritingOptions.init(rawValue: 0))
let jsonString = (NSString(data: jsonData, encoding: String.Encoding.utf8.rawValue)! as String).addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
let result = self.openURL(URL(string: "myapp://com.myapp.share?\(jsonString!)")!)
} catch {
alertView.message = "Error: \(error.localizedDescription)"
}
self.dismiss(animated: false) {
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
}
})
}
// Function must be named exactly like this so a selector can be found by the compiler!
// Anyway - it's another selector in another instance that would be "performed" instead.
#objc func openURL(_ url: URL) -> Bool {
var responder: UIResponder? = self
while responder != nil {
if let application = responder as? UIApplication {
return application.perform(#selector(openURL(_:)), with: url) != nil
}
responder = responder?.next
}
return false
}
}

Technically you can't open containing app from share extension, but you can schedule local notification, and that's what I end up doing. Just before I call super.didSelectPost, I schedule local notification with some text, and if user wants to open containing app, they can, and if not - they can continue with their workflow. I even think its a better approach than automatically opening containing app and disrupting what they are doing.

Currently there's no way to do this. A share extension cannot open the containing app.
The intended approach for share extensions is that they handle all of the necessary work themselves. Extensions can share code with their containing apps by using custom frameworks, so in most cases that's no problem.
If you want to make data available to your app, you can set up an app group so that you have a shared directory. The extension can write data there, and the app can read it. That won't happen until the next time the user launches the app, though.

I opened the host app from shared extension with a trick.
Using a webview with clear background color.
below is the code
NSString *customURL = #"MY_HOST_URL_SCHEME_APP://";
UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 300, 400)];
webView.backgroundColor = [UIColor clearColor];
webView.tintColor = [UIColor clearColor];
[webView setOpaque:NO];
[self.view addSubview:webView];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:customURL]];
[webView loadRequest:urlRequest];
[self didSelectCancel];

Implement custom url schema in host app and call openURL(url:) method
like openURL(url:NSURL(string:"schema_name://"))
extension SLComposeServiceViewController {
func openURL(url: NSURL) -> Bool {
do {
let application = try self.sharedApplication()
return application.performSelector("openURL:", withObject: url) != nil
}
catch {
return false
}
}
func sharedApplication() throws -> UIApplication {
var responder: UIResponder? = self
while responder != nil {
if let application = responder as? UIApplication {
return application
}
responder = responder?.nextResponder()
}
throw NSError(domain: "UIInputViewController+sharedApplication.swift", code: 1, userInfo: nil)
}
}

Xamarin.iOS version of #coyer answer:
using System;
using Foundation;
using UIKit;
using MobileCoreServices;
using CoreFoundation;
using System.Linq;
using Newtonsoft.Json;
using System.Collections.Generic;
using ObjCRuntime;
using System.Runtime.InteropServices;
namespace Your.ShareExtension
{
public partial class ShareViewController : UIViewController
{
public ShareViewController(IntPtr handle) : base(handle)
{
}
string docPath = "";
public override void ViewDidLoad()
{
base.ViewDidLoad();
try
{
var containerURL = new NSFileManager().GetContainerUrl("group.com.qsiga.startbss");
docPath = $"{containerURL.Path}/share";
// Create directory if not exists
try
{
NSFileManager.DefaultManager.CreateDirectory(docPath, true, null);
}
catch (Exception e)
{ }
// removing previous stored files
NSError contentError;
var files = NSFileManager.DefaultManager.GetDirectoryContent(docPath, out contentError);
foreach (var file in files)
{
try
{
NSError err;
NSFileManager.DefaultManager.Remove($"{docPath}/{file}", out err);
}
catch (Exception e)
{ }
}
}
catch (Exception e)
{
Console.WriteLine("ShareViewController exception: " + e);
}
}
public override void ViewDidAppear(bool animated)
{
var alertView = UIAlertController.Create("Export", " ", UIAlertControllerStyle.Alert);
PresentViewController(alertView, true, () =>
{
var group = new DispatchGroup();
foreach (var item in ExtensionContext.InputItems)
{
var inputItem = item as NSExtensionItem;
foreach (var provider in inputItem.Attachments)
{
var itemProvider = provider as NSItemProvider;
group.Enter();
itemProvider.LoadItem(UTType.Data.ToString(), null, (data, error) =>
{
if (error == null)
{
// Note: "data" may be another type (e.g. Data or UIImage). Casting to URL may fail. Better use switch-statement for other types.
// "screenshot-tool" from iOS11 will give you an UIImage here
var url = data as NSUrl;
var path = $"{docPath}/{(url.PathComponents.LastOrDefault() ?? "")}";
NSError err;
NSFileManager.DefaultManager.Copy(url, NSUrl.CreateFileUrl(path, null), out err);
}
group.Leave();
});
}
}
group.Notify(DispatchQueue.MainQueue, () =>
{
try
{
var jsonData = JsonConvert.SerializeObject(new Dictionary<string, string>() { { "action", "incoming-files" } });
var jsonString = NSString.FromData(jsonData, NSStringEncoding.UTF8).CreateStringByAddingPercentEncoding(NSUrlUtilities_NSCharacterSet.UrlQueryAllowedCharacterSet);
var result = openURL(new NSUrl($"startbss://share?{jsonString}"));
}
catch (Exception e)
{
alertView.Message = $"Error: {e.Message}";
}
DismissViewController(false, () =>
{
ExtensionContext?.CompleteRequest(new NSExtensionItem[] { }, null);
});
});
});
}
public bool openURL(NSUrl url)
{
UIResponder responder = this;
while (responder != null)
{
var application = responder as UIApplication;
if (application != null)
return CallSelector(application, url);
responder = responder?.NextResponder;
}
return false;
}
[DllImport(Constants.ObjectiveCLibrary, EntryPoint = "objc_msgSend")]
static extern bool _callSelector(
IntPtr target,
IntPtr selector,
IntPtr url,
IntPtr options,
IntPtr completionHandler
);
private bool CallSelector(UIApplication application, NSUrl url)
{
Selector selector = new Selector("openURL:options:completionHandler:");
return _callSelector(
application.Handle,
selector.Handle,
url.Handle,
IntPtr.Zero,
IntPtr.Zero
);
}
}
}

I'm able to get this working by accessing the shared UIApplication instance via key-value coding and calling openURL on that:
let application = UIApplication.value(forKeyPath: #keyPath(UIApplication.shared)) as! UIApplication
let selector = NSSelectorFromString("openURL:")
let url = URL(string: "jptest://")!
application.perform(selector, with: url)

I was having this problem, and in iOS 11+ none of the previous answers work. I ended up adding a completion handler to my JavaScript code, and from there setting window.location="myapp://". It's a bit hacky but it doesn't look to bad and the user can follow along.

Not only there is no way (and won't be) to do this:
there is no NEED to handle this in the app.
The extension is supposed to handle this with the very
same codebase as the main app. You should create a framework
with extension safe API shared between the app and the extesnion targets.
This is the top topic here:
https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW1
Extra rationale: in the extension you'd have to work with a much
smaller memory allowance meaning: if you use the images
of decent size as in the main app you will likely crash and burn.
In extension you'd have to work with jpeg or reasonable small size
and even then make sure size is small enough otherwise you'd be booted out trying to unpack the image from disk into memory
(see size limitation above)

EDIT: This solution works for today extension (Widget).
An extension can open the hosting app:
- (IBAction)launchHostingApp:(id)sender
{
NSURL *pjURL = [NSURL URLWithString:#"hostingapp://home"];
[self.extensionContext openURL:pjURL completionHandler:nil];
}
And like Apple says in Handling Commons Scenarios :
An extension doesn’t directly tell its containing app to open; instead, it uses the openURL:completionHandler: method of NSExtensionContext to tell the system to open its containing app. When an extension uses this method to open a URL, the system validates the request before fulfilling it.

Related

FileProviderExtension workingSet enumerator didn't call

I have a problem with working set enumerator (Recents tab) in Files app. I implement a working enumerator for Folders, it runs enumerateItems(for observer method when I move to the folder in UI and everything works fine.
And I have a different enumerator for working set too, but it is not created and enumerated anything.
override func enumerator(for containerItemIdentifier: NSFileProviderItemIdentifier) throws -> NSFileProviderEnumerator {
let maybeEnumerator: NSFileProviderEnumerator?
if (containerItemIdentifier == NSFileProviderItemIdentifier.rootContainer) {
let directory = repository.directory(for: containerItemIdentifier)!
maybeEnumerator = FileProviderFolderEnumerator()
} else if (containerItemIdentifier == NSFileProviderItemIdentifier.workingSet) {
maybeEnumerator = FileProviderWorkingSetEnumerator() // not called
} else {
_ = repository.item(for: containerItemIdentifier)
if repository.isDirectory(at: containerItemIdentifier) {
let directory = repository.directory(for: containerItemIdentifier)!
maybeEnumerator = FileProviderFolderEnumerator() // works fine
} else {
maybeEnumerator = FileProviderItemEnumerator()
}
}
guard let enumerator = maybeEnumerator else {
throw NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError, userInfo:[:])
}
return enumerator
}
I'm trying to call working set enumerator in startProvidingItem method using signal, but it doesn't work.
override func startProvidingItem(at url: URL, completionHandler: #escaping ((_ error: Error?) -> Void)) {
loadItem {
//when done with loading
NSFileProviderManager.default.signalEnumerator(
for: NSFileProviderItemIdentifier.workingSet,
completionHandler: { error in
if let err = error { print(err) }
}
}
}
Does FileProviderWorkingSetEnumerator initialize automatically when I open Recents tab in Files app? Should I call it somehow directly from FileExtension?
Thank you!
The working set enumerator doesn't work like what you think.
The file provider extension will enumerate working set in background, even before you open Files app.
That's why you need to prepare working set data in offline.
The WWDC 2017 document might help.

AVAudioSession never stopped

I'm trying to set my AVAudioSession to inactive to get back to normal state.
My utterance function:
class SSpeech : NSObject, AVSpeechSynthesizerDelegate {
var group = DispatchGroup();
var queue = DispatchQueue(label: "co.xxxx.speech", attributes: [])
class var sharedInstance: SSpeech {
struct Static {
static var instance: SSpeech?
}
if !(Static.instance != nil) {
Static.instance = SSpeech()
}
return Static.instance!
}
required override init() {
super.init();
self.speechsynt.delegate = self;
}
deinit {
print("deinit SSpeech")
}
let audioSession = AVAudioSession.sharedInstance();
var speechsynt: AVSpeechSynthesizer = AVSpeechSynthesizer()
var queueTalks = SQueue<String>();
func pause() {
speechsynt.pauseSpeaking(at: .word)
}
func talk(_ sentence: String, languageCode code:String = SUtils.selectedLanguage.code, withEndPausing: Bool = false) {
if SUser.sharedInstance.currentUser.value!.speechOn != 1 {
return
}
queue.async{
self.queueTalks.enQueue(sentence)
do {
let category = AVAudioSessionCategoryPlayback;
var categoryOptions = AVAudioSessionCategoryOptions.duckOthers
if #available(iOS 9.0, *) {
categoryOptions.formUnion(AVAudioSessionCategoryOptions.interruptSpokenAudioAndMixWithOthers)
}
try self.audioSession.setCategory(category, with: categoryOptions)
try self.audioSession.setActive(true);
} catch _ {
return;
}
self.utteranceTalk(sentence, initSentence: false, speechsynt: self.speechsynt, languageCode:code, withEndPausing: withEndPausing)
do {
try self.audioSession.setCategory(AVAudioSessionCategoryPlayback, with: AVAudioSessionCategoryOptions.mixWithOthers)
} catch _ {
return;
}
}
}
func utteranceTalk(_ sentence: String, initSentence: Bool, speechsynt: AVSpeechSynthesizer, languageCode:String = "en-US", withEndPausing: Bool = false){
if SUser.sharedInstance.currentUser.value!.speechOn != 1 {
return
}
let nextSpeech:AVSpeechUtterance = AVSpeechUtterance(string: sentence)
nextSpeech.voice = AVSpeechSynthesisVoice(language: languageCode)
if !initSentence {
nextSpeech.rate = 0.4;
}
if(withEndPausing){
nextSpeech.postUtteranceDelay = 0.2;
}
speechsynt.speak(nextSpeech)
}
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance:AVSpeechUtterance) {
print("Speaker has finished to talk")
queue.async {
do {
try self.audioSession.setActive(false, with: AVAudioSessionSetActiveOptions.notifyOthersOnDeactivation)
}
catch {}
}
}
}
}
My method is correctly called, but my audioSession still active when the utterance is finished. i've tried lot of thing but nothing work :(.
I would suggest using an AvAudioPlayer. They have very easy start and stop commands.
first declare the audio player as a variable
var SoundEffect: AVAudioPlayer!
then select the file you need
let path = Bundle.main.path(forResource: "Untitled2.wav", ofType:nil)!
let url = URL(fileURLWithPath: path)
let sound = try AVAudioPlayer(contentsOf: url)
SoundEffect = sound
sound.numberOfLoops = -1
sound.play()
and to stop the audio player
if SoundEffect != nil {
SoundEffect.stop()
SoundEffect = nil
}
You cannot stop or deactive AudioSession, your app gets it upon launching. Documentation:
An audio session is the intermediary between your app and iOS used to configure your app’s audio behavior. Upon launch, your app automatically gets a singleton audio session.
So method -setActive: does not make your AudioSession "active", it just puts its category and mode configuration into action. For getting back to the "normal state", you could set default settings or just call setActive(false, with:.notifyOthersOnDeactivation), that will be enough.
A part from documentation of AVAudioSession:
Discussion
If another active audio session has higher priority than yours (for
example, a phone call), and neither audio session allows mixing,
attempting to activate your audio session fails. Deactivating your
session will fail if any associated audio objects (such as queues,
converters, players, or recorders) are currently running.
My guess is that the failure to deactivate the session is the running process(es) of your queue as I highlighted in the document quote.
Probably you should make the deactivation process synchronous instead of asynchronous OR make sure that all the running actions under your queue has been processed.
Give this a try:
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance:AVSpeechUtterance) {
print("Speaker has finished to talk")
queue.sync { // <---- `async` changed to `sync`
do {
try self.audioSession.setActive(false, with: AVAudioSessionSetActiveOptions.notifyOthersOnDeactivation)
}
catch {}
}
}
}

UIDocument not saving to file despite indicating success

I'm trying to open, modify, and save a file in iCloud Drive using UIDocument. When I call save(to:for:completionHandler:) with the file location and using .forOverwriting for the UIDocumentSaveOperation, it completes with a status of success = true. However, the iCloud file (as seen in both desktop and iOS file browser) does not update, and when reopening the file, the changes are not shown. I've verified that contents(forType:) returns the correct (modified) file contents when saving.
(Note: I've already looked at this question, but it wasn't very helpful 😕)
Here are the relevant sections of code:
MainViewController.swift:
var saveFile: SBDocument?
#IBAction func bbiOpen_pressed(_ sender: UIBarButtonItem) {
if saveFile == nil {
let importMenu = UIDocumentMenuViewController(documentTypes: self.UTIs, in: .import)
importMenu.delegate = self
importMenu.popoverPresentationController?.barButtonItem = bbiOpen
self.present(importMenu, animated: true, completion: nil)
} else {
willClose()
}
}
func willClose(_ action: UIAlertAction?) {
if saveFile!.hasUnsavedChanges {
dlgYesNoCancel(self, title: "Save Changes?", message: "Would you like to save the changes to your document before closing?", onYes: doSaveAndClose, onNo: doClose, onCancel: nil)
} else {
doSaveAndClose(action)
}
}
func doSaveAndClose(_ action: UIAlertAction?) {
saveFile?.save(to: saveFileURL!, for: .forOverwriting, completionHandler: { Void in
self.saveFile?.close(completionHandler: self.didClose)
})
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
saveFile = SBDocument(fileURL: url)
saveFile!.open(completionHandler: { success in self.finishOpen(didCompleteSuccessfully: success) })
}
func finishOpen(didCompleteSuccessfully result: Bool) {
if result {
print(saveFile!.localizedName)
saveFileURL = saveFile!.fileURL
saveFileName = saveFile!.localizedName
self.navTitleBar.prompt = saveFileName
bbiOpen.title = NSLocalizedString("titleClose", comment: "Close")
bbiOpen.style = .plain
} else {
saveFile = nil
}
}
#IBAction func bbiSave_pressed(_ sender: UIBarButtonItem) {
self.saveFile!.save(to: self.saveFileURL!, for: .forOverwriting, completionHandler: self.didSave)
}
func didSave(_ success: Bool) {
guard success else {
print("Error saving soundboard file to \(String(describing: saveFileURL))")
return
}
print("File saved successfully")
}
SBDocument.swift:
class SBDocument: UIDocument {
override var fileType: String? { get { return "com.whitehatenterprises.SoundBoardFX.sbd" } }
override var savingFileType: String? { get { return "com.whitehatenterprises.SoundBoardFX.sbd" } }
override init(fileURL url: URL) {
super.init(fileURL: url)
}
override func contents(forType typeName: String) throws -> Any {
let arr = NSArray(array: SoundEffects)
let data: NSData = NSKeyedArchiver.archivedData(withRootObject: arr) as NSData
return data
}
}
Update:
I really need help with this, and I've tried everything I can think of to fix this. Any assistance you could give me would be greatly appreciated.
The way the initial file generation works for me is:
let doc = YourUIDocumentClass(fileURL: fileURL)
doc.save(to: fileURL, for: .forCreating) { success in
...
}
Then modify the file and then do:
doc.save(to: fileURL, for: .forOverwriting) { success in
...
}
when done. And subsequent accesses to the file are done by:
doc.open() { success in
...
}
doc.close() { success in
...
}
You might also need to do a:
doc.updateChangeCount(.done)
while the file is open to tell the document there are unsaved changes. Just setting this will cause a save after a few seconds. You don't even need the close to do that.
The ... means that you either have to nest all these or make sure there is enough time between them so they are completed.
In addition to the above answers, another cause of this can be that there's an error during the save process unrelated to contents(forType:).
For example, if you implement fileAttributesToWrite(to:for:) and throw an error, then this can cause a UIDocumentState.savingError even though contents(forType:) returns the correct data.
So according to
https://developer.apple.com/reference/uikit/uidocument
It looks like the save function isn't actually for saving a document. My understanding from reading it is that save is only for creating a new document. I understand that you are using the .forOverwriting to just save over it but there may be something in iCloud that wont let the complete overwrite happen.
In your doSaveAndClose method try calling
self.saveFile?.close(completionHandler: self.didClose)
by itself. You may have to do some type of if query where you check if the file exist. If it doesn't then call the .save(), else call the .close function. It seems that no matter what when the document it closed it saves changes.

Open My application from my keyboard extension in swift 3.0

I am trying to open from my keyboard extension. I am having custom keyboard and I have add that keyboard from setting. On my custom keyboard there is one button “Show More”, and I want to open my app on this button click.
So I have tried following code :
let context = NSExtensionContext()
context.open(url! as URL, completionHandler: nil)
var responder = self as UIResponder?
while (responder != nil) {
if responder?.responds(to: Selector("openURL:")) == true {
responder?.perform(Selector("openURL:"), with: url)
}
responder = responder!.next
}
It is working successfully, but as we know in swift Selector("method_name:") is deprecated and use #selector(classname.methodname(_:)) instead so it is giving warning. And I want to solve that warning. So I have tried as Xcode automatically suggested :
if responder?.responds(to: #selector(UIApplication.openURL(_:))) == true {
responder?.perform(#selector(UIApplication.openURL(_:)), with: url)
}
Also tried :
if responder?.responds(to: #selector(NSExtensionContext.open(_:))) == true {
responder?.perform(#selector(NSExtensionContext.open(_:)), with: url)
}
I have also tried others possible ways, but no luck. If anyone know how to do, please let me know.
I referred this link, Julio Bailon’s answer :
openURL not work in Action Extension
Swift 5.0:
Open Info.plist of hosting app.
Add Url Types -> Item 0 -> Url Schemes -> Item 0 : "yourappname"
Add
Url Types -> Item 0 -> Url Schemes -> URL Identifier: "your bundle
id"
Go to Keyboard App:
Add following code properly:
#objc func openURL(_ url: URL) {
return
}
func openApp(_ urlstring:String) {
var responder: UIResponder? = self as UIResponder
let selector = #selector(openURL(_:))
while responder != nil {
if responder!.responds(to: selector) && responder != self {
responder!.perform(selector, with: URL(string: urlstring)!)
return
}
responder = responder?.next
}
}
Call : openApp ("yourappname://your bundle id")
Following code works on Xcode 8.3.3, iOS10, Swift3 without any compiler warnings:
func openUrl(url: URL?) {
let selector = sel_registerName("openURL:")
var responder = self as UIResponder?
while let r = responder, !r.responds(to: selector) {
responder = r.next
}
_ = responder?.perform(selector, with: url)
}
guard let url = URL(string: UIApplicationOpenSettingsURLString) else { return }
extensionContext?.open(url, completionHandler: { (success) in
if !success {
var responder = self as UIResponder?
while (responder != nil){
let selectorOpenURL = NSSelectorFromString("openURL:")
if responder?.responds(to: selectorOpenURL) == true {
_ = responder?.perform(selectorOpenURL, with: url)
}
responder = responder?.next
}
}
})

Swift, can't open second app using 'deep link'

It seems I can't open the second app using my method. Nothing happened. Is there any silly mistakes here?
My second app .plist file
My first app code
#IBAction func btnCRM(sender: AnyObject) {
var customURL: NSString = "CRM://"
if (UIApplication.sharedApplication().canOpenURL(NSURL(fileURLWithPath: customURL as String)!)){
UIApplication.sharedApplication().openURL(NSURL(fileURLWithPath: customURL as String)!)
}
}
In addition to the URL Schemes under Item 0, you need to add URL identifier which is CFBundleURLName, as outlined here.
try this code:
let url = NSURL(string: "CRM://")
if (UIApplication.sharedApplication().canOpenURL(url!)) {
UIApplication.sharedApplication().openURL(url!)
}
'openURL' was deprecated in iOS 10.0
Updated version:
guard let url = URL(string: "CRM://"), UIApplication.shared.canOpenURL(url) else {
return
}
UIApplication.shared.open(url, options: [:], completionHandler: nil)
Swift 5.7 2023
The code below opens the main application
private func openMainApp() {
self.extensionContext?.completeRequest(returningItems: nil, completionHandler: { _ in
guard let url = URL(string: self.appURL) else {
return
}
_ = self.openURL(url)
})
}
// Courtesy: https://stackoverflow.com/a/44499222/13363449 👇🏾
// Function must be named exactly like this so a selector can be found by the compiler!
// Anyway - it's another selector in another instance that would be "performed" instead.
#objc private func openURL(_ url: URL) -> Bool {
var responder: UIResponder? = self
while responder != nil {
if let application = responder as? UIApplication {
return application.perform(#selector(openURL(_:)), with: url) != nil
}
responder = responder?.next
}
return false
}

Resources