RORK LABEN
APPLE-AI — Appleが初回DL 200万未満の開発者にFoundation Modelsを無償開放。個人開発アプリへのAI組み込みコストが大幅に低下SWIFT-API — Foundation Modelsのサーバーサイド統合で、ClaudeやGeminiを同一Swift APIから呼び出し可能に。画像入力にも対応KOTLIN-MIGRATION — Android Studioの移行エージェントがReact Native製アプリをネイティブKotlinへ自動移行。Rork生成アプリの将来の選択肢にRORK-MAX — Rork MaxはネイティブSwiftコードを生成(月$200)。iPhone・iPad・Watch・TV・Vision Pro・iMessageまで対応SIMULATOR — ブラウザベースのストリーミングiOSシミュレータで、XcodeやMacなしに実機相当のApple環境で検証可能SWIFTUI — WWDC 2026でSwiftUIが進化。並べ替え可能コンテナ・任意コンテナのスワイプアクション・最大2倍速のレイアウトAPPLE-AI — Appleが初回DL 200万未満の開発者にFoundation Modelsを無償開放。個人開発アプリへのAI組み込みコストが大幅に低下SWIFT-API — Foundation Modelsのサーバーサイド統合で、ClaudeやGeminiを同一Swift APIから呼び出し可能に。画像入力にも対応KOTLIN-MIGRATION — Android Studioの移行エージェントがReact Native製アプリをネイティブKotlinへ自動移行。Rork生成アプリの将来の選択肢にRORK-MAX — Rork MaxはネイティブSwiftコードを生成(月$200)。iPhone・iPad・Watch・TV・Vision Pro・iMessageまで対応SIMULATOR — ブラウザベースのストリーミングiOSシミュレータで、XcodeやMacなしに実機相当のApple環境で検証可能SWIFTUI — WWDC 2026でSwiftUIが進化。並べ替え可能コンテナ・任意コンテナのスワイプアクション・最大2倍速のレイアウト
記事一覧/開発ツール
開発ツール/2026-06-12中級

Rork 製アプリにホーム画面ウィジェットを追加する — Expo の制約を越えて WidgetKit を動かすまで

Rork が生成する Expo アプリにはホーム画面ウィジェットを直接追加できません。config plugin と App Group を使って WidgetKit を組み込む実装ルートを、詰まった点も含めて手順で整理します。

Rork377WidgetKit4Expo60iOS73ウィジェット6

Rork でアプリを公開した後、「ホーム画面ウィジェットを付けたい」と思った瞬間に、初めて Expo というプラットフォームの境界線に触れることになります。チャットで「ウィジェットを追加して」と頼んでも、Rork の AI は React Native のコンポーネントを生成するだけで、ホーム画面に置けるウィジェットは出てきません。私も最初にこれを試したとき、生成されたのはアプリ内の「ウィジェット風カード UI」でした。要求した側の意図とはまったく別物です。

なぜそうなるのか、そしてどうすれば本物のウィジェットを Rork 製アプリに組み込めるのか。壁紙アプリで「日替わり画像をウィジェットに表示する」機能を検証したときの手順を整理します。2014年から個人開発を続けてきて、ネイティブの UIKit 側では何度もウィジェットを実装してきましたが、Expo ベースの Rork アプリに組み込むのは勝手が大きく違いました。

ウィジェットが「アプリの外」で動く拡張ターゲットだから生成できない

最初に押さえておきたいのは、iOS のホーム画面ウィジェットは React Native のコードでは書けない、という構造的な事実です。

WidgetKit のウィジェットは、アプリ本体とは別の「App Extension」という独立したバイナリとして動きます。実行されるのはアプリのプロセスではなく、システム側のプロセスです。そこで動かせるのは SwiftUI のビューだけで、JavaScript ランタイムは存在しません。つまり React Native / Expo のコードがどれだけ完成していても、ウィジェット部分だけは Swift で書く必要があります。

Rork のチャット AI が生成するのは Expo(React Native)のコードなので、「ウィジェットを作って」と頼んでもアプリ内 UI しか出てこないのは、AI の能力不足ではなく実行環境の制約です。ここを誤解したまま何度もプロンプトを書き直すと時間だけが溶けていきます。私は最初の 30 分をここで失いました。

実装ルートは 3 つ — 私が config plugin 方式を選んだ理由

