使用 React Native 将 HTML 复制到 UIPasteboard

Copy HTML to UIPasteboard with React Native

我有一个简单的 react-native 应用程序,它有一些功能可以将 HTML 复制到全局剪贴板,从而保留样式。预期的结果是我复制 HTML 并可以将其粘贴到另一个应用程序中,并且样式完好无损。请注意,我不想粘贴 HTML 源代码,这很简单,只需复制为字符串即可完成。

对于iOS 13 我对react-native clipboard native module 做了一个小修改,将其从复制纯文本更改为复制HTML。此代码在 iOS 13 时按预期工作,但自从更新到 14 后它似乎不起作用 - 剪贴板中似乎没有任何值。

CustomClipboard.m

#import "CustomClipboard.h"


#import <UIKit/UIKit.h>
#import <React/RCTBridge.h>
#import <React/RCTEventDispatcher.h>
#import <MobileCoreServices/MobileCoreServices.h>


@implementation CustomClipboard

RCT_EXPORT_MODULE();

- (dispatch_queue_t)methodQueue
{
  return dispatch_get_main_queue();
}

RCT_EXPORT_METHOD(setString:(NSString *)content)
{
  UIPasteboard *clipboard = [UIPasteboard generalPasteboard];
  NSData *data = [content dataUsingEncoding:NSUTF8StringEncoding];
  [clipboard setData:data forPasteboardType:(NSString *)kUTTypeHTML];
}

RCT_EXPORT_METHOD(getString:(RCTPromiseResolveBlock)resolve
                  reject:(__unused RCTPromiseRejectBlock)reject)
{
  UIPasteboard *clipboard = [UIPasteboard generalPasteboard];
  resolve((clipboard.string ? : @""));
}

@end

CustomClipboard.h

#import <React/RCTBridgeModule.h>

@interface CustomClipboard : NSObject <RCTBridgeModule>

@end

原生模块在JS中导入通过:

import { NativeModules } from 'react-native';

export default NativeModules.CustomClipboard;

并通过剪贴板模块向应用程序的其余部分公开:

import NativeClipboard from './NativeClipboard';

/**
 * `Clipboard` gives you an interface for setting and getting content from Clipboard on both iOS and Android
 */
export const Clipboard = {
  /**
   * Get content of string type, this method returns a `Promise`, so you can use following code to get clipboard content
   * ```javascript
   * async _getContent() {
   *   var content = await Clipboard.getString();
   * }
   * ```
   */
  getString(): Promise<string> {
    return NativeClipboard.getString();
  },
  /**
   * Set content of string type. You can use following code to set clipboard content
   * ```javascript
   * _setContent() {
   *   Clipboard.setString('hello world');
   * }
   * ```
   * @param the content to be stored in the clipboard.
   */
  setString(content: string) {
    NativeClipboard.setString(content);
  },
};

代码似乎 运行 正确,但没有值被复制到剪贴板。我还没有找到任何已知的错误。有什么想法吗?

编辑

这是第二次尝试。早些时候我了解到您想将 HTML 复制并粘贴为文本,但在您对问题进行评论和编辑后,我现在了解到您有一些使用 HTML 格式化的文本并且您想粘贴该文本同时保留格式。

大致如下图。

这也说明了如何将其连接到情节提要中。这是代码。

#import "ViewController.h"

#import <MobileCoreServices/UTCoreTypes.h>
#import <WebKit/WebKit.h>

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UILabel    * statusLabel;
@property (weak, nonatomic) IBOutlet WKWebView  * webView;
@property (weak, nonatomic) IBOutlet UITextView * textView;

@end

@implementation ViewController

#pragma mark -
#pragma mark Actions

