☺️

ExpoでReact Native Calendarsを使ってみた

2024/12/20に公開

Expo Calendar App with Bun

ExpoでカレンダーのUIを作る方法を調べていたら、React Native Calendarsというライブラリを使用すればできることを知った。

こんな感じのものを作れる。

タイムゾーンを日本時間に合わせる設定が必要で、合わせていないと日付が1日ずれていた💦

こちらが完成品

環境構築

では早速作ってみよう。

1. 前提条件

  • Node.js がインストールされていること
  • bun がインストールされていること

2. プロジェクトの作成

# bunを使用してExpoプロジェクトを作成
bunx create-expo-app expo-calendar -t expo-template-blank-typescript

# 必要なパッケージのインストール
bun add react-native-calendars

日本時間対応

実装のポイント

// 日本時間で現在の日付を取得
const now = new Date();
const japanTime = now.getTime() + (9 * 60 * 60 * 1000); // UTC+9の調整
const today = new Date(japanTime);
const currentDate = today.toISOString().slice(0, 10); // YYYY-MM-DD形式

// 日付フォーマット関数
const formatDate = (date: Date) => {
    const year = date.getFullYear();
    const month = date.getMonth() + 1;
    const day = date.getDate();
    return `${year}${month}${day}`;
};

主な特徴

  • UTCからの時差計算による安定した日本時間の取得
  • カスタムフォーマット関数による確実な日本語表示
  • 日付の範囲エラー対策

SafeArea 対応

1. コンポーネントのインポート

import { SafeAreaView } from 'react-native';

2. 実装方法

const CalendarComponent = () => {
    return (
        <SafeAreaView style={styles.safeArea}>
            <View style={styles.container}>
                {/* カレンダーコンテンツ */}
            </View>
        </SafeAreaView>
    );
};

const styles = StyleSheet.create({
    safeArea: {
        flex: 1,
        backgroundColor: '#FFFFFF'  // SafeArea背景色
    },
    container: {
        flex: 1,
        padding: 20,
    }
});

主な特徴

  • iPhoneのノッチ部分への対応
  • 適切な余白の確保
  • 背景色の統一

カレンダーのカスタマイズ

1. 日本語表示

monthNames={['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']}
dayNames={['日', '月', '火', '水', '木', '金', '土']}

2. 曜日の色分け

theme={{
    'stylesheet.calendar.header': {
        dayTextAtIndex0: {
            color: '#FF0000'  // 日曜日
        },
        dayTextAtIndex6: {
            color: '#0000FF'  // 土曜日
        }
    }
}}
CalendarComponent.tsx
import React, { useState } from 'react';
import { View, Text, StyleSheet, SafeAreaView } from 'react-native';
import { Calendar } from 'react-native-calendars';

const CalendarComponent = () => {
    // 日本時間で現在の日付を取得(修正版)
    const now = new Date();
    const japanTime = now.getTime() + (9 * 60 * 60 * 1000); // UTC+9の調整
    const today = new Date(japanTime);
    const currentDate = today.toISOString().slice(0, 10); // YYYY-MM-DD形式

    // 選択された日付の状態管理
    const [selected, setSelected] = useState(currentDate);

    // 日付をフォーマットする関数
    const formatDate = (date: Date) => {
        const year = date.getFullYear();
        const month = date.getMonth() + 1;
        const day = date.getDate();
        return `${year}${month}${day}`;
    };

    return (
        <SafeAreaView style={styles.safeArea}>
            <View style={styles.container}>
                <Text style={styles.currentDate}>
                    現在の日付: {formatDate(today)}
                </Text>

                <Calendar
                    current={currentDate}
                    onDayPress={(day: { dateString: string }) => {
                        setSelected(day.dateString);
                    }}
                    onMonthChange={(month: {
                        dateString: string,
                        day: number,
                        month: number,
                        year: number,
                        timestamp: number
                    }) => {
                        console.log('月が変更されました:', month);
                    }}
                    markedDates={{
                        [selected]: {
                            selected: true,
                            selectedColor: '#0066CC',
                        },
                        [currentDate]: {
                            marked: true,
                            dotColor: '#FF0000',
                        }
                    }}
                    theme={{
                        todayTextColor: '#FF0000',
                        selectedDayBackgroundColor: '#0066CC',
                        arrowColor: '#0066CC',
                        monthTextColor: '#000000',
                        textMonthFontSize: 16,
                        textDayFontSize: 14,
                        'stylesheet.calendar.header': {
                            dayTextAtIndex0: {
                                color: '#FF0000'  // 日曜日の色
                            },
                            dayTextAtIndex6: {
                                color: '#0000FF'  // 土曜日の色
                            }
                        }
                    }}
                    monthNames={['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']}
                    dayNames={['日', '月', '火', '水', '木', '金', '土']}
                />
            </View>
        </SafeAreaView>
    );
};

const styles = StyleSheet.create({
    safeArea: {
        flex: 1,
        backgroundColor: '#FFFFFF'
    },
    container: {
        flex: 1,
        padding: 20,
    },
    currentDate: {
        fontSize: 18,
        marginBottom: 20,
        textAlign: 'center',
    },
});

export default CalendarComponent;

コンポーネントを読み込む。

App.tsx
import { View } from 'react-native';
import CalendarComponent from './CalendarComponent';

export default function App() {
  return (
    <View style={{ flex: 1 }}>
      <CalendarComponent />
    </View>
  );
}

ビルドすると上の画像と同じUIになるはず。

今後の改善点

  1. パフォーマンス最適化

    • メモ化によるレンダリング最適化
    • 不要な再レンダリングの防止
  2. 機能拡張

    • 祝日表示機能
    • イベント登録機能
    • 複数日選択機能
  3. UI/UX改善

    • ダークモード対応
    • アニメーション追加
    • アクセシビリティ対応

トラブルシューティング

よくある問題と解決方法

  1. 日付の範囲エラー

    • 原因: タイムゾーン設定の問題
    • 解決: 適切なUTC+9の時差調整を実装
  2. SafeAreaの表示問題

    • 原因: スタイリングの不適切な設定
    • 解決: 適切なflex設定とbackgroundColorの指定

Discussion