How to only disable scroll in ScrollView but not content view?

I tried using the accepted answer provided by Rob Mayoff but the content offset being reset is a frustrating drawback to the suggested approach.

Using the Introspect library (which I would recommend for anyone looking for UIKit level controls for SwiftUI APIs - whilst we wait for SwiftUI 2 at WWDC 2020) - you can access the underlying isScollEnabled boolean flag of the underlying UIScrollView that powers SwiftUI's ScrollView and set is accordingly. Changing the isScollEnabled has no impact on contentOffset and works exactly like it should.

It can be implemented as simply as this:

struct TestView: View {
    @Binding var shouldScroll: Bool

    var body: some View {
        ScrollView {
            Text("Your content here").introspectScrollView { scrollView in
                scrollView.isScrollEnabled = shouldScroll
            }
        }
    }
}

It really is that simple with the Introspect library!


Only pass .horizontal as the scroll axis if you want the view to scroll. Otherwise, pass the empty set.

struct TestView: View {
    @Binding var shouldScroll: Bool

    var body: some View {
        ScrollView(axes, showsIndicators: false) {
            Text("Your content here")
        }
    }

    private var axes: Axis.Set {
        return shouldScroll ? .horizontal : []
    }
}

I keep coming back to this question and have not been satisfied with any of the answers here. What I found has worked is to create a custom wrapper that evaluates whether a ScrollView should be used or not. It was surprisingly easier than I thought.

Creating a preventable scroll view

Essentially we want a custom view where scrolling can be toggled. Using a binding, anyone that uses our view will have full control over the scrolling function.

Forgive my naming but PreventableScrollView was all I could come up with :)

struct PreventableScrollView<Content>: View where Content: View {
    @Binding var canScroll: Bool
    var content: () -> Content
    
    var body: some View {
        if canScroll {
            ScrollView(.vertical, showsIndicators: false, content: content)
        } else {
            content()
        }
    }
}

Essentially all we need to do is decide whether to embed the content in a ScrollView or just return it directly. Pretty straightforward.

I created a demo to showcase how this might work with dynamic content and the ability to toggle anytime. As I add items it also toggles scrolling.

Here is the code if you are interested.

struct DemoView: View {
    @State private var canScroll: Bool = false
    let allColors: [UIColor] = [.purple, .systemPink, .systemGreen, .systemBlue, .black, .cyan, .magenta, .orange, .systemYellow, .systemIndigo, .systemRed]
    @State var colors: [UIColor] = [.white]
    
    var body: some View {
        VStack {
            Spacer()
            VStack {
                PreventableScrollView(canScroll: $canScroll) {
                    ForEach(colors.indices, id: \.self) { index in
                        Rectangle().fill().foregroundColor(Color(colors[index]))
                            .frame(height: 44)
                            .cornerRadius(8)
                    }
                }.fixedSize(horizontal: false, vertical: true)
                HStack {
                    Spacer()
                    Toggle(isOn: $canScroll, label: {
                        Text("Toggle Scroll")
                    }).toggleStyle(SwitchToggleStyle(tint: .orange))
                }.padding(.top)
            }
            .textFieldStyle(RoundedBorderTextFieldStyle())
            .padding()
            .background(Rectangle().fill().foregroundColor(Color(UIColor.secondarySystemFill)).cornerRadius(8))
            HStack {
                Button(action: {
                    addField()
                }, label: {
                    Image(systemName: "plus")
                }).padding()
            }
        }
            .padding()
    }
    
    func addField() {
        canScroll.toggle()
        colors.append(allColors.randomElement()!)
    }
}

Tags:

Ios

Swift

Swiftui