Binding for scrollPosition not working when using ForEach
Closed this issue · 1 comments
Rokas91 commented
Description
Binding for scrollPosition
is not working when using ForEach
, but it works using ForEachStore
.
Checklist
- I have determined whether this bug is also reproducible in a vanilla SwiftUI project.
- If possible, I've reproduced the issue using the
main
branch of this package. - This issue hasn't been addressed in an existing GitHub issue or discussion.
Expected behavior
scrollPosition
should update binding attribute.
Actual behavior
Binding attribute is not updated.
Steps to reproduce
@Reducer
struct ItemFeature {
@ObservableState
struct State: Identifiable, Equatable {
var id: UUID = .init()
}
enum Action: Equatable {}
var body: some ReducerOf<Self> {
EmptyReducer()
}
}
@Reducer
struct ListFeature {
@ObservableState
struct State: Equatable {
var items: IdentifiedArrayOf<ItemFeature.State> = .init(uniqueElements: [.init(), .init(), .init(), .init(), .init()])
var scrolledID: ItemFeature.State.ID?
}
enum Action: BindableAction, Equatable {
case binding(BindingAction<State>)
case items(IdentifiedActionOf<ItemFeature>)
}
var body: some ReducerOf<Self> {
BindingReducer()
Reduce { state, action in
switch action {
case .binding(\.scrolledID):
// Not called.
return .none
default:
return .none
}
}
.forEach(\.items, action: \.items, element: ItemFeature.init)
}
}
struct ListView: View {
@Bindable var store: StoreOf<ListFeature>
var body: some View {
ScrollView {
LazyVStack {
ForEach(store.scope(state: \.items, action: \.items)) { store in
Text(store.id.uuidString)
.frame(height: 500)
}
}
.scrollTargetLayout()
}
.scrollPosition(id: $store.scrolledID)
}
}
Replace ForEach
with ForEachStore
and binding for scroll position will work.
The Composable Architecture version information
1.8.2
Destination operating system
iOS 17.2
Xcode version information
15.2
Swift Compiler version information
No response
mbrandonw commented
Hi @Rokas91, this is mentioned in the migration guide for 1.7, but using ForEach
with a $store
binding uses the child stores for the identity of each row, not the identity of the state in the store. This means your scrollPosition
is a completely different type than the underlying identity of the ForEach
.
To fix you must use an explicit id
key path:
ForEach(
store.scope(state: \.items, action: \.items),
id: \.state.id
) { store in
}
Since this is not an issue with the library I am going to convert it to a discussion.