I know you can use .cornerRadius()
to round all the corners of a swiftUI view but is there a way to round only specific corners such as the top?

- 4,069
- 3
- 33
- 42

- 4,082
- 3
- 17
- 33
-
1I ended up skipping SwiftUI because no matter what I did, the performance was terrible. In the end, I ended up using the maskedCorners property of the CALayer of my representable UIKit view. – Tamás Sengel Jun 01 '21 at 13:29
10 Answers
Demo (Source code is available at the end of the post)
iOS 13+
You can use it like a normal modifier:
.cornerRadius(20, corners: [.topLeft, .bottomRight])
You need to implement a simple extension on View
like this:
extension View {
func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
clipShape( RoundedCorner(radius: radius, corners: corners) )
}
}
And here is the struct behind this:
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)
}
}
You can also use the shape directly as a clipping mask.
Sample Project:
iOS 13 - Source code
iOS 16+ built-in modifier (Xcode 15 needed)
Clip the view using the new UnevenRoundedRectangle
:
.clipShape(
.rect(
topLeadingRadius: 0,
bottomLeadingRadius: 20,
bottomTrailingRadius: 0,
topTrailingRadius: 20
)
)
⚠️ Note: Although it works from iOS 16,
.rect
needs Xcode 15 to be available.
You can use the iOS 13+ method until the release of the Xcode 15
iOS 16 - Source code

