jpo
January 18, 2023, 2:46pm
1
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()
jpo
January 18, 2023, 4:09pm
2
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
jpo
April 24, 2023, 10:48am
3
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();
}
jpo
December 2, 2025, 4:24pm
4
Patch update since UIScene stuff requires it:
in the “@implementation JuceAppSceneDelegate” part:
- (void)handleOpenURLContextsInJuceApp:(NSSet<UIOpenURLContext *> *)URLContexts
{
for (UIOpenURLContext *ctx in URLContexts)
{
NSURL *url = ctx.URL;
String local_copy = nsURLToTempFile(url);
MessageManager::callAsync ([local_copy]()
{
if (auto* app = JUCEApplicationBase::getInstance())
{
app->anotherInstanceStarted(local_copy.quoted());
}
});
}
}
- (void)scene:(UIScene *)scene
openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts
{
[self handleOpenURLContextsInJuceApp:URLContexts];
}
and also add those two lines here:
- (void) scene: (UIScene*) scene
willConnectToSession: (UISceneSession*) session
options: (UISceneConnectionOptions*) connectionOptions
{
if ([scene isKindOfClass: UIWindowScene.class])
windowSceneTracker->setWindowScene (static_cast<UIWindowScene*> (scene));
else
jassertfalse;
+ if (connectionOptions.URLContexts.count > 0)
+ [self handleOpenURLContextsInJuceApp:connectionOptions.URLContexts];
}