IIIF Image API に対応した高解像度画像をARKitで床面に実寸配置し、カメラを近づけると細部が動的に読み込まれるiOSアプリ「IIIF AR」を開発しました。開発にはAnthropicのClaude Codeを活用しています。

アプリの概要

IIIF ARは、IIIF Image APIに対応した画像をiPhoneのカメラで検出した床面に実寸で配置するARアプリです。

主な機能は以下のとおりです。

  • 実寸配置: 画像のピクセルサイズと実際の寸法(cm)から、AR空間に正確なスケールで配置
  • タイルベース深zoom: カメラを近づけるとIIIF Image APIのregionパラメータを利用して、視野内の領域のみ高解像度タイルを動的に取得
  • 配置プレビュー: タップ前に赤いコーナーマーカーで配置予定位置を表示
  • コーナーポール: 四隅に黄色いポールを立てて、障害物がある環境でもサイズを把握可能

サンプル画像

琉球関連の3つの歴史史料をサンプルとして組み込みました。

名称所蔵サイズ
海東諸国紀東京大学史料編纂所32.6×21.2 cm
琉球国之図沖縄美ら島財団96.8×47.3 cm
琉球国図沖縄県立博物館・美術館87.8×175.8 cm

「琉球国図」は縦175.8cmの掛幅で、AR空間に配置すると人の身長に近いサイズになります。

技術スタック

  • 言語: Swift 5.9 / SwiftUI
  • AR: ARKit + RealityKit
  • 画像配信: IIIF Image API 2.0
  • 課金: StoreKit 2(Tip Jar)
  • プロジェクト管理: XcodeGen
  • 開発ツール: Claude Code (Anthropic)

アーキテクチャ

AR配置パイプライン

床面検出 → 配置プレビュー → タップで配置 → タイル読み込み
  1. ARWorldTrackingConfiguration で水平面を検出します
  2. 検出した ARPlaneAnchor に画像を紐づけて安定したトラッキングを実現しています
  3. 低解像度の全体画像(1024px)をベースレイヤーとして常時表示します
  4. カメラ距離に応じて高解像度タイルを上に重ねていきます

IIIFタイルシステム

タイルベースの深zoomは、以下の7ファイルで構成しています。

Tile/
  TileKey.swift           — タイル識別子(scaleFactor, tileX, tileY)
  TileGrid.swift          — IIIFタイル座標系の幾何計算
  ZoomLevelMapper.swift   — カメラ距離→スケールファクタのマッピング
  FrustumCuller.swift     — カメラ視錐台とタイルの可視判定
  TileTextureCache.swift  — NSCacheベースのテクスチャキャッシュ
  TileFetcher.swift       — 非同期タイルダウンロード(最大4並行)
  TileManager.swift       — オーケストレーター

距離とスケールファクタの対応

距離スケールファクタ概要
> 3.0m128全体が1タイル
1.0〜1.5m16立って見る距離
0.5〜0.7m4しゃがんで見る
≤ 0.3m1最高解像度

IIIF URLの構築例

sho.tif(25,167×12,483px)のスケールファクタ8、タイル(0,0)の場合は以下のようになります。

{baseURL}/0,0,4096,4096/512,512/0/default.jpg

エッジタイル(右端)の場合はリージョンがクランプされます。

{baseURL}/24576,0,591,4096/74,512/0/default.jpg

ベースレイヤーの常時表示

開発中、タイルの読み込み中にベース画像が消えてしまう問題がありました。原因は2つありました。

  1. 透明タイルプレースホルダーによる遮蔽: 透明度0のタイルエンティティがRealityKitの深度バッファに書き込まれ、下のベース画像を遮蔽していました。テクスチャが準備できたタイルのみエンティティを作成することで解決しています。

  2. ポール切替時のタイル削除: デバッグ用コーナーポールのON/OFF切替で「imageEntity以外の全子要素を削除」するコードが、タイルエンティティまで巻き込んでいました。poleEntitiesedgeEntitiesを個別に追跡することで解決しています。

Claude Codeによる開発

Claude Codeの「エージェント」機能を活用して開発しました。

並行開発チーム

最大20のエージェントを同時に起動し、以下のような役割分担で並行作業を行いました。

実装チーム:

  • アプリアイコン生成
  • IIIF画像キャッシュ・並列化・リトライ
  • 配置プレビュー(赤コーナーマーカー)
  • ハプティックフィードバック
  • カメラ権限チェック
  • Tip Jar(StoreKit 2)
  • ローカライゼーション(日英)

レビューチーム:

  • TileGrid/TileKey正確性検証
  • FrustumCuller数学検証
  • IIIF タイルURL実動作検証
  • ARViewContainerコード品質
  • プロジェクト設定レビュー

レビューチームが発見した主なバグ

問題原因修正
タイルが画面を覆うと非表示になるFrustumCullerが4隅のみテストし、タイルが画面を包む場合を検出できない中心点テスト+逆テスト追加
ポール切替でタイルが消える全子要素削除がタイルを巻き込むedgeEntities個別追跡
TileFetcherのCPU浪費スピンループ(busy wait)即座にnil返却方式
透明タイルがベース画像を遮蔽深度バッファへの書き込みテクスチャ準備後にのみエンティティ作成

アイコンデザインの試行錯誤

Claude Codeはプログラムによる画像生成(Pillow, SVG)はできますが、デザイン性の高いアイコン作成には向いていないようでした。最終的にはGeminiの画像生成で作成したアイコンを採用しています。Apple HIGに従い、以下の点に注意しました。

  • 正方形で提供(iOSが自動で角丸マスク)
  • 四隅まで背景色で埋める(白い隙間を作らない)
  • テキストなし
  • シンプルな要素数

ソースコード

ソースコードは以下で公開しています。

参考