Search code examples
iosobjective-creact-nativeuipasteboard

Copy HTML to UIPasteboard with React Native


I have a simple react-native app that has some functionality to copy HTML to the global clipboard such that it preserves the styling. The expected result is I copy HTML and can paste it into another app with the styling intact. Note, I’m not looking to paste HTML source code, that is easy and can be done simply by copying as a string.

For iOS 13 I made a minor modification to the react-native clipboard native module to change it from copying plain text to copying HTML. This code worked as expected with iOS 13, but since updating to 14 it doesn't seem to work - no value seems to be present in the clipboard.

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

The native module is imported in JS through:

import { NativeModules } from 'react-native';

export default NativeModules.CustomClipboard;

And the exposed to the rest of the app via a Clipboard module:

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);
  },
};

The code all seems to run correctly, but no value is copied to the clipboard. I haven't been able to find any known bugs on this. Any ideas?


Solution

  • I found the answer here: http://www.andrewhoyer.com/converting-html-to-nsattributedstring-copy-to-clipboard/

    The string needed to be converted to RTF before it was put in the clipboard.

    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];
          
    }