UITextView で「リッチテキストとしてペースト」pasteAsRichText を実装する
OS Xでの挙動
OS Xの NSTextView には
- (void)paste:(id)sender; - (void)pasteAsPlainText:(id)sender; - (void)pasteAsRichText:(id)sender;
これらのメソッドがあって、システムのペーストの挙動をそのまま使う事もできるしプレーンテキストやリッチテキストでペーストをする事もできる。
編集メニューの「ペースト」使った場合は paste: メソッドが呼ばれてペーストボード上にある NSAttributedString がそのままペーストされる。動きとしては pasteAtRichText: メソッドと同じになる。
編集メニューの「ペーストしてスタイルを合わせる」Paste and Match Style を使った場合は pasteAsPlainText: メソッドが呼ばれ、テキストビューのキャレットの位置のアトリビュートが反映された文字列がペーストされる。 *1
iOSでの挙動
iOSの UITextView では標準では
- (void)paste:(id)sender;
が使える。
このメソッドにペースト処理を任せた場合、例えば以下のように黒字と赤字の部分をまとめてコピーしてテキストの末尾にペーストすると赤文字でペーストされる。
ここでコピー

ここでペースト

結果

OS Xでいう「ペーストしてスタイルを合わせる」Paste and Match Style と同じ挙動になるので、iOSとOS Xとではデフォルトのペーストの挙動が違うという事になる。
そこでiOSの方に足りていない、コピーしたリッチテキスト (NSAttributedString) をそのままペーストするための pasteAsRichText: メソッドを実装する。
実装
まずポップアップメニュー表示のメニュー一覧に独自のメソッドが表示されるようメニューの追加を任意のタイミングで行っておく。
UIMenuItem *menuItem = [[UIMenuItem alloc] initWithTitle:@"Paste As Rich Text" action:@selector(pasteAsRichText:)]; [UIMenuController sharedMenuController].menuItems = @[menuItem];
そして設定した pasteAsRichText: メソッドへの対応を UITextView のサブクラスで実装する。
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender { if (action == @selector(pasteAsRichText:)) { UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; return [pasteboard containsPasteboardTypes:@[(NSString *)kUTTypeFlatRTFD]]; } return [super canPerformAction:action withSender:sender]; }
コピー操作後のペーストボードの pasteboardTypes を確認すると
(
"com.apple.flat-rtfd",
"public.utf8-plain-text",
"Apple Web Archive pasteboard type"
)
の3つが入っていたので com.apple.flat-rtfd のタイプを示す kUTTypeFlatRTFD を使用している。
この定義は MobileCoreServices.framework のヘッダの載っていて kUTTypeFlatRTFD Flattened RTFD (pasteboard format) と書かれているので、ペーストボード用のRTFDフォーマットらしいという事が分かる。
canPerformAction:sender: メソッドの実装内容としては、このタイプのデータがペーストボードに入っていれば "Paste As Rich Text"のメニューが有効になる(表示される)ようにしている。
そして実際にペーストを行うメソッド
- (void)pasteAsRichText:(id)sender { UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; NSData *data = [pasteboard dataForPasteboardType:(NSString *)kUTTypeFlatRTFD]; 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); }
ペーストボードから kUTTypeFlatRTFD で取り出せるデータは NSData なので、そこから NSAttributedString を作って UITextView にペーストする。
ペーストの際には単純に追加するのではなく、現在の選択範囲とペースト後の選択範囲(キャレットの位置)を考慮する。
ここまでの実装で "Paste As Rich Text" を実行

これはいけない。コピー元のテキストのアトリビュートが正しく反映されていない。
NSData から作成した NSAttributedString の中身を確認すると
pariatur. Excepteur{
NSFont = "<UICTFont: 0x7f9072c55d00> font-family: \"Helvetica\"; font-weight: normal; font-style: normal; font-size: 12.00pt";
NSParagraphStyle = "Alignment 0, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (\n 28L,\n 56L,\n 84L,\n 112L,\n 140L,\n 168L,\n 196L,\n 224L,\n 252L,\n 280L,\n 308L,\n 336L\n), DefaultTabInterval 36, Blocks (null), Lists (\n), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0";
}
となっていてコピー元のアトリビュートと変わっていた。
おそらく kUTTypeFlatRTFD やデフォルトの copy: 操作の挙動の仕様だろうという事で copy: から挙動をカスタマイズする必要がありそう。
という事で次回に続く。