anotherInstanceStarted on iOS

Hi,

A nice addition to JUCE would be to support JUCEApplication::anotherInstanceStarted() on iOS. If I understand correctly that would mean implementing application: didFinishLaunchingWithOptions: and application:openURL:sourceApplication:annotation: so that when we click on a supported file format in the ios Files browser, it opens the app (this is already working) and calls anotherInstanceStarted()

Here is an example of implementation, the NSURL file is copied in the app temp folder, and anotherInstanceStarted is called with the path of that file. It seems that I only need to define application:openUrl :

     // This is an internal list of callbacks (but currently used between modules)
     Array<AppInactivityCallback*> appBecomingInactiveCallbacks;
+
+#if JUCE_IOS_ANOTHERINSTANCESTARTED
+
+    //copied from juce_ios_FileChooser.mm
+    juce::URL nsURLToJuceURL(NSURL *url)
+    {
+        [url startAccessingSecurityScopedResource];
+
+        NSError* error = nil;
+
+        auto* bookmark = [url bookmarkDataWithOptions: 0
+                              includingResourceValuesForKeys: nil
+                                        relativeToURL: nil
+                                                error: &error];
+
+        [bookmark retain];
+
+        [url stopAccessingSecurityScopedResource];
+
+        URL juceUrl (nsStringToJuce ([url absoluteString]));
+
+        if (error == nil)
+        {
+            setURLBookmark (juceUrl, (void*) bookmark);
+        }
+        else
+        {
+            [[maybe_unused]] auto desc = [error localizedDescription];
+            jassertfalse;
+        }
+        return juceUrl;
+    }
+
+    // download NSURL to a file in the sandbox temp folder
+    String nsURLToTempFile(NSURL *nsurl) {
+        juce::URL url = nsURLToJuceURL(nsurl);
+        File target = File::getSpecialLocation(File::tempDirectory).getChildFile(URL::removeEscapeChars(url.getFileName()));
+        auto input_stream = url.createInputStream(URL::InputStreamOptions (URL::ParameterHandling::inAddress));
+        auto output_stream = target.createOutputStream();
+        output_stream->setPosition(0);
+        MemoryBlock block;
+        while (input_stream->readIntoMemoryBlock(block, 65536)) {
+            output_stream->write(block.getData(), block.getSize());
+        }
+
+        return target.getFullPathName();
+    }
+#endif // JUCE_IOS_ANOTHERINSTANCESTARTED
 }

 #if JUCE_PUSH_NOTIFICATIONS
@@ -57,6 +105,11 @@ namespace juce
 - (void) applicationWillResignActive: (UIApplication*) application;
 - (void) application: (UIApplication*) application handleEventsForBackgroundURLSession: (NSString*) identifier
    completionHandler: (void (^)(void)) completionHandler;
+#if JUCE_IOS_ANOTHERINSTANCESTARTED
+- (BOOL) application:(UIApplication *)application openURL:(NSURL *)url
+   sourceApplication:(NSString *)sourceApplication
+          annotation:(id)annotation;
+#endif // JUCE_IOS_ANOTHERINSTANCESTARTED
 - (void) applicationDidReceiveMemoryWarning: (UIApplication *) application;
 #if JUCE_PUSH_NOTIFICATIONS

@@ -141,6 +194,19 @@ JUCE_END_IGNORE_WARNINGS_GCC_LIKE
     }
 }

+#if JUCE_IOS_ANOTHERINSTANCESTARTED
+- (BOOL) application:(UIApplication *)application openURL:(NSURL *)url
+sourceApplication:(NSString *)sourceApplication
+       annotation:(id)annotation
+{
+    String tgt = nsURLToTempFile(url);
+    if (auto* app = JUCEApplicationBase::getInstance()) {
+        app->anotherInstanceStarted(tgt.quoted());
+    }
+    return true;
+}
+#endif // JUCE_IOS_ANOTHERINSTANCESTARTED
+
 - (void) applicationWillTerminate: (UIApplication*) application
 {
     ignoreUnused (application);

1 Like

hum the nsURLToTempFile was not handling files > 65536 bytes correctly… Here is a better one:

    String nsURLToTempFile(NSURL *nsurl) {
        juce::URL url = nsURLToJuceURL(nsurl);
        File target = File::getSpecialLocation(File::tempDirectory).getChildFile(URL::removeEscapeChars(url.getFileName()));
        auto input_stream = url.createInputStream(URL::InputStreamOptions (URL::ParameterHandling::inAddress));
        target.deleteFile();
        auto output_stream = target.createOutputStream();
        if (output_stream->openedOk()) {
          output_stream->setPosition(0);
          output_stream->truncate();

          MemoryBlock block;
          int bytes = 0;
          while (input_stream->readIntoMemoryBlock(block, 65536)) {
            output_stream->write(block.getData(), block.getSize());
            bytes += block.getSize();
            block.reset();
          }
        }
        output_stream.reset();
        input_stream.reset();

        return target.getFullPathName();
    }