Вставить форматированный текст, а не изображения или HTML

Я пытаюсь эмулировать поведение монтажной панели приложений 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 вы можете попросить монтажную панель возвращать различные типы, предоставить ей как расширенный текст, так и строковое представление, и использовать расширенный текст по своему усмотрению. К сожалению, readObjectsForClasses, похоже, не существует для iOS. Тем не менее, благодаря этому посту, я вижу по логу, что у iOS действительно есть тип монтажной платы, связанный с RTF. Однако я не могу найти способ запросить NSAttributedString версию содержимого монтажной панели, чтобы я мог расставить приоритеты для вставки.

ФОН

У меня есть приложение, которое позволяет базовое редактируемое пользователем форматирование NSAttributedString (то есть полужирный, курсив, подчеркивание) текста в UITextViews. Пользователи хотят копировать текст из других приложений (например, веб-страницу в Safari, текст в приложении Notes), чтобы вставить в UITextView в моем приложении. Разрешение работы с картоном по умолчанию означает, что я могу получить фоновые цвета, изображения, шрифты и т. Д., Которые мое приложение не предназначено для обработки. Пример ниже показывает текст, как выглядит скопированный текст с цветом фона при вставке в UITextView моего приложения.

введите описание изображения здесь

Я могу преодолеть 1 путем создания подклассов UITextView

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

Непреднамеренное последствие - потеря способности сохранять форматирование текста, скопированного из моего приложения. Пользователи могут захотеть скопировать текст из одного UITextView в моем приложении и вставить его в другой UITextView в моем приложении. Они ожидают, что форматирование (т.е. полужирный, курсив, подчеркивание) будет сохранено.

Проницательность и предложения приветствуются.

2 ответа

Несколько копий / вставок вкусностей и идей для вас:)

// 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) }
    
  • Еще лучше, чем использовать пользовательский тип монтажной панели, занести в белый список все возможные теги, выполнить цикл и удалить все остальное при вставке.

  • (Бонус: помогите UX в других приложениях, убрав ненужные атрибуты, которые вы добавили, при копировании (например, шрифт и 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
    }
    

Удачи!

Ссылки: ссылка на унифицированные идентификаторы типов

Это должен быть комментарий к ответу Леонарда Паули, но у меня пока недостаточно репутации, чтобы комментировать.

Вместо:

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

(или attribuStString.string.count, как в более поздних версиях Swift)

Лучше всего использовать:

selectedRange.location += attributedString.length

В противном случае, когда вы вставите текст, содержащий эмодзи, которые приводят к различиям в атрибутах attribute.String.length и attribute.String.string.count, выбор окажется не в том месте.

Другие вопросы по тегам