Search code examples
iphoneipadios5uipasteboarduti

Other apps paste the HTML rather than the text version of our pasteboard data in iOS 5


We have an existing app that allows the user to copy text to the pasteboard. When possible, we put both HTML and plain text on the clipboard, since we don't know what other app the user might be pasting into and want to provide both formatted (HTML) and unformatted data.

This functionality works great in iOS 3 and 4. But as soon as the user installs iOS 5, when they paste text from our app into any other app, the other app gets the HTML text but treats it as plain text. So if they select and copy an H1 heading, when they paste into the other app, instead of seeing "This is a Heading", they see "<h1>This is a Heading</h1>".

Note that this is with existing code that works fine in iOS 3 and 4. There's no change to our code between iOS 3, 4, and 5.

For completeness, here's the code we use to put our text on the pasteboard, with a simple string provided just for reference:

NSString * plainText = @"A Big Heading\r\nA regular paragraph.";
NSString * htmlText = @"<h1>A Big Heading</h1><p>A regular paragraph.</p>";
UIPasteboard * pasteboard = [UIPasteboard generalPasteboard];
pasteboard.items = [NSArray arrayWithObject:
    [NSDictionary dictionaryWithObjectsAndKeys:
        plainText, @"public.utf8-plain-text", htmlText, @"public.html", nil]];

Obviously, my question is "Why do iOS 5 apps get the HTML text when they request plain text, while the same app running under iOS 4 gets the plain text?"


Solution

  • For those of you playing along at home, here's the answer we discovered.

    "public.utf8-plain-text" has historically been the correct UTI to use when putting an NSString containing unformatted text into the pasteboard. The built-in controls all request and use this version of the pasteboard contents during their "paste" operations (for iOS versions prior to 5). If you put only "public.plain-text" or "public.text" text in the pasteboard, the built-in controls ignore it completely, saying the pasteboard is empty (not giving you the "paste" option).

    In iOS 5, something changed and when the built-in controls request plain text in the situation above, they will get the "public.html" text.

    For iOS 5 you must use "public.text" instead of "public.plain-text" or "public.utf8-plain-text" even though the latter two are arguably more correct and the former is too vague to be of use at all.

    Since earlier iOS versions ignore "public.text", our solution is to put all three versions on the pasteboard: "public.text" and "public.utf8-plain-text" will both get you the plain text and "public.html" will get you the HTML text. This seems to satisfy both iOS 4 and 5 without having to put an explicit iOS version test in the code, at the cost of one more entry in the dictionary.

    One more fail for the kids at Apple.

    EDIT for 2016 and iOS 8/9

    I've been trying to solve this problem once and for all ever since I posted this question. Whenever I do a Google search, I always end up back at this question.

    Somewhere along the line, iOS introduced a "web archive" concept for putting HTML on the pasteboard. It's not documented well anywhere. I found an answer here which pre-dates my question, so that's a little frustrating, but it works. I've updated it to take advantage of built-in base64 encoding in later versions of iOS. It goes something like this:

    NSMutableDictionary * contents = [NSMutableDictionary dictionaryWithCapacity:6];
    NSString * htmlText = @"<h1>A Heading</h1><p>A paragraph.</p>"
    //... put other formats in the dictionary, then...
    NSData * data = [htmlText dataUsingEncoding:NSUTF8StringEncoding];
    NSString * base64encodedString = [data base64EncodedStringWithOptions:0];
    NSString * webArchiveString = [NSString stringWithFormat:
        @"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
        "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"
        "<plist version=\"1.0\">"
        "<dict>"
        "<key>WebMainResource</key>"
        "<dict>"
        "<key>WebResourceData</key>"
        "<data>%@</data>"
        "<key>WebResourceFrameName</key>"
        "<string></string>"
        "<key>WebResourceMIMEType</key>"
        "<string>text/html</string>"
        "<key>WebResourceTextEncodingName</key>"
        "<string>UTF-8</string>"
        "<key>WebResourceURL</key>"
        "<string>about:blank</string>"
        "</dict>"
        "</dict>"
        "</plist>", base64encodedString];
    [contents setObject:webArchiveString forKey:@"Apple Web Archive pasteboard type"];
    UIPasteboard * pasteboard = [UIPasteboard generalPasteboard];
    pasteboard.items = [NSArray arrayWithObject:contents];