Article

🧵 編集できるデータと生成結果を分ける:ベジェ曲線実装で学んだ「正本」と派生キャッシュの設計

swift, ios, architecture, testing

図形編集アプリでは、画面に表示する点列だけを保存してしまうと、後から編集機能を追加したときに困ることがあります。

開発中のiPad刺しゅうアプリ「Stitch Atelier」でベジェ曲線を追加した際、編集可能な正本と機械出力用に生成した点列を分ける設計にしました。

Stitch Atelierは現在開発中・未リリースで、実機のApple Pencil操作とミシン実機での試縫いはまだ最終検証前です。

問題:点列だけでは元の曲線を編集できない

3次ベジェ曲線は、始点・終点・2つの制御点で定義できます。

一方、刺しゅうデータにするときは最終的に多数の針位置へ変換する必要があります。

ここで針位置の点列だけを保存すると、元の制御点情報を失います。

見た目は同じでも、「制御点を少し動かして曲線を調整する」という編集ができなくなります。

正本を制御点にする

Stitch Atelierでは、ベジェ曲線の4制御点を編集上の正本として保持します。

針生成に利用する点列は、正本からいつでも作り直せる派生データとして扱います。

ベジェ制御点
   ↓
曲線近似
   ↓
点列
   ↓
ステッチ生成
   ↓
DST / EXP

この構造にすると、保存・Undo・変形では制御点を基準にできます。

派生キャッシュを持つ理由

毎回すべてを再計算すると描画や編集レスポンスが悪くなるため、近似済み点列はキャッシュとして保持します。

ただしキャッシュを正本と同格に扱うと、両者がずれる可能性があります。

そのため保存時には「制御点から再生成した結果とキャッシュが整合しているか」を検証します。

ドラッグ中と確定後の状態も分ける

制御点をドラッグすると大量のイベントが発生します。

各イベントで正本を書き換えてUndo履歴へ積むと、1回のドラッグが大量のUndo単位になります。

そこで、

  • ドラッグ中: 一時表示状態だけ更新
  • ドラッグ終了: 正本を1回だけ更新

と分離しました。

UIの一時状態と永続的な編集状態を分ける考え方は、図形編集以外でも使えます。

非同期処理では古い結果を捨てる

曲線近似や針生成を非同期化すると、古い入力に対する処理結果が後から返ってくることがあります。

そこで入力世代を持たせ、現在の世代と一致しない結果は反映しません。

これは検索サジェスト、画像変換、プレビュー生成などでも使えるパターンです。

変換後の制限も明示する

通常の節点編集が必要になった場合は、ベジェ曲線を点列へ明示的に変換します。

一度点列化した後は、元のベジェ制御点による編集には戻れないことをUI・データモデル上で明確にします。

「何でも自動変換する」より、情報が失われる境界を明示した方が事故を減らせます。

テスト

今回の実装では、曲線近似精度、保存・復元、Undo、変形、上限値、DST出力などを自動テストしています。

さらに、留め縫いや針順再生など後続機能も「編集正本を壊さず派生結果へ適用する」という方針で実装しています。

まとめ

編集系アプリでは、最終出力に必要なデータと、ユーザーが再編集するために必要なデータが同じとは限りません。

  • 編集可能な情報を正本にする
  • 表示・機械出力用の結果は派生データにする
  • キャッシュと正本の整合性を検証する
  • UIの一時状態と確定状態を分ける
  • 非同期処理では古い世代の結果を捨てる

この考え方は、図形編集、動画編集、CAD、画像処理などにも応用できます。

この設計は現在開発中のStitch Atelierで実際に使っています。アプリ自体はまだ未リリースです。