pointfreeco / swift-composable-architecture

A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind.
https://www.pointfree.co/collections/composable-architecture
MIT License
12.25k stars 1.42k forks source link

Paging issue with TabViews that are using TabView(selection: viewStore.binding(...) in Voice Over mode #2339

Closed aj2109 closed 1 year ago

aj2109 commented 1 year ago

Description

Hello,

I have been trying to debug this for a while now and am completely at a loss. We have 3 instances of TabViews using a similar setup to this:

TabView(selection: viewStore.binding( get: .currentPage, send: Examplename.Action.changePage )) { ForEachStore(...) { someStore SomeView() .tag(ViewStore(someStore).id) } )

In all 3 of these instances, when you go into voice over mode, and you scroll slowly (not a prerequisite to replicating, but makes it happen more frequently) it will randomly throw the user back to the first page mode. By contrast, where in the app we are using a @State variable or @Binding variable for the TabView (2 instances), this does not occur, hence me opening this issue.

I cannot see any reason for this happening when debugging, and it works fine without Voice Over mode on, so I am hoping to hear if anyone else experiences this or has any suggestions.

Thanks!

Checklist

Expected behavior

No response

Actual behavior

No response

Steps to reproduce

No response

The Composable Architecture version information

0.53.2

Destination operating system

iOS 16 or 17

Xcode version information

14.3 though has happened on previous versions too

Swift Compiler version information

No response

mbrandonw commented 1 year ago

Hi @aj2109, can you share a simple project that demonstrates the problem you are seeing?

And FWIW, while you may not be able to reproduce the problem with @State, it would be best to use @ObservedObject since that is what TCA uses under the hood. We often find bugs in vanilla SwiftUI that affect observable objects but not @State.

fabstu commented 1 year ago

i think @State bindings are special in the sense that downstream native views can actually only consume the binding change instead of all views in the closure being re-evaluated completely. ObservedObject just seems to re-evaluate everything? Not sure. When you consume the Binding in a way SwiftUI cannot observe (or at least does not know whether only a native special view consumes the binding), the whole closure using @State might still be re-evaluated anyway (so avoid that for selection:).

Anyway, maybe try this wrapper (https://github.com/pointfreeco/swift-composable-architecture/discussions/1461#discussioncomment-3841433). It pipes changes to a State variable, from which we take a binding to feed into selection:. You might have to adapt it a bit to work with the newer TCA version (for example observe: get instead of scope.

mbrandonw commented 1 year ago

Hi @aj2109, I am going to close this since there hasn't been much activity. Feel free to start a discussion if you want to share more details.