OptionalView.swift 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. /// A view used by ``ViewBuilder`` to support non-exhaustive if statements.
  2. public struct OptionalView<V: View> {
  3. public var body = EmptyView()
  4. var view: V?
  5. /// Wraps an optional view.
  6. init(_ view: V?) {
  7. self.view = view
  8. }
  9. }
  10. extension OptionalView: View {
  11. public var _asMenuItems: [MenuItem] {
  12. view?._asMenuItems ?? []
  13. }
  14. }
  15. extension OptionalView: TypeSafeView {
  16. typealias Children = OptionalViewChildren<V>
  17. func children<Backend: BaseAppBackend>(
  18. backend: Backend,
  19. snapshots: [ViewGraphSnapshotter.NodeSnapshot]?,
  20. environment: EnvironmentValues
  21. ) -> OptionalViewChildren<V> {
  22. // TODO: This is a conservative implementation, perhaps there are some situations
  23. // where we could usefully use the snapshots even if there are too many.
  24. let snapshot = snapshots?.count == 1 ? snapshots?.first : nil
  25. return OptionalViewChildren(
  26. from: view,
  27. backend: backend,
  28. snapshot: snapshot,
  29. environment: environment
  30. )
  31. }
  32. func asWidget<Backend: BaseAppBackend>(
  33. _ children: OptionalViewChildren<V>,
  34. backend: Backend
  35. ) -> Backend.Widget {
  36. return backend.createContainer()
  37. }
  38. func computeLayout<Backend: BaseAppBackend>(
  39. _ widget: Backend.Widget,
  40. children: OptionalViewChildren<V>,
  41. proposedSize: ProposedViewSize,
  42. environment: EnvironmentValues,
  43. backend: Backend
  44. ) -> ViewLayoutResult {
  45. let hasToggled: Bool
  46. let result: ViewLayoutResult
  47. if let view {
  48. if let node = children.node {
  49. result = node.computeLayout(
  50. with: view,
  51. proposedSize: proposedSize,
  52. environment: environment
  53. )
  54. hasToggled = false
  55. } else {
  56. let node = AnyViewGraphNode(
  57. for: view,
  58. backend: backend,
  59. environment: environment
  60. )
  61. children.node = node
  62. result = node.computeLayout(
  63. with: view,
  64. proposedSize: proposedSize,
  65. environment: environment
  66. )
  67. hasToggled = true
  68. }
  69. } else {
  70. hasToggled = children.node != nil
  71. children.node = nil
  72. result = ViewLayoutResult(
  73. size: .zero,
  74. childResults: [],
  75. participateInStackLayoutsWhenEmpty: false
  76. )
  77. }
  78. children.hasToggled = children.hasToggled || hasToggled
  79. return result
  80. }
  81. func commit<Backend: BaseAppBackend>(
  82. _ widget: Backend.Widget,
  83. children: OptionalViewChildren<V>,
  84. layout: ViewLayoutResult,
  85. environment: EnvironmentValues,
  86. backend: Backend
  87. ) {
  88. if children.hasToggled {
  89. backend.removeAllChildren(of: widget)
  90. if let node = children.node {
  91. backend.insert(node.widget.into(), into: widget, at: 0)
  92. backend.setPosition(ofChildAt: 0, in: widget, to: .zero)
  93. }
  94. children.hasToggled = false
  95. }
  96. _ = children.node?.commit()
  97. backend.setSize(of: widget, to: layout.size.vector)
  98. }
  99. }
  100. /// Stores a view graph node for the view's child if present. Tracks whether
  101. /// the child has toggled since last time the parent was updated or not.
  102. class OptionalViewChildren<V: View>: ViewGraphNodeChildren {
  103. /// The view graph node for the view's child if present.
  104. var node: AnyViewGraphNode<V>?
  105. /// Whether the view has toggled since the last non-dryrun update. `true`
  106. /// if the first view update hasn't occurred yet.
  107. var hasToggled = true
  108. var widgets: [AnyWidget] {
  109. return [node?.widget].compactMap { $0 }
  110. }
  111. var erasedNodes: [ErasedViewGraphNode] {
  112. if let node {
  113. [ErasedViewGraphNode(wrapping: node)]
  114. } else {
  115. []
  116. }
  117. }
  118. /// Creates storage for an optional view's child if present (which can change at
  119. /// any time).
  120. init<Backend: BaseAppBackend>(
  121. from view: V?,
  122. backend: Backend,
  123. snapshot: ViewGraphSnapshotter.NodeSnapshot?,
  124. environment: EnvironmentValues
  125. ) {
  126. if let view {
  127. node = AnyViewGraphNode(
  128. for: view,
  129. backend: backend,
  130. snapshot: snapshot,
  131. environment: environment
  132. )
  133. }
  134. }
  135. }