listless

A simple list app for Apple platforms
Log | Files | Refs | README | LICENSE

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:
MListlessiOS/Extensions/TaskListView+PullGestures.swift | 5++++-
MListlessiOS/Views/TaskListView.swift | 50+++++++++++++++++++++++++++++++++++---------------
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() }