I have two views with basically same structures, including the same padding and spacing. The only difference is that the first "Welcome step" view uses a larger icon size of 240, while the other views use an icon size of 80. While I'm ok with the text not aligning, I’d like to align the button positions across these views. I’ve attempted adjusting padding and using spacer(), but neither approach has worked. Is there a solution to this problem?
Ideally, I would like to modify the button position in Welcom step view rather than other views.
Any help is appreciated.
struct WelcomeStepView: View {
let onNext: () -> Void
var body: some View {
VStack(spacing: 40) {
Spacer()
// App Icon/Logo
VStack(spacing: 20) {
Image("kacardicon")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: 240)
Text("Welcome to KaCard")
.font(.title)
.fontWeight(.bold)
.multilineTextAlignment(.center)
Text("This is your smart credit card manager. To get the best experience, we'd like to set up a few features.")
.font(.body)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal)
}
Spacer()
VStack(spacing: 16) {
Button(action: onNext) {
Text("Get Started")
.font(.headline)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue)
.cornerRadius(12)
}
}
.padding(.horizontal, 40)
Spacer(minLength: 50)
}
.padding()
}
}
struct NotificationPermissionStepView: View {
let onNext: () async -> Void
let onSkip: () -> Void
@State private var isLoading = false
var body: some View {
VStack(spacing: 40) {
Spacer()
VStack(spacing: 20) {
Image(systemName: "bell.circle.fill")
.font(.system(size: 80))
.foregroundColor(.yellow)
Text("Stay on Top of Your Bills")
.font(.title)
.fontWeight(.bold)
.multilineTextAlignment(.center)
Text("We'll send you timely reminders for bill payments, annual fees, and signup bonus deadlines so you never miss an important date.")
.font(.body)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal)
}
Spacer()
VStack(spacing: 16) {
Button(action: {
isLoading = true
Task {
await onNext()
isLoading = false
}
}) {
HStack {
if isLoading {
ProgressView()
.scaleEffect(0.8)
.progressViewStyle(CircularProgressViewStyle(tint: .white))
}
Text(isLoading ? "Requesting..." : "Enable Notifications")
.font(.headline)
}
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.yellow)
.cornerRadius(12)
}
.disabled(isLoading)
Button("Skip for Now", action: onSkip)
.font(.subheadline)
.foregroundColor(.secondary)
}
.padding(.horizontal, 40)
Spacer(minLength: 50)
}
.padding()
}
}
Both your views are constructed in the same way:
VStack(spacing: 40) {
Spacer()
VStack(spacing: 20) {
// image and text here
}
Spacer()
VStack(spacing: 16) {
// button content here
}
Spacer(minLength: 50)
}
.padding()
The presence of the Spacer
is going to mean, the view will always use all the height available. But the excess space will be divided between the Spacer
. Setting minLength
on the bottom Spacer
does not give you much control over how much space it consumes.
Another complication is that the button panel in NotificationPermissionStepView
includes a second button for skipping the step. If I understand correctly, you want the first button of this view to be aligned with the (only) button in WelcomeStepView
.
One way to fix the alignment is to remove the bottom Spacer
and apply a suitable maximum height to the lower VStack
using .frame
with alignment: .top
. This way,
Spacer
will ensure that the button panel is pushed to the bottom of the screen.frame
modifier will ensure that the buttons are aligned across screens.VStack(spacing: 16) {
// button content here
}
.padding(.horizontal, 40)
.frame(maxHeight: 100, alignment: .top) // 👈 added
// Spacer(minLength: 50) // 👈 removed
These changes need to be applied to both views in the same way.
Here is how it works when using a TabView
as parent for the two views:
TabView {
WelcomeStepView {}
NotificationPermissionStepView {} onSkip: {}
}
.tabViewStyle(.page)