📝

TextKit2 を利用して縦書きレイアウトを行う

2022/08/10に公開2

今年の WWDC22 では大きな取り沙汰はありませんでしたがシステムフレームワークへは順調に TextKit2 への置き換えが進んでいるようです
そこで今回は理解を兼ねて TextKit2 を利用してテキストを縦書きレイアウトを行う一例を紹介します
内容やコードは WWDC21 の Meet TextKit 2 を参考にしているのでそちらも確認すると理解が深まると思います
(上記セッションのサンプルコードの挙動が少しおかしかったり実装が分かりづらかったのが本記事執筆のきっかけです)

完成形は以下の通りです

はじめに

まず前提として NSTextLayoutManager には現状レイアウト方向を指定する方法がない?ためビュー側で吸収する必要があります
また今回は表示のみの機能のため単純な対応になっています
https://github.com/swiftty/TextKit2-Example

縦書きに必要な処理

といっても大仰なことは必要なくテキスト全体に verticalGlyphForm を指定することだけです

string.addAttribute(.verticalGlyphForm, value: true, range: NSRange(location: 0, length: string.length))

余談ですが、 TextKit1 では verticalGlyphForm だけでは実現できず、 Core Text を駆使する必要がありました

ビュー側の対応

ここまでで下記のような結果になります

NSTextLayoutFragment が文章ブロック毎の縦に積まれた座標を持っているので、ここを 90 度回転させて横方向に並べれば縦書きレイアウトが達成できそうです
が、今回はその外側のスクロールビュー自体を 90 度回転させて対応しました

contentView.frame = bounds
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+ contentView.transform = .init(rotationAngle: .pi / 2)

変更範囲は上記の一箇所だけでよく、ビューポート範囲の矩形に関しても transform が適用された状態になるので、そのままで問題ありません

func viewportBounds(for textViewportLayoutController: NSTextViewportLayoutController) -> CGRect {
    CGRect(origin: contentOffset, size: bounds.size)
        .insetBy(dx: 0, dy: -100)
}

おわりに

感想としてはテキスト機能の 編集・選択・表示 のうち表示に限っていえば TextKit2 を用いると、とても簡単に縦書きを実現することができました
(当初は備忘録として記録を残すために書き始めたのですが、逆に書くことがなくて悩みました…)

また NSTextViewportLayoutController のおかけで大きなテキストでも充分にパフォーマンスが発揮されていて色々なものに活用できそうです

Discussion

s516s516

とても参考になります。ありがとうございます。
TextKit 2を利用した場合、ページに分割して表示することは可能でしょうか?

swifttyswiftty

「ページに分割して表示」がどういった振る舞いを期待しているのかわかりませんが、 TextKit2 に限らずUIScrollView.isPagingEnabled はいかがでしょうか?
もしくは NSTextLayoutFragmentCGRect を持っているので、それを利用すれば分割できそうです