|  | 
|  | 1 | +// | 
|  | 2 | +//  Tag.swift | 
|  | 3 | +//  OpenSwiftUICore | 
|  | 4 | +// | 
|  | 5 | +//  Audited for 6.5.4 | 
|  | 6 | +//  Status: Complete | 
|  | 7 | +//  ID: 0F8CE0FEFF8003CACFB16F1C88624A9F (SwiftUICore) | 
|  | 8 | + | 
|  | 9 | +// MARK: - View + Tag | 
|  | 10 | + | 
|  | 11 | +@available(OpenSwiftUI_v1_0, *) | 
|  | 12 | +extension View { | 
|  | 13 | + | 
|  | 14 | +    /// Sets the unique tag value of this view. | 
|  | 15 | +    /// | 
|  | 16 | +    /// Use this modifier to differentiate among certain selectable views, | 
|  | 17 | +    /// like the possible values of a ``Picker`` or the tabs of a ``TabView``. | 
|  | 18 | +    /// Tag values can be of any type that conforms to the | 
|  | 19 | +    /// [Hashable](https://developer.apple.com/documentation/swift/hashable) protocol. | 
|  | 20 | +    /// | 
|  | 21 | +    /// This modifier will write the tag value for the type `V`, as well as | 
|  | 22 | +    /// `Optional<V>` if `includeOptional` is enabled. Containers checking for | 
|  | 23 | +    /// tags of either type will see the value as set. | 
|  | 24 | +    /// | 
|  | 25 | +    /// In the example below, the ``ForEach`` loop in the ``Picker`` view | 
|  | 26 | +    /// builder iterates over the `Flavor` enumeration. It extracts the string | 
|  | 27 | +    /// value of each enumeration element for use in constructing the row | 
|  | 28 | +    /// label, and uses the enumeration value as input to the `tag(_:)` | 
|  | 29 | +    /// modifier. | 
|  | 30 | +    /// | 
|  | 31 | +    ///     struct FlavorPicker: View { | 
|  | 32 | +    ///         enum Flavor: String, CaseIterable, Identifiable { | 
|  | 33 | +    ///             case chocolate, vanilla, strawberry | 
|  | 34 | +    ///             var id: Self { self } | 
|  | 35 | +    ///         } | 
|  | 36 | +    /// | 
|  | 37 | +    ///         @State private var selectedFlavor: Flavor? = nil | 
|  | 38 | +    /// | 
|  | 39 | +    ///         var body: some View { | 
|  | 40 | +    ///             Picker("Flavor", selection: $selectedFlavor) { | 
|  | 41 | +    ///                 ForEach(Flavor.allCases) { flavor in | 
|  | 42 | +    ///                     Text(flavor.rawValue) | 
|  | 43 | +    ///                         .tag(flavor) | 
|  | 44 | +    ///                 } | 
|  | 45 | +    ///             } | 
|  | 46 | +    ///         } | 
|  | 47 | +    ///     } | 
|  | 48 | +    /// | 
|  | 49 | +    /// The selection type of the ``Picker`` is an `Optional<Flavor>` and so it | 
|  | 50 | +    /// will look for tags on its contents of `Optional<Flavor>`type. Since the | 
|  | 51 | +    /// tag modifier defaults to having `includeOptional` enabled, even though | 
|  | 52 | +    /// the tag for each option is a non-optional `Flavor`, the tag modifier | 
|  | 53 | +    /// writes values for both the non-optional, and optional versions of the | 
|  | 54 | +    /// value, allowing the contents to be selectable by the ``Picker``. | 
|  | 55 | +    /// | 
|  | 56 | +    /// A ``ForEach`` automatically applies a default tag to each enumerated | 
|  | 57 | +    /// view using the `id` parameter of the corresponding element. If | 
|  | 58 | +    /// the element's `id` parameter and the picker's `selection` input | 
|  | 59 | +    /// have exactly the same type, or the same type but optional, you can omit | 
|  | 60 | +    /// the explicit tag modifier. | 
|  | 61 | +    /// | 
|  | 62 | +    /// To see examples that don't require an explicit tag, see ``Picker``. | 
|  | 63 | +    /// | 
|  | 64 | +    /// - Parameter tag: A [Hashable](https://developer.apple.com/documentation/swift/hashable) | 
|  | 65 | +    ///   value to use as the view's tag. | 
|  | 66 | +    /// - Parameter includeOptional: If the tag value for `Optional<V>` should | 
|  | 67 | +    ///   also be set. | 
|  | 68 | +    /// | 
|  | 69 | +    /// - Returns: A view with the specified tag set. | 
|  | 70 | +    @_alwaysEmitIntoClient | 
|  | 71 | +    nonisolated public func tag<V>(_ tag: V, includeOptional: Bool = true) -> some View where V: Hashable { | 
|  | 72 | +        _trait(TagValueTraitKey<V>.self, .tagged(tag)) | 
|  | 73 | +            ._trait( | 
|  | 74 | +                TagValueTraitKey<V?>.self, | 
|  | 75 | +                includeOptional ? .tagged(Optional(tag)) : .untagged | 
|  | 76 | +            ) | 
|  | 77 | +    } | 
|  | 78 | + | 
|  | 79 | +    /// Sets the view as acting as explicit untagged / auxiliary content that | 
|  | 80 | +    /// will not be wrapped by container views. | 
|  | 81 | +    /// | 
|  | 82 | +    /// For example, `Picker` treats its contents as option button labels. | 
|  | 83 | +    /// A view that is marked as `untagged()` will result | 
|  | 84 | +    /// in the view not being considered an option, and just an extra element | 
|  | 85 | +    /// in the picker. | 
|  | 86 | +    @inlinable | 
|  | 87 | +    nonisolated public func _untagged() -> some View { | 
|  | 88 | +        _trait(IsAuxiliaryContentTraitKey.self, true) | 
|  | 89 | +    } | 
|  | 90 | + | 
|  | 91 | +    @usableFromInline | 
|  | 92 | +    @MainActor | 
|  | 93 | +    @preconcurrency | 
|  | 94 | +    func tag<V>(_ tag: V) -> some View where V: Hashable { | 
|  | 95 | +        _trait(TagValueTraitKey<V>.self, .tagged(tag)) | 
|  | 96 | +    } | 
|  | 97 | +} | 
|  | 98 | + | 
|  | 99 | +// MARK: - TagValueTraitKey | 
|  | 100 | + | 
|  | 101 | +@available(OpenSwiftUI_v1_0, *) | 
|  | 102 | +@usableFromInline | 
|  | 103 | +package struct TagValueTraitKey<V>: _ViewTraitKey where V: Hashable { | 
|  | 104 | +    @usableFromInline | 
|  | 105 | +    @frozen | 
|  | 106 | +    package enum Value { | 
|  | 107 | +        case untagged | 
|  | 108 | +        case tagged(V) | 
|  | 109 | +    } | 
|  | 110 | + | 
|  | 111 | +    @inlinable | 
|  | 112 | +    package static var defaultValue: TagValueTraitKey<V>.Value { | 
|  | 113 | +        .untagged | 
|  | 114 | +    } | 
|  | 115 | +} | 
|  | 116 | + | 
|  | 117 | +@available(*, unavailable) | 
|  | 118 | +extension TagValueTraitKey.Value: Sendable {} | 
|  | 119 | + | 
|  | 120 | +@available(*, unavailable) | 
|  | 121 | +extension TagValueTraitKey: Sendable {} | 
|  | 122 | + | 
|  | 123 | +// MARK: - IsAuxiliaryContentTraitKey | 
|  | 124 | + | 
|  | 125 | +@available(OpenSwiftUI_v1_0, *) | 
|  | 126 | +@usableFromInline | 
|  | 127 | +package struct IsAuxiliaryContentTraitKey: _ViewTraitKey { | 
|  | 128 | +    @inlinable | 
|  | 129 | +    package static var defaultValue: Bool { | 
|  | 130 | +        false | 
|  | 131 | +    } | 
|  | 132 | +} | 
|  | 133 | + | 
|  | 134 | +@available(*, unavailable) | 
|  | 135 | +extension IsAuxiliaryContentTraitKey: Sendable {} | 
|  | 136 | + | 
|  | 137 | +extension ViewTraitCollection { | 
|  | 138 | +    package var isAuxiliaryContent: Bool { | 
|  | 139 | +        get { self[IsAuxiliaryContentTraitKey.self] } | 
|  | 140 | +        set { self[IsAuxiliaryContentTraitKey.self] = newValue } | 
|  | 141 | +    } | 
|  | 142 | +} | 
|  | 143 | + | 
|  | 144 | +// MARK: - ViewTraitCollection + Tag | 
|  | 145 | + | 
|  | 146 | +extension ViewTraitCollection { | 
|  | 147 | +    package func tagValue<V>(for type: V.Type) -> V? where V: Hashable { | 
|  | 148 | +        let value = self[TagValueTraitKey<V>.self] | 
|  | 149 | +        return switch value { | 
|  | 150 | +        case let .tagged(tag): tag | 
|  | 151 | +        case .untagged: nil | 
|  | 152 | +        } | 
|  | 153 | +    } | 
|  | 154 | + | 
|  | 155 | +    package func tag<V>(for type: V.Type) -> V? where V: Hashable { | 
|  | 156 | +        let value = self[TagValueTraitKey<V>.self] | 
|  | 157 | +        return switch value { | 
|  | 158 | +        case let .tagged(tag): isAuxiliaryContent ? nil : tag | 
|  | 159 | +        case .untagged: nil | 
|  | 160 | +        } | 
|  | 161 | +    } | 
|  | 162 | + | 
|  | 163 | +    package mutating func setTagIfUnset<V>(for type: V.Type, value: V) where V: Hashable { | 
|  | 164 | +        setValueIfUnset(.tagged(value), for: TagValueTraitKey<V>.self) | 
|  | 165 | +    } | 
|  | 166 | + | 
|  | 167 | +    package mutating func setTag<V>(for type: V.Type, value: V) where V: Hashable { | 
|  | 168 | +        self[TagValueTraitKey<V>.self] = .tagged(value) | 
|  | 169 | +    } | 
|  | 170 | +} | 
|  | 171 | + | 
|  | 172 | +// MARK: - Binding + Tag | 
|  | 173 | + | 
|  | 174 | +extension Binding { | 
|  | 175 | +    package func selecting(_ tag: Value?) -> Binding<Bool> where Value: Hashable { | 
|  | 176 | +        guard let tag else { | 
|  | 177 | +            return .false | 
|  | 178 | +        } | 
|  | 179 | +        return self == tag | 
|  | 180 | +    } | 
|  | 181 | +} | 
|  | 182 | + | 
|  | 183 | +extension Binding where Value: Hashable { | 
|  | 184 | +    package func projectingTagIndex(viewList: any ViewList) -> Binding<Int?> { | 
|  | 185 | +        projecting(TagIndexProjection<Value>(list: viewList)) | 
|  | 186 | +    } | 
|  | 187 | +} | 
|  | 188 | + | 
|  | 189 | +// MARK: - TagIndexProjection | 
|  | 190 | + | 
|  | 191 | +private class TagIndexProjection<Value>: Projection where Value: Hashable { | 
|  | 192 | +    let list: any ViewList | 
|  | 193 | +    var nextIndex: Int? = .zero | 
|  | 194 | +    var indexMap: [Int: Value] = [:] | 
|  | 195 | +    var tagMap: [Value: Int] = [:] | 
|  | 196 | + | 
|  | 197 | +    init(list: any ViewList) { | 
|  | 198 | +        self.list = list | 
|  | 199 | +    } | 
|  | 200 | + | 
|  | 201 | +    func get(base: Value) -> Int? { | 
|  | 202 | +        if let index = tagMap[base] { | 
|  | 203 | +            return index | 
|  | 204 | +        } else { | 
|  | 205 | +            var i: Int? = nil | 
|  | 206 | +            readUntil { index, value in | 
|  | 207 | +                let result = value == base | 
|  | 208 | +                if result { | 
|  | 209 | +                    i = index | 
|  | 210 | +                } | 
|  | 211 | +                return result | 
|  | 212 | +            } | 
|  | 213 | +            return i | 
|  | 214 | +        } | 
|  | 215 | +    } | 
|  | 216 | + | 
|  | 217 | +    func set(base: inout Value, newValue: Int?) { | 
|  | 218 | +        guard let newValue else { | 
|  | 219 | +            return | 
|  | 220 | +        } | 
|  | 221 | +        if let tag = indexMap[newValue] { | 
|  | 222 | +            base = tag | 
|  | 223 | +        } else { | 
|  | 224 | +            readUntil { index, value in | 
|  | 225 | +                let result = newValue == index | 
|  | 226 | +                if result { | 
|  | 227 | +                    base = value | 
|  | 228 | +                } | 
|  | 229 | +                return result | 
|  | 230 | +            } | 
|  | 231 | +        } | 
|  | 232 | +    } | 
|  | 233 | + | 
|  | 234 | +    func readUntil(_ body: (Int, Value) -> Bool) { | 
|  | 235 | +        guard var nextIndex else { | 
|  | 236 | +            return | 
|  | 237 | +        } | 
|  | 238 | +        var index = nextIndex | 
|  | 239 | +        let result = list.applySublists( | 
|  | 240 | +            from: &index, | 
|  | 241 | +            list: nil | 
|  | 242 | +        ) { sublist in | 
|  | 243 | +            nextIndex &-= sublist.start | 
|  | 244 | +            defer { nextIndex &+= sublist.count } | 
|  | 245 | +            let traits = sublist.traits | 
|  | 246 | +            guard let tag = traits.tag(for: Value.self) else { | 
|  | 247 | +                return true | 
|  | 248 | +            } | 
|  | 249 | +            tagMap[tag] = nextIndex | 
|  | 250 | +            var index = nextIndex | 
|  | 251 | +            var count = list.count | 
|  | 252 | +            Swift.precondition(index + count >= index) | 
|  | 253 | +            while count != 0 { | 
|  | 254 | +                indexMap[index] = tag | 
|  | 255 | +                index &+= 1 | 
|  | 256 | +                count &-= 1 | 
|  | 257 | +            } | 
|  | 258 | +            return !body(nextIndex, tag) | 
|  | 259 | +        } | 
|  | 260 | +        self.nextIndex = result ? nil : nextIndex | 
|  | 261 | +    } | 
|  | 262 | + | 
|  | 263 | +    func hash(into hasher: inout Hasher) { | 
|  | 264 | +        hasher.combine(ObjectIdentifier(self)) | 
|  | 265 | +    } | 
|  | 266 | + | 
|  | 267 | +    static func == (lhs: TagIndexProjection<Value>, rhs: TagIndexProjection<Value>) -> Bool { | 
|  | 268 | +        lhs === rhs | 
|  | 269 | +    } | 
|  | 270 | +} | 
0 commit comments