Developers / Open In

Open In

UIDocumentInteractionControllerforScore 10.4

On iOS and iPadOS, apps can allow users to share content in a variety of ways. To copy a single file to another app, UIDocumentInteractionController is commonly used. This class makes a user’s file available to any other apps on their device that support the same file type, and as such it’s often helpful to use popular formats whenever possible for maximum compatibility.

If a developer wants to share a file while also providing additional information about that file, they must generally do one of two things: embed that information into the file and rewrite it on disk before sharing it (if the file type can contain the kind of information they’re trying to send), or they can wrap the original file’s data into a custom format. The first option is resource-intensive and not always possible, while the second option significantly reduces compatibility with other apps.

iOS and iPadOS’ sharing protocols allow for a third way of transmitting this information, however, by using UIDocumentInteractionController’s annotation property to store a dictionary of keys and values. In forScore we adopt the following kinds of information if found:

Keyword forScore Value Format
title Title NSString
composers Composers NSString (one or more comma-separated values)
genres Genres
tags Tags
labels Labels
rating Rating NSNumber or NSString representing a whole number between 0 and 5
difficulty Difficulty NSNumber or NSString representing a whole number between 0 and 3
duration Duration NSNumber or NSString representing a non-negative whole number (in seconds)
keysf Key NSNumber or NSString representing a whole number between -7 and 7*
keymi NSNumber or NSString representing 0 (major) or 1 (minor)*

*Key is stored as two MIDI-style values, learn more about these values here.

This method is perfect for sending sheet music to forScore. It allows apps to provide standard formats while also including helpful information—like a score’s duration—without permanently embedding that information into PDF metadata fields that weren’t designed for that purpose. The receiving app can take advantage of this information if desired, otherwise the user’s experience remains completely unchanged and compatibility is not diminished in any way.


Supplying Annotations

NSMutableDictionary *annotation = [[NSMutableDictionary alloc] init];

// Title may be provided, otherwise the filename (minus the .pdf extension) will be used
annotation[@"title"] = @"Nocturne Op. 90 № 2";

// These four keys can contain any number of comma-separated values as NSStrings
// Extra leading/trailing whitespace is trimmed automatically
annotation[@"composers"] = @"Frédéric Chopin";
annotation[@"genres"] = @"My Genre";
annotation[@"tags"] = @"My Tag";
annotation[@"labels"] = @"Label One, Label Two, Label Three";

// These values can be provided as NSNumbers or as digit-only NSStrings
annotation[@"difficulty"] = @(2); // between 0 and 3
annotation[@"rating"] = @(5); // between 0 and 5

// Provide duration as a whole number of seconds: n = (minutes*60)+seconds
// stored as either NSNumber or NSString
annotation[@"duration"] = @(72);

annotation[@"keysf"] = @(0); // follows MIDI formatting; -7 (flats) through +7 (sharps)
annotation[@"keymi"] = @(0); // follows MIDI formatting; 0=major, 1=minor

// This must be stored as an instance variable or it will be deallocated prematurely and not appear
documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:shareURL];
documentInteractionController.annotation = annotation;

// Present with a source view and rectangle; sender here is the button tapped to call this method
[documentInteractionController presentOpenInMenuFromRect:sender.frame inView:sender.superview animated:YES];

Reading Annotations

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options {
	// Assuming here that the file is a PDF; a complete implementation
	// should check if the URL's NSURLTypeIdentifierKey conforms to kUTTypePDF
	// if possible or fall back on checking the url path's file extension
	// import the file by copying or moving to your preferred location
	[[NSFileManager defaultManager] moveItemAtURL:url toURL:myTargetURL error:nil];
	// retrieve the annotation object and use it if it's present and in the format we expect
	id obj = options[UIApplicationOpenURLOptionsAnnotationKey];
	if ([obj isKindOfClass:[NSDictionary class]]) {
		NSDictionary *dictionary = (NSDictionary *)obj;
		NSString *title = dictionary[@"title"];
		// continue retrieving desired values and save as needed
	return YES;

Processing Comma-Separated Values (Composers, Genres, Tags, and Labels)

NSArray *componentArray = [dictionaryValue componentsSeparatedByString:@","];
NSCharacterSet *charSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
for (NSString *separatedValue in componentArray) {
	NSString *cleanedValue = [separatedValue stringByTrimmingCharactersInSet:charSet];
	// save this new value as needed

Processing Number Values (Rating, Difficulty, Duration, and Key)

// certain values may be either NSString or NSNumber objects
id durationObject = dictionary[@"duration"];
if (durationObject) {
	NSInteger seconds = [durationObject integerValue];
	// save as needed