Paste Formatted Text, Not Images or HTML

前端 未结 2 1286
感情败类
感情败类 2020-12-11 05:50

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

相关标签:
2条回答
  • 2020-12-11 06:22

    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

    0 讨论(0)
  • 2020-12-11 06:24

    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.

    0 讨论(0)
提交回复
热议问题