- 95,414
- 31
- 268
- 278
-
Hi @Mojtaba, I like this solution. Is the `var style: RoundedCornerStyle = .continuous` actually getting used here? – Mark Moeykens Nov 23 '19 at 17:57
-
74
-
1
-
2Checkout [this answer](https://stackoverflow.com/a/58632759/5623035) for custom border @SorinLica – Mojtaba Hosseini Feb 11 '20 at 11:33
-
6Do you know how this would be implemented in a SwiftUI view for macOS (not Catalyst)? Looks like `NSRect` doesn't have an equivalent corner object, and `NSBezierPath` doesn't have the `byRoundingCorners` parameter. – TheNeil Mar 04 '20 at 23:12
-
This is a good solution but adding additional modifiers (e.g. `.shadow`) won't work. Use @Peter Kreinz answer below if you want to be able to add additional modifiers. – Darkisa Jun 20 '20 at 22:17
-
10It was working fine until ios14, view from the bottom is disappearing – shanezzar Oct 06 '20 at 12:49
-
8It does not work properly anymore in iOS14, I had some layout problems with it. – Cinn Oct 13 '20 at 13:56
-
-
@MojtabaHosseini I have a List to which only the 2 top corners are rounded, building for iOS14 now hides the bottom of this List. Applying cornerRadius to a container solves it but then the List rows overflow in the rounded corners. – Cinn Oct 13 '20 at 18:13
-
Share me a link to a reproducible code. Maybe I can find a way. @Cinn – Mojtaba Hosseini Oct 13 '20 at 18:26
-
-
Hi, @shanezzar, I met the same problem, please see my answer here: https://stackoverflow.com/a/64571117/4733603 – Kyle Xie Oct 30 '20 at 02:02
-
it stopped working on simulator 14.3 (entire view is not visible, if rounder corners are applied), however it stil works good on device with 14.3.... – Palejandro Dec 20 '20 at 17:12
-
I can't edit my previous comment , so here is another information: standard swift's function .cornerRadius() is not working properly too on simulator... view with rounded corners is also not visible, but it's content is visible.... strange behaviour. – Palejandro Dec 20 '20 at 17:30
-
2This answer does not work if you are trying to round the corners of a `ScrollView`. https://stackoverflow.com/questions/64259513 – joshuakcockrell Apr 29 '21 at 04:21
-
1It's a bug on iOS 14 that `clipShape` is not working as desired on `ScrollView`s @joshuakcockrell – Mojtaba Hosseini May 01 '21 at 10:21
-
1This is just beautiful - love it and it works like a charm! Used it with Xcode 13.0 beta 5 (13A5212g) – Manu Rink Oct 07 '21 at 06:58
-
2
-
it works quite well except for animations. If I animate a view from one shape to another and the view has normal cornerRadius on all corners, the corner radius also animates. With this method though there is no animation of the cornerRadius. It just jumps to the new value. – alionthego Nov 13 '21 at 07:29
-
Is there an alternative to `UIRectCorner` that can respect the layout direction (i.e. `topLeading` instead of `topLeft`) so that leading means left in LTR and right in RTL layout direction? – alobaili Jun 06 '23 at 11:20
-
1This answer would be substantially improved if it included the source code directly in the post as text, rather than images or links. [Text in images cannot be copied to use it, and images cannot be read by users with screen readers](//meta.stackoverflow.com/a/285557/208273). Links may become invalid if the linked site changes. – Ryan M Aug 08 '23 at 00:25
-
@RyanM Thanks for your attention. The source code is included. The images are extra sample project for better understanding and playground and I think adding the code in the same style of the actual code block will reduce the focus of the reader – Mojtaba Hosseini Aug 10 '23 at 14:24
-
Thanks for the thorough answer!Any idea why the iOS 16 solution requires Xcode 15? If it's in SwiftUI for iOS 16, I reckon it ought to work with Xcode 14, too. – VoodooBoot Sep 01 '23 at 22:38
-
1@VoodooBoot it's a `@backDeployed`, You need newer module (included in Xcode 15) to make it work but it can work on older runtimes since it's just a wrapper around the old solutions ;) – Mojtaba Hosseini Sep 02 '23 at 07:40
There are two options, you can use a View
with a Path
, or you can create a custom Shape
. In both cases you can use them standalone, or in a .background(RoundedCorders(...))
Option 1: Using Path + GeometryReader
(more info on GeometryReader: https://swiftui-lab.com/geometryreader-to-the-rescue/)
struct ContentView : View {
var body: some View {
Text("Hello World!")
.foregroundColor(.white)
.font(.largeTitle)
.padding(20)
.background(RoundedCorners(color: .blue, tl: 0, tr: 30, bl: 30, br: 0))
}
}
struct RoundedCorners: View {
var color: Color = .blue
var tl: CGFloat = 0.0
var tr: CGFloat = 0.0
var bl: CGFloat = 0.0
var br: CGFloat = 0.0
var body: some View {
GeometryReader { geometry in
Path { path in
let w = geometry.size.width
let h = geometry.size.height
// Make sure we do not exceed the size of the rectangle
let tr = min(min(self.tr, h/2), w/2)
let tl = min(min(self.tl, h/2), w/2)
let bl = min(min(self.bl, h/2), w/2)
let br = min(min(self.br, h/2), w/2)
path.move(to: CGPoint(x: w / 2.0, y: 0))
path.addLine(to: CGPoint(x: w - tr, y: 0))
path.addArc(center: CGPoint(x: w - tr, y: tr), radius: tr, startAngle: Angle(degrees: -90), endAngle: Angle(degrees: 0), clockwise: false)
path.addLine(to: CGPoint(x: w, y: h - br))
path.addArc(center: CGPoint(x: w - br, y: h - br), radius: br, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 90), clockwise: false)
path.addLine(to: CGPoint(x: bl, y: h))
path.addArc(center: CGPoint(x: bl, y: h - bl), radius: bl, startAngle: Angle(degrees: 90), endAngle: Angle(degrees: 180), clockwise: false)
path.addLine(to: CGPoint(x: 0, y: tl))
path.addArc(center: CGPoint(x: tl, y: tl), radius: tl, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false)
path.closeSubpath()
}
.fill(self.color)
}
}
}
Option 2: Custom Shape
struct ContentView : View {
var body: some View {
Text("Hello World!")
.foregroundColor(.white)
.font(.largeTitle)
.padding(20)
.background(RoundedCorners(tl: 0, tr: 30, bl: 30, br: 0).fill(Color.blue))
}
}
struct RoundedCorners: Shape {
var tl: CGFloat = 0.0
var tr: CGFloat = 0.0
var bl: CGFloat = 0.0
var br: CGFloat = 0.0
func path(in rect: CGRect) -> Path {
var path = Path()
let w = rect.size.width
let h = rect.size.height
// Make sure we do not exceed the size of the rectangle
let tr = min(min(self.tr, h/2), w/2)
let tl = min(min(self.tl, h/2), w/2)
let bl = min(min(self.bl, h/2), w/2)
let br = min(min(self.br, h/2), w/2)
path.move(to: CGPoint(x: w / 2.0, y: 0))
path.addLine(to: CGPoint(x: w - tr, y: 0))
path.addArc(center: CGPoint(x: w - tr, y: tr), radius: tr,
startAngle: Angle(degrees: -90), endAngle: Angle(degrees: 0), clockwise: false)
path.addLine(to: CGPoint(x: w, y: h - br))
path.addArc(center: CGPoint(x: w - br, y: h - br), radius: br,
startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 90), clockwise: false)
path.addLine(to: CGPoint(x: bl, y: h))
path.addArc(center: CGPoint(x: bl, y: h - bl), radius: bl,
startAngle: Angle(degrees: 90), endAngle: Angle(degrees: 180), clockwise: false)
path.addLine(to: CGPoint(x: 0, y: tl))
path.addArc(center: CGPoint(x: tl, y: tl), radius: tl,
startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false)
path.closeSubpath()
return path
}
}

