Creating Custom Rounded Corners in SwiftUI with a View Modifier
SwiftUI provides a powerful and intuitive way to build user interfaces across all Apple platforms. However, some common UI customizations require a bit of extra work. One such customization is applying rounded corners to specific corners of a view with a border (stroke). In this tutorial, we'll explore how to create a custom ViewModifier
to simplify this process.
Why Do We Need a Custom Modifier?
By default, SwiftUI's cornerRadius
modifier applies the radius to all corners of a view. If you want to round specific corners or add a border to a view with custom rounded corners, you often need to stack multiple modifiers like background
, overlay
, and clipShape
. This can make your code verbose and harder to read.
Our custom roundedCorners
modifier simplifies this by encapsulating all the necessary logic into a single, reusable modifier.
The Complete Code
Here's the custom ViewModifier
and supporting structures we'll be discussing:
import SwiftUI
public struct RoundedCornerModifier: ViewModifier {
let radius: CGFloat
let corners: UIRectCorner
let strokeColor: Color
let lineWidth: CGFloat
public func body(content: Content) -> some View {
content
.clipShape(RoundedCorner(radius: radius, corners: corners))
.overlay(
RoundedCorner(radius: radius, corners: corners)
.stroke(strokeColor, lineWidth: lineWidth)
)
}
}
extension View {
public func roundedCorners(
radius: CGFloat,
corners: UIRectCorner = .allCorners,
strokeColor: Color = Color.primary,
lineWidth: CGFloat = 1.0
) -> some View {
self.modifier(
RoundedCornerModifier(
radius: radius,
corners: corners,
strokeColor: strokeColor,
lineWidth: lineWidth
)
)
}
}
public struct RoundedCorner: Shape {
var radius: CGFloat = .infinity
var corners: UIRectCorner = .allCorners
public func path(in rect: CGRect) -> Path {
let path = UIBezierPath(
roundedRect: rect,
byRoundingCorners: corners,
cornerRadii: CGSize(width: radius, height: radius)
)
return Path(path.cgPath)
}
}
Understanding the Components
1. RoundedCorner
Shape
The RoundedCorner
struct conforms to the Shape
protocol and allows us to define a shape with specific corners rounded.
public struct RoundedCorner: Shape {
var radius: CGFloat = .infinity
var corners: UIRectCorner = .allCorners
public func path(in rect: CGRect) -> Path {
let path = UIBezierPath(
roundedRect: rect,
byRoundingCorners: corners,
cornerRadii: CGSize(width: radius, height: radius)
)
return Path(path.cgPath)
}
}
radius
: The radius of the corners.corners
: Specifies which corners to round usingUIRectCorner
.path(in:)
: Creates aPath
from aUIBezierPath
with the specified corners rounded.
2. RoundedCornerModifier
ViewModifier
The RoundedCornerModifier
applies the RoundedCorner
shape to any view it's attached to.
public struct RoundedCornerModifier: ViewModifier {
let radius: CGFloat
let corners: UIRectCorner
let strokeColor: Color
let lineWidth: CGFloat
public func body(content: Content) -> some View {
content
.clipShape(RoundedCorner(radius: radius, corners: corners))
.overlay(
RoundedCorner(radius: radius, corners: corners)
.stroke(strokeColor, lineWidth: lineWidth)
)
}
}
clipShape
: Clips the view to the specifiedRoundedCorner
shape.overlay
: Adds a border (stroke) to the view with the sameRoundedCorner
shape.
3. View Extension
An extension on View
provides a convenient way to use the modifier.
extension View {
public func roundedCorners(
radius: CGFloat,
corners: UIRectCorner = .allCorners,
strokeColor: Color = Color.primary,
lineWidth: CGFloat = 1.0
) -> some View {
self.modifier(
RoundedCornerModifier(
radius: radius,
corners: corners,
strokeColor: strokeColor,
lineWidth: lineWidth
)
)
}
}
Default Parameters: The
corners
,strokeColor
, andlineWidth
parameters have default values, making the modifier flexible and easy to use.
How to Use the Custom Modifier
Here's how you can apply the roundedCorners
modifier to a view:
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello, SwiftUI!")
.padding()
.background(Color.blue)
.roundedCorners(
radius: 20,
corners: [.topLeft, .bottomRight],
strokeColor: Color.white,
lineWidth: 2
)
.padding()
}
}
Explanation
radius: 20
: The corner radius to apply.corners: [.topLeft, .bottomRight]
: Specifies that only the top-left and bottom-right corners should be rounded.strokeColor: Color.white
: The color of the border.lineWidth: 2
: The width of the border line.
Benefits of Using the Custom Modifier
Code Readability: Encapsulates multiple modifiers into one, making the code cleaner.
Reusability: Can be reused across different views in your project.
Flexibility: Easily customize which corners to round, the radius, border color, and line width.
Without the Custom Modifier
Without this custom modifier, achieving the same effect requires more verbose code:
Text("Hello, SwiftUI!")
.padding()
.background(Color.blue)
.clipShape(
RoundedCorner(radius: 20, corners: [.topLeft, .bottomRight])
)
.overlay(
RoundedCorner(radius: 20, corners: [.topLeft, .bottomRight])
.stroke(Color.white, lineWidth: 2)
)
.padding()
As you can see, we have to manually apply clipShape
and overlay
with the RoundedCorner
shape each time.
Customizing Further
You can extend this modifier or create additional ones to fit your specific needs, such as adding shadows or gradients.
Adding a Shadow
extension View {
public func roundedCornersWithShadow(
radius: CGFloat,
corners: UIRectCorner = .allCorners,
strokeColor: Color = Color.primary,
lineWidth: CGFloat = 1.0,
shadowColor: Color = .black.opacity(0.2),
shadowRadius: CGFloat = 5,
shadowX: CGFloat = 0,
shadowY: CGFloat = 5
) -> some View {
self.modifier(
RoundedCornerModifier(
radius: radius,
corners: corners,
strokeColor: strokeColor,
lineWidth: lineWidth
)
)
.shadow(
color: shadowColor,
radius: shadowRadius,
x: shadowX,
y: shadowY
)
}
}
Conclusion
Creating custom view modifiers in SwiftUI allows you to encapsulate complex view customizations into reusable components. The roundedCorners
modifier we've built simplifies the process of applying rounded corners to specific corners of a view and adding a border.
By using this modifier, you enhance code readability and maintainability in your SwiftUI projects.
Full Example in Context
import SwiftUI
struct CardView: View {
var body: some View {
VStack {
Image(systemName: "star.fill")
.foregroundColor(.white)
.padding()
.background(Color.orange)
.roundedCorners(
radius: 30,
corners: [.topLeft, .topRight],
strokeColor: .white,
lineWidth: 3
)
Text("Custom Rounded Corners")
.font(.headline)
.padding()
.background(Color.orange)
.roundedCorners(
radius: 30,
corners: [.bottomLeft, .bottomRight],
strokeColor: .white,
lineWidth: 3
)
}
.padding()
.background(Color.gray.opacity(0.2))
}
}
Preview
To see the result, you can use the SwiftUI preview:
struct CardView_Previews: PreviewProvider {
static var previews: some View {
CardView()
}
}
By integrating this custom modifier into your SwiftUI toolkit, you streamline the process of creating visually appealing and customized UI elements.