Welcome to a short, yet a quite useful post regarding SwiftUI. Today I’m going to show you how it can become possible to round only specific corners of a SwiftUI view. Doing so is usually necessary when we want to decorate views with a different style, and as you will see here it’s actually really easy to achieve it.
Starting by the fact that SwiftUI does not provide a native, built-in way to round certain corners of a view, it becomes obvious that coming up with a custom solution is an unavoidable task. And that solution lies to using an old friend from the past, a class that still remains necessary in SwiftUI; the UIBezierPath.
UIBezierPath allows to define the path for any custom shape we want. It also provides us with some handy initializers regarding common shapes, allowing a few bits of tweaking through their arguments. One of those initializers gives the option to define a rectangle with rounded corners, but choosing which corners will be rounded! This is the UIBezierPath(roundedRect:byRoundingCorners:cornerRadii:)
.
The arguments this initializer expects are:
- The rectangle (CGRect) that the path of the shape is defined into.
- A collection of UIRectCorner values indicating the corners to be rounded; top-left, bottom-left, top-right, bottom-right, or all of them.
- The rounding amount as a CGSize value.
In order to use the above in SwiftUI and create a custom shape with specific rounded corners which can also be reusable, it’s necessary to implement a new structure that will be conforming to the Shape protocol. The corners that should become rounded and the radius value can be provided as arguments upon initializing an instance of that struct:
1 2 3 4 5 6 |
struct RoundedCornersShape: Shape { let corners: UIRectCorner let radius: CGFloat } |
Shape protocol has one requirement; to implement the method path(in:)
where we will define and return the path of the custom shape we are defining. Obviously, this is the place to create a path with the UIBezierPath(roundedRect:byRoundingCorners:cornerRadii:)
initializer:
1 2 3 4 5 6 7 |
func path(in rect: CGRect) -> Path { let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) } |
See that the corners
and radius
property values are used as arguments upon defining the path. However, the above is not enough because a Path value must be returned from the method, not a UIBezierPath.
A Path value can be initialized with paths of other types, but not a UIBezierPath. One of those types is the CGPath, a value that we can easily get from a UIBezierPath by accessing its cgPath
property. So, what the above method will be returning is this:
1 2 3 4 5 6 7 8 |
func path(in rect: CGRect) -> Path { let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) return Path(path.cgPath) } |
The entire RoundedCornersShape struct now is this:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
struct RoundedCornersShape: Shape { let corners: UIRectCorner let radius: CGFloat func path(in rect: CGRect) -> Path { let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) return Path(path.cgPath) } } |
Using RoundedCornersShape in SwiftUI views
Suppose that we have the following basic implementation of a Text view in SwiftUI:
1 2 3 4 5 6 7 8 |
var body: some View { Text(“Round my corners!”) .padding() .padding(.horizontal, 30) .background(Color(UIColor.systemGray4)) } |
Say now that we want to round the top-left and bottom-right corners. We will do that with the RoundedCornersShape we implemented earlier, providing an instance of it as the Text view’s background:
1 2 3 4 5 |
.background( RoundedCornersShape(corners: [.topLeft, .bottomRight], radius: 15) ) |
Note that at the same time the color set as background in the previous snippet must be removed:
.background(Color(UIColor.systemGray4))
The shape that will result by doing the above is this:
Apparently, it’s necessary to set a fill color to it:
1 2 3 4 |
RoundedCornersShape(corners: [.topLeft, .bottomRight], radius: 15) .fill(Color(UIColor.systemGray4)) |
Eventually, the outcome is the following:
Here’s the entire code regarding the sample Text view:
1 2 3 4 5 6 7 8 9 10 11 |
var body: some View { Text(“Round my corners!”) .padding() .padding(.horizontal, 30) .background( RoundedCornersShape(corners: [.topLeft, .bottomRight], radius: 15) .fill(Color(UIColor.systemGray4)) ) } |
Conclusion
There are definitely more ways to round specific corners of a SwiftUI view, however the above is the fastest and easiest approach one can follow in order to achieve that goal. What makes it even more interesting is that the RoundedCornersShape can be reused in as many views as we want, so with one simple implementation we can benefit multiple times.
Thanks for reading and take care!
This post has also been published on Medium.