How to save a URL's bookmark in juce way for on iOS?

Now I get the only way to save a local file’s path for a AUv3 plugin on iOS is to save it as a URL bookmark, so it still can be recalled if user killed the host app. There’re two URL member function for this,

void setURLBookmark (URL& u, void* bookmark)
void* getURLBookmark (URL& u)

But how to save them into say an XML for get/setStateInformation to use? They don’t even have any return type to use. Any suggestions?

I had the same question and solved it like this after looking into how the URL code works. The URL bookmark returns an NSData object, the contents of which you can store as a binary data var in a parameter tree. I store the original URL string separately in the state.

When saving state:

    paramtree.setProperty("fileurl", url.toString(false), nullptr);

    // store bookmark data if necessary
    if (void * bookmark = getURLBookmark(url)) {
        const void * data = nullptr;
        size_t datasize = 0;
        if (urlBookmarkToBinaryData(bookmark, data, datasize)) {
            DBG("File has bookmark, storing it in state, size: " << datasize);
            paramtree.setProperty("fileurl_bookmark", var(data, datasize), nullptr);
        } else {
            DBG("Bookmark is not valid!");
        }
    } 

When loading state:

    String fn = paramtree.getProperty("fileurl");
    if (fn.isNotEmpty())
    {
        URL url(fn);
        // check for bookmark
        auto bptr = paramtree.getPropertyPointer("fileurl_bookmark");
        if (bptr) {
            if (auto * block = bptr->getBinaryData()) {
                DBG("Has file bookmark");
                void * bookmark = binaryDataToUrlBookmark(block->getData(), block->getSize());
                setURLBookmark(url, bookmark);
            }
        }
        else {
            DBG("no url bookmark found");
        }

        // do something with url here
    }

And these helper functions (implemented in an .mm file) which the code above references:

bool urlBookmarkToBinaryData(void * bookmark, const void * & retdata, size_t & retsize)
{
    NSData * data = (NSData*) bookmark;
    if (data && [data isKindOfClass:NSData.class]) {
        retdata = [data bytes];
        retsize = [data length];
        return true;
    }
    return false;
}

void * binaryDataToUrlBookmark(const void * data, size_t size)
{
    NSData * nsdata = [[NSData alloc] initWithBytes:data length:size];

    return nsdata;
}

Thanks for this! Did you try kill your host app then reload the project? Does the URL still work?

Yes, it does for me! But you do have to be sure to use url.createInputStream(...) to open/read the file.

1 Like

Hi, I’m trying to use your method, but got stuck with the .mm file, does it have to include any header for compile?

It seems like I missed some header, but I’m not sure, any idea how to solve this? Thanks in advance!

Sure, here are more complete contents of the .mm file:

(there is also a header file that just declares the functions below which you can include elsewhere, you’ll also want to wrap those declarations in #if JUCE_IOS as well)

#include <juce_core/system/juce_TargetPlatform.h>

#if JUCE_IOS
#import <UIKit/UIView.h>

bool urlBookmarkToBinaryData(void * bookmark, const void * & retdata, size_t & retsize)
{
    NSData * data = (NSData*) bookmark;
    if (data && [data isKindOfClass:NSData.class]) {
        retdata = [data bytes];
        retsize = [data length];
        return true;
    }
    return false;
}

void * binaryDataToUrlBookmark(const void * data, size_t size)
{
    NSData * nsdata = [[NSData alloc] initWithBytes:data length:size];

    return nsdata;
}

#endif

1 Like