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()!)
}
}