SwiftUI Play Time – Article 1 – Building a simple business card

Hey there, welcome to SwiftUI Play Time. In this opening article, we will be building a simple business card. This one is pretty common, can be found not only in a lot of apps – but it’s a fairly common task in coding interviews. Keep in mind, though, that the interviews also focus on other aspects, for example how you organize your code – dependencies and responsibility separation. In this series, we will only focus on the SwiftUI part of it – we only care how to build the element – the rest is up to you!

All the articles, including this one, will be publicly available for download in this Github repository: https://github.com/Rusti18/SwiftUI-Play-Time

Since we’re going to start off slowly, you might find the explanations quite simplistic at the beginning. Feel free to skip whatever you feel you are familiar with.

Let’s start with creating a simple iOS app:

Make sure SwiftUI is selected for the interface option:

You should end up with something very close to this:

Moving forward, we will create a folder structure that is resembles each article. In this case, Article 1 – Business Card.

To begin with, we will create an empty SwiftUI view file that we will name BusinessCard:

Make sure you turn on your preview in order to be able to see in real time the changes you are making.

It will end up looking like this:

Before we start writing any code, let’s take a look at our wireframe and identify the requirements:

It looks pretty simple, nothing too complicated. We extract the following settings:

  • The card needs to have a rounded image at the left
  • There are three labels – on is aligned to the top, the other to the middle, and the last to the bottom of the image
  • Each label has a different font size and style
  • I have not explicitly added the sizes to the wireframe (it will not match 100%, especially the spacing between the image and the text), but for the sake of our exercise, let’s say that:
    • There is a 12px padding at left (image to left side) and right (label max width and right side)
    • There is a 24px padding at top and bottom
    • There is a 30px padding between the image and the text labels
    • The image should have a fixed size of 60×60
    • The top label has a bold font with a 18 size
    • The middle label has a regular font with a 11 size
    • The bottom font has an italic font with a 10 size

Gathering these values is very important as they dictate what SwiftUI structures to use. Since it’s a pretty simple exercise, we can decide on some of that right now. Keep in mind that this is an iterative process – you set some structures and then see how it looks like and then reevaluate.

The following thought process goes to work – in my case, at least – when choosing what structures to use:

  • Images are loaded using the Image component
  • Text labels are added with the Text component
  • We can use a horizontal stack – called HStack in SwiftUI – to place the elements from left to right – and set all the padding properties on it via the .padding modifier
  • Based on this logic, we will place all the text labels in a single vertical stack – VStack in SwiftUI – then use the spacing parameter of the HStack to have that contained
  • The image should have a fixed size – use the .frame to indicate that

Let’s transform these into code.

var body: some View {
HStack(spacing: 30.0) {
Image("man")
.frame(width: 60.0, height: 60.0)
VStack {
Text("John Doe")
Text("Attorney at law")
Text("Berkley Ltd.")
}
}
.padding([.leading, .trailing], 12.0)
.padding([.top, .bottom], 24.0)
}
view raw gistfile1.swift hosted with ❤ by GitHub

This does not look good:

“Why is the man taking up all my screen’s space? Plus, the image is not rounded. It does not look good at all!”

All these have a pretty easy fix. Firstly, let’s understand why this is happening.

The image is loaded at its full size, rather than the frame modifier we apply. The image component will have, indeed, a fixed 60×60 size, but the content view will not clip the image, so it will go outside the bounds of our component. In order to make the image resize according to the frame size, we need to add the .resizable() modifier which solves our issue:

Image("man")
.resizable()
.frame(width: 60.0, height: 60.0)
view raw gistfile1.swift hosted with ❤ by GitHub

It looks a bit better, doesn’t it? We still need to round the image. That’s done by clipping a circle shape on the image – done via the .clipShape() modifier.

Image("man")
.resizable()
.frame(width: 60.0, height: 60.0)
.clipShape(Circle())
view raw gistfile1.swift hosted with ❤ by GitHub

Much better now. Ignore the blue border – it’s not added by us in code, but shows up whenever you select your item in the preview canvas.

The card does not look good enough yet. The labels are not aligned, the fonts are too large and they don’t match the styles in the wireframe. We need to change that.

First of all, text alignment. There are many ways to handle this, but the solution is tailored to a specific problem.

One potential option is using the .multilineTextAlignment modifier. However, this does not work unless there are indeed multiple lines, otherwise the Text items will just be aligned according to the alignment of the parent VStack.

Another option would be to use the .frame(width:…, alignment:…) or .frame(maxWidth:…, alignment:…) modifier, preferably the second with an .infinity maxWidth. What this does is create a fixed, full width frame for our component and applies the alignment onto the Text content. Thus, it will not matter how the VStack horizontally aligns its content, since there’s only one piece of content per line, which already occupies the entire width.

