Lets say in SwiftUI we have a problem where we want a View that consists of:
An overall frame of reference in the form of an aspect ratio (the View will always have this aspect ratio)
A basic (or not) Shape with an outline
Other sub-views layered on top of this outlined shape that are clipped by that same shape
Another View on top of that that is not clipped by the shape.
To illustrate this is the desired behavior:
This is the code I have to accomplish it:
struct ContentView2: View {
var body: some View {
VStack{
Spacer()
Text("hello").padding()
Spacer()
CustomView().padding()
Spacer()
}
}
}
struct CustomView: View {
let aspectRatio = CGSize(width: 4, height: 3)
var body: some View {
let shape = ArbitraryTestShape()
ZStack{
shape
.fill(Color.white)
.stroke(Color.black, lineWidth: 5)
Color.blue
.frame(width:50)
Rectangle().fill(Color.red)
.frame(height:50)
//.clipShape(shape) // <------ uncommenting this line causes red rect height to shrink
Rectangle()
.fill(Color.yellow)
.frame(width:10, height: 200)
.rotationEffect(.degrees(45))
}
.aspectRatio(aspectRatio, contentMode: .fit)
.background(Color.gray)
}
}
struct ArbitraryTestShape: Shape {
func path(in rect: CGRect) -> Path {
let w = rect.width
let h = rect.height
// simple square
let p0 = CGPoint(x:w*1/4,y:h*1/4)
let p1 = CGPoint(x:w*1/4,y:h*3/4)
let p2 = CGPoint(x:w*3/4,y:h*3/4)
let p3 = CGPoint(x:w*3/4,y:h*1/4)
var path = Path()
path.move(to: p0)
path.addLine(to: p1)
path.addLine(to: p2)
path.addLine(to: p3)
path.closeSubpath()
return path
}
}
This is the result of that code (we have not yet clipped the blue and red rects):
But... and this is the problem, when I try to clip the first rect, it applies a new/different frame size to it like this:
I don't like this because it was unexpected and I don't understand why it's doing that.
I doubt this is a bug and I probably just don't understand how SwiftUI is going about rendering this...
I've tried rearranging and re-ordering operations with no good results.
If I did not require the final (yellow) View on top to be unbounded by the shape, this would not matter because in that case I could just clip the entrie ZStack, and that works as expected... but that isn't what I need to do.
Is there a simple fix or a better way to approach the problem?



The issue is that your custom Shape uses the container's width and height. So if you put the shape as an overlay to the rectangle you want to clip, you'll see clearly what's wrong:
The black rectangle is your custom shape and that's what it's using for clipping. Any modifier you apply on a shape will take its size as the container.
So the idea is to make sure the container remains the same size as the shape. Here's a possible solution:
Here, I've added the red rectangle as an overlay to the custom shape, so the parent container stays the same and the
clipShapeworks as expected.