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?

SwiftUI preview

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.