BS
BleepingSwift
Published on

> How to Round Specific Corners of a View in SwiftUI

Authors
  • avatar
    Name
    Mick MacCallum
    Twitter
    @0x7fs

SwiftUI's RoundedRectangle rounds all four corners equally, but sometimes you only want to round specific corners—like the top corners of a card or the bottom corners of a header. iOS 16 introduced UnevenRoundedRectangle for exactly this purpose, though you can also create custom shapes for earlier iOS versions.

Using UnevenRoundedRectangle (iOS 16+)

The easiest approach on iOS 16 and later is UnevenRoundedRectangle, which lets you specify a different radius for each corner:

Text("Top Corners Only")
    .padding()
    .frame(maxWidth: .infinity)
    .background(Color.blue)
    .clipShape(
        UnevenRoundedRectangle(
            topLeadingRadius: 20,
            bottomLeadingRadius: 0,
            bottomTrailingRadius: 0,
            topTrailingRadius: 20
        )
    )

You can also use the corner radii initializer for a more compact syntax:

UnevenRoundedRectangle(
    cornerRadii: .init(
        topLeading: 20,
        bottomLeading: 0,
        bottomTrailing: 0,
        topTrailing: 20
    )
)

Creating a Custom Shape for Earlier iOS

For iOS 15 and earlier, create a custom Shape that uses UIBezierPath:

struct RoundedCorner: Shape {
    var radius: CGFloat = .infinity
    var corners: UIRectCorner = .allCorners

    func path(in rect: CGRect) -> Path {
        let path = UIBezierPath(
            roundedRect: rect,
            byRoundingCorners: corners,
            cornerRadii: CGSize(width: radius, height: radius)
        )
        return Path(path.cgPath)
    }
}

Use it with .clipShape():

Text("Custom Corners")
    .padding()
    .background(Color.green)
    .clipShape(RoundedCorner(radius: 16, corners: [.topLeft, .topRight]))

You can create a convenient view extension:

extension View {
    func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
        clipShape(RoundedCorner(radius: radius, corners: corners))
    }
}

// Usage
Text("Bottom Corners")
    .padding()
    .background(Color.orange)
    .cornerRadius(16, corners: [.bottomLeft, .bottomRight])

Common Patterns

Rounding the top corners is useful for sheets and cards that slide up from the bottom:

struct BottomSheet: View {
    var body: some View {
        VStack {
            Capsule()
                .fill(Color.secondary.opacity(0.5))
                .frame(width: 40, height: 5)
                .padding(.top, 8)

            Text("Sheet Content")
                .padding()

            Spacer()
        }
        .frame(maxWidth: .infinity)
        .background(Color(.systemBackground))
        .clipShape(
            UnevenRoundedRectangle(
                topLeadingRadius: 20,
                bottomLeadingRadius: 0,
                bottomTrailingRadius: 0,
                topTrailingRadius: 20
            )
        )
        .shadow(radius: 10)
    }
}

For message bubbles where corners vary based on the sender:

struct MessageBubble: View {
    let text: String
    let isFromCurrentUser: Bool

    var body: some View {
        Text(text)
            .padding(12)
            .background(isFromCurrentUser ? Color.blue : Color(.systemGray5))
            .foregroundStyle(isFromCurrentUser ? .white : .primary)
            .clipShape(
                UnevenRoundedRectangle(
                    topLeadingRadius: 16,
                    bottomLeadingRadius: isFromCurrentUser ? 16 : 4,
                    bottomTrailingRadius: isFromCurrentUser ? 4 : 16,
                    topTrailingRadius: 16
                )
            )
    }
}

Combining with Strokes

To add a border to a shape with specific rounded corners, use the shape as both the clip and the stroke:

struct StrokedCard: View {
    let cornerRadii = RectangleCornerRadii(
        topLeading: 20,
        bottomLeading: 0,
        bottomTrailing: 0,
        topTrailing: 20
    )

    var body: some View {
        Text("Card Content")
            .padding()
            .frame(maxWidth: .infinity)
            .background(Color.white)
            .clipShape(UnevenRoundedRectangle(cornerRadii: cornerRadii))
            .overlay(
                UnevenRoundedRectangle(cornerRadii: cornerRadii)
                    .stroke(Color.blue, lineWidth: 2)
            )
    }
}

Different Radii for Each Corner

Sometimes each corner needs a completely different radius. UnevenRoundedRectangle handles this naturally:

struct AsymmetricCard: View {
    var body: some View {
        VStack(alignment: .leading) {
            Text("Asymmetric Design")
                .font(.headline)
            Text("Each corner has a different radius")
                .font(.subheadline)
                .foregroundStyle(.secondary)
        }
        .padding()
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(
            LinearGradient(
                colors: [.purple, .blue],
                startPoint: .topLeading,
                endPoint: .bottomTrailing
            )
        )
        .foregroundStyle(.white)
        .clipShape(
            UnevenRoundedRectangle(
                topLeadingRadius: 30,
                bottomLeadingRadius: 5,
                bottomTrailingRadius: 20,
                topTrailingRadius: 10
            )
        )
    }
}

Performance Note

Using clipShape is efficient for most use cases. However, if you're clipping many views in a scrolling list, consider whether you truly need selective corner rounding or if a uniform cornerRadius modifier would suffice. The standard RoundedRectangle is slightly more optimized since it doesn't need to handle asymmetric cases.

For static layouts and moderate numbers of views, the performance difference is negligible. Use whatever shape best serves your design.

Continuous vs Circular Corners

iOS uses "continuous" corner curves (also called squircles) in many system components, which look smoother than circular arcs. Both RoundedRectangle and UnevenRoundedRectangle support this:

UnevenRoundedRectangle(
    topLeadingRadius: 20,
    bottomLeadingRadius: 0,
    bottomTrailingRadius: 0,
    topTrailingRadius: 20,
    style: .continuous  // Smoother Apple-style corners
)

The .continuous style matches the corner style of app icons and system UI elements, while .circular uses traditional circular arcs.

subscribe.sh

// Stay Updated

Get notified when I publish new tutorials on Swift, SwiftUI, and iOS development. No spam, unsubscribe anytime.

>

By subscribing, you agree to our Privacy Policy.