Reputation: 589
OUTLINE
I have 2 views, the first (view1) contains a HStack
and an @ObservableObject
. When the user selects a row from the HStack
the @ObservableObject
is updated to the string name of the row selected.
In view2 I have the same HStack
as the first HStack
in view1. This HStack
observes @ObservableObject
and desaturates all other rows except the one that matches the @ObservableObject
.
PROBLEM
The HStack
list in view2 is wider than the page so I would like to automatically scroll to the saturated/selected row when the view appears. I'm not totally sure how to use ScrollTo
as it needs an integer and I am only storing/observing the string name.
VIEW 1
class selectedApplication: ObservableObject {
@Published var selectedApplication = "application1"
}
struct view1: View {
@ObservedObject var selectedOption = selectedApplication()
var applications = ["application1", "application2", "application3", "application4", "application5", "application6", "application7", "application8", "application9", "application10"]
var body: some View {
VStack{
ScrollView(.horizontal){
HStack{
ForEach(applications, id: \.self) { item in
Button(action: {
self.selectedOption.selectedApplication = item
}) {
VStack(alignment: .center){
Text(item)
}
}
}
}
}
}
}
}
View2:
struct View2: View {
@ObservedObject var application: selectedApplication
var applications = ["application1", "application2", "application3", "application4", "application5", "application6", "application7", "application8", "application9", "application10"]
var body: some View {
HStack{
ScrollView(.horizontal, showsIndicators: false) {
ScrollViewReader{ scroll in
HStack{
ForEach(applications, id: \.self) { item in
Button(action: {
application.selectedApplication = item
}) {
Text(item)
.saturation(application.selectedApplication == item ? 1.0 : 0.05)
}
}
}
}
}
}
}
}
Upvotes: 0
Views: 2340
Reputation: 52387
You can use ScrollViewReader
with the id
that's being applied in the ForEach
, so you don't actually need the row index number (although that, too, is possible to get, if you needed to, either by using enumerated
or searching the applications
array for the index of the item.
Here's my updated code:
struct ContentView : View {
@ObservedObject var selectedOption = SelectedApplicationState()
var body: some View {
VStack {
View1(application: selectedOption)
View2(application: selectedOption)
}
}
}
class SelectedApplicationState: ObservableObject {
@Published var selectedApplication = "application1"
var applications = ["application1", "application2", "application3", "application4", "application5", "application6", "application7", "application8", "application9", "application10"]
}
struct View1: View {
@ObservedObject var application: SelectedApplicationState
var body: some View {
VStack{
ScrollView(.horizontal){
HStack{
ForEach(application.applications, id: \.self) { item in
Button(action: {
self.application.selectedApplication = item
}) {
VStack(alignment: .center){
Text(item)
}
}
}
}
}
}
}
}
struct View2: View {
@ObservedObject var application: SelectedApplicationState
var body: some View {
HStack{
ScrollView(.horizontal, showsIndicators: false) {
ScrollViewReader{ scroll in
HStack{
ForEach(application.applications, id: \.self) { item in
Button(action: {
application.selectedApplication = item
}) {
Text(item)
.saturation(application.selectedApplication == item ? 1.0 : 0.05)
}
}
}.onReceive(application.$selectedApplication) { (app) in
withAnimation {
scroll.scrollTo(app, anchor: .leading)
}
}
}
}
}
}
}
How it works:
SelectedApplicationState
(which was your selectionApplication
ObservableObject (by the way, it is common practice to capitalize your type names -- that's why I changed the name. Plus, it was confusing to have a type and a property of that type with such a similar name) and passes it to both views.SelectedApplicationState
now holds the applications
array, since it was being duplicated across views anywayselectedApplication
in the ObservableObject is set, triggering onReceive
in View2
ScrollViewReader
is told to scroll to the item with the id stored in selectedApplication
, which is passed to the onReceive
closure as app
In the event that these views are on separate pages, the position of View2 will still get set correctly once it is navigated to, because onReceive
will fire on first load and set it to the correct position. The only requirement is passing that instance of SelectedApplicationState
around.
Upvotes: 3