Justin Frazer
Justin Frazer

Reputation: 970

SwiftUI - Add Border to One Edge of an Image

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

Answers (6)

Mojtaba Hosseini
Mojtaba Hosseini

Reputation: 119310

You can use this modifier on any View:

.border(width: 5, edges: [.top, .leading], color: .yellow)
Demo

Demo


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

Gene Bogdanovich
Gene Bogdanovich

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))
    }
}

An image with a bottom border

Upvotes: 7

NintendoJedi
NintendoJedi

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

coreyd303
coreyd303

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

Sherwin Zadeh
Sherwin Zadeh

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

smakus
smakus

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

Related Questions