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,392 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 56;
objects = {
/* Begin PBXBuildFile section */
A1000001 /* TodoSwiftUIApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1000001 /* TodoSwiftUIApp.swift */; };
A1000002 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1000002 /* ContentView.swift */; };
A1000003 /* Todo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1000003 /* Todo.swift */; };
A1000004 /* DatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1000004 /* DatabaseManager.swift */; };
A1000005 /* TodoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1000005 /* TodoViewModel.swift */; };
A1000006 /* FilterBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1000006 /* FilterBar.swift */; };
A1000007 /* TodoItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1000007 /* TodoItemView.swift */; };
A1000008 /* TodoFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1000008 /* TodoFormView.swift */; };
A1000009 /* CategoryPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1000009 /* CategoryPicker.swift */; };
A1000010 /* PriorityPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1000010 /* PriorityPicker.swift */; };
A1000011 /* EmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1000011 /* EmptyStateView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
C1000001 /* TodoSwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TodoSwiftUI.app; sourceTree = BUILT_PRODUCTS_DIR; };
B1000001 /* TodoSwiftUIApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoSwiftUIApp.swift; sourceTree = "<group>"; };
B1000002 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
B1000003 /* Todo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Todo.swift; sourceTree = "<group>"; };
B1000004 /* DatabaseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseManager.swift; sourceTree = "<group>"; };
B1000005 /* TodoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoViewModel.swift; sourceTree = "<group>"; };
B1000006 /* FilterBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterBar.swift; sourceTree = "<group>"; };
B1000007 /* TodoItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoItemView.swift; sourceTree = "<group>"; };
B1000008 /* TodoFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoFormView.swift; sourceTree = "<group>"; };
B1000009 /* CategoryPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryPicker.swift; sourceTree = "<group>"; };
B1000010 /* PriorityPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriorityPicker.swift; sourceTree = "<group>"; };
B1000011 /* EmptyStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyStateView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
D1000001 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
E1000001 = {
isa = PBXGroup;
children = (
E1000002 /* TodoSwiftUI */,
E1000006 /* Products */,
);
sourceTree = "<group>";
};
E1000002 /* TodoSwiftUI */ = {
isa = PBXGroup;
children = (
B1000001 /* TodoSwiftUIApp.swift */,
E1000003 /* Models */,
E1000004 /* ViewModels */,
E1000005 /* Views */,
);
path = TodoSwiftUI;
sourceTree = "<group>";
};
E1000003 /* Models */ = {
isa = PBXGroup;
children = (
B1000003 /* Todo.swift */,
B1000004 /* DatabaseManager.swift */,
);
path = Models;
sourceTree = "<group>";
};
E1000004 /* ViewModels */ = {
isa = PBXGroup;
children = (
B1000005 /* TodoViewModel.swift */,
);
path = ViewModels;
sourceTree = "<group>";
};
E1000005 /* Views */ = {
isa = PBXGroup;
children = (
B1000002 /* ContentView.swift */,
B1000006 /* FilterBar.swift */,
B1000007 /* TodoItemView.swift */,
B1000008 /* TodoFormView.swift */,
B1000009 /* CategoryPicker.swift */,
B1000010 /* PriorityPicker.swift */,
B1000011 /* EmptyStateView.swift */,
);
path = Views;
sourceTree = "<group>";
};
E1000006 /* Products */ = {
isa = PBXGroup;
children = (
C1000001 /* TodoSwiftUI.app */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
F1000001 /* TodoSwiftUI */ = {
isa = PBXNativeTarget;
buildConfigurationList = F2000003 /* Build configuration list for PBXNativeTarget "TodoSwiftUI" */;
buildPhases = (
F1000002 /* Sources */,
D1000001 /* Frameworks */,
F1000003 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = TodoSwiftUI;
productName = TodoSwiftUI;
productReference = C1000001 /* TodoSwiftUI.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
F1000010 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1500;
LastUpgradeCheck = 1500;
TargetAttributes = {
F1000001 = {
CreatedOnToolsVersion = 15.0;
};
};
};
buildConfigurationList = F2000001 /* Build configuration list for PBXProject "TodoSwiftUI" */;
compatibilityVersion = "Xcode 14.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = E1000001;
productRefGroup = E1000006 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
F1000001 /* TodoSwiftUI */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
F1000003 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
F1000002 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A1000001 /* TodoSwiftUIApp.swift in Sources */,
A1000002 /* ContentView.swift in Sources */,
A1000003 /* Todo.swift in Sources */,
A1000004 /* DatabaseManager.swift in Sources */,
A1000005 /* TodoViewModel.swift in Sources */,
A1000006 /* FilterBar.swift in Sources */,
A1000007 /* TodoItemView.swift in Sources */,
A1000008 /* TodoFormView.swift in Sources */,
A1000009 /* CategoryPicker.swift in Sources */,
A1000010 /* PriorityPicker.swift in Sources */,
A1000011 /* EmptyStateView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
F2000010 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
F2000011 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
F2000020 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.TodoSwiftUI;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
F2000021 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.TodoSwiftUI;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
F2000001 /* Build configuration list for PBXProject "TodoSwiftUI" */ = {
isa = XCConfigurationList;
buildConfigurations = (
F2000010 /* Debug */,
F2000011 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
F2000003 /* Build configuration list for PBXNativeTarget "TodoSwiftUI" */ = {
isa = XCConfigurationList;
buildConfigurations = (
F2000020 /* Debug */,
F2000021 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = F1000010 /* Project object */;
}
@@ -0,0 +1,187 @@
import Foundation
import SQLite3
// SQLITE_TRANSIENT tells SQLite to make its own copy of bound text data
private let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
final class DatabaseManager {
static let shared = DatabaseManager()
private var db: OpaquePointer?
private init() {
openDatabase()
createTable()
}
deinit {
if db != nil {
sqlite3_close(db)
}
}
// MARK: - Database Setup
private func openDatabase() {
let fileURL = getDatabasePath()
if sqlite3_open(fileURL.path, &db) != SQLITE_OK {
print("Error opening database: \(String(cString: sqlite3_errmsg(db)))")
db = nil
}
}
private func getDatabasePath() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0].appendingPathComponent("todos.db")
}
private func createTable() {
let sql = """
CREATE TABLE IF NOT EXISTS todos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
completed INTEGER DEFAULT 0,
category TEXT DEFAULT 'Personal',
priority TEXT DEFAULT 'medium',
created_at INTEGER DEFAULT (strftime('%s','now'))
)
"""
var statement: OpaquePointer?
defer { sqlite3_finalize(statement) }
if sqlite3_prepare_v2(db, sql, -1, &statement, nil) == SQLITE_OK {
if sqlite3_step(statement) != SQLITE_DONE {
print("Error creating table: \(String(cString: sqlite3_errmsg(db)))")
}
} else {
print("Error preparing create table: \(String(cString: sqlite3_errmsg(db)))")
}
}
// MARK: - Helper
private func bindText(_ statement: OpaquePointer?, index: Int32, value: String) {
sqlite3_bind_text(statement, index, value, -1, SQLITE_TRANSIENT)
}
// MARK: - CRUD Operations
func loadTodos() -> [Todo] {
let sql = "SELECT id, title, completed, category, priority, created_at FROM todos ORDER BY created_at DESC"
var statement: OpaquePointer?
defer { sqlite3_finalize(statement) }
var todos: [Todo] = []
if sqlite3_prepare_v2(db, sql, -1, &statement, nil) == SQLITE_OK {
while sqlite3_step(statement) == SQLITE_ROW {
let id = sqlite3_column_int64(statement, 0)
let title = String(cString: sqlite3_column_text(statement, 1))
let completed = sqlite3_column_int(statement, 2) == 1
let category = String(cString: sqlite3_column_text(statement, 3))
let priorityStr = String(cString: sqlite3_column_text(statement, 4))
let priority = Priority(rawValue: priorityStr) ?? .medium
let createdAt = sqlite3_column_int64(statement, 5)
let todo = Todo(
id: id,
title: title,
completed: completed,
category: category,
priority: priority,
createdAt: createdAt
)
todos.append(todo)
}
} else {
print("Error loading todos: \(String(cString: sqlite3_errmsg(db)))")
}
return todos
}
@discardableResult
func insertTodo(title: String, category: String, priority: Priority) -> Todo? {
let sql = "INSERT INTO todos (title, category, priority, completed) VALUES (?, ?, ?, 0)"
var statement: OpaquePointer?
defer { sqlite3_finalize(statement) }
guard sqlite3_prepare_v2(db, sql, -1, &statement, nil) == SQLITE_OK else {
print("Error preparing insert: \(String(cString: sqlite3_errmsg(db)))")
return nil
}
bindText(statement, index: 1, value: title)
bindText(statement, index: 2, value: category)
bindText(statement, index: 3, value: priority.rawValue)
guard sqlite3_step(statement) == SQLITE_DONE else {
print("Error inserting todo: \(String(cString: sqlite3_errmsg(db)))")
return nil
}
let id = sqlite3_last_insert_rowid(db)
let createdAt = Int64(Date().timeIntervalSince1970)
return Todo(
id: id,
title: title,
completed: false,
category: category,
priority: priority,
createdAt: createdAt
)
}
func updateTodo(id: Int64, title: String, category: String, priority: Priority) {
let sql = "UPDATE todos SET title = ?, category = ?, priority = ? WHERE id = ?"
var statement: OpaquePointer?
defer { sqlite3_finalize(statement) }
guard sqlite3_prepare_v2(db, sql, -1, &statement, nil) == SQLITE_OK else {
print("Error preparing update: \(String(cString: sqlite3_errmsg(db)))")
return
}
bindText(statement, index: 1, value: title)
bindText(statement, index: 2, value: category)
bindText(statement, index: 3, value: priority.rawValue)
sqlite3_bind_int64(statement, 4, id)
if sqlite3_step(statement) != SQLITE_DONE {
print("Error updating todo: \(String(cString: sqlite3_errmsg(db)))")
}
}
func toggleTodo(id: Int64, completed: Bool) {
let sql = "UPDATE todos SET completed = ? WHERE id = ?"
var statement: OpaquePointer?
defer { sqlite3_finalize(statement) }
guard sqlite3_prepare_v2(db, sql, -1, &statement, nil) == SQLITE_OK else {
print("Error preparing toggle: \(String(cString: sqlite3_errmsg(db)))")
return
}
sqlite3_bind_int(statement, 1, completed ? 1 : 0)
sqlite3_bind_int64(statement, 2, id)
if sqlite3_step(statement) != SQLITE_DONE {
print("Error toggling todo: \(String(cString: sqlite3_errmsg(db)))")
}
}
func deleteTodo(id: Int64) {
let sql = "DELETE FROM todos WHERE id = ?"
var statement: OpaquePointer?
defer { sqlite3_finalize(statement) }
guard sqlite3_prepare_v2(db, sql, -1, &statement, nil) == SQLITE_OK else {
print("Error preparing delete: \(String(cString: sqlite3_errmsg(db)))")
return
}
sqlite3_bind_int64(statement, 1, id)
if sqlite3_step(statement) != SQLITE_DONE {
print("Error deleting todo: \(String(cString: sqlite3_errmsg(db)))")
}
}
}
@@ -0,0 +1,69 @@
import Foundation
// MARK: - Priority
enum Priority: String, CaseIterable, Identifiable {
case low
case medium
case high
var id: String { rawValue }
var displayName: String {
switch self {
case .low: return "Low"
case .medium: return "Medium"
case .high: return "High"
}
}
var colorHex: UInt {
switch self {
case .low: return 0x4CAF50 // green
case .medium: return 0xFF9800 // orange
case .high: return 0xF44336 // red
}
}
}
// MARK: - TodoFilter
enum TodoFilter: String, CaseIterable, Identifiable {
case all
case active
case done
var id: String { rawValue }
var displayName: String {
switch self {
case .all: return "All"
case .active: return "Active"
case .done: return "Done"
}
}
}
// MARK: - Categories
let todoCategories: [String] = ["Personal", "Work", "Shopping", "Health", "Learning"]
// MARK: - Todo
struct Todo: Identifiable, Equatable {
let id: Int64
var title: String
var completed: Bool
var category: String
var priority: Priority
var createdAt: Int64
static func == (lhs: Todo, rhs: Todo) -> Bool {
lhs.id == rhs.id
&& lhs.title == rhs.title
&& lhs.completed == rhs.completed
&& lhs.category == rhs.category
&& lhs.priority == rhs.priority
&& lhs.createdAt == rhs.createdAt
}
}
@@ -0,0 +1,10 @@
import SwiftUI
@main
struct TodoSwiftUIApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
@@ -0,0 +1,84 @@
import Foundation
import SwiftUI
@Observable
final class TodoViewModel {
var todos: [Todo] = []
var currentFilter: TodoFilter = .all
// MARK: - Computed Properties
var filteredTodos: [Todo] {
switch currentFilter {
case .all:
return todos
case .active:
return todos.filter { !$0.completed }
case .done:
return todos.filter { $0.completed }
}
}
var allCount: Int {
todos.count
}
var activeCount: Int {
todos.filter { !$0.completed }.count
}
var doneCount: Int {
todos.filter { $0.completed }.count
}
func count(for filter: TodoFilter) -> Int {
switch filter {
case .all: return allCount
case .active: return activeCount
case .done: return doneCount
}
}
// MARK: - Initialization
init() {
loadTodos()
}
// MARK: - Actions
func loadTodos() {
todos = DatabaseManager.shared.loadTodos()
}
func addTodo(title: String, category: String, priority: Priority) {
if let todo = DatabaseManager.shared.insertTodo(title: title, category: category, priority: priority) {
todos.insert(todo, at: 0)
}
}
func updateTodo(id: Int64, title: String, category: String, priority: Priority) {
DatabaseManager.shared.updateTodo(id: id, title: title, category: category, priority: priority)
if let index = todos.firstIndex(where: { $0.id == id }) {
todos[index].title = title
todos[index].category = category
todos[index].priority = priority
}
}
func toggleTodo(id: Int64) {
if let index = todos.firstIndex(where: { $0.id == id }) {
todos[index].completed.toggle()
DatabaseManager.shared.toggleTodo(id: id, completed: todos[index].completed)
}
}
func deleteTodo(id: Int64) {
DatabaseManager.shared.deleteTodo(id: id)
todos.removeAll { $0.id == id }
}
func setFilter(_ filter: TodoFilter) {
currentFilter = filter
}
}
@@ -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
}
}
}