Expo と Coreviz SDK で AI ファーストの写真アプリを作る
Expoでネイティブ品質のフォトグリッドを作る:複数の仮想化グリッドとクロスフェードでiOS写真のピンチズーム操作を再現する方法。
日本語
コピー

本記事は Wassim Gharbi によるゲスト投稿です。同氏は現在 Tesla のチーフアーキテクトとして、数兆件にのぼるテレメトリデータポイントの可視化プラットフォームを構築しており、高性能 UI と AI システムの研究に注力しています。
…
何万枚もの写真を撮ってきた身として、私はずっと「プロ向け」編集アプリ(Lightroom や Photomator など)とコンシューマー向けアプリ(iOS Photos や Google Photos など)の間にある溝が嫌だった。
片や、タグ付け、フラグ、星評価、一括編集といった強力な機能を備えながら、UI は会計ソフトのように無骨。片や、美しく直感的で滑らかな UI だが、ライブラリが数千枚に膨らんだ途端、整理に必要なツールが一つもない。
そこで Coreviz のモバイルアプリを開発するにあたり、Pro 版の UX の好みはウェブ版に残し、モバイル版はゼロから設計することにした。巨大な写真ライブラリを、手間なく閲覧・検索・タグ付け・整理できる軽量なアプリをユーザーに届けるためだ。

目指したのは、見た目に威圧感のない「プロ向け」メディアライブラリだ。美しいインターフェースで、プロ級の整理・編集機能(Coreviz SDK が中核を担う)を普通の人に届ける——ただ写真ライブラリを散らかしたくないだけの人たちに。
このアプリは完全に Expo で構築されていて、ネイティブコードは一行も書いていない。それでいて、見た目も操作感もほぼ純正の Photos アプリそのままで、ユーザーが愛する細部も一つ残らず揃っている。
だがよく見ると、先進的な AI 機能がアプリの隅々にまでシームレスに組み込まれている。「Edit with AI」は Gemini(Nano Banana)のようなモデルの編集機能を、まさに写真があるその場所に持ち込む。「Tag with AI」なら、ひとつの簡単なプロンプトで写真にまとめてタグを付けられる。たとえば「目を閉じている人が写った写真を全部探して」とか「バスケットボール選手の背番号をそれぞれラベル付けして」といった具合だ。
なぜ Expo なのか?
「Apple らしさ」のあるアプリをどう作るかと考えたとき、React Native や Expo は開発者が真っ先に思い浮かべる道具ではないだろう。少なくとも私の印象では、Expo はずっと汎用性を売りにしてきた。同じコードで iOS と Android の両方にアプリを出したいときに選ぶもので、その代償はさまざまな妥協だった。
今は昔とは違う。
Expo は最新の iOS/Android アップデートへの追随が実にうまく、とりわけ Liquid Glass による iOS の再設計がもたらした最新の UX インタラクションをいち早く取り込む点では先頭を走っている。ネイティブの Tabs、Liquid Glass のボタンやタイトル、共有要素のトランジションなどだ。
同時に React Native エコシステム自体も、パフォーマンスと UX の差を縮めてきた。New Architecture、React Native Skia(上の動画のリップルエフェクトはこれで実装している)、そして LegendList/FlashList——本記事で扱うグリッドビューはこれらが支えている。
こうして Expo は、ネイティブの手触りを保ちながら全プラットフォームをカバーできる理想的な選択肢になり、Coreviz Studio のモバイルアプリにも自然と選ばれた。
課題:拡縮するグリッド
iOS の写真にあるあの滑らかなピンチズーム——密集した小さなサムネイルのグリッドから全画面の写真へとシームレスに移り変わる様子は、まるで魔法だ。
ところが React Native でこれを再現するのは本当に難しい。最終的にこうやって解決した:
目標は単純に見える。ピンチでズームし、異なるグリッド密度の間を滑らかに移行し、スクロール位置を保ち、フレームを落とさない。
最初の試みは最も直感的なやり方だった。ピンチジェスチャーに応じて FlashList の numColumns を直接変える、というものだ。
結果は散々だった。列数が変わるたびにレイアウト全体が再計算され、スクロール位置はあちこちに飛ぶ。写真が数百枚あるだけでひどくカクつき、手触りは最悪。アニメーションは偽物に見えた——実際に偽物だったからだ。React Native が本質的に一から計算し直すものを、こちらがアニメーションさせていただけだった。
コツ:変形させず、クロスフェードする
Claude Code に何時間も怒鳴り散らした末に、こう気づいた。グリッドのレイアウトをそもそも変えなければいいのでは?
変形するグリッドを一つ使う代わりに、完全なグリッドを複数(それぞれ列数は固定)重ねて描画する。ピンチの最中は、それらの間でクロスフェードさせるだけだ。
手元にあるのは:
-
Grid A、1 列
-
Grid B、3 列
-
Grid C、5 列
-
Grid D、9 列
それぞれが独立した FlashList か LegendList で、絶対配置し、同じ写真群を描画する。違うのは item のサイズだけ。ピンチのとき、実際には何も拡縮していない。あらかじめ描画済みのレイアウトをいくつか用意して、その間をクロスフェードしているだけだ。
見た目は一つのグリッドが滑らかに拡縮しているが、実体は四つのグリッドがかくれんぼしている。
待って、これ性能的にすごく高くつかない?
その直感は正しい!Expo/React Native の初期には、何千枚もの写真を含む完全なグリッドを複数描画するなんて考えるだけで正気の沙汰ではなかった。
だが、今の我々には(幸運にも)その余裕がある!
FlashList や LegendList のように仮想化を実装したライブラリのおかげで、これらのレイアウトを描画する性能を心配する必要はない。仮想化があれば、描画されるのは見えている内容だけだ!ユーザーが今見ていない部分は性能にまったく影響しない!見えざるものは心の平穏。
次の図は背後で起きていることを示したもので、各ズームレベルを色付きの枠で囲んでいる:
ここでのコツは、各ズームレベルを揃えて、対象の写真(ピンチで拡大・縮小するときに中心になる写真)がどのズームレベルでも画面上のまったく同じ絶対位置に来るようにすることだ。
こうして react-native-zoom-grid が生まれた。
そこでこの仕組みを、独立して再利用できるオープンソースのコンポーネントとして切り出すことにした。このインタラクションを土台に、高性能でズーム可能なグリッドを提供する。どんな Expo/React Native アプリでも使える。
自分のアプリで同じ動きを再現したいなら、Github でコードを見るか、npm から直接インストールしてほしい。
実際の動作 – Studio app
このズーム可能なグリッド(および前述のその他のインタラクションや AI 機能)が実際に動くところを見てみたいなら。
Studio を試してほしい。AI をあなたの写真のすぐそばに引き寄せる、丁寧に作り込まれたアプリだ。
Studio は App Store で入手できる