Skip to content

Instantly share code, notes, and snippets.

@swiftui-lab
Last active October 27, 2024 07:11
Show Gist options
  • Select an option

  • Save swiftui-lab/283d72e55124d9c7148113710bad46c4 to your computer and use it in GitHub Desktop.

Select an option

Save swiftui-lab/283d72e55124d9c7148113710bad46c4 to your computer and use it in GitHub Desktop.

Revisions

  1. swiftui-lab revised this gist Sep 2, 2022. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion showSizes.swift
    Original file line number Diff line number Diff line change
    @@ -27,7 +27,7 @@ struct MeasureExample: View {
    Image("clouds")
    .resizable()
    .aspectRatio(contentMode: .fit)
    .showSizes()
    .showSizes([.minimum, .ideal, .maximum, .current])


    }
  2. swiftui-lab created this gist Sep 2, 2022.
    116 changes: 116 additions & 0 deletions showSizes.swift
    Original 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)
    }
    }
    }
    }