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 Spacer
s, it won't expand vertically because the ScrollView
isn't imposing a size constraint on it.
In order to get the DetailView
s 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.
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
Coca Pod Chart Not Appearing (Swift4)
How to Disable the Network in iOS Simulator
Scale Image in an Uibutton to Aspectfit
Swift: How to Get Substring from Start to Last Index of Character
What Tool(S) How to Use to Produce iPhone App Screencasts
Xcode Suddenly Stopped Running Project on Hardware: "Could Not Launch Xxx.App: .. No Such File.."
Getting the Value of a Uitextfield as Keystrokes Are Entered
How to Xcodebuild a Static Library with Bitcode Enabled
Xcode6: Run Two Instances of the Simulator
Using Core Data, Icloud and Cloudkit for Syncing and Backup and How It Works Together
How to Add a Container View Programmatically
After Switching to Xcode 7, App Size Grew from 9 Mb to 60 Mb, Is There a Fix
Refresh Uipageviewcontroller - Reorder Pages and Add New Pages
How to Fill Uitableview with a Data from Dictionary. Swift
How to Store Push Notification Alert Message in Userdefault
Astoryboard.Instantiateviewcontrollerwithidentifier("Myid") Returns Nil But Not Nil in Lldb
Googlemaps Basic iOS Demo App Crash - Unrecognized Selector Sent to Instance