swiftPunk
swiftPunk

Reputation: 1

Why strokeBorder does not respect its border in SwiftUI?

I have an issue with my code which the strokeBorder does not stroke inside Border, if I use fill for my Shape it stays and respect to the border, but strokeBorder does not do the same, how to solve this issue?

import SwiftUI

struct ContentView: View {
    var body: some View {
        
        let myCustomInsettableShape = TestShape()
        
        ZStack {

            myCustomInsettableShape
                .fill(Color.white)

            myCustomInsettableShape
                .strokeBorder(Color.black.opacity(0.5), style: StrokeStyle(lineWidth: 50.0, lineCap: .round, lineJoin: .miter))
            
        }
        .background(Color.red)
        .padding(50)
    }
}

struct TestShape: InsettableShape {
    
    var insetAmount: CGFloat = .zero
    
    func path(in rect: CGRect) -> Path {
        
        // Adjust the rect to account for half the stroke width to ensure it stays inside
        let insetRect = rect.insetBy(dx: insetAmount, dy: insetAmount)
        
        // Create the path for the shape
        return Path { path in
            path.move(to: CGPoint(x: insetRect.minX, y: insetRect.midY))
            path.addLine(to: CGPoint(x: insetRect.midX, y: insetRect.minY))
            path.addLine(to: CGPoint(x: insetRect.maxX, y: insetRect.midY))
            path.addLine(to: CGPoint(x: insetRect.midX, y: insetRect.maxY))
            path.closeSubpath()
        }
        
    }
    
    func inset(by amount: CGFloat) -> some InsettableShape {
        var myShape = self
        myShape.insetAmount += amount
        return myShape
    }
    
}

enter image description here

Upvotes: 1

Views: 98

Answers (1)

Benzy Neez
Benzy Neez

Reputation: 21590

When you use .strokeBorder, it relies on .inset working properly, as explained in the documentation:

Returns a view that is the result of insetting self by style.lineWidth / 2, stroking the resulting shape with style, and then filling with content.

I think the way the inset is being applied when TestShape creates a path is probably too primitive. Let's try a test:

ZStack {
    TestShape()
        .stroke()

    TestShape()
        .inset(by: 50)
        .stroke()
}
.padding(50)

Screenshot

I would have expected to see a smaller diamond shape with the edges running parallel to the larger shape.

So let's fix the path function first. This requires using a little Pythagoras and trigonometry:

Diagram

func path(in rect: CGRect) -> Path {
    Path { path in
        let dx: CGFloat
        let dy: CGFloat
        if insetAmount > 0 && rect.width > 0 && rect.height > 0 {
            let diagonal = sqrt((rect.width * rect.width) + (rect.height * rect.height))
            let cosAlpha = rect.height / diagonal
            dx = insetAmount / cosAlpha
            let sinAlpha = rect.width / diagonal
            dy = insetAmount / sinAlpha
        } else {
            dx = 0
            dy = 0
        }
        path.move(to: CGPoint(x: rect.minX + dx, y: rect.midY))
        path.addLine(to: CGPoint(x: rect.midX, y: rect.minY + dy))
        path.addLine(to: CGPoint(x: rect.maxX - dx, y: rect.midY))
        path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY - dy))
        path.closeSubpath()
    }
}

Trying the test again:

Screenshot

That's looking better. Now let's try strokeBorder again with the original code:

Screenshot

Problem solved!

Upvotes: 2

Related Questions