Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI: Make ScrollView scrollable only if it exceeds the height of the screen

Currently I have a view that looks like this.

struct StatsView: View {
    var body: some View {
        ScrollView {
            Text("Test1")
            Text("Test2")
            Text("Test3")
        }
    }
}

This renders a view that contains 3 texts inside a scroll view, whenever I drag any of these texts in the screen the view will move cause its scrollable, even if these 3 texts fit in the screen and there is remaining space. What I want to achieve is to only make the ScrollView scrollable if its content exceeds the screen height size, if not, I want the view to be static and don't move. I've tried using GeometryReader and setting the scrollview frame to the screen width and height, also the same for the content but I continue to have the same behaviour, also I have tried setting the minHeight, maxHeight without any luck.

How can I achieve this?

like image 436
ravelinx Avatar asked Jun 19 '20 04:06

ravelinx


2 Answers

For some reason I could not make work any of the above, but it did inspire me find a solution that did in my case. It's not as flexible as others, but could easily be adapted to support both axes of scrolling.

import SwiftUI

struct OverflowContentViewModifier: ViewModifier {
    @State private var contentOverflow: Bool = false
    
    func body(content: Content) -> some View {
        GeometryReader { geometry in
            content
            .background(
                GeometryReader { contentGeometry in
                    Color.clear.onAppear {
                        contentOverflow = contentGeometry.size.height > geometry.size.height
                    }
                }
            )
            .wrappedInScrollView(when: contentOverflow)
        }
    }
}

extension View {
    @ViewBuilder
    func wrappedInScrollView(when condition: Bool) -> some View {
        if condition {
            ScrollView {
                self
            }
        } else {
            self
        }
    }
}

extension View {
    func scrollOnOverflow() -> some View {
        modifier(OverflowContentViewModifier())
    }
}

Usage

VStack {
   // Your content
}
.scrollOnOverflow()
like image 62
user16401900 Avatar answered Oct 23 '22 19:10

user16401900


Here is a possible approach if a content of scroll view does not require user interaction (as in PO question):

Tested with Xcode 11.4 / iOS 13.4

struct StatsView: View {
    @State private var fitInScreen = false
    var body: some View {
        GeometryReader { gp in
            ScrollView {
                VStack {          // container to calculate total height
                    Text("Test1")
                    Text("Test2")
                    Text("Test3")
                    //ForEach(0..<50) { _ in Text("Test") } // uncomment for test
                }
                .background(GeometryReader {
                    // calculate height by consumed background and store in 
                    // view preference
                    Color.clear.preference(key: ViewHeightKey.self,
                        value: $0.frame(in: .local).size.height) })
            }
            .onPreferenceChange(ViewHeightKey.self) {
                 self.fitInScreen = $0 < gp.size.height    // << here !!
            }
            .disabled(self.fitInScreen)
        }
    }
}

Note: ViewHeightKey preference key is taken from this my solution

like image 19
Asperi Avatar answered Oct 23 '22 19:10

Asperi