- 52
- 1
- 5

- 37,663
- 13
- 111
- 125
-
If you define a custom `Shape` instead, you don't have to involve `GeometryReader`. – rob mayoff Aug 11 '19 at 05:24
-
Just a small correction on option 2: I think the path starts at the wrong x value since it looks to cut off the top line in its left half. I changed the path start point to `path.move(to: CGPoint(x: tl, y: 0))` and that seemed to fix it. – Alex H Nov 24 '20 at 01:21
-
This isn't as clean as answers below, but it's the only one that works as of iOS 14 when I want to round 3 corners. The other method ends up rounding all 4 when I want them rounded to `.infinity` – Trev14 Mar 15 '21 at 23:28
-
1While using `UIBezierPath` works well on iOS, it does not work on macOS or other places were UIKit is not available. Manually drawing the path in pure SwiftUI works great on all Apple platforms. – Eneko Alonso Jul 24 '21 at 21:42
-
A custom `Shape` is clearly the best way to accomplish this because it uses Native SwiftUI. @Trev14 It makes no sense to round a corner to `.infinity`. – Peter Schorn Jan 27 '22 at 22:44
-
@PeterSchorn SwiftUI handles rounding to infinity by doing the maximum amount of rounding for the current size, resulting in a capsule-like shape. It's nice to just say infinity and not have to calculate anything (note that SwiftUI has similar behaviors across the board for `.infinity`, like frame sizing where you want maximum expansion) – Trev14 Feb 17 '22 at 16:39
View Modifiers made it easy:
struct CornerRadiusStyle: ViewModifier {
var radius: CGFloat
var corners: UIRectCorner
struct CornerRadiusShape: 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)
}
}
func body(content: Content) -> some View {
content
.clipShape(CornerRadiusShape(radius: radius, corners: corners))
}
}
extension View {
func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
ModifiedContent(content: self, modifier: CornerRadiusStyle(radius: radius, corners: corners))
}
}
Example:
//left Button
.cornerRadius(6, corners: [.topLeft, .bottomLeft])
//right Button
.cornerRadius(6, corners: [.topRight, .bottomRight])

- 7,979
- 1
- 64
- 49
-
1Do you know how this would be implemented in a SwiftUI view for macOS (not Catalyst)? Looks like `NSRect` doesn't have an equivalent corner object, and `NSBezierPath` doesn't have the `byRoundingCorners` parameter. – TheNeil Mar 04 '20 at 23:09
-
Any else using this, or the above version on iOS 14? I find it clips any scrollview to the edges - same code runs fine on iOS 13 devices/simulators. – Richard Groves Sep 23 '20 at 13:26
-
Hi, @RichardGroves, I met the exact same problem as you. See my answer here: https://stackoverflow.com/a/64571117/4733603 – Kyle Xie Oct 30 '20 at 01:59
-
@KyleXie Thanks but I need it for cases where just 2 corners are rounded and there is no standard shape to do that, which is why I'd got to the custom path shape in the first place. – Richard Groves Nov 04 '20 at 17:24
-
@RichardGroves, ah, I see. I currently use full rounded corners and use something else covered the bottom corners. I know it's really hacking, but I have no other way to make it work. – Kyle Xie Nov 06 '20 at 01:50
-
-
-
This solution works, but won't work when you need the element to have just a stroke instead of a .fill(). Since it clips the shape, it will clip the stroke. – Tim Kranen Jul 20 '22 at 08:45
I have discovered a simple approach for rounding one-sided corners. It uses a 'positive-negative padding dance' to accomplish just what I was looking for.
So it basically works like this:
- Add some padding to the bottom of your view
- Round all corners with
.cornerRadius(_:)
- Remove the padding by applying negative padding of the same value
struct OnlyTopRoundedCornersDemo: View {
let radius = 12 // radius we need
var body: some View {
Rectangle()
.frame(height: 50)
.foregroundColor(.black)
.padding(.bottom, radius)
.cornerRadius(radius)
.padding(.bottom, -radius)
}
}
The resulting view looks like this:
As you can see, its frame is perfectly aligned with its content (blue border). Same approach could be used to round pairs ob bottom or side corners. Hope this helps somebody!

