In this article we will be creating a card with a small animation using SwiftUI, turning that card into a component to organize our code, and then setting up Binding so that we can keep the animation we’ve created on the card.

Creating Components is very important, it’s what keeps your code clean and improves your apps performance. @Binding is used to link a specific state on a certain page.

The component view that we will be building

The component view above is what we will build. We’ll begin by setting up the card itself. The first thing we want to do is add a ZStack that will host our card and act as the background of the page.

	import SwiftUI
	 
	struct CodeSnippet: View {
		var body: some View {
			ZStack {
				// Card Container & Background
			}
		}
	}
	 
	struct CodeSnippet_Previews: PreviewProvider {
		static var previews: some View {
			CodeSnippet()
		}
	}
	
	
	Next, well add a `VStack`, which will be the actual card. 
	
	 
	import SwiftUI
	 
	struct CodeSnippet: View {
		var body: some View {
			ZStack {
				// Card Container & Background
				VStack {
					 // Card
				}
			}
		}
	}
	 
	struct CodeSnippet_Previews: PreviewProvider {
		static var previews: some View {
			CodeSnippet()
		}
	}

Working within the VStack for the card’s content, we’ll next add an HStack to hold the title of our card and the icon we will animate later.

import SwiftUI
 
struct CodeSnippet: View {
	var body: some View {
		ZStack {
			// Card Container & Background
			VStack {
				// Card
				HStack {
					// Top Card Content
				}
			}
		}
	}
}
 
struct CodeSnippet_Previews: PreviewProvider {
	static var previews: some View {
		CodeSnippet()
	}
}

Now that we have our nested set of stacks, we’ll add a title Text and an icon Image to the HStack as well as a Text for the card’s description to the VStack. We’ll also set up a preview using a PreviewProvider.

This is what you should have so far:

import SwiftUI
 
struct CodeSnippet: View {
	var body: some View {
		ZStack {
			// Card Container & Background
			VStack {
				// Card
				HStack {
					// Top Card Content
					Text("Component Card")
					Image(systemName: "bell.circle")
				}
				
				Text("This is a description for the iOS Card we're building")
			}
		}
	}
}
 
struct CodeSnippet_Previews: PreviewProvider {
	static var previews: some View {
		CodeSnippet()
	}
}

Styling the Card

The contents of our card are in place, but it doesn’t look like much. We’ll add some design to our code next to make it look like a card. First, let’s add one background color to the page and another background color to our card.

import SwiftUI
 
struct CodeSnippet: View {
	var body: some View {
		ZStack {
			// Card Container & Background
			
			Color.gray.opacity(0.1)
				.edgesIgnoringSafeArea(.all)
			
			VStack {
				// Card
				HStack {
					// Top Card Content
					Text("Component Card")
					Image(systemName: "bell.circle")
				}
				
				Text("This is a description for the iOS Card we're building")
			}
			.background(Color.white)
		}
	}
}
 
struct CodeSnippet_Previews: PreviewProvider {
	static var previews: some View {
		CodeSnippet()
	}
}

Here we added a Color.gray.opacity(0.1) modifier to our ZStack along with an .edgesIgnoringSafeArea(.all) modifier so that the Color can take up the whole background. We also added a .background(Color.white) modifier to the card.

Let’s further refine the design of our card by adding .frame and .padding modifiers.

import SwiftUI
 
struct CodeSnippet: View {
	var body: some View {
		ZStack {
			// Card Container & Background
			
			Color.gray.opacity(0.1)
				.edgesIgnoringSafeArea(.all)
			
			VStack {
				// Card
				HStack {
					// Top Card Content
					Text("Component Card")
					Image(systemName: "bell.circle")
				}
				
				Text("This is a description for the iOS Card we're building")
			}
			.frame(maxWidth: .infinity)
			.frame(height: 200)
			.background(Color.white)
			.padding(.horizontal, 20)
		}
	}
}
 
struct CodeSnippet_Previews: PreviewProvider {
	static var previews: some View {
		CodeSnippet()
	}
}

Our design is starting to come together! The next thing to do is to start styling our text and the icon inside the card.

VStack {
	// Card
	HStack {
		// Top Card Content
		Text("Component Card")
			.font(.system(size: 28, weight: .bold))
		Spacer()
		Image(systemName: "bell.circle")
			.font(.system(size: 28))
			.foregroundColor(.blue)
	}
	
	Text("This is a description for the iOS Card we're building")
		.font(.system(size: 22, weight: .medium))
		.foregroundColor(.gray)
		.padding(.horizontal, 4)
		.padding(.top, 4)
}

We’ve formatted the text and the icon, and we used a Spacer inside the HStack to separate the text and the icon.

Now that the contents of the card are looking good, let’s add a couple styles to the card itself.

