GeometryReader.swift 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. /// A container view that allows its content to read the size proposed to it.
  2. ///
  3. /// Geometry readers always take up the size proposed to them; no more, no less.
  4. /// This is to decouple the geometry reader's size from the size of its content
  5. /// in order to avoid feedback loops.
  6. ///
  7. /// ```swift
  8. /// struct MeasurementView: View {
  9. /// var body: some View {
  10. /// GeometryReader { proxy in
  11. /// Text("Width: \(proxy.size.x)")
  12. /// Text("Height: \(proxy.size.y)")
  13. /// }
  14. /// }
  15. /// }
  16. /// ```
  17. ///
  18. /// > Note: Geometry reader content may get evaluated multiple times with various
  19. /// > sizes before the layout system settles on a size. Do not depend on the size
  20. /// > proposal always being final.
  21. public struct GeometryReader<Content: View>: TypeSafeView, View {
  22. var content: (GeometryProxy) -> Content
  23. public var body = EmptyView()
  24. public init(@ViewBuilder content: @escaping (GeometryProxy) -> Content) {
  25. self.content = content
  26. }
  27. func children<Backend: BaseAppBackend>(
  28. backend: Backend,
  29. snapshots: [ViewGraphSnapshotter.NodeSnapshot]?,
  30. environment: EnvironmentValues
  31. ) -> GeometryReaderChildren<Content> {
  32. GeometryReaderChildren()
  33. }
  34. func layoutableChildren<Backend: BaseAppBackend>(
  35. backend: Backend,
  36. children: GeometryReaderChildren<Content>
  37. ) -> [LayoutSystem.LayoutableChild] {
  38. []
  39. }
  40. func asWidget<Backend: BaseAppBackend>(
  41. _ children: GeometryReaderChildren<Content>,
  42. backend: Backend
  43. ) -> Backend.Widget {
  44. // This is a little different to our usual wrapper implementations
  45. // because we want to avoid calling the user's content closure before
  46. // we actually have to.
  47. return backend.createContainer()
  48. }
  49. func computeLayout<Backend: BaseAppBackend>(
  50. _ container: Backend.Widget,
  51. children: GeometryReaderChildren<Content>,
  52. proposedSize: ProposedViewSize,
  53. environment: EnvironmentValues,
  54. backend: Backend
  55. ) -> ViewLayoutResult {
  56. let size = proposedSize.replacingUnspecifiedDimensions(by: ViewSize(10, 10))
  57. let view = content(GeometryProxy(size: size))
  58. let environment = environment.with(\.layoutAlignment, .leading)
  59. let contentNode: AnyViewGraphNode<Content>
  60. if let node = children.node {
  61. contentNode = node
  62. } else {
  63. contentNode = AnyViewGraphNode(
  64. for: view,
  65. backend: backend,
  66. environment: environment
  67. )
  68. children.node = contentNode
  69. backend.insert(contentNode.widget.into(), into: container, at: 0)
  70. }
  71. let contentResult = contentNode.computeLayout(
  72. with: view,
  73. proposedSize: ProposedViewSize(size),
  74. environment: environment
  75. )
  76. return ViewLayoutResult(
  77. size: size,
  78. childResults: [contentResult]
  79. )
  80. }
  81. func commit<Backend: BaseAppBackend>(
  82. _ widget: Backend.Widget,
  83. children: GeometryReaderChildren<Content>,
  84. layout: ViewLayoutResult,
  85. environment: EnvironmentValues,
  86. backend: Backend
  87. ) {
  88. _ = children.node?.commit()
  89. backend.setPosition(ofChildAt: 0, in: widget, to: .zero)
  90. backend.setSize(of: widget, to: layout.size.vector)
  91. }
  92. }
  93. class GeometryReaderChildren<Content: View>: ViewGraphNodeChildren {
  94. var node: AnyViewGraphNode<Content>?
  95. var widgets: [AnyWidget] {
  96. [node?.widget].compactMap { $0 }
  97. }
  98. var erasedNodes: [ErasedViewGraphNode] {
  99. [node.map(ErasedViewGraphNode.init(wrapping:))].compactMap { $0 }
  100. }
  101. }