Widgets と Live Activities は、ユーザーがわざわざアプリを開くよりも手間がかかる場面を減らすために最適です。配達の到着予想、ワークアウトタイマー、フライトステータス、次のミーティング、保存したショートカット――こうした情報はユーザーが別の作業をしている最中に欲しいものです。ウィジェットと Live Activities は、ユーザーにアプリを開くことを要求せずにアプリを有用に保ちます。
React Native アプリからこれらのサーフェスを構築するには従来、別の Xcode ターゲット、App Groups、SwiftUI のレイアウトコード、そしてその拡張をアプリ本体と同期し続ける負担が伴っていました。SDK 56 からは、expo-widgets が安定版になりました。Expo UI を使って React コンポーネントとしてウィジェットと Live Activities を構築できます。Continuous Native Generation が Widget Extension ターゲット、App Group の設定、SwiftUI のスキャフォールディングを処理するので、別個の iOS プロジェクトを維持する必要はありません。
これは SDK 55 でアルファ版として出荷した同じライブラリで、耐久性を高めて安定化したものです。コアの考え方は変わっていません:ウィジェットを React で書き、システムに SwiftUI 経由でネイティブにレンダリングさせ、更新は Expo アプリから管理します。SDK 56 で変わったのは、安定化の保証と実用的な改善点のセットです:ウィジェットと Live Activities は環境情報を直接受け取るようになり、メインアプリで事前レンダリングする必要がなくなり、エラー処理も改善されました。
なぜウィジェットと Live Activities を作る価値があるのか
目的は UI の完全性ではなく、サーフェスの拡張です。アプリアイコンはアプリへの一回のタップです。ウィジェットはゼロタップで、ユーザーが別の作業をしている間にも価値を提供できます。Live Activity は進行中のイベント中に常に表示され、状況が変わるたびに更新され、デバイスがロックされていても有効です。これらのサーフェスは、フルセッションが不要な時に関連情報を近くに保つことでアプリを補完します。
Flighty は良い公開事例です。Interaction 部門で 2023 Apple Design Award を受賞し、Apple の解説でもここで重要な点が挙げられています:Live Activities、Dynamic Island 連携、ウィジェット対応、および旅行者にとって必要な主要なフライト情報を適切な場所に配置している点です。フライトトラッカーにとって、これはプロダクト体験そのものです。旅行者はゲート変更、搭乗状況、到着時間を繰り返しアプリを開かずに知りたいと考えています。Flighty はそれらの情報をロック画面と Dynamic Island に置き、実際に旅が進行しているときに利用できるようにしています。
実運用で勝つパターン:
- ウィジェット:ユーザーが手元に置いておきたい情報(カレンダー、天気、習慣の連続記録、残高など)
- Live Activities:開始と終了が明確な「進行中」のイベント(配達の ETA、ワークアウトタイマー、ライドシェア、進行中のフライトなど)
どちらもユーザーがアプリ内に積極的にいる時間外で、アプリの関連性を保つことができます。
OneSignal の 2024 State of Customer Engagement Report(やや古いがデータは具体的)によれば、iOS Live Activities を利用しているアプリは平均 30 日保持率が 23.7% 高いと報告されています。Live Activity を追加すれば自動的に保持率が上がるという保証ではありませんが、同じプロダクトパターンを示唆しています:適切な場所で適時に情報を表示することは、アプリに戻ってくるハードルを下げ得ます。
SDK 56 で何が安定したか
アルファは SDK 55(3月)で公開されていました。それ以降、コミュニティでのテストと内部の反復によって、安定版と呼ぶ前に解消すべき粗さが明らかになりました。主な変更点:
- Widgets と Live Activities は
WidgetEnvironment を通じて完全な環境を受け取るようになりました。これには widget family、content margins、color scheme、rendering mode、presentation や accessibility のコンテキストなどが含まれ、ウィジェットがどのようにどこに表示されるかに応じて適応できます。
- ウィジェットと Live Activities はもはやアプリ側で事前レンダリングする必要がありません。アプリの React ツリーを起動時に走査してウィジェットレイアウトを準備する代わりに、ウィジェットバンドルは現在のサーフェスに必要なコンテキストで独立してレンダリングできます。
- 設定可能なウィジェット(configurable widgets)をサポートし、ウィジェットギャラリー上でユーザーがウィジェットの表示内容を選べるようになりました。従来のようにすべての選択をアプリ側で行う必要はありません。
最初のウィジェットを作る
ライブラリをインストールして prebuild を実行した後、最小のウィジェットは次のようになります(そのままのコード):
import { Button, Text, VStack } from '@expo/ui/swift-ui';
import { font, foregroundStyle } from '@expo/ui/swift-ui/modifiers';
import { createWidget, type WidgetEnvironment } from 'expo-widgets';
type Props = { count: number };
const CoffeeCounter = (props: Props, environment: WidgetEnvironment) => {
'widget';
return (
<VStack spacing={8}>
<Text modifiers={[font({ size: 48 })]}>☕</Text>
<Text modifiers={[font({ size: 32, weight: 'bold' })]}>
{props.count}
</Text>
<Button modifiers={[foregroundStyle('white')]} label="+" target="increment" onPress={() => ({ count: props.count + 1 })} />
</VStack>
);
};
export default createWidget('CoffeeCounter', CoffeeCounter);
関数本体をウィジェットレンダーコンテキストとしてマークするのが "widget" ディレクティブです。これはメインアプリとは別のサンドボックス化された JS ランタイムで実行されます。Expo UI のコンポーネントは SwiftUI のプリミティブに直接マッピングされるので、システムはウィジェットをネイティブにレンダリングしつつ、ウィジェットのロジックは TypeScript 側に残ります。
Live Activities でも同様のモデルが適用されますが、複数のスロット(ロック画面バナー、Dynamic Island の compact/expanded/minimal など)を記述し、システムがコンテキストに応じて適切なスロットを選びます。更新はサーバー経由の APNs でプッシュするか、アプリから直接トリガーできます。
ウィジェット vs Live Activity:どう決めるか
概要は前述で十分ですが、実装上のトレードオフは異なります。まず共有したい情報を定義してください。
- ウィジェットの場合:アプリ外に恒久的に置くに値する情報は何か、どの頻度で更新する必要があるか、どのウィジェットサイズが有用か、タップ時に何が起きるべきかを決めます。ベストなウィジェットは通常、ひとつの明確な役割を持ちます。
- Live Activity の場合:ライフサイクルを最初に定義します。何がアクティビティを開始するのか?アクティビティ中に何が変化し得るのか?終了時にどの最終状態を表示すべきか?更新はアプリ側から来るのか、サーバーから来るのか、あるいは両方か?
Live Activities はプッシュトークン、サーバーサイドの更新ロジック、状態遷移、解散ハンドリングを含むため、これらの質問は重要です。両方を検討しているなら、ウィジェットから始めるのが現実的なことが多いです。可動部分が少なく、レンダリングモデルに慣れる実用的な手段になります。そのワークフローに慣れたら、開始・更新・終了が明確な製品部分に対して Live Activity を追加してください。
expo-widgets を始めるには
- expo-widgets をインストールし、widgets reference に従って最初のウィジェットをスキャフォールドしてください。
- ユーザーがアプリ外で見て恩恵を受ける一つの情報を選び、そのウィジェットを最初に出荷しましょう。
- アプリに自然な進行中の瞬間があるなら、そのフローのために Live Activity をプロトタイプしてください。
もし実運用での導入や保持・エンゲージメントに関するデータ共有、または次に何を出すべきかのアイデアがあれば、Discord や GitHub で教えてください。実際の数値は、今後の優先順位を決めるうえで特に重要です。