Search code examples
swiftswiftui

How to add a border in SwiftUI with clipShape?


I'm confused how I add a border with a stroke color and line width to a SwiftUI View using clipShape. Basically, I just want to have rounded corners with a border color.

Rectangle()
    .frame(width: 80, height: 80)
    .foregroundStyle(.gray)
    .clipShape(
        RoundedRectangle(cornerRadius: 6)
    )

Solution

  • Applying a border around a clipped shape

    Unfortunately, you cannot simply apply .border in combination with .clipShape, because this always draws a square border:

    • If .border is applied before .clipShape then it is clipped at the corners. You notice it more when the corner radius is large.
    • If .border is applied after .clipShape then the corners of the border are square.

    So to add a border around a clipped shape, you need to stroke it as a separate operation using the same shape. An overlay works well:

    Rectangle()
        .frame(width: 80, height: 80)
        .foregroundStyle(.gray)
        .clipShape(
            RoundedRectangle(cornerRadius: 6)
        )
        .overlay(
            RoundedRectangle(cornerRadius: 6)
                .stroke(.red, lineWidth: 2)
        )
    

    Corner

    A simpler alternative

    The question was about using .clipShape. However, if you just want to show a filled shape with a border in the background of a view, it can be done without using .clipShape.

    → Add the shape in the background, fill it with a color or gradient, then stroke its border.

    Pre iOS 17

    Pre iOS 17, the border needs to be stroked using a second shape definition, as shown above.

    For example, if the foreground content would be in an HStack, the background can be applied as follows:

    HStack {
        // ... foreground content
    }
    .padding()
    .background {
        RoundedRectangle(cornerRadius: 6)
            .fill(.gray)
            .overlay(
                RoundedRectangle(cornerRadius: 6)
                    .stroke(.red, lineWidth: 2)
            )
    }
    

    This is not much different to what we had before, except that an unnecessary clip operation is avoided.

    iOS 17+

    Since iOS 17, a different .fill overload is available, see fill(_:style:). Now the shape only needs to be defined once:

    HStack {
        // ... foreground content
    }
    .padding()
    .background {
        RoundedRectangle(cornerRadius: 6)
            .fill(.gray)
            .stroke(.red, lineWidth: 2)
    }