问题
I am trying to emulate the pasteboard behavior of the iOS Pages and Keynote apps. In short, allowing basic NSAttributedString text formatting (i.e. BIU) to be pasted into a UITextView, but not images, HTML, etc.
BEHAVIOR SUMMARY
- If you copy formatted text from the Notes app, Evernote, or text and images from a web site, Pages will only paste the plain text string
- If you copy formatted text from within Pages or Keynote, it will paste the formatted text elsewhere in Pages, Keynote, etc.
- An undesired consequence, but perhaps important to acknowledge, is that neither Notes app or Evernote will paste formatted text copied from Pages or Keynote. I am speculating that the discrepancy between apps is the use of NSAttributedStrings, versus HTML?
How is this accomplished? On Mac OS, it appears you can ask the pasteboard to return different types of itself, have it provide both a rich text and a string representation, and use rich text as preferred. Unfortunately, the readObjectsForClasses doesn't appear to exist for iOS. That said, I can see via log that iOS does have an RTF related type of pasteboard, thanks to this post. I can't however, find a way to request an NSAttributedString version of pasteboard contents so I can prioritize it for pasting.
BACKGROUND
I have an app that allows basic NSAttributedString user editable formatting (i.e. bold, italic, underline) of text in UITextViews. Users want to copy text from other apps (e.g. web page in Safari, text in Notes app), to paste into a UITextView in my app. Allowing pasteboard to operate as default means I may end up with background colors, images, fonts, etc. that my app isn't intended to handle. Example below shows text how copied text with a background color looks when pasted into my app's UITextView.
I can overcome 1 by subclassing UITextView
- (void)paste:(id)sender
{
UIPasteboard *pasteBoard = [UIPasteboard generalPasteboard];
NSString *string = pasteBoard.string;
NSLog(@"Pasteboard string: %@", string);
[self insertText:string];
}
The unintended consequence is, losing the ability to retain formatting of text that's copied from within my app. Users may want to copy text from one UITextView in my app, and paste it to another UITextView in my app. They will expect formatting (i.e. bold, italics, underline) to be retained.
Insight and suggestions appreciated.
回答1:
This should be a comment on Leonard Pauli's answer, but I don't have enough reputation to make comments yet.
Instead of:
selectedRange.location += attributedString.string.characters.count
(or attributedString.string.count as it is in more recent versions of Swift)
It's best to use:
selectedRange.location += attributedString.length
Otherwise when you paste text that contains emoji that cause attributedString.length and attributedString.string.count to differ, the selection will end up in the wrong place.
回答2:
Some copy/paste goodies and ideas, for you :)
// Setup code in overridden UITextView.copy/paste
let pb = UIPasteboard.generalPasteboard()
let selectedRange = self.selectedRange
let selectedText = self.attributedText.attributedSubstringFromRange(selectedRange)
// UTI List
let utf8StringType = "public.utf8-plain-text"
let rtfdStringType = "com.apple.flat-rtfd"
let myType = "com.my-domain.my-type"
- Override UITextView copy: and use your custom pasteboard type
pb.setValue(selectedText.string, forPasteboardType: myType)
To allow rich text copy (in copy:):
// Try custom copy do { // Convert attributedString to rtfd data let fullRange = NSRange(location: 0, length: selectedText.string.characters.count) let data:NSData? = try selectedText.dataFromRange(fullRange, documentAttributes: [NSDocumentTypeDocumentAttribute: NSRTFDTextDocumentType]) if let data = data { // Set pasteboard values (rtfd and plain text fallback) pb.items = [[rtfdStringType: data], [utf8StringType: selectedText.string]] return } } catch { print("Couldn't copy") } // If custom copy not available; // Copy as usual super.copy(sender)
To allow rich text paste (in paste:):
// Custom handling for rtfd type pasteboard data if let data = pb.dataForPasteboardType(rtfdStringType) { do { // Convert rtfd data to attributedString let attStr = try NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSRTFDTextDocumentType], documentAttributes: nil) // Bonus: Possibly strip all unwanted attributes here. // Insert it into textview replaceSelection(attStr) return } catch {print("Couldn't convert pasted rtfd")} } // Default handling otherwise (plain-text) else { super.paste(sender) }
Even better then using a custom pasteboard type, white-list all possibly wanted tags, loop through and strip away all other on paste.
- (Bonus: help UX in other apps by stripping away unnecessary attributes you've added, on copy (like font and fg-color))
Also worth noting, the textView might not want to allow pasting when the pasteboard contains a specific type, to fix that:
// Allow all sort of paste (might want to use a white list to check pb.items agains here) override func canPerformAction(action: Selector, withSender sender: AnyObject?) -> Bool { if action == #selector(UITextView.paste(_:)) { return true } return super.canPerformAction(action, withSender: sender) }
Furthermore, cut: could be nice to implement as well. Basically just
copy:
andreplaceSelection(emptyString)
For your convenience:
// Helper to insert attributed text at current selection/cursor position func replaceSelection(attributedString: NSAttributedString) { var selectedRange = self.selectedRange let m = NSMutableAttributedString(attributedString: self.attributedText) m.replaceCharactersInRange(self.selectedRange, withAttributedString: attributedString) selectedRange.location += attributedString.string.characters.count selectedRange.length = 0 self.attributedText = m self.selectedRange = selectedRange }
Good luck!
Refs: Uniform Type Identifiers Reference
来源:https://stackoverflow.com/questions/27769595/paste-formatted-text-not-images-or-html