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
}
}
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)
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:
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:
That's looking better. Now let's try strokeBorder
again with the original code:
Problem solved!