◉RORK LABEN
●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…
記事一覧/開発ツール
⬡ 開発ツール/2026-05-23中級

Rorkでexpo-hapticsが本番ビルドだけ無反応 — シミュレータ・実機・Low Power Modeの落とし穴

Rorkで生成したアプリで、ボタンタップ時のハプティクスがシミュレータでは効くのにTestFlight版だけ反応しない、片方のプラットフォームだけ鳴らない、突然鳴らなくなる――10本以上の壁紙系アプリで遭遇した原因と、Expo Hapticsの正しい呼び出しパターンを整理しました。

expo-hapticsiOS115Android55本番ビルドトラブルシューティング85

Rork で生成したアプリの「お気に入りに追加」ボタンを押したとき、Expo Go では指先に小さな振動が返ってくるのに、TestFlight に上げた瞬間まったく無反応になる――先週もまさにこのパターンで半日詰まりました。

私は2014年から壁紙系・癒し系のアプリを個人開発で運営していて、AdMob の eCPM 計測ついでにユーザー行動を眺めていると、ハプティクスが効いているアプリと効いていないアプリでは平均セッション時間が体感で1〜2割違います。たった数十ミリ秒の触覚フィードバックなのですが、無いと「タップした感」が消えてしまうので、本番で無反応のまま気付かずリリースしてしまうと、レビュースコアにじわじわ効いてくる地味に怖い不具合です。

expo-haptics 自体のコード量はごく少ないのですが、それゆえに「動かない理由が全部外側にある」のがやっかいなところ。原因を5つに分けて、上から順に潰していくと多くのケースで直ります。

まず切り分けるべき3つの観点

調査に入る前に、以下を確認します。これだけで原因の半分は絞れます。

  • 「実機の iPhone で物理的に振動しない」のか「振動はしているが弱くて気付かない」のか
  • iOS だけ無反応か、Android だけ無反応か、両方無反応か
  • Expo Go では動くのに EAS Build や TestFlight でだけ無反応か

シミュレータでは仕様上ハプティクスは絶対に発火しません。「シミュレータで動かない=バグ」ではないので、必ず実機で確認してください。私自身、新しい M シリーズの Mac に乗り換えた直後、シミュレータでデバッグばかりしていてここで時間を溶かしました。

原因1: iPhone 側のシステム設定でハプティクスが無効

最初に疑うべきは、ユーザー(または開発者自身)の iPhone 設定です。設定 → サウンドと触覚 → システムの触覚 がオフになっていると、expo-haptics の呼び出しは内部的にエラーを返さず黙ってスキップされます。

これは仕様で、Apple の HIG 上「触覚は OS 設定を尊重する」と書かれているため、アプリ側で強制発火させる正規 API はありません。本番でユーザーから「振動しない」報告が来たときは、まずこの設定を疑うようガイドする必要があります。

App Store のレビューで「振動しません」と書かれた星3を見て焦りましたが、原因はユーザーが触覚オフにしていただけ、というケースが体感で半数以上を占めます。アプリ内のヘルプに「触覚は iOS 設定の影響を受けます」と一文添えるだけでだいぶ減ります。

原因2: Low Power Mode で触覚エンジンが落ちる

iOS の Low Power Mode(低電力モード)に入っていると、Taptic Engine が省電力のために制限されます。Haptics.impactAsync を呼んでも音だけ鳴って振動しない、あるいはまったく無反応というケースが普通にあります。

これは Apple の公式仕様で、ドキュメントには明記が薄いものの、UIDevice.current.isLowPowerModeEnabled で取得できる状態と連動しています。React Native 側からは expo-battery で同等の判定ができます。

import * as Haptics from 'expo-haptics';
import * as Battery from 'expo-battery';
 
async function softTap() {
  const isLowPower = await Battery.isLowPowerModeEnabledAsync();
  if (isLowPower) {
    // 期待する出力: 触覚は省略、視覚フィードバックだけで代替する
    return;
  }
  await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
}

UI 側で軽いカラーアニメーションを併走させておくと、低電力時でも「タップした感」が失われません。私が運営している壁紙アプリでは、画像保存成功時に必ず色変化+触覚のセットで返すようにしていて、低電力モードでも違和感ゼロにできています。

原因3: Android で expo-haptics を直接呼んでいる(VIBRATE 権限の罠)

Android では expo-haptics 内部で VIBRATE 権限を使います。Expo SDK 50 以降のデフォルト設定では権限が自動付与されますが、app.json で android.permissions を手動で絞っていると VIBRATE が落ちることがあります。

