I have a horizontal ScrollView in SwiftUI that I would like to 'dynamically' center. the content within the scrollview will be a dynamic chart, and the user can change the width of it by changing years (5, 10, 15 year chart grows horizontally).
Anyway I have two problems:
My content in the horizontal scrollview seems to be aligned left, and any attempt at centering it with padding is just leaving it off centered and makes it difficult to account for various screen sizes.
When I try using a GeometryReader, my chart's 'legend' at the bottom gets shoved to the bottom of the page, Spacer() doesn't fix it.
struct ChartView: View {
var body: some View {
VStack(alignment: .center) {
GeometryReader { geo in
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(0..<self.chartSettings.getChartYears(), id:\.self) { index in
ColumnView()
}
}
}
.background(Color.gray)
.frame(maxHeight: geo.size.height/2)
}
//Chart Legend
HStack{
Rectangle()
.frame(width: 10, height: 10)
.foregroundColor(Color.init(#colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1)))
Text("First Value")
.font(.system(size: 12))
Rectangle()
.frame(width: 10, height: 10)
.foregroundColor(Color.init(#colorLiteral(red: 0.2263821661, green: 0.4659538441, blue: 0.08977641062, alpha: 1)))
Text("Second Value")
.font(.system(size: 12))
}
}
}
}
Left is what it looks like with current code and right is what I'm hoping it looks like regardless of screen size/iPhone model.

Note This is the reason I'm using a scrollview:
<iframe src='https://gfycat.com/ifr/DeficientNiceInchworm' frameborder='0' scrolling='no' allowfullscreen width='400' height='210'></iframe>
In order to correctly center your chart in the ScrollView, set the minimum width of the content to be the same as the width of the ScrollView itself. To do this, call .frame(minWidth: geo.size.width) on the HStack inside of your ScrollView. A full explanation of this solution can be found here.
The reason the legend gets pushed to the bottom of the screen is that GeometryReader is itself a view that expands to fill all the available space. There are two solutions that I can think of that will solve this issue.
If the height of the chart is static, or if you know what the height will be before you render your views, you can use that known dimension to set the height of the GeometryReader.
struct ChartView: View {
private let chartHeight: CGFloat = 300 // declare a height parameter
var body: some View {
VStack(alignment: .center) {
GeometryReader { geo in
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(0..<self.chartSettings.getChartYears(), id:\.self) { index in
ColumnView()
}
}
.frame(minWidth: geo.size.width)
}
.background(Color.gray)
}
.frame(height: chartHeight) // set the height of the GeometryReader
//Chart Legend
HStack{
Rectangle()
.frame(width: 10, height: 10)
.foregroundColor(Color.init(#colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1)))
Text("First Value")
.font(.system(size: 12))
Rectangle()
.frame(width: 10, height: 10)
.foregroundColor(Color.init(#colorLiteral(red: 0.2263821661, green: 0.4659538441, blue: 0.08977641062, alpha: 1)))
Text("Second Value")
.font(.system(size: 12))
}
}
}
}
If you want the height of the chart to be dynamic and allow the ScrollView to determine its height based on the content, you can use Spacers() and call the .layoutPriority(0) method on them so that the GeometryReader and spacers are given equal priority when laying out their views. With this solution, the GeometryReader will only place a small default padding around the ScrollView.
Optionally, you can use a negative padding value if you want the legend to be closer to the ScrollView.
struct ChartView: View {
var body: some View {
VStack(alignment: .center) {
Spacer().layoutPriority(0) // add a spacer above
GeometryReader { geo in
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(0..<self.chartSettings.getChartYears(), id:\.self) { index in
ColumnView()
}
}
.frame(minWidth: geo.size.width)
}
.background(Color.gray)
}
//Chart Legend
HStack{
Rectangle()
.frame(width: 10, height: 10)
.foregroundColor(Color.init(#colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1)))
Text("First Value")
.font(.system(size: 12))
Rectangle()
.frame(width: 10, height: 10)
.foregroundColor(Color.init(#colorLiteral(red: 0.2263821661, green: 0.4659538441, blue: 0.08977641062, alpha: 1)))
Text("Second Value")
.font(.system(size: 12))
}
.padding(.top, -20) // optionally move the legend closer to the ScrollView
Spacer().layoutPriority(0) // add a spacer below
}
}
}
Move GeometryReader outside of VStack:
struct ChartView: View {
var body: some View {
GeometryReader { geo in
VStack(alignment: .center) {
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(0..<self.chartSettings.getChartYears(), id:\.self) { index in
ColumnView()
}
}
}
.background(Color.gray)
.frame(maxHeight: geo.size.height/2)
//Chart Legend
HStack{
Rectangle()
.frame(width: 10, height: 10)
.foregroundColor(Color.init(#colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1)))
Text("First Value")
.font(.system(size: 12))
Rectangle()
.frame(width: 10, height: 10)
.foregroundColor(Color.init(#colorLiteral(red: 0.2263821661, green: 0.4659538441, blue: 0.08977641062, alpha: 1)))
Text("Second Value")
.font(.system(size: 12))
}
}
}
}
}
Note: horizontal scroll view content always start from left (it is not alignment). If you want just center HStack on screen - remove ScrollView.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With