Updated the TaskListItem view in the app target to use the new view modifiers.
This commit is contained in:
@@ -20,7 +20,7 @@ extension TaskListView {
|
|||||||
|
|
||||||
/// Tracks which text field currently has keyboard focus.
|
/// Tracks which text field currently has keyboard focus.
|
||||||
@FocusState
|
@FocusState
|
||||||
private var focusOnField: FieldFocus?
|
private var inputFocus: InputFieldFocus?
|
||||||
|
|
||||||
/// The todo task to display.
|
/// The todo task to display.
|
||||||
@Bindable
|
@Bindable
|
||||||
@@ -35,7 +35,7 @@ extension TaskListView {
|
|||||||
///
|
///
|
||||||
/// - Parameter item: The todo task to display and edit.
|
/// - Parameter item: The todo task to display and edit.
|
||||||
init(_ item: Todo) {
|
init(_ item: Todo) {
|
||||||
_item = Bindable(item)
|
self._item = Bindable(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Body
|
// MARK: Body
|
||||||
@@ -65,14 +65,19 @@ extension TaskListView {
|
|||||||
) {
|
) {
|
||||||
TextField(
|
TextField(
|
||||||
"view.task-list-item.placeholder.title",
|
"view.task-list-item.placeholder.title",
|
||||||
text: title,
|
text: $item.title,
|
||||||
axis: .vertical
|
axis: .vertical
|
||||||
)
|
)
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.focused(
|
.focused(
|
||||||
$focusOnField,
|
$inputFocus,
|
||||||
equals: .title
|
equals: .title
|
||||||
)
|
)
|
||||||
|
.onNewlineFocus(
|
||||||
|
$item.title,
|
||||||
|
to: .note,
|
||||||
|
on: $inputFocus
|
||||||
|
)
|
||||||
|
|
||||||
if isNoteShown {
|
if isNoteShown {
|
||||||
TextField(
|
TextField(
|
||||||
@@ -82,9 +87,13 @@ extension TaskListView {
|
|||||||
)
|
)
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.focused(
|
.focused(
|
||||||
$focusOnField,
|
$inputFocus,
|
||||||
equals: .note
|
equals: .note
|
||||||
)
|
)
|
||||||
|
.onEmptyNewlineResignFocus(
|
||||||
|
text: note,
|
||||||
|
from: $inputFocus
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
DatePicker(
|
DatePicker(
|
||||||
@@ -99,7 +108,7 @@ extension TaskListView {
|
|||||||
.autocapitalization(.sentences)
|
.autocapitalization(.sentences)
|
||||||
.autocorrectionDisabled(false)
|
.autocorrectionDisabled(false)
|
||||||
.foregroundStyle(foregroundStyleView)
|
.foregroundStyle(foregroundStyleView)
|
||||||
.keyboardType(.asciiCapable)
|
.keyboardType(.default)
|
||||||
}
|
}
|
||||||
.animation(
|
.animation(
|
||||||
.easeInOut,
|
.easeInOut,
|
||||||
@@ -110,20 +119,6 @@ extension TaskListView {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
// MARK: - Helpers
|
||||||
|
|
||||||
private extension TaskListView.Item {
|
private extension TaskListView.Item {
|
||||||
@@ -151,19 +146,7 @@ private extension TaskListView.Item {
|
|||||||
.init {
|
.init {
|
||||||
item.note ?? ""
|
item.note ?? ""
|
||||||
} set: {
|
} set: {
|
||||||
setNote(with: $0)
|
item.note = $0.isEmpty ? nil : $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<String> {
|
|
||||||
.init {
|
|
||||||
item.title
|
|
||||||
} set: {
|
|
||||||
setTitle(with: $0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +167,7 @@ private extension TaskListView.Item {
|
|||||||
/// - Any text field has focus (allowing users to add a note even if one doesn't exist yet)
|
/// - 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
|
/// - The todo task has a note with non-whitespace content
|
||||||
var isNoteShown: Bool {
|
var isNoteShown: Bool {
|
||||||
guard focusOnField != nil else {
|
guard inputFocus != nil else {
|
||||||
guard let note = item.note else {
|
guard let note = item.note else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -195,57 +178,6 @@ private extension TaskListView.Item {
|
|||||||
return true
|
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
|
// MARK: - Previews
|
||||||
@@ -264,9 +196,3 @@ private extension TaskListView.Item {
|
|||||||
}
|
}
|
||||||
.listStyle(.grouped)
|
.listStyle(.grouped)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Character+Constants
|
|
||||||
|
|
||||||
private extension Character {
|
|
||||||
static let newLine: Self = "\n"
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user