app.json を以下のように明示します。

{
  "expo": {
    "android": {
      "permissions": ["VIBRATE"]
    }
  }
}

明示的に他の権限を列挙しているのに VIBRATE を書き忘れているケースが多いので、adb shell dumpsys package <package-name> | grep VIBRATE で実機上の権限を確認します。プレビュービルドで動いていても、本番ビルドで権限プロファイルが差し変わると無反応になるパターンがありますので注意が必要です。

原因4: イベントハンドラ内で await し損ねている

Rork が生成してくる定型コードでよく見るのが、onPress 内で await せずに Haptics.impactAsync を呼び出して、その直後に画面遷移してしまうパターンです。

// ❌ 触覚が間に合わずに画面遷移してしまう
<Pressable onPress={() => {
  Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
  router.push('/detail');
}}>

impactAsync は Promise を返すので、画面遷移が先に走ると遷移中の Activity がフォアグラウンドから外れて触覚自体がキャンセルされる挙動になります。

// ✅ 触覚を発火してから遷移する
<Pressable onPress={async () => {
  await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
  router.push('/detail');
}}>

体感はわずか数十ミリ秒の差ですが、await の有無で「タップした感」が劇的に変わります。実装では「触覚→遷移」の順を守ることを徹底すると、本番で無反応に見える事象がほぼ消えます。

原因5: 連打されて触覚エンジンが詰まる

ハプティクスは内部キューを持っていて、短時間に連続発火させるとキューが詰まって以降の呼び出しが無視されます。スワイプで連続インタラクションを起こすカード型 UI などで、ユーザーが速くスワイプしたあと数秒間ハプティクスが止まる、という症状が出ます。

簡易的なスロットルで回避できます。

import * as Haptics from 'expo-haptics';
 
let lastHapticAt = 0;
const HAPTIC_MIN_INTERVAL = 80; // ミリ秒
 
export async function throttledLightImpact() {
  const now = Date.now();
  if (now - lastHapticAt < HAPTIC_MIN_INTERVAL) return;
  lastHapticAt = now;
  await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
}

私の壁紙アプリでは、サムネをスワイプで切り替えるたびに発火するハプティクスがこれを入れる前は1秒間に8回くらい鳴ろうとして詰まっていました。80ms スロットルにすると、体感はむしろ「キレが良くなった」と感じられるくらいに改善しました。

本番リリース前に確認したいチェックリスト

ここまでの内容を本番チェックリストに落とすと次のようになります。

  • 実機(iOS/Android 両方)で物理的に振動するか
  • iOS のシステム触覚設定オン/オフ両方で意図通りの挙動か
  • Low Power Mode 時にフォールバック動作するか
  • 連打時に詰まらないか(80〜100ms スロットル推奨)
  • await で発火を待ってから遷移しているか

私の場合、TestFlight に上げた直後の Internal Testing で必ずこの5点を確認するワークフローにしてから、ハプティクス起因のレビュー指摘がほぼゼロになりました。地味な部分ですが、ここを丁寧にやるかどうかでアプリ全体の「触り心地」がはっきり変わります。

最後までお読みいただきありがとうございました。皆さんのアプリの触り心地が、少しでも気持ちよくなるきっかけになれば嬉しいです。

シェア

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

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

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

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

関連記事

⬡ 開発ツール2026-10-07
日本語の見出しだけ太字にならない — Android で fontWeight が効かないときの切り分けと直し方
Rork で作ったアプリで、iPhone では太字なのに Android では日本語の見出しが細いままになる症状の切り分け記事です。原因の見分け方、fontFamily を重みごとに分ける直し方、確認用コードを載せています。
⬡ 開発ツール2026-09-21
exit code 0 で止まるビルド — ログを黙らせていた設定と、空ファイルを通した存在チェック
eas build --local が exit code 0 but produced no further output で止まる件と、0 バイトのスタブが埋め込まれず dyld で落ちる件。どちらも原因は出力を減らす設定でした。自分のスクリプトを棚卸しする手順まで書き残します。
⬡ 開発ツール2026-09-10
画面を暗くする機能を足したら、アプリを閉じたあとも iPhone が暗いままでした
expo-brightness の setBrightnessAsync は、Android では現在のアクティビティにだけ効き、iOS では端末の明るさそのものを変えます。戻す責任が片側にしかない理由と、AppState で後始末をするフックの実装をまとめました。