-
-
Save Kyle-Ye/09476d3aa5a906207eeca71b5faa9c4a to your computer and use it in GitHub Desktop.
A debugging modifier for SwiftUI views (showSizes)
This file contains 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 | |
@available(iOS 16.0, *) | |
@ViewBuilder | |
public func showSizes(_ proposals: [MeasureLayout.SizeRequest] = [.minimum, .ideal, .maximum]) -> some View { | |
Measure(proposals: proposals) { self } | |
} | |
} | |
@available(iOS 16.0, *) | |
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: " - ")) | |
} | |
} | |
@available(iOS 16.0, *) | |
public struct MeasureLayout: Layout { | |
public func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { | |
return subviews[0].sizeThatFits(proposal) | |
} | |
public 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 | |
} | |
public 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