There is a similar question here on stackoverflow for how to do it when they are all the same size but not when they are different sizes,I have 4 views in a HStack that I want to evenly space out but since the Picker is larger than the other 3 its impossible to do it using Spacer and since there are now 4 instead of 3 I cannot use alignment anymore either. What is the solution for ensuring they are all evenly spaced out?
import SwiftUI
@EnvironmentObject var captureDelegate: CaptureDelegate
let images = ["person.slash.fill", "person.fill", "person.2.fill", "person.2.fill", "person.2.fill"]
@Binding var timerPress: Bool
private var rotationAngle: Angle {
switch captureDelegate.orientationLast {
case .landscapeRight:
return .degrees(90)
case .landscapeLeft:
return .degrees(-90) // Fixes the upside-down issue
default:
return .degrees(0)
}
}
var body: some View {
HStack() {
Image(systemName: "gearshape")
.resizable()
.frame(width: 25, height: 25)
.foregroundStyle(captureDelegate.cameraPressed ? Color(white: 0.4) : .white )
.disabled(captureDelegate.cameraPressed)
.rotationEffect(rotationAngle)
.frame(maxWidth: .infinity, alignment: .leading)
.onTapGesture {
if !captureDelegate.cameraPressed {
}
}
Image(systemName: "timer")
.resizable()
.frame(width: 25, height: 25)
.foregroundStyle(captureDelegate.cameraPressed ? Color(white: 0.4) : .white )
.disabled(captureDelegate.cameraPressed)
.rotationEffect(rotationAngle)
.frame(maxWidth: .infinity)
.onTapGesture {
if !captureDelegate.cameraPressed {
timerPress.toggle()
}
}
Image(systemName: "timer")
.resizable()
.frame(width: 25, height: 25)
.foregroundStyle(captureDelegate.cameraPressed ? Color(white: 0.4) : .white )
.disabled(captureDelegate.cameraPressed)
.rotationEffect(rotationAngle)
.frame(maxWidth: .infinity)
.onTapGesture {
if !captureDelegate.cameraPressed {
timerPress.toggle()
}
}
Picker("Pick a number of people", selection: $captureDelegate.userSelectedNumber) {
ForEach(0...4, id: \.self) { i in
HStack(spacing: 70) {
Image(systemName: self.images[i])
.resizable()
.frame(width: 20, height: 20)
.rotationEffect(rotationAngle)
Text("\(i)")
.font(.system(size: 42))
.rotationEffect(rotationAngle)
}.tag(i)
.rotationEffect(rotationAngle)
}
}
.tint(.white)
.clipped()
.foregroundStyle(captureDelegate.cameraPressed ? Color(white: 0.4) : .white )
.disabled(captureDelegate.cameraPressed)
.rotationEffect(rotationAngle)
.animation(.easeInOut(duration: 0.5), value: rotationAngle)
.frame(maxWidth: .infinity, alignment: .trailing)
}
.font(.system(size: 24))
.padding([.leading,.trailing], 15)
}
}
I have tried using Spacer but it doesnt work because they are not all the same size, when I had 3 views I had success using alignment leading center and trailing but now that there are 4 views it no longer works.
Here I have attempted to use a ZStack to position them putting the two center views in the same HStack:
ZStack {
HStack {
Image(systemName: "gearshape")
.resizable()
.frame(width: 25, height: 25)
.foregroundStyle(captureDelegate.cameraPressed ? Color(white: 0.4) : .white )
.disabled(captureDelegate.cameraPressed)
.rotationEffect(rotationAngle)
.frame(maxWidth: .infinity, alignment: .leading)
.onTapGesture {
if !captureDelegate.cameraPressed {
}
}
Picker("Pick a number of people", selection: $captureDelegate.userSelectedNumber) {
ForEach(0...4, id: \.self) { i in
HStack(spacing: 70) {
Image(systemName: self.images[i])
.resizable()
.frame(width: 20, height: 20)
.rotationEffect(rotationAngle)
Text("\(i)")
.font(.system(size: 42))
.rotationEffect(rotationAngle)
}.tag(i)
.rotationEffect(rotationAngle)
}
}
.tint(.white)
.clipped()
.foregroundStyle(captureDelegate.cameraPressed ? Color(white: 0.4) : .white )
.disabled(captureDelegate.cameraPressed)
.rotationEffect(rotationAngle)
.animation(.easeInOut(duration: 0.5), value: rotationAngle)
.frame(maxWidth: .infinity, alignment: .trailing)
}
HStack {
Text("\(captureDelegate.totalPhotosToTake)")
.font(.system(size: 15))
.foregroundStyle(.white)
.fontWeight(.bold)
.padding(.horizontal, 9)
.padding(.vertical, 5)
.overlay(
RoundedRectangle(cornerRadius: 5).stroke(.white, lineWidth: 2)
)
.frame(maxWidth: .infinity, alignment: .center)
Image(systemName: "timer")
.resizable()
.frame(width: 25, height: 25)
.foregroundStyle(captureDelegate.cameraPressed ? Color(white: 0.4) : .white )
.disabled(captureDelegate.cameraPressed)
.rotationEffect(rotationAngle)
.onTapGesture {
if !captureDelegate.cameraPressed {
timerPress.toggle()
}
}
.frame(maxWidth: .infinity, alignment: .center)
}
}
.font(.system(size: 24))
.padding([.leading,.trailing], 15)
And changing the two center views alignments from center to trialing and leading produces this affect:
There is a similar question here on stackoverflow for how to do it when they are all the same size but not when they are different sizes,I have 4 views in a HStack that I want to evenly space out but since the Picker is larger than the other 3 its impossible to do it using Spacer and since there are now 4 instead of 3 I cannot use alignment anymore either. What is the solution for ensuring they are all evenly spaced out?
import SwiftUI
@EnvironmentObject var captureDelegate: CaptureDelegate
let images = ["person.slash.fill", "person.fill", "person.2.fill", "person.2.fill", "person.2.fill"]
@Binding var timerPress: Bool
private var rotationAngle: Angle {
switch captureDelegate.orientationLast {
case .landscapeRight:
return .degrees(90)
case .landscapeLeft:
return .degrees(-90) // Fixes the upside-down issue
default:
return .degrees(0)
}
}
var body: some View {
HStack() {
Image(systemName: "gearshape")
.resizable()
.frame(width: 25, height: 25)
.foregroundStyle(captureDelegate.cameraPressed ? Color(white: 0.4) : .white )
.disabled(captureDelegate.cameraPressed)
.rotationEffect(rotationAngle)
.frame(maxWidth: .infinity, alignment: .leading)
.onTapGesture {
if !captureDelegate.cameraPressed {
}
}
Image(systemName: "timer")
.resizable()
.frame(width: 25, height: 25)
.foregroundStyle(captureDelegate.cameraPressed ? Color(white: 0.4) : .white )
.disabled(captureDelegate.cameraPressed)
.rotationEffect(rotationAngle)
.frame(maxWidth: .infinity)
.onTapGesture {
if !captureDelegate.cameraPressed {
timerPress.toggle()
}
}
Image(systemName: "timer")
.resizable()
.frame(width: 25, height: 25)
.foregroundStyle(captureDelegate.cameraPressed ? Color(white: 0.4) : .white )
.disabled(captureDelegate.cameraPressed)
.rotationEffect(rotationAngle)
.frame(maxWidth: .infinity)
.onTapGesture {
if !captureDelegate.cameraPressed {
timerPress.toggle()
}
}
Picker("Pick a number of people", selection: $captureDelegate.userSelectedNumber) {
ForEach(0...4, id: \.self) { i in
HStack(spacing: 70) {
Image(systemName: self.images[i])
.resizable()
.frame(width: 20, height: 20)
.rotationEffect(rotationAngle)
Text("\(i)")
.font(.system(size: 42))
.rotationEffect(rotationAngle)
}.tag(i)
.rotationEffect(rotationAngle)
}
}
.tint(.white)
.clipped()
.foregroundStyle(captureDelegate.cameraPressed ? Color(white: 0.4) : .white )
.disabled(captureDelegate.cameraPressed)
.rotationEffect(rotationAngle)
.animation(.easeInOut(duration: 0.5), value: rotationAngle)
.frame(maxWidth: .infinity, alignment: .trailing)
}
.font(.system(size: 24))
.padding([.leading,.trailing], 15)
}
}
I have tried using Spacer but it doesnt work because they are not all the same size, when I had 3 views I had success using alignment leading center and trailing but now that there are 4 views it no longer works.
Here I have attempted to use a ZStack to position them putting the two center views in the same HStack:
ZStack {
HStack {
Image(systemName: "gearshape")
.resizable()
.frame(width: 25, height: 25)
.foregroundStyle(captureDelegate.cameraPressed ? Color(white: 0.4) : .white )
.disabled(captureDelegate.cameraPressed)
.rotationEffect(rotationAngle)
.frame(maxWidth: .infinity, alignment: .leading)
.onTapGesture {
if !captureDelegate.cameraPressed {
}
}
Picker("Pick a number of people", selection: $captureDelegate.userSelectedNumber) {
ForEach(0...4, id: \.self) { i in
HStack(spacing: 70) {
Image(systemName: self.images[i])
.resizable()
.frame(width: 20, height: 20)
.rotationEffect(rotationAngle)
Text("\(i)")
.font(.system(size: 42))
.rotationEffect(rotationAngle)
}.tag(i)
.rotationEffect(rotationAngle)
}
}
.tint(.white)
.clipped()
.foregroundStyle(captureDelegate.cameraPressed ? Color(white: 0.4) : .white )
.disabled(captureDelegate.cameraPressed)
.rotationEffect(rotationAngle)
.animation(.easeInOut(duration: 0.5), value: rotationAngle)
.frame(maxWidth: .infinity, alignment: .trailing)
}
HStack {
Text("\(captureDelegate.totalPhotosToTake)")
.font(.system(size: 15))
.foregroundStyle(.white)
.fontWeight(.bold)
.padding(.horizontal, 9)
.padding(.vertical, 5)
.overlay(
RoundedRectangle(cornerRadius: 5).stroke(.white, lineWidth: 2)
)
.frame(maxWidth: .infinity, alignment: .center)
Image(systemName: "timer")
.resizable()
.frame(width: 25, height: 25)
.foregroundStyle(captureDelegate.cameraPressed ? Color(white: 0.4) : .white )
.disabled(captureDelegate.cameraPressed)
.rotationEffect(rotationAngle)
.onTapGesture {
if !captureDelegate.cameraPressed {
timerPress.toggle()
}
}
.frame(maxWidth: .infinity, alignment: .center)
}
}
.font(.system(size: 24))
.padding([.leading,.trailing], 15)
And changing the two center views alignments from center to trialing and leading produces this affect:
Share Improve this question edited Mar 28 at 16:07 john smith asked Mar 28 at 14:12 john smithjohn smith 1516 bronze badges 3 |2 Answers
Reset to default 0Using a ZStack
is certainly one way to lay out the items. I am not sure if your solution using Spacer
is precise (especially since an HStack
adds spacing between the items), but it might still be fit for purpose.
If you want a precise solution then I think the width of the screen needs to be known.
When the width of the screen is known, the width of the middle items can be enlarged to two-thirds of the screen width (with center alignment). Then, these items can be aligned to the leading or trailing edge of the
ZStack
using a frame withmaxWidth: .infinity
(as for the first and last items).Your items are spread across the full width of the screen. So one way of finding the screen width is to use
.containerRelativeFrame
. The container in this case is the screen.Since the alignment of the middle items is based on the full screen width, you should not apply any horizontal padding to the container (now the
ZStack
). To keep the first and last items away from the sides, apply padding to these items individually.
ZStack {
Image(systemName: "gearshape")
.resizable()
.scaledToFit()
.frame(width: 25, height: 25)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.leading, 15)
Image(systemName: "timer")
.resizable()
.scaledToFit()
.frame(width: 25, height: 25)
.containerRelativeFrame(.horizontal) { screenWidth, _ in
2 * screenWidth / 3
}
.frame(maxWidth: .infinity, alignment: .leading)
Image(systemName: "timer")
.resizable()
.scaledToFit()
.frame(width: 25, height: 25)
.containerRelativeFrame(.horizontal) { screenWidth, _ in
2 * screenWidth / 3
}
.frame(maxWidth: .infinity, alignment: .trailing)
Picker("Pick a number of people", selection: $userSelectedNumber) {
ForEach(Array(images.enumerated()), id: \.offset) { i, imageName in
HStack(spacing: 70) {
Image(systemName: imageName)
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
Text("\(i)")
.font(.system(size: 42))
}
.tag(i)
}
}
.tint(.white)
.clipped()
.frame(maxWidth: .infinity, alignment: .trailing)
.padding(.trailing, 15)
}
.foregroundStyle(.white)
.font(.system(size: 24))
Visually, this still doesn't look quite right to me. But you now have more control of the spacing, so the layout can be tweaked, either by changing the fraction of the screen width for the middle items, or by changing the padding. For example:
- change the leading padding on the first item from 15 to 5
- remove the trailing padding from the last item (the
Picker
) - add horizontal padding of 10 to the
ZStack
The ZStack attempt can be made to work with the use of Spacers to achieve the 1/3 and 2/3 positioning
HStack {
Spacer()
Spacer()
Spacer()
Text("\(captureDelegate.totalPhotosToTake)")
.font(.system(size: 15))
.foregroundStyle(.white)
.fontWeight(.bold)
.padding(.horizontal, 9)
.padding(.vertical, 5)
.overlay(
RoundedRectangle(cornerRadius: 5).stroke(.white, lineWidth: 2)
)
// .frame(maxWidth: .infinity, alignment: .center)
Spacer()
Spacer()
Image(systemName: "timer")
.resizable()
.frame(width: 25, height: 25)
.foregroundStyle(captureDelegate.cameraPressed ? Color(white: 0.4) : .white )
.disabled(captureDelegate.cameraPressed)
.rotationEffect(rotationAngle)
.onTapGesture {
if !captureDelegate.cameraPressed {
timerPress.toggle()
}
}
// .frame(maxWidth: .infinity, alignment: .center)
Spacer()
Spacer()
Spacer()
}
ZStack
might be one way to do it, or wrapping in aGeometryReader
, or a customLayout
. The post How do I center these images in a straight line inside my HStack in SwiftUI might help. – Benzy Neez Commented Mar 28 at 15:53