Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
240 views
in Technique[技术] by (71.8m points)

ios - Paste Formatted Text, Not Images or HTML

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

  1. 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
  2. If you copy formatted text from within Pages or Keynote, it will paste the formatted text elsewhere in Pages, Keynote, etc.
  3. 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.

enter image description here

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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: 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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...