swiftswiftuilayoutgeometryreader

How could I set the cornerRadius of a RoundedRectangle to be proportional to its size?


I have a RoundedRectangle that could get pretty big on large screens, i.e. iPads or MacOS windows, and in that case a fixed cornerRadius chosen for iPhone-size screens would seem inappropriate.

I've tried using a GeometryReader to get the current available width and multiply it by a certain scale factor to get the cornerRadius, this approach works, but the use of GeometryReader changes my layout (Probably because it takes up all the space available?). My current approach:

GeometryReader { geometry in
            let cornerRadius = geometry.size.width * 0.27
            RoundedRectangle(cornerRadius: cornerRadius
                .aspectRatio(5/7, contentMode: .fit)
        }

I've found that applying a .aspectRatio() modifier would solve the problem:

GeometryReader { geometry in
            let cornerRadius = geometry.size.width * 0.27
            RoundedRectangle(cornerRadius: cornerRadius)
                .aspectRatio(5/7, contentMode: .fit)
        }
        .aspectRatio(5/7, contentMode: .fit)

But this workaround doesn't seem proper since I probably wouldn't have a fixed aspect ratio for the content inside the next time I'm in this situation.

Without a GeometryReader: The original layout

With GeometryReader: You can see that the vertical spacing between rectangles are a bit wider

(I'm new to StackOverflow, so I can't directly put images here yet...)

Is there a cleaner approach to prevent GeometryReader from changing the layout or is there a cleaner way to make the cornerRadius proportional to the size of the RoundedRectangle? Any help would be greatly appreciated!


Solution

  • You could create your own Shape for this.

    A Shape must implement a path function and this function is supplied with a CGRect. This gives us the dimensions of the area being drawn into:

    struct ProportionalRoundedRectangle: Shape {
        let cornerFraction: CGFloat
    
        func path(in rect: CGRect) -> Path {
            let minSize = min(rect.width, rect.height)
            let cornerSize = min(0.5, max(0, cornerFraction)) * minSize
            return Path(
                roundedRect: rect,
                cornerSize: .init(width: cornerSize, height: cornerSize)
            )
        }
    }
    

    Example use:

    VStack {
        ProportionalRoundedRectangle(cornerFraction: 0.1)
            .stroke(.blue, lineWidth: 3)
            .frame(width: 200, height: 100)
        ProportionalRoundedRectangle(cornerFraction: 0.25)
            .stroke(.red, lineWidth: 3)
            .frame(width: 200, height: 100)
            .overlay {
                ProportionalRoundedRectangle(cornerFraction: 0.4)
                    .fill(.green)
                    .padding()
            }
    }
    

    Screenshot