add native examples

This commit is contained in:
2026-02-26 21:24:20 -05:00
parent 4d9e715361
commit a6a76c90b9
28 changed files with 1720 additions and 0 deletions
@@ -0,0 +1,38 @@
import SwiftUI
struct CategoryPicker: View {
@Binding var selectedCategory: String
private let columns = [
GridItem(.adaptive(minimum: 90), spacing: 8)
]
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text("Category")
.font(.subheadline)
.fontWeight(.semibold)
LazyVGrid(columns: columns, spacing: 8) {
ForEach(todoCategories, id: \.self) { category in
let isSelected = selectedCategory == category
Button {
selectedCategory = category
} label: {
Text(category)
.font(.subheadline)
.frame(maxWidth: .infinity)
.padding(.vertical, 8)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(isSelected ? Color.accentColor : Color(.systemGray5))
)
.foregroundStyle(isSelected ? .white : .primary)
}
.buttonStyle(.plain)
}
}
}
}
}
@@ -0,0 +1,90 @@
import SwiftUI
struct ContentView: View {
@State private var viewModel = TodoViewModel()
@State private var showingAddSheet = false
@State private var editingTodo: Todo? = nil
var body: some View {
NavigationStack {
VStack(spacing: 0) {
// Filter bar
FilterBar(
currentFilter: viewModel.currentFilter,
countForFilter: { viewModel.count(for: $0) },
onFilterSelected: { viewModel.setFilter($0) }
)
Divider()
// Todo list or empty state
if viewModel.filteredTodos.isEmpty {
EmptyStateView()
} else {
List {
ForEach(viewModel.filteredTodos) { todo in
TodoItemView(
todo: todo,
onToggle: {
viewModel.toggleTodo(id: todo.id)
},
onTap: {
editingTodo = todo
}
)
.listRowInsets(EdgeInsets())
.listRowSeparator(.visible)
}
.onDelete { indexSet in
let filtered = viewModel.filteredTodos
for index in indexSet {
let todo = filtered[index]
viewModel.deleteTodo(id: todo.id)
}
}
}
.listStyle(.plain)
}
}
.navigationTitle("Todos \u{2013} SwiftUI")
.navigationBarTitleDisplayMode(.large)
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button {
showingAddSheet = true
} label: {
Image(systemName: "plus")
.fontWeight(.semibold)
}
}
}
.sheet(isPresented: $showingAddSheet) {
TodoFormView(
editingTodo: nil,
onSave: { title, category, priority in
viewModel.addTodo(title: title, category: category, priority: priority)
showingAddSheet = false
},
onCancel: {
showingAddSheet = false
}
)
.presentationDetents([.medium, .large])
}
.sheet(item: $editingTodo) { todo in
TodoFormView(
editingTodo: todo,
onSave: { title, category, priority in
viewModel.updateTodo(id: todo.id, title: title, category: category, priority: priority)
editingTodo = nil
},
onCancel: {
editingTodo = nil
}
)
.presentationDetents([.medium, .large])
}
}
.tint(Color(red: 0x62/255.0, green: 0x00/255.0, blue: 0xEE/255.0))
}
}
@@ -0,0 +1,17 @@
import SwiftUI
struct EmptyStateView: View {
var body: some View {
VStack(spacing: 16) {
Image(systemName: "checklist")
.font(.system(size: 64))
.foregroundStyle(Color(.systemGray3))
Text("No todos yet!\nTap + to add one")
.font(.title3)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
@@ -0,0 +1,35 @@
import SwiftUI
struct FilterBar: View {
let currentFilter: TodoFilter
let countForFilter: (TodoFilter) -> Int
let onFilterSelected: (TodoFilter) -> Void
var body: some View {
HStack(spacing: 8) {
ForEach(TodoFilter.allCases) { filter in
let isSelected = currentFilter == filter
let count = countForFilter(filter)
Button {
onFilterSelected(filter)
} label: {
Text("\(filter.displayName) (\(count))")
.font(.subheadline)
.fontWeight(isSelected ? .semibold : .regular)
.padding(.horizontal, 14)
.padding(.vertical, 8)
.background(
Capsule()
.fill(isSelected ? Color.accentColor : Color(.systemGray5))
)
.foregroundStyle(isSelected ? .white : .primary)
}
.buttonStyle(.plain)
}
Spacer()
}
.padding(.horizontal, 16)
.padding(.vertical, 8)
}
}
@@ -0,0 +1,51 @@
import SwiftUI
struct PriorityPicker: View {
@Binding var selectedPriority: Priority
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text("Priority")
.font(.subheadline)
.fontWeight(.semibold)
HStack(spacing: 8) {
ForEach(Priority.allCases) { priority in
let isSelected = selectedPriority == priority
Button {
selectedPriority = priority
} label: {
HStack(spacing: 6) {
Circle()
.fill(colorForPriority(priority))
.frame(width: 10, height: 10)
Text(priority.displayName)
.font(.subheadline)
}
.frame(maxWidth: .infinity)
.padding(.vertical, 10)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(isSelected ? colorForPriority(priority).opacity(0.15) : Color(.systemGray6))
)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(isSelected ? colorForPriority(priority) : Color.clear, lineWidth: 2)
)
.foregroundStyle(isSelected ? colorForPriority(priority) : .primary)
}
.buttonStyle(.plain)
}
}
}
}
private func colorForPriority(_ priority: Priority) -> Color {
switch priority {
case .low: return .green
case .medium: return .orange
case .high: return .red
}
}
}
@@ -0,0 +1,61 @@
import SwiftUI
struct TodoFormView: View {
let editingTodo: Todo?
let onSave: (String, String, Priority) -> Void
let onCancel: () -> Void
@State private var title: String = ""
@State private var selectedCategory: String = "Personal"
@State private var selectedPriority: Priority = .medium
private var isEditing: Bool {
editingTodo != nil
}
var body: some View {
NavigationStack {
Form {
Section {
TextField("What needs to be done?", text: $title)
.textInputAutocapitalization(.sentences)
}
Section {
CategoryPicker(selectedCategory: $selectedCategory)
}
.listRowInsets(EdgeInsets(top: 12, leading: 16, bottom: 12, trailing: 16))
Section {
PriorityPicker(selectedPriority: $selectedPriority)
}
.listRowInsets(EdgeInsets(top: 12, leading: 16, bottom: 12, trailing: 16))
}
.navigationTitle(isEditing ? "Edit Todo" : "Add New Todo")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
onCancel()
}
}
ToolbarItem(placement: .confirmationAction) {
Button(isEditing ? "Save" : "Add") {
let trimmed = title.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return }
onSave(trimmed, selectedCategory, selectedPriority)
}
.fontWeight(.semibold)
.disabled(title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
}
}
.onAppear {
if let todo = editingTodo {
title = todo.title
selectedCategory = todo.category
selectedPriority = todo.priority
}
}
}
}
}
@@ -0,0 +1,72 @@
import SwiftUI
struct TodoItemView: View {
let todo: Todo
let onToggle: () -> Void
let onTap: () -> Void
var body: some View {
Button(action: onTap) {
HStack(spacing: 12) {
// Checkbox toggle
Button(action: onToggle) {
Image(systemName: todo.completed ? "checkmark.circle.fill" : "circle")
.font(.title2)
.foregroundStyle(todo.completed ? Color.accentColor : Color(.systemGray3))
}
.buttonStyle(.plain)
// Title and metadata
VStack(alignment: .leading, spacing: 4) {
Text(todo.title)
.font(.body)
.strikethrough(todo.completed)
.foregroundStyle(todo.completed ? .secondary : .primary)
.lineLimit(2)
.multilineTextAlignment(.leading)
HStack(spacing: 8) {
// Category tag
Text(todo.category)
.font(.caption)
.padding(.horizontal, 8)
.padding(.vertical, 3)
.background(
Capsule()
.fill(Color(.systemGray5))
)
.foregroundStyle(.secondary)
// Priority dot and label
HStack(spacing: 4) {
Circle()
.fill(priorityColor(todo.priority))
.frame(width: 8, height: 8)
Text(todo.priority.displayName)
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
Spacer()
Image(systemName: "chevron.right")
.font(.caption)
.foregroundStyle(Color(.systemGray3))
}
.padding(.vertical, 8)
.padding(.horizontal, 16)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
}
private func priorityColor(_ priority: Priority) -> Color {
switch priority {
case .low: return .green
case .medium: return .orange
case .high: return .red
}
}
}