Post

Replies

Boosts

Views

Activity

iOS 27: swipeActions outside List never re-evaluates its content closure (stale labels, if gating ignored) — intended?
We are adopting the iOS 27 support for swipeActions on rows inside a ScrollView { LazyVStack } (with swipeActionsContainer() on the scroll view). We expected the row modifier to behave like it does inside List, but on iOS 27.0 (24A437, iPhone 17; also 27.0 simulator) the content closure appears to be evaluated only once, when the row is first registered, and never again while the row stays on screen. Is this the intended behavior, or a bug? What we observe Stale button labels / actions. A button whose title depends on state (Button(isPinned ? "Unpin" : "Pin")) keeps its first title after the state changes. The row body itself re-renders with the new state, but the swipe actions do not, until the row is recycled by the lazy stack (scroll it off-screen and back) — then it shows the new value. Conditional content is ignored. swipeActions { if !editing { Button(...) } } keeps showing the buttons after editing becomes true, and the row can still be swiped. Views placed inside the content that observe state themselves (an @Observable/@ObservedObject child view) do update their text. But an if inside that child view does not remove the buttons, and emptying a ForEach inside it removes the buttons while the row still slides open (onPresentationChanged fires with true). onLongPressGesture(minimumDuration: 0.7) on the same row fires only on touch-up (after ~3 s of holding) when the row has swipe actions in a ScrollView; inside List, and in a ScrollView without swipe actions, it fires at ~0.7 s while still pressed. The same row code inside List behaves as expected for all four points. Attaching or omitting swipeActionsContainer() and using the old swipeActions(edge:allowsFullSwipe:content:) overload instead of the new onPresentationChanged overload makes no difference. Minimal reproduction (iOS 27 only, single file) import SwiftUI @Observable final class Model { var editing = false var pinned: Set<Int> = [] } @available(iOS 27, *) struct ContentView: View { @State private var model = Model() @State private var lastLongPress = "-" var body: some View { VStack(spacing: 8) { Toggle("Editing (swipe should be off)", isOn: $model.editing) Text("long press: \(lastLongPress)").font(.caption) ScrollView { LazyVStack(spacing: 0) { ForEach(1...30, id: \.self) { n in HStack { Text("Row \(n)") Spacer() Text(model.pinned.contains(n) ? "pinned" : "").foregroundStyle(.secondary) } .frame(height: 56) .contentShape(Rectangle()) .onLongPressGesture(minimumDuration: 0.7) { lastLongPress = "row \(n) @ \(Date().formatted(date: .omitted, time: .standard))" } .swipeActions(edge: .trailing, allowsFullSwipe: false) { if !model.editing { // (2) ignored after first registration Button(model.pinned.contains(n) ? "Unpin" : "Pin") { // (1) title stays "Pin" if model.pinned.contains(n) { model.pinned.remove(n) } else { model.pinned.insert(n) } } .tint(.blue) } } onPresentationChanged: { _ in } } } } .swipeActionsContainer() } .padding(.horizontal) } } Steps: Swipe row 3, tap Pin. The row shows "pinned". Swipe row 3 again → the button still says Pin (in List it says Unpin). Tapping it runs the closure with the current model, so it unpins, but the label was wrong. Turn Editing on, swipe any row → it still opens with the button (in List it does not open). Press and hold a row for 3 s → "long press" timestamp is written only when the finger lifts. Questions Is "evaluate the swipeActions content once per row registration" the intended contract for swipe actions outside List? The documentation for swipeActionsContainer() only describes coordination (single open row, dismiss on scroll / outside tap), and swipeActions(edge:allowsFullSwipe:content:) doesn't mention any difference between List and other containers. If it is intended, what is the recommended way to (a) disable swipe for a row temporarily (e.g. during an edit mode) and (b) update button titles from state, without recreating the row view? We currently work around it with a child view that observes the model for titles, and allowsHitTesting(false) on the row to disable swiping, but both feel like they rely on undocumented behavior. Is the delayed onLongPressGesture (fires on touch-up) expected when a row carries swipe actions in a ScrollView? simultaneousGesture(LongPressGesture(...)) fires while pressed, which suggests the swipe recognizer imposes a failure requirement on the row's gestures.
0
0
61
2d
Using NSFetchedResultsController with viewContext seems to have bad performance
Hello! Our team have been using a NSFetchedResultsController with viewContext for list UI. But we found some UI Hang logs, and I'm guessing that it happens because of Faulting in viewContext. The main thread call stacks in the logs look like below. 0 libsystem_kernel.dylib 0x00000001f378dbcc kevent_id + 8 1 libdispatch.dylib 0x00000001b4047b0f _dispatch_kq_poll + 227 2 libdispatch.dylib 0x00000001b40484df _dispatch_event_loop_wait_for_ownership + 435 3 libdispatch.dylib 0x00000001b4034983 __DISPATCH_WAIT_FOR_QUEUE__ + 339 4 libdispatch.dylib 0x00000001b403454b _dispatch_sync_f_slow + 147 5 CoreData 0x00000001b42859b3 _perform + 203 6 CoreData 0x00000001b4372767 -[NSManagedObjectContext+ 1275751 (_NestedContextSupport) newValuesForObjectWithID:withContext:error:] + 163 7 CoreData 0x00000001b424cf87 _PFFaultHandlerLookupRow + 275 8 CoreData 0x00000001b424cbbf _PF_FulfillDeferredFault + 191 9 CoreData 0x00000001b424cab7 _pvfk_header + 167 10 CoreData 0x00000001b424c9d7 _sharedIMPL_pvfk_core + 31 11 Works 0x000000010445517b closure #1 in static MailUIMailFactory.make(mail:needFolderName:) + 26530171 (MailUIMailFactory.swift:109) ... 0 libsystem_kernel.dylib 0x00000001dbd4254c kevent_id + 8 1 libdispatch.dylib 0x000000019b7057d3 _dispatch_kq_poll + 227 2 libdispatch.dylib 0x000000019b7061bb _dispatch_event_loop_wait_for_ownership + 435 3 libdispatch.dylib 0x000000019b6f2593 __DISPATCH_WAIT_FOR_QUEUE__ + 339 4 libdispatch.dylib 0x000000019b6f215b _dispatch_sync_f_slow + 147 5 CoreData 0x000000019b94f24b _perform + 203 6 CoreData 0x000000019ba3c41b -[NSManagedObjectContext+ 1274907 (_NestedContextSupport) newValueForRelationship:forObjectWithID:withContext:error:] + 167 7 CoreData 0x000000019b914387 -[NSFaultHandler retainedFulfillAggregateFaultForObject:andRelationship:withContext:] + 391 8 CoreData 0x000000019b9118bf -[_NSFaultingMutableSet willReadWithContents:] + 391 9 CoreData 0x000000019b91e663 -[_NSFaultingMutableSet count] + 23 10 Foundation 0x0000000192dbeb3f -[NSSet+ 7609151 (NSKeyValueSorting) sortedArrayUsingDescriptors:] + 55 11 Works 0x000000010249ec1f specialized static MailUIMailFactory.make(mail:needFolderName:) + 27470879 (MailUIMailFactory.swift:102) ... We just use NSFetchedResultsController's fetchedObjects in table view's cellForRowAt method, and make a new model by MailUIMailFactory.make. So we're thinking that we need to initialize NSFetchedResultsController with bg Context. What you guys think? Do I avoid using viewContext in heavy UI?
0
0
459
Jun ’24
iOS 27: swipeActions outside List never re-evaluates its content closure (stale labels, if gating ignored) — intended?
We are adopting the iOS 27 support for swipeActions on rows inside a ScrollView { LazyVStack } (with swipeActionsContainer() on the scroll view). We expected the row modifier to behave like it does inside List, but on iOS 27.0 (24A437, iPhone 17; also 27.0 simulator) the content closure appears to be evaluated only once, when the row is first registered, and never again while the row stays on screen. Is this the intended behavior, or a bug? What we observe Stale button labels / actions. A button whose title depends on state (Button(isPinned ? "Unpin" : "Pin")) keeps its first title after the state changes. The row body itself re-renders with the new state, but the swipe actions do not, until the row is recycled by the lazy stack (scroll it off-screen and back) — then it shows the new value. Conditional content is ignored. swipeActions { if !editing { Button(...) } } keeps showing the buttons after editing becomes true, and the row can still be swiped. Views placed inside the content that observe state themselves (an @Observable/@ObservedObject child view) do update their text. But an if inside that child view does not remove the buttons, and emptying a ForEach inside it removes the buttons while the row still slides open (onPresentationChanged fires with true). onLongPressGesture(minimumDuration: 0.7) on the same row fires only on touch-up (after ~3 s of holding) when the row has swipe actions in a ScrollView; inside List, and in a ScrollView without swipe actions, it fires at ~0.7 s while still pressed. The same row code inside List behaves as expected for all four points. Attaching or omitting swipeActionsContainer() and using the old swipeActions(edge:allowsFullSwipe:content:) overload instead of the new onPresentationChanged overload makes no difference. Minimal reproduction (iOS 27 only, single file) import SwiftUI @Observable final class Model { var editing = false var pinned: Set<Int> = [] } @available(iOS 27, *) struct ContentView: View { @State private var model = Model() @State private var lastLongPress = "-" var body: some View { VStack(spacing: 8) { Toggle("Editing (swipe should be off)", isOn: $model.editing) Text("long press: \(lastLongPress)").font(.caption) ScrollView { LazyVStack(spacing: 0) { ForEach(1...30, id: \.self) { n in HStack { Text("Row \(n)") Spacer() Text(model.pinned.contains(n) ? "pinned" : "").foregroundStyle(.secondary) } .frame(height: 56) .contentShape(Rectangle()) .onLongPressGesture(minimumDuration: 0.7) { lastLongPress = "row \(n) @ \(Date().formatted(date: .omitted, time: .standard))" } .swipeActions(edge: .trailing, allowsFullSwipe: false) { if !model.editing { // (2) ignored after first registration Button(model.pinned.contains(n) ? "Unpin" : "Pin") { // (1) title stays "Pin" if model.pinned.contains(n) { model.pinned.remove(n) } else { model.pinned.insert(n) } } .tint(.blue) } } onPresentationChanged: { _ in } } } } .swipeActionsContainer() } .padding(.horizontal) } } Steps: Swipe row 3, tap Pin. The row shows "pinned". Swipe row 3 again → the button still says Pin (in List it says Unpin). Tapping it runs the closure with the current model, so it unpins, but the label was wrong. Turn Editing on, swipe any row → it still opens with the button (in List it does not open). Press and hold a row for 3 s → "long press" timestamp is written only when the finger lifts. Questions Is "evaluate the swipeActions content once per row registration" the intended contract for swipe actions outside List? The documentation for swipeActionsContainer() only describes coordination (single open row, dismiss on scroll / outside tap), and swipeActions(edge:allowsFullSwipe:content:) doesn't mention any difference between List and other containers. If it is intended, what is the recommended way to (a) disable swipe for a row temporarily (e.g. during an edit mode) and (b) update button titles from state, without recreating the row view? We currently work around it with a child view that observes the model for titles, and allowsHitTesting(false) on the row to disable swiping, but both feel like they rely on undocumented behavior. Is the delayed onLongPressGesture (fires on touch-up) expected when a row carries swipe actions in a ScrollView? simultaneousGesture(LongPressGesture(...)) fires while pressed, which suggests the swipe recognizer imposes a failure requirement on the row's gestures.
Replies
0
Boosts
0
Views
61
Activity
2d
Using NSFetchedResultsController with viewContext seems to have bad performance
Hello! Our team have been using a NSFetchedResultsController with viewContext for list UI. But we found some UI Hang logs, and I'm guessing that it happens because of Faulting in viewContext. The main thread call stacks in the logs look like below. 0 libsystem_kernel.dylib 0x00000001f378dbcc kevent_id + 8 1 libdispatch.dylib 0x00000001b4047b0f _dispatch_kq_poll + 227 2 libdispatch.dylib 0x00000001b40484df _dispatch_event_loop_wait_for_ownership + 435 3 libdispatch.dylib 0x00000001b4034983 __DISPATCH_WAIT_FOR_QUEUE__ + 339 4 libdispatch.dylib 0x00000001b403454b _dispatch_sync_f_slow + 147 5 CoreData 0x00000001b42859b3 _perform + 203 6 CoreData 0x00000001b4372767 -[NSManagedObjectContext+ 1275751 (_NestedContextSupport) newValuesForObjectWithID:withContext:error:] + 163 7 CoreData 0x00000001b424cf87 _PFFaultHandlerLookupRow + 275 8 CoreData 0x00000001b424cbbf _PF_FulfillDeferredFault + 191 9 CoreData 0x00000001b424cab7 _pvfk_header + 167 10 CoreData 0x00000001b424c9d7 _sharedIMPL_pvfk_core + 31 11 Works 0x000000010445517b closure #1 in static MailUIMailFactory.make(mail:needFolderName:) + 26530171 (MailUIMailFactory.swift:109) ... 0 libsystem_kernel.dylib 0x00000001dbd4254c kevent_id + 8 1 libdispatch.dylib 0x000000019b7057d3 _dispatch_kq_poll + 227 2 libdispatch.dylib 0x000000019b7061bb _dispatch_event_loop_wait_for_ownership + 435 3 libdispatch.dylib 0x000000019b6f2593 __DISPATCH_WAIT_FOR_QUEUE__ + 339 4 libdispatch.dylib 0x000000019b6f215b _dispatch_sync_f_slow + 147 5 CoreData 0x000000019b94f24b _perform + 203 6 CoreData 0x000000019ba3c41b -[NSManagedObjectContext+ 1274907 (_NestedContextSupport) newValueForRelationship:forObjectWithID:withContext:error:] + 167 7 CoreData 0x000000019b914387 -[NSFaultHandler retainedFulfillAggregateFaultForObject:andRelationship:withContext:] + 391 8 CoreData 0x000000019b9118bf -[_NSFaultingMutableSet willReadWithContents:] + 391 9 CoreData 0x000000019b91e663 -[_NSFaultingMutableSet count] + 23 10 Foundation 0x0000000192dbeb3f -[NSSet+ 7609151 (NSKeyValueSorting) sortedArrayUsingDescriptors:] + 55 11 Works 0x000000010249ec1f specialized static MailUIMailFactory.make(mail:needFolderName:) + 27470879 (MailUIMailFactory.swift:102) ... We just use NSFetchedResultsController's fetchedObjects in table view's cellForRowAt method, and make a new model by MailUIMailFactory.make. So we're thinking that we need to initialize NSFetchedResultsController with bg Context. What you guys think? Do I avoid using viewContext in heavy UI?
Replies
0
Boosts
0
Views
459
Activity
Jun ’24