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
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"
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.
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:
and replaceSelection(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
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.