Last active
October 27, 2024 07:11
-
-
Save swiftui-lab/283d72e55124d9c7148113710bad46c4 to your computer and use it in GitHub Desktop.
Revisions
-
swiftui-lab revised this gist
Sep 2, 2022 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -27,7 +27,7 @@ struct MeasureExample: View { Image("clouds") .resizable() .aspectRatio(contentMode: .fit) .showSizes([.minimum, .ideal, .maximum, .current]) } -
swiftui-lab created this gist
Sep 2, 2022 .There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,116 @@ // 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() } .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) } } } }