import SwiftUI extension TaskListView { /// A view that displays a single editable todo task in a list. /// /// This view provides a comprehensive interface for viewing and editing task information, /// including: /// - A completion toggle using a circular checkbox design /// - An editable title field with multi-line support /// - An optional note field that appears when the task has notes /// - A date picker for selecting the task's due date /// /// The view automatically adjusts its appearance based on completion state, dimming /// completed tasks to provide visual feedback. All text fields support automatic /// capitalization and keyboard correction for improved user experience. struct Item: View { // MARK: Properties /// Tracks which text field currently has keyboard focus. @FocusState private var focusOnField: FieldFocus? /// The todo task to display. @Bindable private var item: Todo // MARK: Initializers /// Creates a new task list item view for the specified todo. /// /// The item is wrapped in a `@Bindable` property wrapper to enable two-way /// data binding for editing task properties directly within the view. /// /// - Parameter item: The todo task to display and edit. init(_ item: Todo) { _item = Bindable(item) } // MARK: Body /// The content and behavior of the view. /// /// Renders an editable task list item with a completion toggle, title field, /// optional note field, and date picker. The view's foreground color automatically /// adjusts based on completion state, and changes animate smoothly. var body: some View { HStack( alignment: .top, spacing: 16 ) { Toggle( "view.task-list-item.placeholder.completed", isOn: isCompleted ) .toggleStyle(.completion) VStack( alignment: .leading, spacing: 8 ) { TextField( "view.task-list-item.placeholder.title", text: title, axis: .vertical ) .font(.headline) .focused($focusOnField, equals: .title) if isNoteShown { TextField( "view.task-list-item.placeholder.note", text: note, axis: .vertical ) .font(.subheadline) .focused($focusOnField, equals: .note) } DatePicker(selection: $item.due, displayedComponents: .date) { Image(systemName: "calendar") } .frame(width: 160) .disabled(isCompleted.wrappedValue) } .autocapitalization(.sentences) .autocorrectionDisabled(false) .foregroundStyle(foregroundStyleView) .keyboardType(.asciiCapable) } .animation( .easeInOut, value: item.completed ) } } } // MARK: - Enumerations private extension TaskListView.Item { /// Identifies which text field should receive keyboard focus. enum FieldFocus { /// The note text field has focus. case note /// The title text field has focus. case title } } // MARK: - Helpers private extension TaskListView.Item { // MARK: Bindings /// A binding to the task's completion state. /// /// This computed binding converts between the optional `completed` date and a boolean /// value for the completion toggle. When set to `true`, it marks the task as completed /// with the current date; when set to `false`, it clears the completion date. var isCompleted: Binding { .init { item.completed != nil } set: { item.completed = $0 ? .now : nil } } /// A binding to the note text of a todo task. /// /// This computed binding provides a non-optional string binding for the note field, /// converting `nil` values to empty strings for seamless text field interaction. var note: Binding { .init { item.note ?? "" } set: { setNote(with: $0) } } /// A binding to the title text of a todo task. /// /// This computed binding provides access to the title of a todo task with special handling /// for newline characters. var title: Binding { .init { item.title } set: { setTitle(with: $0) } } // MARK: Computed /// The foreground color style for the components in this view. /// /// Returns `.primary` for incomplete tasks and `.secondary` for completed tasks, /// providing visual feedback about the task's completion state by dimming /// completed items. var foregroundStyleView: Color { item.completed == nil ? .primary : .secondary } /// Determines whether the note field should be visible. /// /// The note field is shown when: /// - Any text field has focus (allowing users to add a note even if one doesn't exist yet) /// - The todo task has a note with non-whitespace content var isNoteShown: Bool { guard focusOnField != nil else { guard let note = item.note else { return false } return note.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false } return true } // MARK: Functions /// Updates the note of a todo task with special handling for newlines and empty content. /// /// This method provides intelligent note field behavior: /// - Empty text immediately clears the note /// - Newlines at the end of the text trigger special handling: /// - If the text is only whitespace/newlines, the note is cleared and focus is removed /// - Otherwise, the text (including newlines) is saved normally /// - All other text is saved as-is /// /// This allows users to press return on an empty note field to dismiss it, /// while still supporting multi-line notes. /// /// - Parameter text: The new note text to set. func setNote(with text: String) { guard text.isEmpty == false else { item.note = text return } if text.last == .newLine { guard text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true else { item.note = text return } item.note = nil focusOnField = nil } else { item.note = text } } /// Updates the title of a todo task with special handling for newline characters. /// /// When the user presses return in the title field (adding a newline), this function /// prevents the newline from being added to the title and instead moves keyboard /// focus to the note field. If no newline is present, the title is updated normally. /// /// - Parameter text: The new title text to set. func setTitle(with text: String) { guard text.lastIndex(of: .newLine) == nil else { focusOnField = .note return } item.title = text } } // MARK: - Previews #Preview("Task list item") { @Previewable var todo: Todo = .init( title: "Complete project documentation", note: "Add detailed comments and README", due: .now, completed: nil ) List { TaskListView.Item(todo) } .listStyle(.grouped) } // MARK: - Character+Constants private extension Character { static let newLine: Self = "\n" }