友人に自作の壁紙アプリを見せようとして、いつも同じところで詰まります。「まず1枚だけ試してほしい」だけなのに、相手は App Store を開き、検索し、ダウンロードを待ち、起動して、ようやく1枚目にたどり着く。その頃には最初の熱は少し冷めています。
見せたいのは1枚だけ。インストールという壁を、その1枚の後ろに回せないか。App Clip は、まさにこの順番を入れ替えるための仕組みでした。
Rork Max はネイティブ Swift を出力します。React Native では届きにくい App Clip のような領域にも踏み込めるのは、この点が大きい。ただし App Clip はアプリのターゲットを分ける話が絡むため、生成されたコードをそのまま使うのではなく、構造を理解して手を入れる必要があります。手を入れる場所を、実装ベースで残しておきます。
App Clip は何を解くのか
App Clip は、親アプリの一部機能だけを、インストールなしで即座に起動できる小さなアプリです。QR コードや専用の App Clip Code、NFC タグ、あるいは Web ページの Smart App Banner から起動し、目的の1画面だけを数秒で提示します。
大切なのは「劣化版」ではないという点です。壁紙を1枚プレビューする、店舗のメニューを見る、駐輪場の料金を払う。ユーザーが「今この瞬間にやりたいこと」だけを切り出し、それ以外を親アプリへ委ねる。機能の引き算そのものが設計になります。
個人開発の視点では、これは配布導線の実験でもあります。名刺やポスターに App Clip Code を刷り、読んだ人がその場でアプリの核を体験する。ダウンロードは、体験して「もっと欲しい」と思った後に回せます。
ターゲットを分ける — 15MB を守る最初の判断
App Clip は独立したターゲットとして親アプリに同梱されます。ここで最初の制約に向き合います。App Clip のバイナリは、非圧縮状態で厳しいサイズ上限を持ちます。
対象 OS App Clip の非圧縮サイズ上限
iOS 16 以降 15 MB
iOS 15 以前 10 MB
15 MB は、画像アセットを数枚まともに積むとすぐ埋まる大きさです。ここを守るには、親アプリと App Clip でコードを共有しつつ、App Clip に持ち込むものを明確に絞る設計が要ります。
実務上の分け方はこうしています。共有ロジック(モデル・ネットワーク層・1画面分のビュー)は Swift Package か共有ターゲットに切り出し、両方から参照する。一方で、親アプリだけが必要とする重い依存(動画再生、解析 SDK、課金基盤)は App Clip のターゲットからリンクしない。App Clip の Podfile やパッケージ依存は、親アプリのコピーではなく、必要最小限を明示的に列挙します。
要素 App Clip に入れる 親アプリだけ
プレビュー1画面のビュー 入れる —
共有モデル・ネットワーク層 入れる(共有ターゲット) —
解析 SDK・広告 SDK — 親アプリだけ
全カタログ画像 — 親アプリだけ
App Clip 側の画像は、オンデバイスに同梱せず起動 URL のパラメータから必要な1枚だけ取得する。この割り切りが、サイズ上限を守る一番効く手でした。
起動 URL を NSUserActivity で受け取る
App Clip は URL で起動します。https://example.com/w?id=1042 のような URL を QR や App Clip Code に紐付け、起動時にその URL を受け取って目的の画面へ直行させます。
SwiftUI では、onContinueUserActivity で NSUserActivityTypeBrowsingWeb を受け取り、webpageURL を読み解きます。
import SwiftUI
@main
struct WallpaperClipApp : App {
@State private var wallpaperID: String ?
var body: some Scene {
WindowGroup {
ClipRootView ( wallpaperID : wallpaperID)
. onContinueUserActivity (NSUserActivityTypeBrowsingWeb) { activity in
guard let url = activity.webpageURL else { return }
wallpaperID = parseWallpaperID ( from : url)
}
}
}
private func parseWallpaperID ( from url: URL) -> String ? {
let components = URLComponents ( url : url, resolvingAgainstBaseURL : false )
return components ? .queryItems ? . first ( where : { $0 .name == "id" }) ? . value
}
}
ここでの勘所は、wallpaperID が確定するまで中身を空で待たせないことです。URL の解釈は起動直後の一瞬に走るため、ClipRootView は「読み込み中」の姿を先に見せ、ID が入った瞬間に目的の1枚へ切り替える。この数百ミリ秒の設計が、App Clip の体感速度を決めます。
なぜ URL 起動にこだわるのか。App Clip の価値は「文脈を持って開く」ことにあるからです。ただアプリが立ち上がるのではなく、読んだ QR が指し示す1枚を、最初から開いている。この文脈の受け渡しが URL パラメータの役割です。
apple-app-site-association に appclips を書く
URL 起動を成立させるには、ドメイン側の宣言と App Clip 側の Associated Domains エンタイトルメントを対応させます。
App Clip ターゲットの Associated Domains には、appclips: プレフィックスで対象ドメインを登録します。
appclips:example.com
サーバー側は https://example.com/.well-known/apple-app-site-association に、appclips キーを含む JSON を配置します。
{
"appclips" : {
"apps" : [ "ABCDE12345.com.example.Wallpaper.Clip" ]
}
}
ABCDE12345 は Team ID、com.example.Wallpaper.Clip は App Clip のバンドル ID です。このファイルは Content-Type: application/json で、リダイレクトなしに配信します。ここが揃わないと、QR を読んでも App Clip ではなく Safari が開くだけになります。私はこの JSON のバンドル ID を1文字打ち間違え、半日「なぜか Safari に落ちる」を追いかけました。まず疑うべきはこの対応です。ここは App Clip 実装で最もハマる注意点でした。
起動元の使い分け
同じ URL でも、どの入口から起動させるかで体験が変わります。
起動元 向いている場面 備考
App Clip Code ポスター・名刺・店頭 NFC 内蔵版なら、かざすだけでも読み取れる
QR コード オンライン・印刷物全般 手軽だがカメラを向ける動作が要る
NFC タグ 物理的な設置物 かざす動作が直感的
Smart App Banner Web からの流入 既存サイトを持つなら追加コストが小さい
個人開発で最初に試すなら App Clip Code を推奨します。Apple が提供するデザインで、専用の見た目そのものが「かざすと何か起きる」と伝えてくれる。QR より説明が要りません。
App Clip の状態を親アプリへ引き継ぐ
App Clip で「もっと見たい」と思ったユーザーは、そこから親アプリのインストールへ進みます。このとき、App Clip で触れた状態を捨ててしまうと、インストール後にまた最初からになります。橋渡しが要ります。
橋渡しの土台は App Group です。親アプリと App Clip を同じ App Group に所属させ、共有コンテナ経由でデータを渡します。
struct ClipHandoff {
static let suite = "group.com.example.Wallpaper"
static func store ( previewedID : String ) {
let defaults = UserDefaults ( suiteName : suite)
defaults ? . set (previewedID, forKey : "lastPreviewedID" )
defaults ? . set ( Date (), forKey : "clipHandoffDate" )
}
}
親アプリは初回起動時にこの値を読み、App Clip で見ていた1枚を最初に開く。「さっき試したあの壁紙」がインストール直後にそのまま出てくる体験が、離脱を減らします。
認証済みの情報など、より秘匿性の高い状態は共有 Keychain を使います。Keychain のアクセスグループを親アプリと App Clip で共有し、kSecAttrAccessGroup を揃えて保存する。App Group が「普通のデータ」、共有 Keychain が「渡してよい秘密」という役割分担です。
なお App Group の共有コンテナは、App Clip が削除された後も一定期間は残りますが、恒久的な保管場所ではありません。引き継ぎは「あくまで初回起動時に一度読む」前提で設計し、親アプリ側では値がない場合のフォールバックを必ず用意します。
App Clip でやらないことを決める
App Clip の設計で一番効くのは、足す判断ではなく足さない判断です。App Clip はいくつかの機能を意図的に持ちません。
できること できない / 避けること
Apple Pay での決済 通常の In-App Purchase
Sign in with Apple 独自 ID/パスワードの長い登録フロー
一時的な通知(最大8時間) 常時のプッシュ通知許諾
位置情報の一回確認 バックグラウンド常駐処理
課金や本格的な会員登録は親アプリの仕事です。App Clip 側でそれを試みると、サイズも複雑さも膨らみ、15 MB の壁に跳ね返される。App Clip から親アプリへ誘導するときは SKOverlay を使い、体験の流れを止めずにダウンロードを勧められます。
一時的な通知は覚えておくと便利です。App Clip は、常時許諾を求めずに最大8時間の通知を送れます。「プレビューした壁紙、気に入ったらこちら」を数時間後に一度だけ届ける、といった控えめな導線に使えます。使いすぎないことが上品さです。
本番前の確認
ローカル検証では、Xcode のスキームに _XCAppClipURL 環境変数を設定し、実際の起動 URL を擬似的に流し込んで挙動を確かめます。QR を刷る前に、URL パラメータ違いで正しい画面へ直行するかを一通り通しておく。
公開時は、App Store Connect で Advanced App Clip Experience を登録すると、特定 URL ごとに固有のカード(タイトル・画像・アクション)を出し分けられます。既定の体験だけで始め、反応を見てから URL 別の出し分けを足す、という順で十分でした。
Rork Max が土台の Swift を出してくれるとしても、App Clip はターゲット分割・ドメイン宣言・引き継ぎという「アプリの外側の段取り」が本体です。生成コードはあくまで出発点。その先の配線を、この記事の順番で一つずつ確かめていただければ、QR を読んだ数秒でアプリの核が開く導線にたどり着けるはずです。
実装の助けになれば幸いです。お読みいただきありがとうございました。