Kristopher Baker iOSを土台に · プロダクトシステム · AI支援ワークフロー
← Aoede

shipped · 2026.06.30 · 約3分

段落ごとに1セル

前回、AoedeのリーダーをTextKit 2へ移した話を書きました。そこで紹介したバージョンは、本まるごとを一本の長いアトリビュート文字列として描画していました。見た目はきれいで、短いドキュメントなら問題なかったのですが、スケールしませんでした。長い本では、最初のページを出す前に全体をレイアウトする必要があり、iOSではそれが遅く、メモリも重く、壊れているように感じるほどでした。前回の構造的な賭けは、半分だけ当たっていました。TextKitは正しい道具でしたが、本を一つのオブジェクトとして描くのは、間違った形だったのです。

意外だったのは、そこから正しい形にたどり着くのがどれだけ大変だったかです。作業をしているAIにスケールの問題を直すよう頼むと、持ちこたえない設計に何度も手を伸ばすのです。一本の巨大なアトリビュート文字列を別のやり方で作り直したり、古いSwiftUIリーダーのコンポーネントをこっそりTextKitの経路に戻したり。後者は、せっかく逃れようとしていた単語ごとにビューを作るコストを、まるごと引きずり戻してきます。どの方法もきれいに実装はできるのに、本物の400ページの本で倒れるほうを選び続けるのです。突破口は、自分で決めて押し通すしかなかった設計判断でした。本を一つのものとして描くのをやめる。リストに入れて、段落ごとに1セル、各セルをTextKitでレイアウトする。iOSではUICollectionView、macOSでは段落ごとの高さのためにカスタムレイアウトを付けたNSCollectionView。リストは画面付近の段落しか実体化しないので、常に1画面ぶんしかレイアウトしません。だから長い本も、短い本と同じ速さで開きます。

前回の位置に戻る処理は、それ自体にもう一段の作業が要りました。長い段落の中の今読んでいる単語まで含めて、本を正確に元の場所で開き直すには、リストはそこへスクロールする前に各セルの高さを知っていなければなりません。だから、セルが現れるときに遅延で自分のサイズを決めさせるのではなく、高さを前もって計算します。画像の多い本にはもう少し必要でした。写真の高さは読み込むまでわからないので、復帰処理はレイアウトが安定するまで目標位置を主張し直します。このセルリスト方式のリーダーは、いまや両プラットフォームのデフォルトです。本まるごとを描く方式はなくなり、元のSwiftUIリーダーは比較用にLabsのトグルの裏に残してあります。長い本がようやく速く開くようになりました。iPhoneでもiPadでも。そして、止めたまさにその場所に戻ってきます。何度も学び直しているのは、モデルは設計を作るのはとても上手いけれど、本物の本との接触を生き延びる設計を選ぶのは必ずしも上手くない、ということです。そこはやはり、私の出番でした。