How to show text inside a circle with SwiftUI

Jan 24, 2021

This time, we will look at a couple of ways to show a text label inside a circle using SwiftUI. We will dig deeper into three different ways using the ZStack view, .background and .overlay modifiers. In the end, as a small bonus, we will check out how to present a text label over a circle using the .clipShape modifier.

Our end goal is something like this:

Text inside a circle

Layout out with ZStack

ZStack in SwiftUI is a unique view that shows all its children on top of each other. It works similarly to z-index in CSS. So let's try to show a Circle view and then the Text view on top. To not allow the Circle to fill the whole screen, we need to set a constant view width and height.

ZStack {
  Circle()
    .stroke(circleColor, lineWidth: 4)
  
  Text("13")
}
.frame(width: 40, height: 40)

Practicing the .background modifier

Now let's see how to use the .background modifier. Sadly Apple has not provided us with any documentation on how to use it, so we need to understand it ourselves the hard way. We can check out the source code and try to understand what is this view modifier doing.

func background<Background>(_ background: Background, alignment: Alignment = .center) -> some View where Background : View

By applying this modifier, we need to pass in a View and optionally specify the alignment in both axes. Let's set how can we use it for our Text field. This time we are not going to check out the alignment parameter. We want to show a circle as a background for the Text field.

Text("13")
  .background(
    Circle()
      .stroke(circleColor, lineWidth: 4)
      .padding(6)
  )
Text inside a circle without padding

The outcome is not exactly what we wanted. To improve it, we need some space between the text and the circle to look a bit better.

To have a nice gap between the number and the circle, we could use the .padding() modifier. Now the text and the circle have some room to breathe between them.

Text("13")
  .padding()
  .background(
    Circle()
      .stroke(circleColor, lineWidth: 4)
      .padding(6)
  )
Text inside a circle with padding

Untangling the overlay modifier

This time Apple hasn't provided documentation explanation again. Let's read the source code. It is relatively straight forward and similar to .background modifier.

func overlay<Overlay>(_ overlay: Overlay, alignment: Alignment = .center) -> some View where Overlay : View

It places the view on top of it, not under. We should not forget about the .padding() modifier to have a gap between the number and the circle. Using the previous example and changing background to overlay we have the same outcome.

Text("13")
  .padding()
  .overlay(
    Circle()
      .stroke(circleColor, lineWidth: 4)
      .padding(6)
  )

Although it looks identical, it works differently. This approach comes in handy if we would like to show an interactable view on top. Let's say we would like to present a tappable button on top. I wouldn't specifically advise doing so, but it explains the idea.

Bonus: Clipping as Shape

As a bonus tip, let's check out how we can use the .clipShape modifier to show a text field inside the circle filled with a color. The .clipShape modifier clips the view to a specific shape that we should pass as a parameter. For that, we can use a couple of shapes provided by Apple like circles, capsules, rectangles, and more. Or we can draw a shape ourselves with the help of the Path outline. Custom shapes we are leaving out of the scope of this blog post.

Text("13")
  .padding()
  .background(circleColor)
  .clipShape(Circle())
Text on top of a circle

TL;DR

With SwiftUI achieving the same thing visually can be done in various ways. Placing a circle around the text can be done either stacking all the views on top of them using ZStack. Another approach is using .background or .overlay modifier.

Links