swiftswiftuiscrollviewdisclosuregroup

How do I get a DisclosureGroup to scroll up when it is expanded and part of it goes off the screen?


I have a .sheet that launches when a button is pushed, and it displays a DisclosureGroup which has a List of items, and at the very bottom a DisclosureGroup which has some text and a Button:

struct TestView: View
{
    @State private var sheetIsPresented: Bool = false
    @State private var outerDisclosureIsExpanded: Bool = false
    @State private var innerDisclosureIsExpanded: Bool = false
    
    var body: some View
    {
        Button
        {
            self.sheetIsPresented = true
        }
        label:
        {
            Label("Add Item", systemImage: "plus")
        }
        .sheet(isPresented: self.$sheetIsPresented)
        {
            VStack
            {
                Form
                {
                    Section(header: Text("Section"))
                    {
                        DisclosureGroup("Outer Disclosure Group", isExpanded: self.$outerDisclosureIsExpanded)
                        {
                            ForEach(1...20, id: \.self)
                            {
                                index in Text("Value \(index)")
                            }
                            DisclosureGroup("Inner Disclosure Group", isExpanded: self.$innerDisclosureIsExpanded)
                            {
                                Text("Text")
                                
                                Button("Button")
                                {
                                    print("Button Pressed")
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

The .sheet seems to be a ScrollView by default because when the DisclosureGroup has a list that's longer than can be displayed on the screen, you can scroll up to see the bottom. I would like to make it so that when the inner DisclosureGroup is expanded and its items are off screen, that the ScrollView scrolls up so that the full contents of hte inner DisclosureGroup can be seen, like so:

enter image description here

enter image description here

I've tried wrapping the VStack in a ScrollViewReader with proxy and ScrollView, setting the id of the Button within the inner DisclosureGroup and doing:

withAnimation 
{
     proxy.scrollTo("Button", anchor: .top)
}

But it changed the formatting of my List and also did not work. Does anyone know how I can accomplish this?


Solution

  • As you mentioned scrolling can be achieved by using ScrollViewReader. Keeping ScrollViewReader as the outermost view does not affect the list formatting. Add id to button and use the same id in scrollTo method.

    ScrollViewReader { proxy in
        Form {
            Section(header: Text("Section")) {
                DisclosureGroup("Outer Disclosure Group", isExpanded: self.$outerDisclosureIsExpanded) {
                    ForEach(1...20, id: \.self) {
                        index in Text("Value \(index)")
                    }
                    DisclosureGroup("Inner Disclosure Group", isExpanded: self.$innerDisclosureIsExpanded) {
                        Text("Text")
                        
                        Button("Button") {
                            print("Button Pressed")
                        }
                        .id("ButtonID")
                    }
                }
            }
        }
        .onChange(of: self.innerDisclosureIsExpanded) { _, newValue in
            if innerDisclosureIsExpanded {
                withAnimation {
                    proxy.scrollTo("ButtonID")
                }
            }
        }
    }