Скопируйте HTML в UIPsteboard с помощью React Native

У меня есть простое приложение для реагирования, которое имеет некоторые функции для копирования HTML в глобальный буфер обмена, чтобы сохранить стиль. Ожидаемый результат: я копирую HTML и могу вставить его в другое приложение с неповрежденным стилем. Обратите внимание: я не собираюсь вставлять исходный код HTML, это легко сделать, просто скопировав строку.

Для iOS 13 я внес небольшую модификацию в родной модуль буфера обмена, чтобы изменить это от копирования обычного текста до копирования 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);
  },
};

Кажется, весь код работает правильно, но в буфер обмена значения не копируются. Я не смог найти каких-либо известных ошибок в этом. Любые идеи?


person Nathan Totten    schedule 15.12.2020    source источник
comment
На основании ваших комментариев и правок я обновил свой ответ - см. обновленный ответ.   -  person skaak    schedule 22.12.2020


Ответы (2)


ИЗМЕНИТЬ

Это вторая попытка. Ранее я понял, что вы хотели скопировать и вставить HTML как текст, но после ваших комментариев и правок в вопросе я теперь понимаю, что у вас есть текст, который вы форматируете с помощью 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, он извлекается и форматируется с использованием строки с атрибутами. Он также отлично работает, если вы, например. вставьте в Заметки BTW.

Это иллюстрирует проблему, с которой вы имеете дело здесь. Я мог бы точно так же использовать код, который я использовал ранее, и все бы работало, но вместо форматированного HTML я получил бы неформатированный/исходный HTML.

Здесь я предполагаю, что вас интересует довольно простой, но отформатированный текст. Вы не упомянули конкретно текст, и если вы, например. хотел скопировать таблицу в формате HTML, тогда мой пример не подходит, так как я использую 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;

ХТН

person skaak    schedule 21.12.2020
comment
К сожалению, это не то, что я ищу. У меня уже есть строка HTML, я хочу скопировать HTML-код в буфер обмена, отформатированный таким образом, чтобы при вставке в другое приложение он сохранял стиль. Изменение моего существующего кода для копирования в виде строки делает именно то, что показывает ваш пример, но намного проще, но это не то, что я ищу. - person Nathan Totten; 21.12.2020
comment
Хорошо, но копирование и вставка строки не должно изменять ее форматирование или стиль. HTML уже отформатирован / оформлен, и вы теряете его при копировании и вставке? Что вы подразумеваете под этим форматированием или стилем? Отступ? Подсветка синтаксиса? В любом случае целевое приложение может переформатироваться при вставке, поэтому буфер обмена не может быть виновником. - person skaak; 21.12.2020
comment
Спасибо за ответ. Это указало мне правильное направление! - person Nathan Totten; 23.12.2020

Я нашел ответ здесь: 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];
      
}
person Nathan Totten    schedule 23.12.2020