🐥

ネイティブアプリ開発日記(4/100)入力機能の改善

に公開

前回はこちら
https://zenn.dev/nabeken/articles/b9982ad50e832f

■前回作成した家計簿アプリの機能
●支出の金額、カテゴリ、年月日を登録できる ←今回の改善対象
●登録した支出の一覧を参照できる
●月毎の支出の合計を参照できる

■今回の改善
支出の金額、カテゴリ、年月日を登録する際の入力機能を改善する。
具体的には、

  • 年月日
    • 自由記述ではなくカレンダーから選択できるようにする。デフォルトで現在年月日が入る
  • 金額
    • 自由記述ではなくテンキーを用いて入力できるようにする
  • カテゴリ
    • 自由記述ではなく選択肢から選択できるようにする

以下のイメージ
入力欄全体

年月日の選択

金額の入力

カテゴリの選択

実装面の学び1. カレンダー入力

  • カレンダーの表示
    • @react-native-community/datetimepickerの活用
    • DateTimePicker で display='spinner'を選択
  • 表示/非表示の切り替え
    • const [showPicker, setShowPicker] = useState(false) で状態を定義
    • 「日付を選択」ボタンを押下すると表示
      • <Button title='日付を選択' onPress={() => setShowPicker(true)} />
    • 日付を選択し「OK」を押下すると非表示
      • <TouchableOpacity onPress={() => setShowPicker(false)}>
      • TouchableOpacity
        • タップしたときに 透明度(opacity)を下げて、押されたことを視覚的に示す
AddExpenseScreen.js
import  DateTimePicker from '@react-native-community/datetimepicker';
const [showPicker, setShowPicker] = useState(false); 
(略)
<Button title='日付を選択' onPress={() => setShowPicker(true)} />
{showPicker && (
    <View>
        <DateTimePicker
            value={date}
            mode="date"
            display='spinner'
            onChange={(date) => setDate(date)}
        />
        {Platform.OS === 'ios' && (
            <TouchableOpacity onPress={() => setShowPicker(false)}>
                <Text>OK</Text>
            </TouchableOpacity>
        )}
    </View>
)}

実装面の学び2. テンキー入力

  • 支出金額の管理
    • const [amount, setAmount] = useState('')で状態を定義
  • テンキー入力
    • TouchableOpacityを用いて、愚直に1,2,...,9,0を表示
    • key={digit} onPress={() => handlePress(digit)} で押下を検出
    • handlePressは setAmount((prev) => prev + digit) で状態を更新
  • 入力した数値の修正
    • TouchableOpacity onPress={handleDelete} で押下を検出
    • handleDeleteは setAmount((prev) => prev.slice(0, -1)) で状態を更新
  • 入力した数値の確定
    • TouchableOpacity onPress={() => onConfirm(Number(amount))} で押下を検出
    • onConfirm は呼び出し元(親)から渡される関数(後述)
utils/numberInput.js
export default function AmountInputPad({ onConfirm }) {
    const [amount, setAmount] = useState(''); 

    // 数字ボタン(0〜9)が押されたとき、金額の文字列にその数字を追加する。最大9桁とした
    const handlePress = (digit) => {
        if (amount.length < 9) {
            setAmount((prev) => prev + digit);
        }
    };

    // 削除ボタンが押された時、一番右の一桁を削除する
    const handleDelete = () => {
        setAmount((prev) => prev.slice(0, -1));
    };

   // onConfirm は親(呼び出し元)で指定するコールバック関数
    return (
        <View>
          <Text>¥{amount || '0'}</Text>
          <View>
            {['1','2', '3', '4', '5', '6', '7', '8', '9', '0'].map((digit) => (
                <TouchableOpacity key={digit} onPress={() => handlePress(digit)}>
                    <Text>{digit}</Text>
                </TouchableOpacity>
            ))}

            <TouchableOpacity onPress={handleDelete}>
                <Text></Text>
            </TouchableOpacity>

            <TouchableOpacity onPress={() => onConfirm(Number(amount))}>
                <Text>OK</Text>
            </TouchableOpacity>
          </View>
        </View>
    );
}
  • 親(呼び出し元)
    • const [amount, setAmount] = useState('') で状態を定義
    • AmountInputPad(子)を呼び出し、onConfirmとして状態の更新関数を渡す
AddExpenseScreen.js
const [amount, setAmount] = useState('');
(略)
<AmountInputPad onConfirm={(value) => setAmount(value)} />

実装面の学び3. 選択肢から選択

  • リストから選択するUI
    • @react-native-picker/picker の活用
    • Picker タグ内で、Picker.Itemで一つずつ表示
      • {リスト.map((要素) => (<Picker.Item key={要素.value} /> ))}
  • 選択した値で更新する処理
    • const [category, setCategory] = useState('-') で状態を定義
    • onValueChange={(itemValue) => setCategory(itemValue)} で更新
AddExpenseScreen.js
import { Picker } from '@react-native-picker/picker';
import { categories } from '../parameters/categories';
(略) 
const [category, setCategory] = useState('-');
(略) 
 <View>
    <Picker
        selectedValue={category}
        onValueChange={(itemValue) => setCategory(itemValue)}
    >
        {categories.map((cat) => (
            <Picker.Item key={cat.value} label={cat.label} value={cat.value} />
        ))}
    </Picker>
</View>
  • 選択肢の外部定義
parameters/categories.js
export const categories = [
    { label: '-', value: '-' }, // 未入力用
    { label: '食費', value: '食費' },
    { label: '交通費', value: '交通費' },
    (略)
]

Discussion