Rork 製アプリにウィジェットを足す現実的なルートは、次の 3 つに絞られます。

  • ① config plugin(@bacons/apple-targets 等)で拡張ターゲットを注入する: Expo の managed workflow を保ったまま、ビルド時にウィジェット用の Xcode ターゲットを自動生成する方式です
  • ② prebuild して bare workflow に降りる: npx expo prebuild で ios フォルダを展開し、Xcode で直接ウィジェットターゲットを追加する方式です。自由度は最高ですが、以後 Rork のチャットでの修正と手元のネイティブ変更が衝突しやすくなります
  • ③ Rork Max の SwiftUI ネイティブ生成に乗り換える: アプリ全体を SwiftUI で生成し直す選択肢です。ウィジェットとの親和性は最も高いものの、既存の Expo アプリを作り直すコストが発生します

既に運用中の Expo アプリに後付けするなら、私は ① を選びます。理由は一つで、Rork 側のコード生成フローを壊さずに済むからです。② に降りると、その後チャットで UI を修正するたびにネイティブ側との整合性を自分で管理する必要が出てきます。個人開発で複数アプリを並行運用している身としては、この管理コストが一番重いと感じています。

ビルドは EAS Build(クラウド)に任せられるので、Mac の Xcode を開かずに完結できる点も ① の利点です。クラウドビルドの基本的な流れは Rork Max クラウドコンパイル — Mac不要でネイティブアプリを構築 で書いた内容がそのまま使えます。

App Group でアプリとウィジェットのデータを橋渡しする

ウィジェット実装で最初に設計すべきはデータの受け渡しです。アプリ本体(JS 側)とウィジェット(Swift 側)は別プロセスなので、変数もストレージも共有されません。両者をつなぐ標準的な手段が App Group です。

仕組みは単純で、group.com.example.myapp のような識別子を両ターゲットに設定すると、その名前空間の UserDefaults を双方から読み書きできるようになります。React Native 側からは expo-shared-group-preferences 系のモジュール、もしくは小さな Expo Module を自作して書き込みます。

検証した壁紙ウィジェットでは、JS 側が「今日の画像 URL とタイトル」を App Group に書き、Swift 側のウィジェットがそれを読んで表示する、という一方向の流れにしました。双方向同期にすると排他制御の考慮が増えるので、ウィジェットは「読み取り専用のビュー」と割り切るのが安全だと考えています。

// JS側: 日替わりコンテンツを App Group の UserDefaults に書き込む
// (expo-modules-core で自作した小さなネイティブモジュール経由)
import { setWidgetData } from "./modules/widget-bridge";
 
export async function publishTodayToWidget(item: WallpaperItem) {
  // ウィジェットが読むキーは1つの JSON にまとめる(キー散らばり防止)
  await setWidgetData("group.net.example.wallpaper", {
    title: item.title,            // 例: "雨上がりの新宿御苑"
    imageUrl: item.thumbnailUrl,  // ウィジェットは小サイズ画像で十分
    updatedAt: new Date().toISOString(),
  });
  // 期待する動作: 次回のタイムライン更新時にウィジェットへ反映される
}

ここで注意したいのは画像の扱いです。ウィジェットのプロセスにはメモリ上限(おおよそ 30MB 前後)があり、フルサイズの壁紙画像を読み込むと描画ごと失敗します。サムネイル URL を渡すか、事前に縮小した画像を App Group の共有コンテナにファイルとして置くのが現実的でした。

WidgetKit 側は「タイムライン」の考え方さえ掴めば短い

Swift 側のコードは、config plugin が生成したターゲットの中に置きます。WidgetKit は「今後表示すべきスナップショットの配列(タイムライン)」を OS に渡す設計で、ここさえ理解すれば書く量は多くありません。

// Widget側: App Group から日替わりデータを読んで表示する
struct DailyEntry: TimelineEntry {
    let date: Date
    let title: String
}
 
