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:
から挙動をカスタマイズする必要がありそう。
という事で次回に続く。