...
 }
			.frame(maxWidth: .infinity)
			.frame(height: 200)
			.background(Color.white)
			.mask(RoundedRectangle(cornerRadius: 30))
			.shadow(color: Color.black.opacity(0.05), radius: 30, y:15)
			.padding(.horizontal, 20)
		}
	}
}

We’ve added .mask and .shadow modifiers to the VStack that represents our card.

Our design is complete! Now let’s dive into state.

Using State for Animations

We will be adding an animation for when the user taps the notification icon. First, we need to create a state object to control the animation.

struct CodeSnippet: View {

@State var notifications = false

var body: some View {
	ZStack {
		// Card Container & Background
		
		Color.gray.opacity(0.1)
			.edgesIgnoringSafeArea(.all)
		
}

Next we have to set up what happens when we toggle this state. We’re going to add notifications ? bell.circle.fill : bell.circle to the Image that represents our notification icon. When a user toggles the notification state, the bell icon will change from a stroke icon to a filled icon. You will see this come to life when we add the .onTapGesture to our image next.

HStack {
	// Top Card Content
	Text("Component Card")
		.font(.system(size: 28, weight: .bold))
	Spacer()
	Image(systemName: notifications ? "bell.circle.fill ": "bell.circle")
		.font(.system(size: 28))
		.foregroundColor(.blue)
}

Now let’s apply the .onTapGesture modifier.

HStack {
	// Top Card Content
	Text("Component Card")
		.font(.system(size: 28, weight: .bold))
	Spacer()
	Image(systemName: notifications ? "bell.circle.fill ": "bell.circle")
		.font(.system(size: 28))
		.foregroundColor(.blue)
		.onTapGesture {
			notifications.toggle()
		}
}

Tap play on your preview, then tap the notification icon. You will see it change from stroked to filled.

Congratulations! We’ve successfully designed a card using SwiftUI, added state to our card, and added an onTapGesture to toggle this state. The last thing we’ll do is turn the card into a component and bind the state we just created to the component.

To make these changes, we are going to move our VStack code into a new file. Create a new group in your Xcode file called Components. Add a new file into that group called Card.

Back in our original file, let’s cut the VStack code so we can paste it in the new file. To make this easier, Command-click on the first VStack bracket and select “Fold.” Your code should look like this:

VStack {
		// Card
		HStack {
			// Top Card Content
			Text("Component Card")
				.font(.system(size: 28, weight: .bold))
			Spacer()
			Image(systemName: notifications ? "bell.circle.fill ": "bell.circle")
				.font(.system(size: 28))
				.foregroundColor(.blue)
				.onTapGesture {
					notifications.toggle()
				}
		}
		
		Text("This is a description for the iOS Card we're building")
			.font(.system(size: 22, weight: .medium))
			.foregroundColor(.gray)
			.padding(.horizontal, 4)
			.padding(.top, 4)
		}
		.frame(maxWidth: .infinity)
		.frame(height: 200)
		.background(Color.white)
		.mask(RoundedRectangle(cornerRadius: 30))
		.shadow(color: Color.black.opacity(0.05), radius: 30, y:15)
		.padding(.horizontal, 20)
		}
	}
}

Now highlight the VStack code, cut it, and paste it into the new Card file you just created. Your new Card file should look something like this. Don’t worry - we will be fixing the errors you see in a moment, with just one line of code.

Xcode showing the code errors mentioned above

To fix these errors, all we have to do is add Binding for our notification state to this new Card file. Here’s what it should look like:

import SwiftUI
 
struct Card: View {
	
	@Binding var notifications: Bool
	
	var body: some View {
		
		VStack {
			// Card
			HStack {
				// Top Card Content
				Text("Component Card")
					.font(.system(size: 28, weight: .bold))
				Spacer()
				Image(systemName: notifications ? "bell.circle.fill ": "bell.circle")
					.font(.system(size: 28))
					.foregroundColor(.blue)
					.onTapGesture {
						notifications.toggle()
					}
			}
			
			Text("This is a description for the iOS Card we're building")
				.font(.system(size: 22, weight: .medium))
				.foregroundColor(.gray)
				.padding(.horizontal, 4)
				.padding(.top, 4)
		}
		.frame(maxWidth: .infinity)
		.frame(height: 200)
		.background(Color.white)
		.mask(RoundedRectangle(cornerRadius: 30))
		.shadow(color: Color.black.opacity(0.05), radius: 30, y:15)
		.padding(.horizontal, 20)
	}
}

And that’s it! We’ve successfully designed a card using SwiftUI, turned the card into a component, and added state and binding.

I hope this tutorial was helpful. Thank you for tuning in!

AJ Picard

Mobile Designer

MartianCraft is a US-based mobile software development agency. For nearly two decades, we have been building world-class and award-winning mobile apps for all types of businesses. We would love to create a custom software solution that meets your specific needs. Let's get in touch.