- (IBAction)reloadButtonAction:(id)sender {

    [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.webfx.com/blog/images/assets/cdn.sixrevisions.com/0435-01_html5_download_attribute_demo/samp/htmldoc.html"]]];

}

- (IBAction)copyButtonAction:(id)sender {

    if ( self.webView.loading ) {

        self.statusLabel.text = @"Loading";

    } else {

        self.statusLabel.text = @"Copying ...";

        [self.webView evaluateJavaScript:@"document.documentElement.outerHTML.toString()"
                   completionHandler: ^ ( id content, NSError * error ) {

            if ( error ) {

                self.statusLabel.text = @"Error";

            } else if ( [content isKindOfClass:NSString.class] ) {

                [UIPasteboard.generalPasteboard setData:[( NSString * ) content dataUsingEncoding:NSUTF8StringEncoding]
                                  forPasteboardType:( NSString * ) kUTTypeHTML];
                self.statusLabel.text = @"Copied HTML";

            } else {

                self.statusLabel.text = @"Unknown type";

            }

        }];

    }

}

- (IBAction)pasteButtonAction:(id)sender {


    if ( [UIPasteboard.generalPasteboard containsPasteboardTypes:@[( NSString * ) kUTTypeHTML]] ) {

        // Convert to attributed string
        NSError * cvtErr;
        NSData  * data = [UIPasteboard.generalPasteboard dataForPasteboardType:( NSString * ) kUTTypeHTML];
        NSAttributedString * attr = [[NSAttributedString alloc] initWithData:data
                                         options:@{ NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType }
                                  documentAttributes:NULL
                                           error: & cvtErr];

        if ( cvtErr ) {

            self.statusLabel.text = @"Convert error";

        } else if ( attr ) {

            self.statusLabel.text = @"Attributed";
            self.textView.attributedText = attr;

        } else {

            self.statusLabel.text = @"Unable to decode";

        }

    } else {

        NSString * content = UIPasteboard.generalPasteboard.string;

        if ( content ) {

            // Paste plain text
            self.textView.text = content;
            self.statusLabel.text = @"Pasted";

        } else {

            self.statusLabel.text = @"No string content";

        }

    }

}

@end

代码与之前非常相似。问题是 HTML = 纯文本,因此复制 HTML 并保留样式或格式也取决于您将其粘贴到的目标应用程序。该目标应用需要正确处理 HTML 以实现您想要实现的目标。

无论如何,这是较早前的变化。

在复制方面:文本现在被复制为 HTML 而不是字符串。除了现在与以前不同,字符串被转换为数据并且类型被标记为 kUTTypeHTML.

之外,几乎没有什么不同。

在粘贴方面:真正的区别就在这里。如果粘贴板包含 HTML,则使用属性字符串对其进行检索和格式化。如果你例如,它也可以正常工作粘贴到 Notes 顺便说一句。

这说明了您在这里处理的问题。我也可以使用我之前使用的代码,一切都可以正常工作,但我会得到未格式化的 / source HTML.

而不是格式化的 HTML

这里我假设您对相当简单但格式化的文本感兴趣。您没有具体提及 text,例如,如果您想复制一个 HTML 格式的 table 那么我的例子是不够的,因为我使用 UITextView 作为目标。

对于更复杂的 HTML,我也会使用 WKWebView 作为目标,并将其 HTML 字符串设置为粘贴在 HTML 中,但同样,这显示目标应用程序还需要如何合作才能正确处理 HTML。

最后,查看更新后的答案和您的代码很难发现差异,所以我怀疑您的问题可能早于 content 本身在

RCT_EXPORT_METHOD(setString:(NSString *)content)

更新

我还查看了您提到的 git 存储库。在那里我发现了以下内容

RCT_EXPORT_METHOD(hasString:(RCTPromiseResolveBlock)resolve
                  reject:(__unused RCTPromiseRejectBlock)reject)
{
  BOOL stringPresent = YES;
  UIPasteboard *clipboard = [UIPasteboard generalPasteboard];
  if (@available(iOS 10, *)) {
    stringPresent = clipboard.hasStrings;
  } else {
    NSString* stringInPasteboard = clipboard.string;
    stringPresent = [stringInPasteboard length] == 0;
  }

  resolve([NSNumber numberWithBool: stringPresent]);
}

好像不对,我觉得一行应该是

stringPresent = [stringInPasteboard length] != 0;

HTH

我在这里找到了答案:http://www.andrewhoyer.com/converting-html-to-nsattributedstring-copy-to-clipboard/

字符串在放入剪贴板之前需要转换为 RTF。

RCT_EXPORT_METHOD(setString:(NSString *)content)
{
  // Source: http://www.andrewhoyer.com/converting-html-to-nsattributedstring-copy-to-clipboard/
  
  UIPasteboard *clipboard = [UIPasteboard generalPasteboard];

  // Sets up the attributes for creating Rich Text.
  NSDictionary *documentAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
                                      NSRTFTextDocumentType, NSDocumentTypeDocumentAttribute,
                                      NSCharacterEncodingDocumentAttribute, [NSNumber numberWithInt:NSUTF8StringEncoding],
                                      nil];
  
   
  // Create the attributed string using options to indicate HTML text and UTF8 string as the source.
  NSAttributedString *atr = [[NSAttributedString alloc]
                              initWithData: [content dataUsingEncoding:NSUTF8StringEncoding]
                              options: @{
                                  NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
                                  NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)
                              }
                              documentAttributes:nil error:nil];
    
  // Generate the Rich Text data using the attributed string and the previously created attributes.
  NSData *rtf = [atr dataFromRange:NSMakeRange(0, [atr length]) documentAttributes:documentAttributes error:nil];
  
  // Set the Rich Text to the clipboard using an RTF Type
  // Note that this requires <MobileCoreServices/MobileCoreServices.h> to be imported
  [clipboard setData:rtf forPasteboardType:(NSString*)kUTTypeRTF];
      
}