Reputation: 8932
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?
Upvotes: 0
Views: 1011
Reputation: 8932
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];
}
Upvotes: 0
Reputation: 3018
EDIT
This is the second attempt. Earlier I understood you wanted to copy and paste the HTML as text but after your comments and edits to the question I now understand that you have some text that you format using HTML and you want to paste that text while retaining the formatting.
More or less as shown below.
This also illustrates how to wire it up in storyboard. Here is the code.
#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
The code is very similar to before. The problem is that HTML = plain text so to copy HTML and retain the style or formatting also depends on the destination app into which you paste it. That destination app needs to handle the HTML correctly for what you want to achieve.
Anyhow, here are the changes from earlier.
On the copy side: the text is now copied as HTML and not as a string. There is very little difference there except that now, unlike before, the string is converted to data and the type is marked as kUTTypeHTML
.
On the paste side: the real difference is here. If the pasteboard contains HTML then it is retrieved and formatted using an attributed string. It also works fine if you e.g. paste into Notes BTW.
This illustrates the problem you are dealing with here. I could just as well have used the code I used earlier and everything would work but in stead of formatted HTML I would get the unformatted / source HTML.
Here I assume that you are interested in fairly simple but formatted text. You did not mention text specifically and if you e.g. wanted to copy an HTML formatted table then my example is not adequate as I use a UITextView
as destination.
For more complex HTML I'd use a WKWebView
as destination as well and set its HTML string to the pasted in HTML, but again, this shows how the destination app also needs to cooperate in handling the HTML correctly.
Finally, looking at the updated answer and your code it is difficult to spot a difference, so I suspect your problem could be earlier with content
itself in
RCT_EXPORT_METHOD(setString:(NSString *)content)
UPDATE
I also looked at the git repository you mention. There I found the following
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]);
}
It looks wrong, I think that one line should be
stringPresent = [stringInPasteboard length] != 0;
HTH
Upvotes: 1