|
| 1 | +// |
| 2 | +// DynamicTypeSize.swift |
| 3 | +// OpenSwiftUICore |
| 4 | +// |
| 5 | +// Audited for iOS 18.0 |
| 6 | +// Status: Complete |
| 7 | +// ID: B498FA81088CF7FADFFFFFC897E05C74 (SwiftUICore) |
| 8 | + |
| 9 | +/// A Dynamic Type size, which specifies how large scalable content should be. |
| 10 | +/// |
| 11 | +/// For more information, see |
| 12 | +/// [Typography](https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/typography/) |
| 13 | +/// in the Human Interface Guidelines. |
| 14 | +public enum DynamicTypeSize: Hashable, Comparable, CaseIterable, Sendable { |
| 15 | + /// An extra small size. |
| 16 | + case xSmall |
| 17 | + |
| 18 | + /// A small size. |
| 19 | + case small |
| 20 | + |
| 21 | + /// A medium size. |
| 22 | + case medium |
| 23 | + |
| 24 | + /// A large size. |
| 25 | + case large |
| 26 | + |
| 27 | + /// An extra large size. |
| 28 | + case xLarge |
| 29 | + |
| 30 | + /// An extra extra large size. |
| 31 | + case xxLarge |
| 32 | + |
| 33 | + /// An extra extra extra large size. |
| 34 | + case xxxLarge |
| 35 | + |
| 36 | + /// The first accessibility size. |
| 37 | + case accessibility1 |
| 38 | + |
| 39 | + /// The second accessibility size. |
| 40 | + case accessibility2 |
| 41 | + |
| 42 | + /// The third accessibility size. |
| 43 | + case accessibility3 |
| 44 | + |
| 45 | + /// The fourth accessibility size. |
| 46 | + case accessibility4 |
| 47 | + |
| 48 | + /// The fifth accessibility size. |
| 49 | + case accessibility5 |
| 50 | + |
| 51 | + /// A Boolean value indicating whether the size is one that is associated |
| 52 | + /// with accessibility. |
| 53 | + public var isAccessibilitySize: Bool { |
| 54 | + switch self { |
| 55 | + case .accessibility1, .accessibility2, .accessibility3, .accessibility4, .accessibility5: true |
| 56 | + default: false |
| 57 | + } |
| 58 | + } |
| 59 | + |
| 60 | + package static var systemDefault: DynamicTypeSize { |
| 61 | + .large |
| 62 | + // CoreGlue2.shared.systemDefaultDynamicTypeSize |
| 63 | + } |
| 64 | +} |
| 65 | + |
| 66 | +private struct DynamicTypeSizeKey: EnvironmentKey { |
| 67 | + static var defaultValue: DynamicTypeSize { .large } |
| 68 | +} |
| 69 | + |
| 70 | +extension EnvironmentValues { |
| 71 | + public var dynamicTypeSize: DynamicTypeSize { |
| 72 | + get { self[DynamicTypeSizeKey.self] } |
| 73 | + set { self[DynamicTypeSizeKey.self] = newValue } |
| 74 | + } |
| 75 | +} |
| 76 | + |
| 77 | +extension View { |
| 78 | + nonisolated public func dynamicTypeSize(_ size: DynamicTypeSize) -> some View { |
| 79 | + self.environment(\.dynamicTypeSize, size) |
| 80 | + } |
| 81 | + |
| 82 | + nonisolated public func dynamicTypeSize<T>(_ range: T) -> some View where T: RangeExpression, T.Bound == DynamicTypeSize { |
| 83 | + self.transformEnvironment(\.dynamicTypeSize) { $0 = $0.clamped(to: range) } |
| 84 | + } |
| 85 | +} |
| 86 | + |
| 87 | +private struct DynamicTypeSizeCollection: Collection { |
| 88 | + func index(after i: DynamicTypeSize) -> DynamicTypeSize { |
| 89 | + let index = DynamicTypeSize.allCases.firstIndex(of: i)! |
| 90 | + let targetIndex = index + 1 |
| 91 | + let count = DynamicTypeSize.allCases.count |
| 92 | + let resultIndex = targetIndex < count ? targetIndex : count-1 |
| 93 | + return DynamicTypeSize.allCases[resultIndex] |
| 94 | + } |
| 95 | + |
| 96 | + subscript(position: DynamicTypeSize) -> Element { |
| 97 | + _read { yield position } |
| 98 | + } |
| 99 | + |
| 100 | + typealias Index = DynamicTypeSize |
| 101 | + |
| 102 | + typealias Element = DynamicTypeSize |
| 103 | + |
| 104 | + var startIndex: Index { .small } |
| 105 | + |
| 106 | + var endIndex: Index { .accessibility5 } |
| 107 | +} |
| 108 | + |
| 109 | +extension DynamicTypeSize { |
| 110 | + package func clamped<T>(to range: T) -> DynamicTypeSize where T: RangeExpression, T.Bound == DynamicTypeSize { |
| 111 | + let bound = range.relative(to: DynamicTypeSizeCollection()) |
| 112 | + |
| 113 | + let upperBound: DynamicTypeSize |
| 114 | + if range.contains(bound.upperBound) { |
| 115 | + upperBound = bound.upperBound |
| 116 | + } else { |
| 117 | + let index = DynamicTypeSize.allCases.firstIndex(of: bound.upperBound)! |
| 118 | + let targetIndex = index + 1 |
| 119 | + let resultIndex = max(0, index) |
| 120 | + upperBound = DynamicTypeSize.allCases[resultIndex] |
| 121 | + } |
| 122 | + return clamp(min: bound.lowerBound, max: upperBound) |
| 123 | + } |
| 124 | +} |
0 commit comments