SwiftUI Basics: VStack, HStack, ZStack, and Spacer
SwiftUI
is Apple’s innovative declarative framework for building user interfaces, designed to simplify the process of creating views across all Apple platforms. Rather than providing detailed instructions for rendering each element, developers declare the desired outcome of their UI, allowing SwiftUI to handle the rendering process efficiently. This approach offers a streamlined and modern way to design robust and visually appealing applications.
Today, let’s dive into some of the basic layouts that SwiftUI supports. SwiftUI offers a simple yet powerful layout system with VStack
, HStack
, ZStack
, and Spacer
. These tools form the foundation for creating dynamic and responsive UIs. In this article, we’ll explore how to use these layouts effectively and enhance them with common modifiers.
VStack: Vertical Arrangement
VStack
arranges view vertically in a column. Here’s how you can use VStack
along with common modifiers.
struct VStackWithModifiers: View {
var body: some View {
VStack(alignment: .center, spacing: 10) {
Text("Welcome to VStack")
.font(.headline) // Sets font style
.foregroundColor(.blue) // Sets text color
Image(systemName: "star.fill")
.resizable() // Makes the image scalable
.frame(width: 50, height: 50) // Defines image size
.foregroundColor(.yellow) // Sets image color
Text("This is a simple VStack example.")
.multilineTextAlignment(.center) // Aligns text for multiline
.padding() // Adds spacing around the view
}
.padding() // Adds spacing around VStack
.background(Color(.green)) // Sets background color
.cornerRadius(12) // Rounds the corners
.shadow(radius: 5) // Adds a shadow effect
}
}
Key Modifiers:
alignment
: Aligns child views (.leading
,.center
,.trailing
).spacing
: Sets the gap between elements.padding
: Adds space inside the container.background
: Adds a background color or view.cornerRadius
: Rounds container corners.
HStack: Horizontal Arrangement
HStack
lays out its children in a horizontal row, great for toolbars or grouped data.
struct HStackWithModifiers: View {
var body: some View {
HStack(spacing: 20) {
Image(systemName: "flame.fill")
.resizable()
.scaledToFit() // Maintains aspect ratio
.frame(width: 40, height: 40)
.foregroundColor(.orange)
Text("HStack Example")
.font(.title2)
.bold() // Makes text bold
.padding(.vertical, 5) // Adds padding to top and bottom
Spacer() // Pushes content to opposite sides
}
.padding() // Adds space around the HStack
.background(Color(.systemGroupedBackground)) // Light gray background
.shadow(radius: 3) // Light shadow
}
}
Key Modifiers:
alignment
: Aligns child views vertically (.top
,.center
,.bottom
).spacing
: Controls the space between elements.frame
: Adjusts the size of individual views.Spacer()
: Dynamically adjusts space between elements.
ZStack: Layering Views
ZStack
lets you overlay views, ideal for creating custom backgrounds or stacked designs.
struct ZStackCardExample: View {
var body: some View {
ZStack {
// Background Rectangle
RoundedRectangle(cornerRadius: 15)
.fill(LinearGradient(
gradient: Gradient(colors: [Color.blue, Color.purple]),
startPoint: .top,
endPoint: .bottom
))
.frame(width: 300, height: 150)
.shadow(radius: 10) // Shadow for depth
// Overlay Content
VStack {
Text("Premium Plan")
.font(.headline)
.foregroundColor(.white)
.padding(.bottom, 5)
Text("$9.99 / month")
.font(.title2)
.bold()
.foregroundColor(.white)
}
}
}
}
Key Modifiers:
cornerRadius
: Rounds the corners of the card for a smooth look.fill
: Fills the shape with a gradient for vibrant colors.frame
: Sets the card’s width and height.shadow
: Adds depth by creating a soft shadow.font
: Defines the style and size of the text.foregroundColor
: Changes the color of the text to ensure contrast.padding
: Adjusts spacing around or between elements.
Combining ZStack, HStack and VStack
Let’s create a practical example by combining VStack, HStack, and Spacer to build a clean, responsive profile card.
struct ProfileCardView: View {
var body: some View {
ZStack {
// Background
RoundedRectangle(cornerRadius: 20)
.fill(
LinearGradient(
gradient: Gradient(colors: [Color.blue, Color.purple]),
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.frame(width: 350, height: 200)
.shadow(radius: 10)
// Content
HStack(spacing: 20) {
// Profile Image
ZStack {
Circle()
.fill(Color.white)
.frame(width: 100, height: 100)
Image(systemName: "person.fill")
.resizable()
.scaledToFit()
.frame(width: 60, height: 60)
.foregroundColor(Color.blue)
}
// Profile Details
VStack(alignment: .trailing, spacing: 5) { // Adjusted spacing for name and title
// Name and Title
VStack(alignment: .leading, spacing: 2) {
Text("Nhat Doan")
.font(.title2)
.fontWeight(.bold)
.foregroundColor(.white)
Text("iOS Developer")
.font(.subheadline)
.foregroundColor(.white.opacity(0.8))
}
Spacer() // Push the stats to the bottom
// Social Stats
HStack {
Spacer() // Align stats to the right
VStack {
Text("20K")
.font(.headline)
.foregroundColor(.white)
Text("Followers")
.font(.caption)
.foregroundColor(.white.opacity(0.8))
}
Spacer()
VStack {
Text("100")
.font(.headline)
.foregroundColor(.white)
Text("Posts")
.font(.caption)
.foregroundColor(.white.opacity(0.8))
}
Spacer()
VStack {
Text("500")
.font(.headline)
.foregroundColor(.white)
Text("Likes")
.font(.caption)
.foregroundColor(.white.opacity(0.8))
}
}
}
}
.padding(20) // Added padding to the content inside the card
}
.frame(width: 350, height: 200) // Outer ZStack frame
.padding(20) // Added padding around the entire card
}
}
When you enable the preview in Xcode, it will look something like this. Pretty cool, right?
It’s working great, but the body in ProfileCardView
feels a bit lengthy, which can impact readability and maintainability. Let’s explore how we can break it down into smaller, more manageable views.
A common way to simplify the code is by breaking it into multiple view properties, like this:
Background View
// MARK: - Background View
private var backgroundView: some View {
RoundedRectangle(cornerRadius: 20)
.fill(
LinearGradient(
gradient: Gradient(colors: [Color.blue, Color.purple]),
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.shadow(radius: 10)
}
Profile Image View
// MARK: - Profile Image View
private var profileImageView: some View {
ZStack {
Circle()
.fill(Color.white)
.frame(width: 100, height: 100)
Image(systemName: "person.fill")
.resizable()
.scaledToFit()
.frame(width: 60, height: 60)
.foregroundColor(Color.blue)
}
}
Profile Details View
// MARK: - Profile Details View
private var profileDetailsView: some View {
VStack(alignment: .leading, spacing: 5) {
// Name and Title
VStack(alignment: .leading, spacing: 2) {
Text("Nhat Doan")
.font(.title2)
.fontWeight(.bold)
.foregroundColor(.white)
Text("iOS Developer")
.font(.subheadline)
.foregroundColor(.white.opacity(0.8))
}
Spacer() // Push the stats to the bottom
// Social Stats
socialStatsView
}
}
Social Stats View
// MARK: - Social Stats View
private var socialStatsView: some View {
HStack {
Spacer()
VStack {
Text("20K")
.font(.headline)
.foregroundColor(.white)
Text("Followers")
.font(.caption)
.foregroundColor(.white.opacity(0.8))
}
Spacer()
VStack {
Text("100")
.font(.headline)
.foregroundColor(.white)
Text("Posts")
.font(.caption)
.foregroundColor(.white.opacity(0.8))
}
Spacer()
VStack {
Text("500")
.font(.headline)
.foregroundColor(.white)
Text("Likes")
.font(.caption)
.foregroundColor(.white.opacity(0.8))
}
}
}
Now, our body
becomes much cleaner and more readable
var body: some View {
ZStack {
// Background
backgroundView
// Content
HStack(spacing: 20) {
profileImageView
profileDetailsView
}
.padding(20) // Padding for content inside the card
}
.frame(width: 350, height: 200) // Outer ZStack frame
.padding(20) // Padding around the entire card
}
Conclusion
I believe this is an excellent time for new iOS developers or anyone interested in Apple’s development framework to dive into learning SwiftUI. While the concept of declarative UI isn’t entirely new compared to other mobile frameworks, working with SwiftUI has been an enjoyable experience for me. Many startups and companies are already transitioning their UIKit apps to SwiftUI. Adopting a “declarative” mindset does take some adjustment, but I truly believe it’s worth it in the long run.
Happy reading!
Like what you're reading?
If this article hit the spot, why not subscribe to the weekly newsletter?
Every weekend, you'll get a short and sweet summary of the latest posts and tips—free and easy, no strings
attached!
You can unsubscribe anytime.