---
name: ios-polish
description: iOSネイティブの操作感に近づけるUI仕上げを行う。ジェスチャー、アニメーション、ブラーエフェクト、ダークモード対応などを実装する。「iOSっぽくして」「操作感を良くして」「アニメーション追加」「ダークモード対応」などのリクエストで使用する。
allowed-tools: Read, Write, Edit, Glob, Grep, Bash
argument-hint: [対象画面名 or 機能名]
---
# iOS 仕上げスキル
iPhoneユーザーが「ネイティブアプリのように感じる」操作感を実現するための仕上げを行う。
## デザイン前提: Liquid Glass
本プロジェクトは Apple iOS 26 の **Liquid Glass** デザインを前提とする。
`@callstack/liquid-glass` ライブラリを使用し、ガラス質感のUIを実現する。
### ライブラリ要件
- `@callstack/liquid-glass` — Liquid Glass コンポーネント
- Xcode >= 26, React Native 0.80+
- **Expo Go では動作しない**(Development Build が必要)
### コアコンポーネント
**LiquidGlassView** — ガラスエフェクトを持つ View:
```tsx
import { LiquidGlassView, isLiquidGlassSupported } from '@callstack/liquid-glass';
コンテンツ
```
**LiquidGlassContainerView** — 複数ガラス要素のマージ:
```tsx
import { LiquidGlassContainerView, LiquidGlassView } from '@callstack/liquid-glass';
// タブバーのように近接するガラス要素をグループ化
```
### 適用箇所ガイド
| UI要素 | effect | interactive | 備考 |
|--------|--------|-------------|------|
| カード(広場カード、投稿カード) | `regular` | `true` | タップ可能なカードに最適 |
| ツールバー / ナビゲーションバー | `regular` | `false` | 背景ブラーのみ |
| タブバー | `regular` | `true` | `LiquidGlassContainerView` でグループ化 |
| モーダル / ボトムシート | `clear` | `false` | より透明なガラスで背景を透過 |
| ボタン(プライマリ) | `regular` | `true` | `tintColor` でブランドカラーを適用 |
| フローティングアクションボタン | `regular` | `true` | 丸いガラスボタン |
| 装飾・背景 | — | — | **ガラスを使わない**(全画面ガラスはNG) |
### フォールバックパターン
```tsx
import { LiquidGlassView, isLiquidGlassSupported } from '@callstack/liquid-glass';
const GlassCard: React.FC<{ children: React.ReactNode }> = ({ children }) => {
if (isLiquidGlassSupported) {
return (
{children}
);
}
// iOS 26未満 / Android フォールバック
return (
{children}
);
};
const styles = StyleSheet.create({
card: {
borderRadius: 20,
padding: 16,
},
cardFallback: {
backgroundColor: 'rgba(255, 255, 255, 0.85)',
// 既存デザイントークンの shadow.soft を適用
},
});
```
### テキスト色のガイドライン
```tsx
import { PlatformColor } from 'react-native';
// ガラス上のテキスト(iOS 26で自動適応)
const glassTextStyle = {
color: PlatformColor('labelColor'), // メインテキスト
// or
color: PlatformColor('secondaryLabelColor'), // サブテキスト
};
// フォールバック(PlatformColor非対応環境)
const fallbackTextStyle = {
color: '#1B1B1D', // color.ink from tokens.ts
};
```
## 対象ファイル
- `src/screens/**/*.tsx` — 画面コンポーネント
- `src/components/**/*.tsx` — UIコンポーネント
- `src/theme/tokens.ts` — デザイントークン
- `src/App.tsx` — ナビゲーション
## カテゴリ別 実装手順
---
### 1. プレスフィードバック(ボタンのアニメーション)
すべてのインタラクティブ要素にプレス時の視覚的フィードバックを追加する。
**Pressable の style 関数パターン:**
```tsx
[
styles.button,
{
opacity: pressed ? 0.7 : 1,
transform: [{ scale: pressed ? 0.97 : 1 }],
},
]}
>
```
**カードのプレスフィードバック:**
```tsx
[
styles.card,
{
opacity: pressed ? 0.85 : 1,
transform: [{ scale: pressed ? 0.98 : 1 }],
},
]}
>
```
---
### 2. iOS標準アイコンへの置き換え
絵文字アイコンを `@expo/vector-icons` の Ionicons(iOS標準に最も近い)に置き換える。
**インストール確認:**
```bash
# @expo/vector-icons は Expo に同梱済み。追加インストール不要
```
**置き換えマッピング:**
| 現在(絵文字) | 置き換え先 | Ionicons名 |
|---------------|-----------|------------|
| ← (戻る) | `` | `chevron-back` |
| ••• (設定) | `` | `ellipsis-horizontal` |
| + (追加) | `` | `add` |
| 🏠 (ホーム) | `` / `home-outline` | `home` / `home-outline` |
| 🔔 (通知) | `` / `notifications-outline` | `notifications` |
| ⚙️ (設定) | `` / `settings-outline` | `settings` |
| 📷 (カメラ) | `` / `camera-outline` | `camera` |
| 👍 (いいね) | `` / `heart-outline` | `heart` |
**使用パターン:**
```tsx
import { Ionicons } from '@expo/vector-icons';
```
**タブバーでの使い分け(選択/非選択):**
```tsx
```
---
### 3. ナビゲーションのジェスチャー対応
#### スワイプバック(推奨: react-native-gesture-handler)
現在の状態ベースルーターでスワイプバックを実装する場合:
```tsx
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
// 画面コンポーネントのルートに追加
const swipeBack = Gesture.Pan()
.activeOffsetX(20) // 右方向20px以上で発動
.onEnd((event) => {
if (event.translationX > 80) {
// 前の画面に戻る
onBack();
}
});
{/* 画面コンテンツ */}
```
#### プルトゥリフレッシュ
```tsx
import { RefreshControl } from 'react-native';
}
/>
```
---
### 4. 画面遷移アニメーション
Animated API を使った画面のフェードイン:
```tsx
import { useEffect, useRef } from 'react';
import { Animated } from 'react-native';
const fadeAnim = useRef(new Animated.Value(0)).current;
const slideAnim = useRef(new Animated.Value(20)).current;
useEffect(() => {
Animated.parallel([
Animated.timing(fadeAnim, {
toValue: 1,
duration: 300,
useNativeDriver: true,
}),
Animated.timing(slideAnim, {
toValue: 0,
duration: 300,
useNativeDriver: true,
}),
]).start();
}, []);
{/* コンテンツ */}
```
---
### 5. ロングプレスメニュー
投稿カードやリストアイテムにロングプレスアクションを追加:
```tsx
{
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy);
showActionSheet(); // or setMenuVisible(true)
}}
delayLongPress={500}
>
```
---
### 6. StatusBar の適切な管理
各画面の背景色に応じた StatusBar スタイル:
```tsx
import { StatusBar } from 'expo-status-bar';
// 明るい背景の画面
// 暗い/グラデーション背景の画面
// 自動判定(推奨)
```
---
### 7. タッチターゲットの最適化
iOS Human Interface Guidelines: 最低44x44ptのタッチ領域:
```tsx
```
---
### 8. ダークモード対応(オプション)
`useColorScheme` でシステムのダークモード設定を取得:
```tsx
import { useColorScheme } from 'react-native';
const colorScheme = useColorScheme(); // 'light' | 'dark'
const themeColors = {
background: colorScheme === 'dark' ? '#1B1B1D' : '#F7F7FB',
text: colorScheme === 'dark' ? '#F7F7FB' : '#1B1B1D',
card: colorScheme === 'dark' ? '#2C2C2E' : '#FFFFFF',
// ...
};
```
**注意:** ダークモード対応は `src/theme/tokens.ts` のデザイントークンと連携して行うこと。
## 適用優先度
1. **必須**: プレスフィードバック(全ボタン)、アイコン置き換え(絵文字→Ionicons)
2. **推奨**: StatusBar管理、タッチターゲット最適化、プルトゥリフレッシュ
3. **任意**: 画面遷移アニメーション、スワイプバック、ダークモード
## ルール
- React Native の `Animated` API を優先する(`react-native-reanimated` は必要な場合のみ)
- `useNativeDriver: true` を必ず設定する(JS スレッドをブロックしない)
- 既存のデザイントークン (`src/theme/tokens.ts`) の値を使う
- アニメーションの duration は 200-400ms の範囲(iOS標準に合わせる)
- 新規パッケージの追加は最小限にする
- 変更はUIの見た目を壊さない範囲で行う