Skip to content

Instantly share code, notes, and snippets.

Last active April 2, 2024 22:37
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save swiftui-lab/283d72e55124d9c7148113710bad46c4 to your computer and use it in GitHub Desktop.
Save swiftui-lab/283d72e55124d9c7148113710bad46c4 to your computer and use it in GitHub Desktop.
A debugging modifier for SwiftUI views (showSizes)
// Author: SwiftUI-Lab (
// Description: Implementation of the showSizes() debugging modifier
// blog article:
import SwiftUI
struct MeasureExample: View {
var body: some View {
VStack {
HStack {
ScrollView {
Text("Hello world!")
.showSizes([.current, .maximum])
Text("Hello world")
.aspectRatio(contentMode: .fit)
.showSizes([.minimum, .ideal, .maximum, .current])
.frame(maxWidth: .infinity, maxHeight: .infinity)
extension View {
// If proposal is nil, get min, ideal and max sizes
@ViewBuilder func showSizes(_ proposals: [MeasureLayout.SizeRequest] = [.minimum, .ideal, .maximum]) -> some View {
Measure(proposals: proposals) { self }
struct Measure<V: View>: View {
@State private var reportedSizes: [CGSize] = []
let proposals: [MeasureLayout.SizeRequest]
@ViewBuilder let content: () -> V
var body: some View {
MeasureLayout {
.layoutValue(key: MeasureLayout.InfoRequest.self, value: proposals)
.layoutValue(key: MeasureLayout.InfoReply.self, value: $reportedSizes)
.overlay(alignment: .topTrailing) {
.offset(y: -20)
var mergedSizes: String {
String( { String(format: "(%.1f, %.1f)" , $0.width, $0.height) }.joined(separator: " - "))
struct MeasureLayout: Layout {
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
return subviews[0].sizeThatFits(proposal)
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
DispatchQueue.main.async {
subviews[0][InfoReply.self]?.wrappedValue = subviews[0][InfoRequest.self].map {
$0.size(view: subviews[0], proposal: proposal)
subviews[0].place(at: CGPoint(x: bounds.midX, y: bounds.midY), anchor: .center, proposal: proposal)
struct InfoRequest: LayoutValueKey {
static var defaultValue: [SizeRequest] = []
struct InfoReply: LayoutValueKey {
static var defaultValue: Binding<[CGSize]>? = nil
enum SizeRequest {
case minimum
case ideal
case maximum
case current
case proposal(size: ProposedViewSize)
func size(view: LayoutSubview, proposal: ProposedViewSize) -> CGSize {
switch self {
case .minimum: return view.sizeThatFits(.zero)
case .ideal: return view.sizeThatFits(.unspecified)
case .maximum: return view.sizeThatFits(.infinity)
case .current: return view.sizeThatFits(proposal)
case .proposal(let prop): return view.sizeThatFits(prop)
Copy link

Very nice – but be aware String format %.1f uses rounding. So the decimal you see may be rounded and incorrect!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment