Hello there, I ran into a very strange layout issue in SwiftUI. Here's the View:
struct OnboardingGreeting: View {
/// ...
let carouselSpacing: CGFloat = 7
let carouselItemSize: CGFloat = 110
let carouselVelocities: [CGFloat] = [0.5, -0.25, 0.3, 0.2]
var body: some View {
ZStack(alignment: Alignment(horizontal: .center, vertical: .iconToCarouselBottom)) {
VStack(spacing: carouselSpacing) {
ForEach(carouselVelocities, id: \.self) { velocityValue in
InfiniteHorizontalCarousel(
albumNames: albums,
artistNames: artists,
itemSize: carouselItemSize,
itemSpacing: carouselSpacing,
velocity: velocityValue
)
}
}
.alignmentGuide(.iconToCarouselBottom) { context in
context[VerticalAlignment.bottom]
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .top)
LinearGradient(
colors: [.black, .clear],
startPoint: .bottom,
endPoint: .top
)
// Top VStack
VStack(spacing: 0) {
RoundedRectangle(cornerRadius: 18)
.fill(.red)
.frame(width: 95, height: 95)
.alignmentGuide(.iconToCarouselBottom) { context in
context.height / 2
}
Text("Welcome to Music Radar!")
.font(.title)
.fontDesign(.serif)
.bold()
Text("Lorem Impsum Lorem Impsum Lorem Impsum Lorem Impsum Lorem Impsum Lorem Impsum Lorem Impsum Lorem Impsum Lorem Impsum Lorem Impsum Lorem Impsum Lorem Impsum Lorem Impsum Lorem Impsum Lorem Impsum Lorem Impsum")
.font(.body)
.fontDesign(.serif)
PrimaryActionButton("Next") {
// navigate to the next screen
}
.padding(.horizontal)
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
.ignoresSafeArea(.all, edges: .top)
.statusBarHidden()
}
}
extension VerticalAlignment {
private struct IconToCarouselBottomAlignment: AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat {
context[VerticalAlignment.center]
}
}
static let iconToCarouselBottom = VerticalAlignment(
IconToCarouselBottomAlignment.self
)
}
At this point the view looks like this:
I want to push the Button in the Top VStack
to the bottom of the screen and the red rounded rectangle to stay pinned to the bottom of the horizontal carousel (hence the custom alignment guide being introduced). The natural solution would be to add the Spacer()
. However, for some reason when I do it, it results in the button going all the way down to outside of the screen, which means that the bottom safe area isn't being respected. Using the alignment parameter in the flexible frame also doesn't work the way I want it to. I suspect that custom alignment guide can cause this behavior but I can't find the way to fix it. Help me out, please.
@DTS Engineer Ended up making it work with a VStack after trying different other options. I dropped the custom alignment guide in favor of using offset. Here is the code if anyone is interested:
struct OnboardingGreeting: View {
let carouselSpacing: CGFloat = 7
let carouselItemSize: CGFloat = 110
let carouselVelocities: [CGFloat] = [0.5, -0.25, 0.3, 0.2]
let iconSize: CGFloat = 95
var body: some View {
VStack(spacing: 0) {
VStack(spacing: carouselSpacing) {
ForEach(carouselVelocities, id: \.self) { velocityValue in
InfiniteHorizontalCarousel(
albumNames: albums,
artistNames: artists,
itemSize: carouselItemSize,
itemSpacing: carouselSpacing,
velocity: velocityValue
)
}
}
.overlay {
LinearGradient(
stops: [
.init(color: .black.opacity(0.5), location: 0.07),
.init(color: .clear, location: 0.12)
],
startPoint: .bottom,
endPoint: .top
)
}
VStack(spacing: 8) {
RoundedRectangle(cornerRadius: 18)
.fill(.red)
.frame(width: iconSize, height: iconSize)
Text("Welcome to Music Radar")
.font(.title)
.fontDesign(.serif)
.bold()
Text("It's your place to find new friends and share your tastes with the world. Enjoy!")
.font(.body)
.fontDesign(.serif)
}
.offset(y: -(iconSize/2))
Spacer()
PrimaryActionButton("Next") {
// navigate to the next screen
}
.padding(.horizontal)
}
.ignoresSafeArea(edges: .top)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.statusBarHidden()
}
}
and the final result: