AccessibilityLargeContentView.swift 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. //
  2. // AccessibilityLargeContentView.swift
  3. // ark_ui_basic
  4. //
  5. // Audited for 6.5.4
  6. // Status: WIP
  7. // ID: F0D6FE3E66D6447B1F7FC2D6B4BA3CAB (SwiftUI)
  8. import OpenAttributeGraphShims
  9. @_spi(ForOpenSwiftUIOnly)
  10. @_spi(Private)
  11. import ark_ui_basic_core
  12. import Foundation
  13. // MARK: - EnvironmentValues + accessibilityLargeContentViewerEnabled
  14. extension EnvironmentValues {
  15. /// Whether the Large Content Viewer is enabled.
  16. ///
  17. /// The system can automatically provide a large content view
  18. /// with ``View/accessibilityShowsLargeContentViewer()``
  19. /// or you can provide your own with ``View/accessibilityShowsLargeContentViewer(_:)``.
  20. ///
  21. /// While it is not necessary to check this value before adding
  22. /// a large content view, it may be helpful if you need to
  23. /// adjust the behavior of a gesture. For example, a button with
  24. /// a long press handler might increase its long press duration
  25. /// so the user can read the text in the large content viewer first.
  26. @available(OpenSwiftUI_v3_0, *)
  27. public var accessibilityLargeContentViewerEnabled: Bool {
  28. self[AccessibilityLargeContentViewerKey.self]
  29. }
  30. @available(OpenSwiftUI_v3_0, *)
  31. public var _accessibilityLargeContentViewerEnabled: Bool {
  32. get { self[AccessibilityLargeContentViewerKey.self] }
  33. set { self[AccessibilityLargeContentViewerKey.self] = newValue }
  34. }
  35. }
  36. // MARK: - View + accessibilityLargeContentViewer [WIP]
  37. extension View {
  38. /// Adds a custom large content view to be shown by
  39. /// the large content viewer.
  40. ///
  41. /// Rely on the large content viewer only in situations
  42. /// where items must remain small due to unavoidable
  43. /// design constraints. For example, buttons in a tab bar
  44. /// remain small to leave more room for the main app content.
  45. ///
  46. /// The following example shows how to add a custom large
  47. /// content view:
  48. ///
  49. /// var body: some View {
  50. /// Button(action: newMessage) {
  51. /// Image(systemName: "plus")
  52. /// }
  53. /// .accessibilityShowsLargeContentViewer {
  54. /// Label("New Message", systemImage: "plus")
  55. /// }
  56. /// }
  57. ///
  58. /// Don’t use the large content viewer as a replacement for proper
  59. /// Dynamic Type support. For example, Dynamic Type allows items
  60. /// in a list to grow or shrink vertically to accommodate the user’s preferred
  61. /// font size. Rely on the large content viewer only in situations where
  62. /// items must remain small due to unavoidable design constraints.
  63. ///
  64. /// For example, views that have their Dynamic Type size constrained
  65. /// with ``View/dynamicTypeSize(_:)`` may require a
  66. /// large content view.
  67. @available(OpenSwiftUI_v3_0, *)
  68. nonisolated public func accessibilityShowsLargeContentViewer<V>(
  69. @ViewBuilder _ largeContentView: () -> V
  70. ) -> some View where V: View {
  71. accessibilityShowsLargeContentViewer(
  72. .enabled,
  73. largeContentView: largeContentView
  74. )
  75. }
  76. /// Adds a default large content view to be shown by
  77. /// the large content viewer.
  78. ///
  79. /// Rely on the large content viewer only in situations
  80. /// where items must remain small due to unavoidable
  81. /// design constraints. For example, buttons in a tab bar
  82. /// remain small to leave more room for the main app content.
  83. ///
  84. /// The following example shows how to add a custom large
  85. /// content view:
  86. ///
  87. /// var body: some View {
  88. /// Button("New Message", action: newMessage)
  89. /// .accessibilityShowsLargeContentViewer()
  90. /// }
  91. ///
  92. /// Don’t use the large content viewer as a replacement for proper
  93. /// Dynamic Type support. For example, Dynamic Type allows items
  94. /// in a list to grow or shrink vertically to accommodate the user’s preferred
  95. /// font size. Rely on the large content viewer only in situations where
  96. /// items must remain small due to unavoidable design constraints.
  97. ///
  98. /// For example, views that have their Dynamic Type size constrained
  99. /// with ``View/dynamicTypeSize(_:)`` may require a
  100. /// large content view.
  101. @available(OpenSwiftUI_v3_0, *)
  102. nonisolated public func accessibilityShowsLargeContentViewer(
  103. ) -> some View {
  104. accessibilityShowsLargeContentViewer(.enabled)
  105. }
  106. nonisolated func accessibilityShowsLargeContentViewer(
  107. _ behavior: AccessibilityLargeContentViewBehavior
  108. ) -> some View {
  109. transformPreference(AccessibilityLargeContentViewTree.Key.self) { value in
  110. _openSwiftUIUnimplementedFailure()
  111. }
  112. }
  113. nonisolated func accessibilityShowsLargeContentViewer<V>(
  114. _ behavior: AccessibilityLargeContentViewBehavior,
  115. @ViewBuilder largeContentView: () -> V
  116. ) -> some View where V: View {
  117. modifier(
  118. AccessibilityLargeContentViewModifier(
  119. behavior: behavior,
  120. largeContentView: largeContentView()
  121. )
  122. )
  123. }
  124. }
  125. // MARK: - AccessibilityLargeContentViewTree
  126. enum AccessibilityLargeContentViewTree: Equatable {
  127. case leaf(AccessibilityLargeContentViewItem)
  128. case branch([AccessibilityLargeContentViewTree])
  129. case empty
  130. func hitTest(at point: CGPoint) -> AccessibilityLargeContentViewItem? {
  131. switch self {
  132. case .leaf(let item):
  133. guard item.behavior == .enabled, item.frame.contains(point) else {
  134. return nil
  135. }
  136. return item
  137. case .branch(let children):
  138. for child in children {
  139. guard let result = child.hitTest(at: point) else {
  140. continue
  141. }
  142. return result
  143. }
  144. return nil
  145. case .empty:
  146. return nil
  147. }
  148. }
  149. // MARK: - AccessibilityLargeContentViewTree.Key
  150. struct Key: HostPreferenceKey {
  151. static let defaultValue: AccessibilityLargeContentViewTree = .empty
  152. static func reduce(
  153. value: inout AccessibilityLargeContentViewTree,
  154. nextValue: () -> AccessibilityLargeContentViewTree
  155. ) {
  156. let newValue = nextValue()
  157. switch (value, newValue) {
  158. case (_, .empty):
  159. break
  160. case (.empty, _):
  161. value = newValue
  162. case (.branch(let oldArray), .branch(let newArray)):
  163. value = .branch(oldArray + newArray)
  164. case (.branch(let oldArray), _):
  165. value = .branch(oldArray + [newValue])
  166. case (_, .branch(let newArray)):
  167. value = .branch([value] + newArray)
  168. case (_, _):
  169. value = .branch([value, newValue])
  170. }
  171. }
  172. }
  173. }
  174. // MARK: - AccessibilityLargeContentViewItem
  175. struct AccessibilityLargeContentViewItem: Equatable {
  176. var title: String?
  177. var image: Image.Resolved?
  178. var frame: CGRect
  179. var behavior: AccessibilityLargeContentViewBehavior
  180. }
  181. // MARK: - AccessibilityLargeContentViewModifier [WIP]
  182. private struct AccessibilityLargeContentViewModifier<Content: View>: MultiViewModifier, PrimitiveViewModifier {
  183. var behavior: AccessibilityLargeContentViewBehavior
  184. var largeContentView: Content
  185. nonisolated static func _makeView(
  186. modifier: _GraphValue<Self>,
  187. inputs: _ViewInputs,
  188. body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs
  189. ) -> _ViewOutputs {
  190. _openSwiftUIUnimplementedFailure()
  191. }
  192. }
  193. package struct AccessibilityLargeContentViewerKey: EnvironmentKey {
  194. package static var defaultValue: Bool { false }
  195. }
  196. // MARK: - AccessibilityLargeContentViewHitTestingTransform
  197. private struct AccessibilityLargeContentViewHitTestingTransform: Rule {
  198. @Attribute var allowsHitTesting: Bool
  199. var value: (inout AccessibilityLargeContentViewTree) -> Void {
  200. {
  201. guard !allowsHitTesting else {
  202. return
  203. }
  204. $0 = .empty
  205. }
  206. }
  207. }
  208. // MARK: - AccessibilityLargeContentViewTransform
  209. private struct AccessibilityLargeContentViewTransform: Rule {
  210. @Attribute var behavior: AccessibilityLargeContentViewBehavior
  211. @Attribute var platformItemList: PlatformItemList
  212. @Attribute var size: ViewSize
  213. @Attribute var position: CGPoint
  214. @Attribute var transform: ViewTransform
  215. var value: (inout AccessibilityLargeContentViewTree) -> Void {
  216. var viewTransform = transform
  217. viewTransform.appendPosition(position)
  218. var frame = CGRect(origin: .zero, size: size.value)
  219. frame.convert(to: .global, transform: viewTransform)
  220. let mergedContentItems = platformItemList.mergedContentItems
  221. let title = mergedContentItems.text?.string ?? mergedContentItems.label?.string
  222. let image = mergedContentItems.resolvedImage
  223. let behavior = behavior
  224. let item = AccessibilityLargeContentViewItem(
  225. title: title,
  226. image: image,
  227. frame: frame,
  228. behavior: behavior
  229. )
  230. return { tree in
  231. tree = .leaf(item)
  232. }
  233. }
  234. }
  235. // MARK: - AccessibilityLargeContentViewBehavior
  236. enum AccessibilityLargeContentViewBehavior: UInt8, Hashable {
  237. case disabled
  238. case placeholder
  239. case enabled
  240. }