SwiftUI - Add Border to One Edge of an Image
If somebody ever needs to just add a quick 1 (or more) sided border to a view (e.g., the top edge, or any random combination of edges), I've found this works well and is tweakable:
top edge:
.overlay(Rectangle().frame(width: nil, height: 1, alignment: .top).foregroundColor(Color.gray), alignment: .top)
leading edge:
.overlay(Rectangle().frame(width: 1, height: nil, alignment: .leading).foregroundColor(Color.gray), alignment: .leading)
etc.
Just tweak the height, width, and edge to produce the combination of borders you want.
If you don't need to control thickness, you can do this:
.overlay(Divider(), alignment: .top)
.overlay(Divider(), alignment: .bottom)
Set the color of the divider using:
.overlay(Divider().background(.red), alignment: .left)
Demo
Implementation
You can use this modifier on any View
:
.border(width: 5, edges: [.top, .leading], color: .yellow)
With the help of this simple extension:
extension View {
func border(width: CGFloat, edges: [Edge], color: Color) -> some View {
overlay(EdgeBorder(width: width, edges: edges).foregroundColor(color))
}
}
And here is the magic struct behind this:
struct EdgeBorder: Shape {
var width: CGFloat
var edges: [Edge]
func path(in rect: CGRect) -> Path {
var path = Path()
for edge in edges {
var x: CGFloat {
switch edge {
case .top, .bottom, .leading: return rect.minX
case .trailing: return rect.maxX - width
}
}
var y: CGFloat {
switch edge {
case .top, .leading, .trailing: return rect.minY
case .bottom: return rect.maxY - width
}
}
var w: CGFloat {
switch edge {
case .top, .bottom: return rect.width
case .leading, .trailing: return self.width
}
}
var h: CGFloat {
switch edge {
case .top, .bottom: return self.width
case .leading, .trailing: return rect.height
}
}
path.addPath(Path(CGRect(x: x, y: y, width: w, height: h)))
}
return path
}
}
Add a top border aka Divider:
.overlay( Divider()
.frame(maxWidth: .infinity, maxHeight:1)
.background(Color.green), alignment: .top)
Example Usage:
Image("YouImageName")
.resizable()
.scaledToFit()
.frame(height: 40)
.padding(.top, 6) // padding above you Image, before your border
.overlay( Divider()
.frame(maxWidth: .infinity, maxHeight:1)
.background(Color.green), alignment: .top) // End Overlay
.padding(.top, 0) // padding above border
Explanation:
For a horizontal border aka Divider, frame width is the length of the border and height is the thickness of the border. Vertical border the frame width is thickness and frame height is the length.
The .background will set the color of the border.
Alignment will set, where the border will draw. For example "alignment: .bottom" will place the border on the bottom of the Image and "alignment: .top" on the top of the Image.
".leading & .trailing" will draw the border on the left and right of the Image correspondingly.
For a vertical border:
.overlay( Divider()
.frame(maxWidth: 1, maxHeight: .infinity)
.background(Color.green), alignment: .leading )