41
            ZStack {
                VStack {
                    Text("some text")
                    Button("Ok") {}
                     .foregroundColor(.cyan)
                     .padding()
                }
                .padding()
            }
            .background(.red)
            .border(.blue, width: 5)
            .cornerRadius(20)

enter image description here

I want the entire view to have the blue border with rounded corners (instead of the red square overlapping the rounded blue border. How? I've tried seemingly all variations of ordering the modifiers.

soleil
  • 12,133
  • 33
  • 112
  • 183

4 Answers4

83

SwiftUI borders have straight edges no matter what corner radius you apply (.cornerRadius simply clips the view to a rounded mask and doesn't adjust the border's appearance).

If you want a rounded border, you'll need to overlay and .stroke a rounded rectangle:

VStack {
    Text("some text")
    Button("Ok") {}
        .foregroundColor(.cyan)
        .padding()
}
.padding()
.background(.red)
.cornerRadius(20) /// make the background rounded
.overlay( /// apply a rounded border
    RoundedRectangle(cornerRadius: 20)
        .stroke(.blue, lineWidth: 5)
)

Result:

Rounded blue border

aheze
  • 24,434
  • 8
  • 68
  • 125
16

sometimes just adding the overlay may produce unsatisfying results (this is the case when the used view is designed with infinite width/height)

for example

so I'd recommend using an inset when trying to add rounded border

.overlay(
    RoundedRectangle(cornerRadius: 20)
        .inset(by: 5) // inset value should be same as lineWidth in .stroke
        .stroke(.blue, lineWidth: 5)
)
Levan Apakidze
  • 221
  • 2
  • 6
  • This answer works for HStack etc. too, great! The only thing that worked for me, most other solutions are for buttons but leave strange line width differences, this here does not. Exactly what your sample image shows was my problem, thx!! – trinity420 Feb 07 '23 at 14:10
  • 1
    This solution works incredibly well with things like LazyVGrids that have a spacing and you use `.frame(maxWidth: .infinity)` on the elements. Before the spacing was too small then, now with the inset it works like a charm! – Brudus Apr 27 '23 at 10:38
0

Adding over the other answers, here is a neat extension for your SwiftUI app:

fileprivate struct ModifierCornerRadiusWithBorder: ViewModifier {
    var radius: CGFloat
    var borderLineWidth: CGFloat = 1
    var borderColor: Color = .gray
    var antialiased: Bool = true
    
    func body(content: Content) -> some View {
        content
            .cornerRadius(self.radius, antialiased: self.antialiased)
            .overlay(
                RoundedRectangle(cornerRadius: self.radius)
                    .inset(by: self.borderLineWidth)
                    .strokeBorder(self.borderColor, lineWidth: self.borderLineWidth, antialiased: self.antialiased)
            )
    }
}

extension View {
    func cornerRadiusWithBorder(radius: CGFloat, borderLineWidth: CGFloat = 1, borderColor: Color = .gray, antialiased: Bool = true) -> some View {
        modifier(ModifierCornerRadiusWithBorder(radius: radius, borderLineWidth: borderLineWidth, borderColor: borderColor, antialiased: antialiased))
    }
}

You can then just do

VStack {
    Text("some text")
    Button("Ok") {}
        .foregroundColor(.cyan)
        .padding()
}
.padding()
.background(.red)
.cornerRadiusWithBorder(radius: 20, borderLineWidth: 5, borderColor: . blue)

to achieve this

enter image description here

Radu Ursache
  • 1,230
  • 1
  • 22
  • 35
0

Using ".stroke" might be considered a best practice approach. However, if you'd like to try, the following method could also suffice:

RoundedRectangle(cornerRadius: 20)
    .foregroundColor(.blue)
    .frame(width: 100, height: 100)
    .overlay(
        RoundedRectangle(cornerRadius: 20)
            .foregroundColor(.red)
            .frame(width: 90, height: 90)
            .overlay(
                VStack {
                    Text("some text")
                    Button("Ok") {}
                }
            )
    )

You will get the same result with this.