A different approach would be to embed each Text into an HStack with 0 spacing + a Spacer(). This basically achieves a very similar result to the previous approach – the HStack will occupy all the available space, which it will fill with the text first, then the rest of it will be allotted to the spacer.

The final, easiest and the most natural approach at this point would be to just set the alignment parameter on the VStack to .leading, which is what we will do for now.

To set the fonts, we will use the .font() modifier + the static function that produces system fonts .system(size:…, weight:…, design: …) while ignoring the design parameter.

VStack(alignment: .leading) {
Text("John Doe")
.font(.system(size: 18, weight: .bold))
Text("Attorney at law")
.font(.system(size: 11, weight: .regular))
Text("Berkley Ltd.")
.font(.system(size: 10, weight: .regular))
.italic()
}
view raw gistfile1.swift hosted with ❤ by GitHub

.bold and .regular are font weights, while italic() is a style that needs to be applied on the font.

This is getting close. There is one aspect we haven’t covered yet and it is this requirement: “There are three labels – on is aligned to the top, the other to the middle, and the last to the bottom of the image”

We will tackle this by using one of the strategies we described for the horizontal alignment, but this time, the other way around: we will set a 0 spacing on the VStack and add Spacer() to force the items to the desired positions. Unless we set a fixed height via a .frame on the VStack, it will vertically occupy the entire space it has at its disposal. Thus we will set the VStack to the same height as the image, as that can also be deduced from the requirements.

The result is as follows:

HStack(spacing: 30.0) {
Image("man")
.resizable()
.frame(width: 60.0, height: 60.0)
.clipShape(Circle())
VStack(alignment: .leading, spacing: 0.0) {
Text("John Doe")
.font(.system(size: 18, weight: .bold))
Spacer()
Text("Attorney at law")
.font(.system(size: 11, weight: .regular))
Spacer()
Text("Berkley Ltd.")
.font(.system(size: 10, weight: .regular))
.italic()
}.frame(height: 60.0)
}
.padding([.leading, .trailing], 12.0)
.padding([.top, .bottom], 24.0)
view raw gistfile1.swift hosted with ❤ by GitHub

We’ll clean the code up a bit and use some constants and we’ll obtain this:

struct BusinessCard: View {
private enum Constants {
static var imageToLabelsSpacing: CGFloat { 30.0 }
static var imageSize: CGSize { CGSize(width: 60.0, height: 60.0) }
static var horizontalPadding: CGFloat { 12.0 }
static var verticalPadding: CGFloat { 24.0 }
static var nameFontHeight: CGFloat { 18.0 }
static var occupationFontHeight: CGFloat { 11.0 }
static var companyFontHeight: CGFloat { 10.0 }
}
var body: some View {
HStack(spacing: Constants.imageToLabelsSpacing) {
Image("man")
.resizable()
.frame(width: Constants.imageSize.width, height: Constants.imageSize.height)
.clipShape(Circle())
VStack(alignment: .leading, spacing: 0.0) {
Text("John Doe")
.font(.system(size: Constants.nameFontHeight, weight: .bold))
Spacer()
Text("Attorney at law")
.font(.system(size: Constants.occupationFontHeight, weight: .regular))
Spacer()
Text("Berkley Ltd.")
.font(.system(size: Constants.companyFontHeight, weight: .regular))
.italic()
}.frame(height: Constants.imageSize.height)
}.padding([.leading, .trailing], Constants.horizontalPadding)
.padding([.top, .bottom], Constants.verticalPadding)
}
}
view raw gistfile1.swift hosted with ❤ by GitHub

Let’s take a final look at the list of requirements:

  • The card needs to have a rounded image at the left ✅
  • There are three labels – on is aligned to the top, the other to the middle, and the last to the bottom of the image✅
  • Each label has a different font size and style ✅
  • I have not explicitly added the sizes to the wireframe (it will not match 100%, especially the spacing between the image and the text), but for the sake of our exercise, let’s say that:
    • There is a 12px padding at left (image to left side) and right (label max width and right side)✅
    • There is a 24px padding at top and bottom✅
    • There is a 30px padding between the image and the text labels✅
    • The image should have a fixed size of 60×60✅
    • The top label has a bold font with a 18 size✅
    • The middle label has a regular font with a 11 size✅
    • The bottom font has an italic font with a 10 size✅

That’s it! We started from the wireframe requirement and managed to built the requested structure – and we didn’t have to run the app once! That’s the beauty of SwiftUI previews.

The code is also available here: https://github.com/Rusti18/SwiftUI-Play-Time

Hope you learnt something new today. Let me know if you have any questions. Cheers!

*Image taken from https://pixabay.com/photos/human-man-face-view-beard-cap-1411499/

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: