DrivingScrollView not selected after startup
The problem
In our productive app we have a problem with the new way to determine the driving scroll view. Namely the modified ScrollView is not actually driving after the overlay is first shown. After moving the sheet to a different notch the ScrollView is actually driving and everything is working as expected.
Unfortunately I have not been able to create a simple example for this, since this seems to be some kind of race condition. This file here basically shows what we do in my companies app: https://github.com/Lumentus/DynamicOverlay/blob/driving-scroll-view-test/DynamicOverlay_Example/DynamicOverlay_Example/View/DrivingScrollViewTest.swift
This code alone will unfortunately not fail (on my simulators), but with a change to the ActivatedOverlayArea struct, that I added mainly to get better insight into what is going on, this will produce the problems that we have in our app too. The changed file can be seen here: https://github.com/Lumentus/DynamicOverlay/blob/driving-scroll-view-test/Source/Internal/Handle/ActivatedOverlayArea.swift
My diagnosis
I have tried to figure out what exactly is going wrong here and with two debug outputs I think I have been able to get to the bottom of the problem, though I have no idea how to fix this.
I added two outputs that print the ActivatedOverlayArea that is present at that location. One to DynamicOverlayScrollViewProxy.findScrollView and one to the onPreferenceChange closure in OnDrivingScrollViewChangeViewModifier.body.
This produces something like the following output when the scroll view is correctly found:
placeholder appeared
preferenceChange: ActivatedOverlayArea(spots: [])
content appeared
findScrollView: ActivatedOverlayArea(spots: [])
preferenceChange: ActivatedOverlayArea(spots: [DynamicOverlay.ActivatedOverlayArea.(unknown context at $10ffa8ba0).Spot(frame: (0.0, 44.80908203125, 0.0, 0.0))])
preferenceChange: ActivatedOverlayArea(spots: [DynamicOverlay.ActivatedOverlayArea.(unknown context at $10ffa8ba0).Spot(frame: (104.0, 65.28483072916669, 182.0, 589.7151692708333))])
findScrollView: ActivatedOverlayArea(spots: [DynamicOverlay.ActivatedOverlayArea.(unknown context at $10ffa8ba0).Spot(frame: (0.0, 44.80908203125, 0.0, 0.0))])
findScrollView: ActivatedOverlayArea(spots: [DynamicOverlay.ActivatedOverlayArea.(unknown context at $10ffa8ba0).Spot(frame: (104.0, 65.28483072916669, 182.0, 589.7151692708333))])
And something like the following when the ScrollView is not driving after startup:
placeholder appeared
preferenceChange: [] from 1643807010.705128
content appeared
findScrollView: [] from 1643807010.705204
preferenceChange: [DynamicOverlay.ActivatedOverlayArea.(unknown context at $10de51bd0).Spot(frame: (0.0, 44.80908203125, 0.0, 0.0))] from 1643807010.837788
preferenceChange: [DynamicOverlay.ActivatedOverlayArea.(unknown context at $10de51bd0).Spot(frame: (104.0, 65.28483072916669, 182.0, 589.7151692708333))] from 1643807010.86582
findScrollView: [DynamicOverlay.ActivatedOverlayArea.(unknown context at $10de51bd0).Spot(frame: (0.0, 44.80908203125, 0.0, 0.0))] from 1643807010.837788
findScrollView: [DynamicOverlay.ActivatedOverlayArea.(unknown context at $10de51bd0).Spot(frame: (104.0, 65.28483072916669, 182.0, 589.7151692708333))] from 1643807010.86582
findScrollView: [] from 1643807010.705128
What is pretty obvious is that in the erroneous case the last call to findScrollView is called with an ActivatedOverlayArea that has no spots set, while in the successful case there is a spot defined. Judging by the timestamps that I added to the ActivatedOverlayArea for debug purposes (and causes the error to surface on my simulator), the ActivatedOverlayArea used for the last findScrollView is the first one that was received in the preferenceChange event.
Hey @Lumentus, thank you for the detailed issue!
It is hard to tell without a sample code... it sounds like the area is not propagated correctly.
Could you try to put a dispatch async in the onChange modifier? A classic SwiftUI fix 😅.
func body(content: Content) -> some View {
content.onPreferenceChange(DynamicOverlayScrollViewProxyPreferenceKey.self, perform: { value in
print("preferenceChange: \(value)")
DispatchQueue.main.async { handler(DynamicOverlayScrollViewProxy(area: value)) }
})
}
@gaetanzanella Unfortunately that does not fix the problem. Did you try my branch with a simulator of your own?
I can reproduce this issue. Whenever the overlay gets hidden (.fractional(0)) and then reappears on the screen the scrollView is not driving the overlay anymore. When I drag to another notch (e.g. max) then it starts to work again as long as the overlay remains visible.
A quick workaround is to disable the scrollView in the initial position. However, this doesn't resolve the underlying issue in the SDK.
ScrollView {
content
}
. scrollDisablediOS15(self.notch == .min)
struct DisableScrollingModifier: ViewModifier {
var isDisabled: Bool
func body(content: Content) -> some View {
if #available(iOS 16.0, *) {
content
.scrollDisabled(isDisabled)
} else {
if isDisabled {
content
.simultaneousGesture(DragGesture(minimumDistance: 0))
} else {
content
}
}
}
}
public extension View {
func scrollDisablediOS15(_ isDisabled: Bool) -> some View {
modifier(DisableScrollingModifier(isDisabled: isDisabled))
}
}