swift　Todoリスト

//ToDoリストアプリ

```
import SwiftUI

// メインのビューを定義
struct ContentView: View {
    @State private var todos: [Todo] = [] // Todoの配列を保持する状態変数
    @State private var newTodoText: String = "" // 新しいTodoのテキストを保持する状態変数
    @State private var showingInputView = false // 入力ビューを表示するかどうかを制御する状態変数
    @State private var editMode: EditMode = .inactive // 編集モードを管理する状態変数

    var body: some View {
        NavigationView { // ナビゲーションを提供するビュー
            VStack { // 垂直方向にビューを配置
                List { // Todoのリストを表示
                    if todos.isEmpty { // タスクがない場合の処理
                        Text("タスクがありません") // タスクがないことを示すメッセージ
                            .foregroundColor(.gray) // メッセージの色を設定
                    } else { // タスクがある場合の処理
                        ForEach($todos) { $todo in // todos配列を反復処理
                            HStack { // 水平方向にビューを配置
                                Button(action: { // チェックボタンのアクション
                                    todo.isChecked.toggle() // チェック状態を切り替える
                                }) {
                                    Image(systemName: todo.isChecked ? "checkmark.circle.fill" : "circle") // チェック状態に応じたアイコンを表示
                                        .foregroundColor(todo.isChecked ? .pink.opacity(0.3) : .gray) // チェック状態に応じた色を設定
                                        .padding(.trailing, 5) // アイコンの右側にパディングを追加
                                }
                                Text(todo.text) // Todoのテキストを表示
                                    .foregroundColor(todo.textColor) // タスクの文字色を設定
                            }
                        }
                    }
                }
                .navigationTitle("Todo List") // ナビゲーションバーのタイトルを設定
                .environment(\.editMode, $editMode) // 編集モードを環境変数として設定
                .foregroundColor(Color.black.opacity(0.9)) // リストのテキスト色を設定
            }
            .overlay( // ビューの上に重ねる要素を追加
                VStack {
                    Spacer() // 上部にスペースを追加
                    HStack {
                        Button(action: { // ゴミ箱ボタンのアクション
                            deleteCheckedTodos() // チェック済みのタスクを削除
                        }) {
                            Image(systemName: "trash") // ゴミ箱アイコンを表示
                                .resizable()
                                .frame(width: 30, height: 30) // アイコンのサイズを設定
                                .foregroundColor(Color.pink.opacity(0.3)) // アイコンの色を設定
                                .padding() // アイコンの周囲にパディングを追加
                                .background(Color.white) // アイコンの背景色を設定
                                .clipShape(Circle()) // アイコンを円形にクリップ
                                .shadow(radius: 1) // アイコンに影を追加
                        }
                        .padding()
                        Spacer() // 中央にスペースを追加
                        Button(action: { // プラスボタンのアクション
                            if todos.count < 15 { // タスクが15個未満の場合
                                showingInputView = true // 入力ビューを表示する
                            } else {
                                print("タスクは15個までしか追加できません") // タスク制限の通知
                            }
                        }) {
                            Image(systemName: "plus.circle.fill") // プラスアイコンを表示
                                .resizable()
                                .frame(width: 60, height: 60) // アイコンのサイズを設定
                                .foregroundColor(Color.pink.opacity(0.3)) // アイコンの色を設定
                                .padding() // アイコンの周囲にパディングを追加
                        }
                    }
                }
            )
            .sheet(isPresented: $showingInputView) { // モーダルシートを表示
                InputView(isPresented: $showingInputView, todos: $todos) // 入力ビューを表示
                    .presentationDetents([.fraction(1/3)]) // モーダルシートの高さを画面の3分の1に設定
            }
        }
    }

    private func deleteCheckedTodos() { // チェック済みのTodoを削除する関数
        todos.removeAll { $0.isChecked } // チェック済みのTodoをすべて削除
    }
}

// タスク追加用のビューを定義
struct InputView: View {
    @Binding var isPresented: Bool // 親ビューからの表示状態をバインディング
    @Binding var todos: [Todo] // 親ビューからのTodo配列をバインディング
    @State private var inputText = "" // 入力されたテキストを保持する状態変数
    @State private var selectedColor: Color = .black // デフォルトの文字色

    var body: some View {
        VStack { // 垂直方向にビューを配置
            TextField("新しいタスクを入力", text: $inputText) // テキストフィールドを表示
                .textFieldStyle(RoundedBorderTextFieldStyle()) // テキストフィールドのスタイルを設定
                .padding() // テキストフィールドの周囲にパディングを追加

            // 色選択ボタン
            HStack {
                ForEach([Color.black.opacity(0.6),Color.white.opacity(0.6), Color.green.opacity(0.6),Color.blue.opacity(0.6), Color.orange.opacity(0.6)], id: \.self) { color in
                    Button(action: {
                        selectedColor = color // 選択された色を設定
                    }) {
                        Circle()
                            .fill(color) // ボタンの色を設定
                            .frame(width: 30, height: 30) // ボタンのサイズを設定
                            .overlay(
                                Circle()
                                    .stroke(selectedColor == color ? Color.gray : Color.clear, lineWidth: 2) // 選択状態の表示
                            )
                    }
                }
            }
            .padding()

            Button("追加") { // 追加ボタン
                if todos.count < 15 { // タスクが15個未満の場合
                    let newTodo = Todo(text: inputText, isChecked: false, textColor: selectedColor) // 新しいTodoを作成
                    todos.append(newTodo) // Todo配列に追加
                    inputText = "" // 入力フィールドをクリア
                    isPresented = false // 入力ビューを閉じる
                } else {
                    print("タスクは15個までしか追加できません") // タスク制限の通知
                }
            }
            .padding()
            .background(Color.white.opacity(0.3)) // ボタンの背景色を設定
            .foregroundColor(.white) // ボタンのテキスト色を設定
            .cornerRadius(8) // ボタンの角を丸める
        }
        .navigationTitle("新しいタスク") // ナビゲーションバーのタイトルを設定
        .navigationBarItems(leading: Button("キャンセル") { // キャンセルボタン
            isPresented = false // 入力ビューを閉じる
        })
    }
}

// Todoのデータモデルを定義
struct Todo: Identifiable {
    let id = UUID() // 一意の識別子
    var text: String // Todoのテキスト
    var isChecked: Bool // チェック状態を保持するフラグ
    var textColor: Color // 文字色を保持するプロパティ
}

// プレビュー用のビューを定義
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView() // プレビュー用のContentViewを表示
    }
}
```