Copy NSAttributedString in UIPasteBoard
How do you copy an NSAttributedString in the pasteboard, to allow the user to paste, or to paste programmatically (with - (void)paste:(id)sender
, from UIResponderStandardEditActions protocol).
I tried:
UIPasteboard *pasteBoard = [UIPasteboard generalPasteboard];
[pasteBoard setValue:attributedString forPasteboardType:(NSString *)kUTTypeRTF];
but this crash with:
-[UIPasteboard setValue:forPasteboardType:]: value is not a valid property list type'
which is to be expected, because NSAttributedString is not a property list value.
If the user paste the content of the pasteboard in my app, I would like to keep all the standards and custom attributes of the attributed string.
Solution 1:
I have found that when I (as a user of the application) copy rich text from a UITextView into the pasteboard, the pasteboard contains two types:
"public.text",
"Apple Web Archive pasteboard type
Based on that, I created a convenient category on UIPasteboard.
(With heavy use of code from this answer).
It works, but:
The conversion to html format means I will lose custom attributes. Any clean solution will be gladly accepted.
File UIPasteboard+AttributedString.h:
@interface UIPasteboard (AttributedString)
- (void) setAttributedString:(NSAttributedString *)attributedString;
@end
File UIPasteboard+AttributedString.m:
#import <MobileCoreServices/UTCoreTypes.h>
#import "UIPasteboard+AttributedString.h"
@implementation UIPasteboard (AttributedString)
- (void) setAttributedString:(NSAttributedString *)attributedString {
NSString *htmlString = [attributedString htmlString]; // This uses DTCoreText category NSAttributedString+HTML - https://github.com/Cocoanetics/DTCoreText
NSDictionary *resourceDictionary = @{ @"WebResourceData" : [htmlString dataUsingEncoding:NSUTF8StringEncoding],
@"WebResourceFrameName": @"",
@"WebResourceMIMEType" : @"text/html",
@"WebResourceTextEncodingName" : @"UTF-8",
@"WebResourceURL" : @"about:blank" };
NSDictionary *htmlItem = @{ (NSString *)kUTTypeText : [attributedString string],
@"Apple Web Archive pasteboard type" : @{ @"WebMainResource" : resourceDictionary } };
[self setItems:@[ htmlItem ]];
}
@end
Only implemented setter. If you want to write the getter, and/or put it on GitHub, be my guest :)
Solution 2:
Instead of involving HTML, the clean solution is to insert NSAttributedString as RTF (plus plaintext fallback) into the paste board:
- (void)setAttributedString:(NSAttributedString *)attributedString {
NSData *rtf = [attributedString dataFromRange:NSMakeRange(0, attributedString.length)
documentAttributes:@{NSDocumentTypeDocumentAttribute: NSRTFTextDocumentType}
error:nil];
self.items = @[@{(id)kUTTypeRTF: [[NSString alloc] initWithData:rtf encoding:NSUTF8StringEncoding],
(id)kUTTypeUTF8PlainText: attributedString.string}];
}
Swift 5
import MobileCoreServices
public extension UIPasteboard {
func set(attributedString: NSAttributedString) {
do {
let rtf = try attributedString.data(from: NSMakeRange(0, attributedString.length), documentAttributes: [NSAttributedString.DocumentAttributeKey.documentType: NSAttributedString.DocumentType.rtf])
items = [[kUTTypeRTF as String: NSString(data: rtf, encoding: String.Encoding.utf8.rawValue)!, kUTTypeUTF8PlainText as String: attributedString.string]]
} catch {
}
}
}
Solution 3:
It is quite simple:
#import <MobileCoreServices/UTCoreTypes.h>
NSMutableDictionary *item = [[NSMutableDictionary alloc] init];
NSData *rtf = [attributedString dataFromRange:NSMakeRange(0, attributedString.length)
documentAttributes:@{NSDocumentTypeDocumentAttribute: NSRTFDTextDocumentType}
error:nil];
if (rtf) {
[item setObject:rtf forKey:(id)kUTTypeFlatRTFD];
}
[item setObject:attributedString.string forKey:(id)kUTTypeUTF8PlainText];
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
pasteboard.items = @[item];