-
-
Save cjazz/a090bb04fbebbf6086ca9f912ae0109d to your computer and use it in GitHub Desktop.
A debugging modifier for SwiftUI views (showSizes)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Author: SwiftUI-Lab (swiftui-lab.com) | |
// Description: Implementation of the showSizes() debugging modifier | |
// blog article: https://swiftui-lab.com/layout-protocol-part-2 | |
import SwiftUI | |
struct MeasureExample: View { | |
var body: some View { | |
VStack { | |
HStack { | |
ScrollView { | |
Text("Hello world!") | |
} | |
.showSizes([.current, .maximum]) | |
Rectangle() | |
.fill(.yellow) | |
.showSizes() | |
Text("Hello world") | |
.showSizes() | |
Image("clouds") | |
.showSizes() | |
Image("clouds") | |
.resizable() | |
.aspectRatio(contentMode: .fit) | |
.showSizes([.minimum, .ideal, .maximum, .current]) | |
} | |
.padding(60) | |
} | |
.frame(maxWidth: .infinity, maxHeight: .infinity) | |
.background(.white) | |
} | |
} | |
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 { | |
content() | |
.layoutValue(key: MeasureLayout.InfoRequest.self, value: proposals) | |
.layoutValue(key: MeasureLayout.InfoReply.self, value: $reportedSizes) | |
.overlay(alignment: .topTrailing) { | |
Text(mergedSizes) | |
.background(.gray) | |
.foregroundColor(.white) | |
.font(.caption) | |
.offset(y: -20) | |
.fixedSize() | |
} | |
} | |
} | |
var mergedSizes: String { | |
String(reportedSizes.map { 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) | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment