Swiftui: Make Scrollview Scrollable Only If It Exceeds the Height of the Screen

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

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

How to make an item in ScrollView take up the height of the entire screen in SwiftUI?

In your current code, the ScrollView will take up all available space in one screen. Then, the DetailView will only take up as much space as it needs. Even with the Spacers, it won't expand vertically because the ScrollView isn't imposing a size constraint on it.

In order to get the DetailViews to be the size of the screen, you can use a GeometryReader and then pass down the height to the child views:

struct ContentView : View {
var body: some View {
GeometryReader { proxy in
ScrollView {
VStack(spacing: 0) {
DetailView().frame(height: proxy.size.height)
DetailView().frame(height: proxy.size.height)
}
}
}.ignoresSafeArea()
}
}

struct DetailView: View {
var body: some View {
VStack {
Spacer()
Text("This should take up the height of the entire screen.")
Spacer()
}
//.frame(maxWidth: .infinity) //add this if you want the view to extend to the horizontal edges
.background(Color.blue)
}
}

Fill height in scrollView in swiftUI

Here is a demo of possible approach based on view preferences (ViewHeightKey is taken from my this solution. Tested with Xcode 12 / iOS 14.

demo

struct DemoLayoutInScrollView: View {
@State private var height1: CGFloat = .zero
@State private var height2: CGFloat = .zero
var body: some View {
VStack(spacing: 0) {
HeaderView()

GeometryReader { gp in
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 0) {
FriendsHorizontalScrollView()
.background(GeometryReader {
Color.clear
.preference(key: ViewHeightKey.self, value: $0.frame(in: .local).size.height)
})
.onPreferenceChange(ViewHeightKey.self) { self.height1 = $0 }
VStack(spacing: 0) {
// I want this view to take all the remaining height left in the scrollView
FullHeightView()
.background(GeometryReader {
Color.clear
.preference(key: ViewHeightKey.self, value: $0.frame(in: .local).size.height)
})
}.frame(height: max(gp.size.height - self.height1, self.height2))
.background(Color.yellow)
}
}
.onPreferenceChange(ViewHeightKey.self) { self.height2 = $0 }
}
}.frame(maxWidth: .infinity)
}
}

replicated helper views (for testing)

struct FriendsHorizontalScrollView: View {
var body: some View {
Text("Horizontal Scroller")
.frame(maxWidth: .infinity)
.frame(height: 100)
.background(Color.green)
}
}

struct FullHeightView: View {
var body: some View {
ZStack {
Color.red
Text("Dynamic Content")
}
.frame(maxWidth: .infinity)
.frame(height: 300)
}
}

struct HeaderView: View {
var body: some View {
Rectangle().foregroundColor(.blue)
.frame(height: 60)
.overlay(Text("Header"))
}
}

SwiftUI: Put List into ScrollView (get List's content height)

Table contentSize is calculated based on the cells that already appear on the screen. It is assumed that the size of the remaining cells will be the same as the previous ones.

You can see how the scroll indicator changes as you scroll: it may decrease or increase depending on the size of the new cells. In my test, I use a cell height that depends on the index to show this behavior.

In its turn introspectTableView is called only once when creating the view: you can examine the source code.

I suggest you use the key path observing in this case:

struct ContentView: View {
@State
private var observation: NSKeyValueObservation?

var body: some View {
List {
ForEach(0..<100) { i in
Text(String(i))
.frame(height: CGFloat(i))
}
}.introspectTableView { tableView in
print("initial size", tableView.contentSize)
observation = tableView.observe(\.contentSize) { tableView, value in
print("new size", tableView.contentSize)
}
}
}
}


Related Topics



Leave a reply



Submit