【问题标题】:Paste Formatted Text, Not Images or HTML粘贴格式化文本,而不是图像或 HTML
【发布时间】:2015-01-04 19:43:01
【问题描述】:

我正在尝试模拟 iOS Pages 和 Keynote 应用程序的粘贴板行为。简而言之,允许将基本的 NSAttributedString 文本格式(即 BIU)粘贴到 UITextView 中,但不能粘贴图像、HTML 等。

行为总结

  1. 如果您从 Notes 应用程序、Evernote 复制格式化文本或从网站复制文本和图像,Pages 将仅粘贴纯文本字符串
  2. 如果您从 Pages 或 Keynote 中复制格式化文本,它会将格式化文本粘贴到 Pages、Keynote 等的其他位置。
  3. 一个不希望的结果,但可能需要承认的是,Notes 应用程序或 Evernote 都不会粘贴从 Pages 或 Keynote 复制的格式化文本。我推测应用程序之间的差异是使用 NSAttributedStrings,而不是 HTML?

这是如何实现的?在 Mac OS 上,您似乎可以自己 ask the pasteboard to return different types,让它同时提供富文本和字符串表示,并首选使用富文本。不幸的是,对于 iOS,readObjectsForClasses 似乎不存在。也就是说,我可以通过日志看到 iOS 确实有一个与 RTF 相关的粘贴板类型,thanks to this post。但是,我无法找到一种方法来请求粘贴板内容的 NSAttributedString 版本,以便我可以优先考虑粘贴。

背景

我有一个应用程序,它允许用户在 UITextViews 中对文本进行基本的 NSAttributedString 可编辑格式(即粗体、斜体、下划线)。用户想要从其他应用程序(例如 Safari 中的网页,Notes 应用程序中的文本)复制文本,以粘贴到我的应用程序中的 UITextView。允许粘贴板作为默认操作意味着我最终可能会得到我的应用程序不打算处理的背景颜色、图像、字体等。下面的示例显示了文本在粘贴到我的应用程序的 UITextView 时具有背景颜色的复制文本的外观。

我可以通过继承 UITextView 来克服 1

- (void)paste:(id)sender
{
    UIPasteboard *pasteBoard = [UIPasteboard generalPasteboard];
    NSString *string = pasteBoard.string;
    NSLog(@"Pasteboard string: %@", string);
    [self insertText:string];
}

意想不到的后果是,失去了保留从我的应用程序中复制的文本格式的能力。用户可能希望从我的应用程序中的一个 UITextView 复制文本,并将其粘贴到我的应用程序中的另一个 UITextView。他们希望保留格式(即粗体、斜体、下划线)。

感谢您的见解和建议。

【问题讨论】:

  • 是的,它可能是 RTFD 与 HTML 的区别,当涉及到样式间应用粘贴性:)

标签: ios ios7 copy-paste nsattributedstring uipasteboard


【解决方案1】:

一些复制/粘贴的好东西和想法,给你:)

// 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"
  • 覆盖 UITextView 副本:并使用您的自定义粘贴板类型 pb.setValue(selectedText.string, forPasteboardType: myType)
  • 允许富文本复制(复制中:):

    // 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)
    
  • 允许富文本粘贴(在粘贴中:):

    // 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) }
    
  • 比使用自定义粘贴板类型更好,将所有可能需要的标签列入白名单,循环遍历并在粘贴时剥离所有其他标签。

  • (奖励:通过删除您在复制时添加的不必要属性(如字体和 fg-color)来帮助其他应用程序中的用户体验)
  • 另外值得注意的是,当粘贴板包含特定类型时,textView 可能不希望允许粘贴,以解决该问题:

    // 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)
    }
    
  • 此外,cut: 也可以很好地实现。基本上只有copy:replaceSelection(emptyString)

  • 为了您的方便:

    // 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
    }
    

祝你好运!

参考: Uniform Type Identifiers Reference

【讨论】:

    【解决方案2】:

    这应该是对 Leonard Pauli 答案的评论,但我还没有足够的声誉来制作 cmets。

    代替:

    selectedRange.location += attributedString.string.characters.count 
    

    (或最近版本的 Swift 中的属性String.string.count)

    最好使用:

    selectedRange.location += attributedString.length
    

    否则,当您粘贴的文本包含导致属性字符串长度和属性字符串计数不同的表情符号时,选择最终会出现在错误的位置。

    【讨论】:

      猜你喜欢
      • 2023-03-29
      • 2011-06-07
      • 1970-01-01
      • 2021-05-18
      • 1970-01-01
      • 2021-09-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多