Reputation: 1188
I am trying to add a border to a view and round the topLeading and topTrailing corner only. It seems extremely difficult to achieve? It's easy enough to just round the corners with this extension:
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)
}
}
extension View {
func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
clipShape( RoundedCorner(radius: radius, corners: corners) )
}
}
But this does not work when you apply a stroke. Any ideas how to achieve this?
Upvotes: 2
Views: 2105
Reputation: 5115
If for you need to implement specific radius for specific corners here is a solution in pure SwiftUI.
// Shape itself
struct SpecificCornerShape: Shape {
var topLeft: CGFloat = 0
var bottomLeft: CGFloat = 0
var topRight: CGFloat = 0
var bottomRight: CGFloat = 0
func path(in rect: CGRect) -> Path {
let minX = rect.minX
let minY = rect.minY
let maxX = rect.maxX
let maxY = rect.maxY
var path = Path()
path.move(to: CGPoint(x: minX + topLeft, y: minY))
path.addLine(to: CGPoint(x: maxX - topRight, y: minY))
path.addArc(
center: CGPoint(x: maxX - topRight, y: minY + topRight),
radius: topRight,
startAngle: Angle(radians: 3 * .pi / 2),
endAngle: Angle.zero,
clockwise: false)
path.addLine(to: CGPoint(x: maxX, y: maxY - bottomRight))
path.addArc(
center: CGPoint(x: maxX - bottomRight, y: maxY - bottomRight),
radius: bottomRight,
startAngle: Angle.zero,
endAngle: Angle(radians: .pi / 2),
clockwise: false)
path.addLine(to: CGPoint(x: minX + bottomLeft, y: maxY))
path.addArc(
center: CGPoint(x: minX + bottomLeft, y: maxY - bottomLeft),
radius: bottomLeft,
startAngle: Angle(radians: .pi / 2),
endAngle: Angle(radians: .pi),
clockwise: false)
path.addLine(to: CGPoint(x: minX, y: minY + topLeft))
path.addArc(
center: CGPoint(x: minX + topLeft, y: minY + topLeft),
radius: topLeft,
startAngle: Angle(radians: .pi),
endAngle: Angle(radians: 3 * .pi / 2),
clockwise: false)
path.closeSubpath()
return path
}
}
// Helpful modifier for clipping and stroking with any shape and style (color, gradient, material)
struct ClipAndStroke<S: Shape, SH: ShapeStyle>: ViewModifier {
let shape: S
let shapeStyle: SH
let lineWidth: CGFloat
func body(content: Content) -> some View {
content
.clipShape(shape)
.overlay(
shape
.stroke(shapeStyle, lineWidth: lineWidth)
)
}
}
// Convenience function to apply our modifier
extension View {
func clipAndStroke<S: Shape, SH: ShapeStyle>(_ shape: S, shapeStyle: SH, lineWidth: CGFloat = 1) -> some View {
modifier(ClipAndStroke(shape: shape, shapeStyle: shapeStyle, lineWidth: lineWidth))
}
}
Usage sample:
struct ContentView: View {
var body: some View {
Color.yellow
.frame(width: 300, height: 150)
.clipAndStroke(SpecificCornerShape(bottomLeft: 10, bottomRight: 30), shapeStyle: .red, lineWidth: 15)
}
}
Upvotes: 2
Reputation: 781
The common way to add a border to a view in SwiftUI is via the .overlay()
modifier. Using the RoundedCorner
shape you've already made, we can modify this answer to create a new modifier that'll both round the shape and add a border.
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)
}
}
extension View {
public func borderRadius<S>(_ content: S, width: CGFloat = 1, cornerRadius: CGFloat, corners: UIRectCorner) -> some View where S : ShapeStyle {
let roundedRect = RoundedCorner(radius: cornerRadius, corners: [.topLeft, .topRight])
return clipShape(roundedRect)
.overlay(roundedRect.stroke(content, lineWidth: width))
}
}
Usage:
Color.yellow
.borderRadius(Color.red, width: 15, cornerRadius: 25, corners: [.topLeft, .topRight])
.padding()
.frame(width: 300, height: 150)
Upvotes: 3