commit 13bc7a756f8a0c1a343dc6e971ef3b0423690b84
parent 618ffff00f8c46474d1e3f5e486b538c88c304eb
Author: Michael Camilleri <[email protected]>
Date: Fri, 27 Feb 2026 14:08:22 +0900
Fix focused text field being in view
Co-Authored-By: Codex GPT 5.3 <[email protected]>
Diffstat:
2 files changed, 39 insertions(+), 16 deletions(-)
diff --git a/ListlessiOS/Extensions/TaskListView+PullGestures.swift b/ListlessiOS/Extensions/TaskListView+PullGestures.swift
@@ -77,9 +77,12 @@ private struct PullCreationGestureModifier: ViewModifier {
pullToCreate.updatePullDistance(pullDistance)
}
.onScrollGeometryChange(for: CGFloat.self) { geo in
+ // Subtract the 20pt bottom content margin (set on ScrollView in TaskListView)
+ // so it doesn't create a dead zone before overscroll registers.
+ let adjustedBottomInset = geo.contentInsets.bottom - 20
let maxOffset = max(
-geo.contentInsets.top,
- geo.contentSize.height - geo.bounds.size.height + geo.contentInsets.bottom
+ geo.contentSize.height - geo.bounds.size.height + adjustedBottomInset
)
return max(0, geo.contentOffset.y - maxOffset)
} action: { _, pullDistance in
diff --git a/ListlessiOS/Views/TaskListView.swift b/ListlessiOS/Views/TaskListView.swift
@@ -145,21 +145,6 @@ struct TaskListView: View, TaskListViewProtocol {
}
fState.focusedField = focusedFieldBinding
}
- .onChange(of: focusedFieldBinding) { oldValue, newValue in
- fState.focusedField = newValue
- handleFocusChange(from: oldValue, to: newValue)
-
- if newValue == nil {
- if let pending = pendingFocus {
- focusedFieldBinding = pending
- fState.focusedField = pending
- pendingFocus = nil
- } else {
- focusedFieldBinding = .scrollView
- fState.focusedField = .scrollView
- }
- }
- }
.onChange(of: undoManager, initial: true) { _, newValue in
managedObjectContext.undoManager = newValue
}
@@ -200,6 +185,7 @@ struct TaskListView: View, TaskListViewProtocol {
private var taskScrollView: some View {
ScrollView {
+ ScrollViewReader { scrollProxy in
VStack(alignment: .leading, spacing: vStackSpacing) {
navigationHeader
pullToCreateIndicatorRow
@@ -239,6 +225,7 @@ struct TaskListView: View, TaskListViewProtocol {
} action: { frame in
rowFrames[taskID] = frame
}
+ .id(taskID)
}
ForEach(completedTasks) { task in
@@ -256,15 +243,48 @@ struct TaskListView: View, TaskListViewProtocol {
)
.opacity(isBeingCleared ? 0 : 1)
.offset(y: isBeingCleared ? 40 : 0)
+ .id(taskID)
}
}
.frame(maxWidth: .infinity, alignment: .topLeading)
.padding(.trailing, 16)
.padding(.vertical, 12)
.offset(y: -pullToCreate.pullOffset)
+ .onChange(of: focusedFieldBinding) { oldValue, newValue in
+ fState.focusedField = newValue
+ handleFocusChange(from: oldValue, to: newValue)
+
+ if newValue == nil {
+ if let pending = pendingFocus {
+ focusedFieldBinding = pending
+ fState.focusedField = pending
+ pendingFocus = nil
+ } else {
+ focusedFieldBinding = .scrollView
+ fState.focusedField = .scrollView
+ }
+ }
+
+ if case .task(let id) = (newValue ?? fState.focusedField),
+ draggedTaskID == nil
+ {
+ withAnimation {
+ scrollProxy.scrollTo(id)
+ }
+ }
+ }
+ .onChange(of: fState.selectedTaskID) { _, newID in
+ if let newID, draggedTaskID == nil {
+ withAnimation {
+ scrollProxy.scrollTo(newID)
+ }
+ }
+ }
+ }
}
.scrollDisabled(draggedTaskID != nil)
.scrollBounceBehavior(.always)
+ .contentMargins(.bottom, 20)
.background {
Color.outerBackground.ignoresSafeArea()
}