– Model:データ構造(ToDoアイテム)
– ViewModel:データ管理・ロジック
– View:UI(ユーザーが触れる部分)
ディレクトリ構成
MVVMToDoApp/
├── Models/
│ └── Todo.swift
├── ViewModels/
│ └── TodoViewModel.swift
└── Views/
└── ContentView.swift
### Model
Todo.swift
import Foundation
struct Todo: Identifiable, Codable {
let id = UUID()
var title: String
var isCompleted: Bool = false
}
### View
//
// ContentView.swift
// CoreDataTest
//
// Created by mac on 2025/10/25.
//
import SwiftUI
struct ContentView: View {
@StateObject private var viewModel = TodoViewModel()
@State private var newTodoTitle = ""
var body: some View {
NavigationView {
VStack {
HStack {
TextField("新しいタスクを入力", text: $newTodoTitle)
.textFieldStyle(RoundedBorderTextFieldStyle())
Button(action: {
viewModel.addTodo(title: newTodoTitle)
newTodoTitle = ""
}) {
Image(systemName: "plus.circle.fill")
.font(.title2)
}
}
.padding()
List {
ForEach(viewModel.todos) { todo in
HStack {
Image(systemName: todo.isCompleted ? "checkmark.circle.fill" : "circle")
.foregroundColor(todo.isCompleted ? .green : .gray)
.onTapGesture {
viewModel.toggleCompletion(for: todo)
}
Text(todo.title)
.strikethrough(todo.isCompleted)
}
}
.onDelete(perform: viewModel.deleteTodo)
}
}
.navigationTitle("ToDoリスト")
}
}
}
### ViewModels
import Foundation
class TodoViewModel: ObservableObject {
@Published var todos: [Todo] = []
func addTodo(title: String) {
guard !title.isEmpty else { return }
let newTodo = Todo(title: title)
todos.append(newTodo)
}
func toggleCompletion(for todo: Todo) {
if let index = todos.firstIndex(where: { $0.id == todo.id }) {
todos[index].isCompleted.toggle()
}
}
func deleteTodo(at offsets: IndexSet) {
todos.remove(atOffsets: offsets)
}
}
main
import SwiftUI
@main
struct MVVMToDoApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

なるほど〜