-
Notifications
You must be signed in to change notification settings - Fork 84
Expand file tree
/
Copy pathAccessibilityHierarchy+Codable.swift
More file actions
184 lines (157 loc) · 6.04 KB
/
AccessibilityHierarchy+Codable.swift
File metadata and controls
184 lines (157 loc) · 6.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
import UIKit
// MARK: - UIKit Type Codable Extensions
#if compiler(>=6.0)
extension UIAccessibilityTraits: @retroactive Codable {}
#else
extension UIAccessibilityTraits: Codable {}
#endif
// MARK: - UIAccessibilityTraits Codable
public extension UIAccessibilityTraits {
/// Known trait names for human-readable encoding
private static let knownTraits: [(trait: UIAccessibilityTraits, name: String)] = [
(.button, "button"),
(.link, "link"),
(.image, "image"),
(.selected, "selected"),
(.playsSound, "playsSound"),
(.keyboardKey, "keyboardKey"),
(.staticText, "staticText"),
(.summaryElement, "summaryElement"),
(.notEnabled, "notEnabled"),
(.updatesFrequently, "updatesFrequently"),
(.searchField, "searchField"),
(.startsMediaSession, "startsMediaSession"),
(.adjustable, "adjustable"),
(.allowsDirectInteraction, "allowsDirectInteraction"),
(.causesPageTurn, "causesPageTurn"),
(.header, "header"),
(.tabBar, "tabBar"),
// Private traits (defined in UIAccessibility+SnapshotAdditions.swift)
(.textEntry, "textEntry"),
(.isEditing, "isEditing"),
(.secureTextField, "secureTextField"),
(.backButton, "backButton"),
(.tabBarItem, "tabBarItem"),
(.textArea, "textArea"),
(.switchButton, "switchButton"),
]
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let traitNames = try container.decode([String].self)
var traits = UIAccessibilityTraits()
var unknownValues: UInt64 = 0
for name in traitNames {
if let known = Self.knownTraits.first(where: { $0.name == name }) {
traits.insert(known.trait)
} else if name.hasPrefix("unknown("), name.hasSuffix(")") {
// Parse unknown raw values: "unknown(12345)"
let startIndex = name.index(name.startIndex, offsetBy: 8)
let endIndex = name.index(name.endIndex, offsetBy: -1)
if let rawValue = UInt64(name[startIndex ..< endIndex]) {
unknownValues |= rawValue
}
}
}
self = UIAccessibilityTraits(rawValue: traits.rawValue | unknownValues)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
var traitNames: [String] = []
var remainingRawValue = rawValue
for (trait, name) in Self.knownTraits {
if contains(trait) {
traitNames.append(name)
remainingRawValue &= ~trait.rawValue
}
}
// Encode any unknown traits as raw values for forward compatibility
if remainingRawValue != 0 {
traitNames.append("unknown(\(remainingRawValue))")
}
try container.encode(traitNames)
}
}
// MARK: - Shape Codable
extension AccessibilityElement.Shape: Codable {
private enum CodingKeys: String, CodingKey {
case type
case frame
case pathElements
}
private enum ShapeType: String, Codable {
case frame
case path
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(ShapeType.self, forKey: .type)
switch type {
case .frame:
let frame = try container.decode(CGRect.self, forKey: .frame)
self = .frame(frame)
case .path:
let elements = try container.decode([PathElement].self, forKey: .pathElements)
let path = UIBezierPath()
for element in elements {
element.apply(to: path)
}
self = .path(path)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .frame(frame):
try container.encode(ShapeType.frame, forKey: .type)
try container.encode(frame, forKey: .frame)
case let .path(path):
try container.encode(ShapeType.path, forKey: .type)
let elements = PathElement.elements(from: path.cgPath)
try container.encode(elements, forKey: .pathElements)
}
}
}
// MARK: - PathElement for Path Encoding
/// Represents a single element of a CGPath for Codable serialization
private enum PathElement: Codable, Equatable {
case move(to: CGPoint)
case line(to: CGPoint)
case quadCurve(to: CGPoint, control: CGPoint)
case curve(to: CGPoint, control1: CGPoint, control2: CGPoint)
case closeSubpath
func apply(to path: UIBezierPath) {
switch self {
case let .move(to):
path.move(to: to)
case let .line(to):
path.addLine(to: to)
case let .quadCurve(to, control):
path.addQuadCurve(to: to, controlPoint: control)
case let .curve(to, control1, control2):
path.addCurve(to: to, controlPoint1: control1, controlPoint2: control2)
case .closeSubpath:
path.close()
}
}
static func elements(from cgPath: CGPath) -> [PathElement] {
var elements: [PathElement] = []
cgPath.applyWithBlock { elementPointer in
let element = elementPointer.pointee
switch element.type {
case .moveToPoint:
elements.append(.move(to: element.points[0]))
case .addLineToPoint:
elements.append(.line(to: element.points[0]))
case .addQuadCurveToPoint:
elements.append(.quadCurve(to: element.points[1], control: element.points[0]))
case .addCurveToPoint:
elements.append(.curve(to: element.points[2], control1: element.points[0], control2: element.points[1]))
case .closeSubpath:
elements.append(.closeSubpath)
@unknown default:
break
}
}
return elements
}
}