- 1,230
- 1
- 12
- 12
-
3
-
-
1I was looking to remove the optimization opportunity "A CAShapeLayer is used with a path that's a rect, a rounded-rect, or an ellipse. Instead, use an appropriately transformed plain CALayer with cornerRadius set", and this solution worked for me. I'd just point that radius should be defined as CGFloat, otherwise Xcode throws an error. – Alex Luque Jun 01 '23 at 15:58
Another option (maybe better) is actually to step back to UIKIt for this. Eg:
struct ButtonBackgroundShape: Shape {
var cornerRadius: CGFloat
var style: RoundedCornerStyle
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
return Path(path.cgPath)
}
}

- 13,234
- 14
- 63
- 73
Here an adaption for macOS:
// defines OptionSet, which corners to be rounded – same as UIRectCorner
struct RectCorner: OptionSet {
let rawValue: Int
static let topLeft = RectCorner(rawValue: 1 << 0)
static let topRight = RectCorner(rawValue: 1 << 1)
static let bottomRight = RectCorner(rawValue: 1 << 2)
static let bottomLeft = RectCorner(rawValue: 1 << 3)
static let allCorners: RectCorner = [.topLeft, topRight, .bottomLeft, .bottomRight]
}
// draws shape with specified rounded corners applying corner radius
struct RoundedCornersShape: Shape {
var radius: CGFloat = .zero
var corners: RectCorner = .allCorners
func path(in rect: CGRect) -> Path {
var path = Path()
let p1 = CGPoint(x: rect.minX, y: corners.contains(.topLeft) ? rect.minY + radius : rect.minY )
let p2 = CGPoint(x: corners.contains(.topLeft) ? rect.minX + radius : rect.minX, y: rect.minY )
let p3 = CGPoint(x: corners.contains(.topRight) ? rect.maxX - radius : rect.maxX, y: rect.minY )
let p4 = CGPoint(x: rect.maxX, y: corners.contains(.topRight) ? rect.minY + radius : rect.minY )
let p5 = CGPoint(x: rect.maxX, y: corners.contains(.bottomRight) ? rect.maxY - radius : rect.maxY )
let p6 = CGPoint(x: corners.contains(.bottomRight) ? rect.maxX - radius : rect.maxX, y: rect.maxY )
let p7 = CGPoint(x: corners.contains(.bottomLeft) ? rect.minX + radius : rect.minX, y: rect.maxY )
let p8 = CGPoint(x: rect.minX, y: corners.contains(.bottomLeft) ? rect.maxY - radius : rect.maxY )
path.move(to: p1)
path.addArc(tangent1End: CGPoint(x: rect.minX, y: rect.minY),
tangent2End: p2,
radius: radius)
path.addLine(to: p3)
path.addArc(tangent1End: CGPoint(x: rect.maxX, y: rect.minY),
tangent2End: p4,
radius: radius)
path.addLine(to: p5)
path.addArc(tangent1End: CGPoint(x: rect.maxX, y: rect.maxY),
tangent2End: p6,
radius: radius)
path.addLine(to: p7)
path.addArc(tangent1End: CGPoint(x: rect.minX, y: rect.maxY),
tangent2End: p8,
radius: radius)
path.closeSubpath()
return path
}
}
// View extension, to be used like modifier:
// SomeView().roundedCorners(radius: 20, corners: [.topLeft, .bottomRight])
extension View {
func roundedCorners(radius: CGFloat, corners: RectCorner) -> some View {
clipShape( RoundedCornersShape(radius: radius, corners: corners) )
}
}

- 9,523
- 1
- 8
- 26
One more option to the top cleanest (iOS 15+):
.background(Color.orange, in: RoundedRectangle(cornerRadius: 20))
.background(content: { Color.white.padding(.top, 20) })

- 5,105
- 1
- 23
- 29
I'd like to add to Kontiki's answer;
If you're using option 2 and want to add a stroke to the shape, be sure to add the following right before returning the path:
path.addLine(to: CGPoint(x: w/2.0, y: 0))
Otherwise, the stroke will be broken from the top left corner to the middle of the top side.

- 11
- 4
Use the code in this link where the struct of type shape is RoundedCorners:Shape{ //} https://stackoverflow.com/a/56763282/10637692
use below lines of code in this link before path.closeSubpath()
path.move(to: CGPoint(x: 280, y: 20))
path.addLine(to: CGPoint(x: w, y: 0))
path.addArc(center: CGPoint(x: w, y: 70), radius: br, //x = move triangle to right left
startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 180), clockwise: false)

- 89
- 5