struct DailyProvider: TimelineProvider {
    func getTimeline(in context: Context,
                     completion: @escaping (Timeline<DailyEntry>) -> Void) {
        // アプリ本体が書き込んだ App Group の UserDefaults を読む
        let store = UserDefaults(suiteName: "group.net.example.wallpaper")
        let title = store?.string(forKey: "title") ?? "本日の一枚"
        let entry = DailyEntry(date: Date(), title: title)
        // 次の更新は翌朝6時に1回だけ依頼する(更新予算の節約)
        let next = Calendar.current.nextDate(after: Date(),
                     matching: DateComponents(hour: 6), matchingPolicy: .nextTime)!
        completion(Timeline(entries: [entry], policy: .after(next)))
    }
    // placeholder / getSnapshot は省略(実装は数行です)
}
// 期待する動作: ホーム画面のウィジェットに「本日の一枚」のタイトルが表示され、
// 毎朝6時のタイムライン更新で新しい内容に切り替わる

公式ドキュメントを読むだけでは気づきにくいのが 更新頻度の「予算」 です。ウィジェットのタイムライン更新は OS がスケジュールし、1 日あたりの回数に上限があります(体感では 40〜70 回程度に制限されます)。「15 分ごとに更新」のようなタイムラインを組むと、予算切れで日中の後半はまったく更新されなくなります。日替わりコンテンツなら 1 日 1 回の .after 指定で十分で、即時反映が必要な操作だけアプリ側から WidgetCenter.shared.reloadAllTimelines() を呼ぶ構成が安定しました。

ビルドと実機確認で詰まった 2 点

実装より時間を取られたのがビルド周りでした。詰まった点を 2 つ残しておきます。

1 つ目は プロビジョニングです。ウィジェットは独立したターゲットなので、Bundle ID(例: net.example.wallpaper.widget)にも個別のプロビジョニングプロファイルが必要になります。EAS Build は基本的に自動生成してくれますが、App Group の entitlement を後から足した場合、古いプロファイルが残って署名エラーになることがありました。eas credentials から該当プロファイルを一度削除して再生成すると解消します。development / preview / production でプロファイルを切り替えている場合の注意点は Rork の EAS Build プロファイルを切り替えたら動かなくなった — development・preview・production の落とし穴と対処法 も参考になるはずです。

2 つ目は 実機確認の方法です。ウィジェットはシミュレーターでも動きますが、タイムライン更新の挙動(予算による間引き)はシミュレーターと実機でまったく違います。日次更新の検証は実機で 2〜3 日動かして初めて信頼できる結果になりました。実機への入れ方自体は Rork Companion で iPhone 実機テスト — Apple Developer登録不要でQRコード1枚から始める の手順が使えますが、ウィジェットを含むビルドは Companion のプレビューでは確認できず、EAS Build → TestFlight 経由になる点だけ注意してください。

まとめ — まず「読み取り専用の小さなウィジェット」から

ウィジェットは機能を盛るほどメモリ上限と更新予算に苦しむので、最初の 1 個は「App Group から文字列を 1 つ読んで表示するだけ」の最小構成で作ってみてください。そこまで通れば、画像表示やディープリンク対応は積み増しで対応できます。累計 5,000 万 DL 規模まで壁紙アプリ群を育ててきた経験からも、ウィジェットのような「毎日目に入る小さな接点」は起動率への効きが想像以上に大きい機能です。Expo の制約はありますが、越える価値は十分にあると感じています。

お読みいただきありがとうございました。

シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

開発ツール2026-05-10
Rorkで作ったアプリのiOSウィジェットが「古いまま」になる本当の原因 — App Group共有とタイムライン更新の設計を見直す
Rorkで生成したアプリにiOSウィジェットを追加したら、起動するたびに古い情報を表示する。App Groupの発行・UserDefaults共有・タイムライン更新の3点を一本の設計に収めて、本番運用に耐える親子間データ共有を組み立てる実装ノートです。
開発ツール2026-06-02
iOS 実機の Release ビルドで『No bundle URL present』が出るときの原因と対処
Rork から書き出したアプリを Xcode でローカルビルドしたときに出やすい『No bundle URL present』エラーの読み解き方をまとめました。Metro 接続が切れている場合と、Release ビルドに JS バンドルが埋め込まれていない場合を切り分け、オフラインバンドルを手動生成する手順までコード付きで解説します。
開発ツール2026-05-28
Rork アプリで iOS の BGTaskScheduler.submit が Error Code=1 で失敗するときの切り分け手順
Rork で作った iOS アプリで BGTaskScheduler.submit が Error Code=1 (Unavailable) を返してバックグラウンド更新が動かないときに、原因をひとつずつ潰していくための実用チェックリストです。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →