ObjecTips

Swift & Objective-C で iOS とか macOS とか

UITextView で「リッチテキストとしてペースト」pasteAsRichText を実装する その2

前回

前回の結果を踏まえて copy: メソッドもオーバーライドして挙動をカスタマイズする事にする。
簡単なのは copy: メソッドの super の挙動を呼び出してペーストボードの中身を作ってから自前で追加したいものを加える方法。

実装

- (void)copy:(id)sender
{
    [super copy:sender];

    // RTFDを作成
    NSAttributedString *aString = [self.textStorage attributedSubstringFromRange:self.selectedRange];
    NSData *data = [aString dataFromRange:NSMakeRange(0, aString.length)
                       documentAttributes:@{NSDocumentTypeDocumentAttribute: NSRTFDTextDocumentType}
                                    error:nil];
    // アイテムを追加
    UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
    [pasteboard addItems:@[@{(NSString *)kUTTypeRTFD: data}]];
}

ペースト側の valueForPasteboardType: メソッドでは2つ目のアイテムを取得する事が出来ないのでペースト時のデータ取得の実装も少し修正する

- (void)pasteAsRichText:(id)sender
{
    UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
    NSData *data = [pasteboard dataForPasteboardType:(NSString *)kUTTypeRTFD inItemSet:nil].lastObject;
    NSAttributedString *aString = [[NSAttributedString alloc] initWithData:data
                                                                   options:nil
                                                        documentAttributes:nil
                                                                     error:nil];
    NSRange selectedRange = self.selectedRange;
    [self.textStorage replaceCharactersInRange:selectedRange withAttributedString:aString];
    // ペースト後にキャレット位置を移動
    self.selectedRange = NSMakeRange(selectedRange.location + aString.length, 0);
}

同様の理由で canPerformAction:withSender: メソッドも少し修正する。

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
    if (action == @selector(pasteAsRichText:)) {
        UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
        return [pasteboard containsPasteboardTypes:@[(NSString *)kUTTypeRTFD] inItemSet:nil];
    }
    return [super canPerformAction:action withSender:sender];
}

この実装でペーストしてみる

f:id:Koze:20160208094314p:plain

コピー元のアトリビュートがそのまま反映された。pasteAsRichText: に成功。

まとめ

問題点

若干の問題点として
[super copy:sender]; をした後に addItems: をしているため、UIPasteboardchangeCount が2回インクリメントされてしまうというのがある。
これが問題にならなければ上記の実装で良いかも知れない。

もしこの現象を避けるのであればペーストボードの中身を全て自分で作ってから一気にアイテムを設定してやればいい。
その際 kUTTypeWebArchive Apple Web Archive pasteboard type の中身のデータを自作するのがネックになるので、このペーストボードのタイプを落とせない場合はペーストボードの中身の自作はあまりお勧めできない。

別のアプローチとして [super copy:sender]; は呼びつつ、自前で追加するRFTDのアイテムは generalPasteboard を使わないで pasteboardWithName:create: メソッドで作った自前のペーストボードを使うという方法がある。
この場合は独自に作ったペーストボードの名前を知っているアプリ(アプリ自体と他の自作アプリ)間ではコピーペーストでのRFTDの受け渡しができるけど、他のアプリとの連携ができなくなってなってしまう。
状況に合わせて判断という事になると思う。