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

本記事は Wassim Gharbi によるゲスト投稿です。同氏は現在 Tesla のチーフアーキテクトとして、数兆件のテレメトリデータポイントを扱うデータ可視化プラットフォームを構築しており、高性能 UI と AI システムの研究に取り組んでいます。
…
何万枚もの写真を撮ってきた身として、私はずっと「プロ向け」レタッチアプリ(Lightroom や Photomator など)とコンシューマー向けアプリ(iOS Photos や Google Photos など)の間にある溝が嫌だった。
片側には強力なタグ付け、フラグ、星評価、一括編集機能があり、その代わりインターフェースは会計ソフトのようだ。もう片側には美しく直感的で滑らかなインターフェースがあるが、ライブラリが数千枚に膨らんだ途端、必要な整理ツールが一つも見当たらない。
そこで 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 Photos のあの滑らかなピンチズーム——密集した小さなサムネイルのグリッドから全画面の写真へとシームレスに移り変わる様子は、使っていてまるで魔法のようだ。
だが React Native でこれを再現するのは本当に難しい。私たちは最終的にこうやって解決した:
目標は単純に見える。ピンチズームで異なるグリッド密度の間を滑らかに移行し、スクロール位置を保ち、フレームを落とさない。
最初の試みは最も直感的なやり方だった。ピンチジェスチャーに応じて FlashList の numColumns を直接変える、というものだ。
結果は散々だった。列数が変わるたびにレイアウト全体が再計算され、スクロール位置があちこち跳ねる。数百枚の写真でさっそくカクつき、手触りは最悪。アニメーションは偽物に見えた——実際に偽物だったからだ。私たちは本質的に、React Native が一から計算し直すしかないものをアニメーションさせていた。
コツ:変形させず、クロスフェードする
Claude Code に何時間も怒鳴り散らした末、私たちは気づいた。グリッドのレイアウトをそもそも変えなければいいのでは?
変形するグリッドを一つ使う代わりに、完全なグリッドを複数(それぞれ列数は固定)重ねてレンダリングする。ピンチジェスチャーの間は、それらの間でクロスフェードするだけだ。
手元にあるのは:
-
Grid A、1 列
-
Grid B、3 列
-
Grid C、5 列
-
Grid D、9 列
それぞれが独立した FlashList または LegendList で、絶対配置され、同じ写真群をレンダリングする。違うのはアイテムのサイズだけだ。ピンチ中に実際に拡大縮小しているものは何もなく、あらかじめレンダリングされた複数のレイアウトの間でクロスフェードしているだけ。
見た目は一つのグリッドが滑らかに拡大縮小しているようで、実態は四つのグリッドがかくれんぼしている。
待って、これ性能的にすごく高くつかない?
その直感は正しい!Expo/React Native の初期には、数千枚の写真を含む完全なグリッドを複数レンダリングするなんて想像するだけで正気の沙汰ではなかった。
ただ、今の私たちには(幸運にも)その余裕がある!
FlashList や LegendList のように仮想化を実装したライブラリのおかげで、これらのレイアウトをレンダリングする性能を心配する必要はない。仮想化があれば、レンダリングされるのは見えている内容だけだ!ユーザーが今見ていない部分は性能に一切影響しない!見えざるものは心の平穏。
The following diagram shows the underlying structure, with each zoom level indicated by a colored frame:
The trick here is to align the zoom levels so that the target photo — the one the user centers when pinching in or out — lands at exactly the same absolute position on screen at every zoom level.
And so react-native-zoom-grid was born.
So I decided to extract this mechanism into a standalone, reusable open-source component. Built on this interaction, it provides a high-performance, scalable grid that can be dropped into any Expo/React Native app.
If you want to reproduce the same behavior in your own app, check out the code on GitHub or install it directly from npm.
See it in action – Studio app
If you want to see this zoomable grid (along with the other interactions and AI features mentioned above) in action.
Try Studio — a carefully crafted app that brings AI right next to your photos.
Studio is available on the App Store