Mr Duck
Mr Duck

Reputation: 195

SwiftUI getting ScrollViewReader to scroll when using Swift structures and id's

I've got a couple of simple Swift UI screens all running of a structure. The structure defines a widget's view, its name, and the order it goes in. I'm trying to make a horizontal list where each button, when pressed, centres itself in the scroll view. This is what I'm trying to make:

What I'm trying to make

The probelm that I've having is that I can't get ScrollViewReader to move the scoll view in to the corect position. It think that this has something to do with the ids, but I'm not sure. I'm very new to SwiftUI, and this is proabbly a very basic question, but any help is appreciated!

And here's my structure

struct WidgetView: Identifiable, Hashable{
    static func == (lhs: WidgetView, rhs: WidgetView) -> Bool {
        return lhs.id == rhs.id && lhs.friendlyName == rhs.friendlyName && lhs.order == rhs.order && lhs.selected == rhs.selected
    }
    
    let id = UUID()
    var friendlyName: String
    var view: Any
    var order: Int
    var selected: Bool
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

extension WidgetView {
    static var data: [WidgetView] {
        [
            WidgetView(friendlyName: "News", view: MusicView(), order: 1, selected: false),
            WidgetView(friendlyName: "Music", view: MusicView(), order: 2, selected: false),
            WidgetView(friendlyName: "Ambiance", view: AmbianceView(), order: 3, selected: true),
            WidgetView(friendlyName: "Weather", view: WeatherView(), order: 4, selected: false),
            WidgetView(friendlyName: "Routines", view: MusicView(), order: 5, selected: false)
        ]
    }
}

And here are my two SwiftUI views, the first one being just the little text box, the second being the one with scroll view thats not working.

struct WidgetName: View {
    let widget: WidgetView
    
    var body: some View {
        ZStack(alignment: .center) {
            Color.clear
            Button(action: {
                print("Hello")
            }){
                ZStack{
                    Text(widget.friendlyName)
                        .fontWeight(.semibold)
                        .opacity(0)
                        .padding(widget.selected ? 15: 12)
                        .overlay(
                            RoundedRectangle(cornerRadius: 15)
                                    .fill(Color.white)
                                .opacity(widget.selected ? 1: 0.5)
                        )
                    Text(widget.friendlyName)
                        .fontWeight(.semibold)
                        .foregroundColor(.black)
                        .opacity(widget.selected ? 1: 0.5)
                }
            }
        }.padding()
    }
}

struct WidgetName_Previews: PreviewProvider {
    static var previews: some View {
        WidgetName(widget: WidgetView.data[1])
            .previewLayout(.fixed(width: 400, height: 60))
            .background(Color.purple)
        WidgetName(widget: WidgetView.data[2])
            .previewLayout(.fixed(width: 400, height: 60))
            .background(Color.purple)
        WidgetName(widget: WidgetView.data[3])
            .previewLayout(.fixed(width: 400, height: 60))
            .background(Color.purple)
    }
    
}
    struct WidgetNameStrip: View {
        var widgets: [WidgetView]
    
        var body: some View {
            VStack{
                Spacer()
                ScrollView(.horizontal, showsIndicators: false) {
                    
                    ScrollViewReader { value in
                        
                        Button("Jump to #32") {
                            value.scrollTo(3)
                        }
                        .foregroundColor(.white)
                        
                        HStack(spacing: 10) {
                            ForEach(widgets, id: \.self) { widget in
                                WidgetName(widget: widget)
                            }
                        }
                        
                    }
                    
                }
                .frame(height: 80)
            }
        }
        
    }
    
    struct WidgetNameStrip_Previews: PreviewProvider {
        static var previews: some View {
            WidgetNameStrip(widgets: WidgetView.data)
                .background(Color.purple)
                .previewDevice("iPhone 8")
        }
    }



Upvotes: 3

Views: 2637

Answers (2)

Hackinator
Hackinator

Reputation: 162

Building on @Asperi's response, you can also use the anchor parameter on the .scrollTo after you establish the id.

Anchor controls where the selected item is positioned inside of the scroll view once it is scrolled to.

Button("Jump to #32") {
  value.scrollTo(id, anchor: .center)
}

You can animate it by using withAnimation

withAnimation { value.scrollTo(3, anchor: .center) }

Upvotes: 1

Asperi
Asperi

Reputation: 258541

For your code, as I see, it needs to use order as id for view, because scrollTo uses id/s to find a view, ie.:

Button("Jump to #32") {
    value.scrollTo(3)
}
.foregroundColor(.white)

HStack(spacing: 10) {
    ForEach(widgets, id: \.self) { widget in
        WidgetName(widget: widget)
            .id(widget.order)     // << here !!
    }
}

Upvotes: 2

Related Questions