Comments (7)
Hi @lukeredpath, can you please provide an updated project with the minimal reproducible code? I'm not sure what to update based on your new messages above.
from swift-composable-architecture.
The above example project targets iOS 17, but just to rule other things out I also tried:
- Pure SwiftUI using an
@Observable
model instead of@State
. Works normally. - Changing the target to iOS 16.4 and using a
@Perceptible
model andWithPerceptionTracking
. Works normally on both 17.2 and 16.4 simulators.
So this seems to narrow it down somehow to the TCA/perception integration with @Presents
but I'm not sure how, as that is using the sheet(item:)
API which is built in to SwiftUI.
from swift-composable-architecture.
One more test, I updated the SwiftUI example to use sheet(item:)
instead of sheet(isPresented:)
and this still works fine:
@main
struct PerceptionTextViewBugApp_SwiftUI: App {
@State
var store = Store(initialState: ComposeFeature.State()) {
ComposeFeature()
}
@State
var destination: Destination?
enum Destination: Identifiable {
case compose
var id: Self { self }
}
var body: some Scene {
WindowGroup {
Text("SwiftUI App")
Button("Open Compose") {
destination = .compose
}
.sheet(item: $destination) { _ in
ComposeView(store: store)
}
}
}
}
from swift-composable-architecture.
It seems that the presentation API is a bit of a red-herring. The bug only seems to happen when the root view holds on to a store of some ObservableState
.
With that in mind, I seem to have narrowed this down to the most minimal reproducer:
@Reducer
struct RootFeature: Reducer {
// @ObservableState
struct State: Equatable {
var compose = ComposeFeature.State()
}
enum Action {
case compose(ComposeFeature.Action)
}
var body: some ReducerOf<Self> {
Scope(state: \.compose, action: \.compose) {
ComposeFeature()
}
}
}
@main
struct PerceptionTextViewBugApp_ObservableSwiftUI: App {
@State
var store = Store(initialState: RootFeature.State()) {
RootFeature()._printChanges()
}
@State
var isPresented: Bool = false
var body: some Scene {
WindowGroup {
Text("Observable TCA + SwiftUI App")
Button("Open Compose") {
isPresented = true
}
.sheet(isPresented: $isPresented) {
ComposeView(store: store.scope(state: \.compose, action: \.compose))
}
}
}
}
You can run the above example and see that the text view works as expected if you uncomment out @ObservableState
, it stops working.
from swift-composable-architecture.
Some good news - now I have a minimal reproducer, I have found two possible workarounds for this bug:
- Adding
@ObservationStateIgnored
to thevar compose: ComposeFeature.State
property in the root feature state fixes the problem. - Presenting a version of
ComposeFeature
that uses observation also fixes the problem.
I'm not sure if there are other side effects of the first workaround and I'm not sure if it would be helpful in the case where you're using @Presents
with a Destination
enum where some cases are observable and some are not.
The second workaround at least gives me a potential fix for this, although it requires me to put more effort into migrating more leaf features of my app to use @ObservableState
than I had planned.
I've added an updated demo here:
https://gist.github.com/lukeredpath/1657db6c28cf2edf9868f0f9d19dd821
from swift-composable-architecture.
Here's an updated project with only the relevant code.
Firstly, I was wrong about iOS 16 - turns out this bug does not affect iOS 16. It only affects iOS 17 below 17.4 (although I have not been able to test on 17.0 or 17.1).
In the project attached:
- If you run on iOS 17.4, both the compose and observable compose sheets work correctly and you should be able to type.
- If you run in the 17.3 or 17.2 simulator, the observable compose works OK, but the normal compose does not work, unless you uncomment the
@ObservationStateIgnored
macro.
Out of curiosity, I also tried swapping out the the text state binding in ComposeView
from the view store binding to a binding to some local @State var textState
property. This caused every other letter to appear. I then swapped out the viewStore.send(.selectionChanged($0))
in the selection changed handler to directly mutate the local @State var
and after that change it worked normally. So it appears to be more precisely, some kind of weird interaction between the representable view and view store bindings (or the view store in general) when presented from an observable feature.
from swift-composable-architecture.
I think this is a problem with over-rendering and UITextField
/ representables not being able to handle the over-renders. Because the parent is modern and the child is legacy, the parent feature is going to over-render a bit. You can put a print inside the WithPerceptionTracking
of the .sheet(item:)
to see that it prints with each key stroke.
Typically SwiftUI can deal with over-renders just fine, but there are a few key areas that it gets overwhelmed. Navigation is the biggest example, but this is another example (or maybe the root cause of the problem is the same in both situations).
However, the solution is much simpler. It is not necessary to wrap your store.scope
in the sheet in WithPerceptionTracking
. That is only necessary when scoping to observable features, and since \.compose
is not an observable feature, there is nothing to observe.
So, just making this change fixes it:
.sheet(item: $destination) { destination in
switch destination {
case .compose:
// ❗️ No `WithPerceptionTracking` here
ComposeView(store: Self.store.scope(state: \.compose, action: \.compose))
case .observableCompose:
WithPerceptionTracking {
ObservableComposeView(store: Self.store.scope(state: \.observableCompose, action: \.observableCompose))
}
}
}
By putting WithPerceptionTracking
around ComposeView
you are observing all changes in ComposeFeature
, and since it's not an observable feature it means any change (no matter what) causes a view render.
Since this isn't an issue with the library I am going to convert it to a discussion.
from swift-composable-architecture.
Related Issues (20)
- Key path cannot refer to static member '....' Error HOT 1
- TCA holds on to state for longer than expected HOT 1
- Nested enum reducer error: cannot be constructed because it has no accessible initializers HOT 3
- ForEach<_StoreCollection<String, State, Action>, ObjectIdentifier, SingleTaskView>: the ID ObjectIdentifier(0x0000600003fe8b40) occurs multiple times within the collection, this will give undefined results! HOT 2
- Exhaustive testing @Shared variable mutating before action received HOT 2
- Extra Perception Warning HOT 1
- Non-main thread warning when calling using .refreshable from child @ViewBuilder property HOT 1
- Focus state doesn't work as described in the Building SyncUps Tutorial
- UUID creation not working as described in the SyncUps Tutorial HOT 2
- FileStorage persistence sometimes doesn't save data to disk. HOT 1
- Some texts not appearing correctly: body-8lumc
- SharedState with UserDefaults(suiteName:) not working properly HOT 3
- NSLock Memory leaks HOT 1
- CRASH: Could not cast value of type 'Observation.ObservationRegistrar' to 'Perception._PerceptionRegistrar' HOT 2
- SyncUps Tutorial: Removing attendee test does not pass HOT 1
- isPresented dependency: Incorrectly set to 'true' in tests. HOT 7
- Using both Sharing Data and backward-compatible NavigationLink seems to create some strange behaviors. HOT 1
- Incorrect Confirmation Dialog Presentation on iPad HOT 1
- Ios 17 TextField Binding Issue HOT 1
- Stack overflow crash on physical device HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from swift-composable-architecture.