Reputation: 970
It's a pretty straight-forward question - How does one apply a border effect to only the wanted edges of an Image with SwiftUI?
For example, I only want to apply a border to the top and bottom edges of an image because the image is taking up the entire width of the screen.
Image(mission.missionImageString)
.resizable()
.aspectRatio(contentMode: .fit)
.border(Color.white, width: 2) //Adds a border to all 4 edges
Any help is appreciated!
Upvotes: 74
Views: 40699
Reputation: 119310
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 {
edges.map { edge -> Path in
switch edge {
case .top: return Path(.init(x: rect.minX, y: rect.minY, width: rect.width, height: width))
case .bottom: return Path(.init(x: rect.minX, y: rect.maxY - width, width: rect.width, height: width))
case .leading: return Path(.init(x: rect.minX, y: rect.minY, width: width, height: rect.height))
case .trailing: return Path(.init(x: rect.maxX - width, y: rect.minY, width: width, height: rect.height))
}
}.reduce(into: Path()) { $0.addPath($1) }
}
}
Upvotes: 119
Reputation: 1014
I find the most elegant solution is to create a custom shape and add it as an overlay. It works nicely with the SwiftUI image.
struct BottomBorder: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: rect.minX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
return path
}
}
struct ContentView: View {
var body: some View {
Image("image")
.overlay(BottomBorder().stroke(Color.red, lineWidth: 8))
}
}
Upvotes: 7
Reputation: 101
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 )
Upvotes: 10
Reputation: 87
a very easy way to accomplish this
VStack(spacing: 0) {
ViewThatNeedsABorder()
Divider() // or use rectangle
}
you could swap the divider for a rectangle as well and get the same effect. Style the divider or rectangle as needed using frame, or background etc.
if you need left/right borders use an HStack instead of a VStack, or even combine HStack and VStack to get borders on multiple sides ie: left and top.
Upvotes: 0
Reputation: 1452
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)
Upvotes: 47
Reputation: 1417
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.
Upvotes: 81