Skip to content

Commit 2b4eea1

Browse files
Deduplicate markerOverlay/shouldShowActivationPoint and guard against non-finite shape geometry
- Extract shared markerOverlayView() and shouldShowActivationPoint() free functions, eliminating identical copies in AccessibilitySnapshotView and PreParsedAccessibilitySnapshotView. - Add AccessibilityShape.isFinite to the portable model layer so all consumers (rendering, Codable round-trips, external callers of the public init) are protected from degenerate coordinates. - ElementOverlay.body bails to an empty view when shape.isFinite is false, preventing Int(_:) traps on NaN/infinity in BadgePlacement.
1 parent 0dceff8 commit 2b4eea1

3 files changed

Lines changed: 68 additions & 57 deletions

File tree

‎Sources/AccessibilitySnapshot/AccessibilitySnapshotPreviews/ElementView.swift‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,12 @@ public struct ElementOverlay: View {
5151
private var strokeColor: Color { palette.strokeColor(at: index) }
5252

5353
public var body: some View {
54-
ZStack(alignment: .topLeading) {
55-
shapeView
56-
NumberBadge(index: index, palette: palette)
57-
.position(badgeCenter)
54+
if shape.isFinite {
55+
ZStack(alignment: .topLeading) {
56+
shapeView
57+
NumberBadge(index: index, palette: palette)
58+
.position(badgeCenter)
59+
}
5860
}
5961
}
6062

‎Sources/AccessibilitySnapshot/AccessibilitySnapshotPreviews/SwiftUIAccessibilitySnapshotView.swift‎

Lines changed: 45 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -70,30 +70,18 @@ public struct AccessibilitySnapshotView<Content: View>: View {
7070
.frame(width: renderSize.width, height: renderSize.height)
7171

7272
ForEach(markers.indices, id: \.self) { index in
73-
markerOverlay(at: index)
73+
markerOverlayView(
74+
marker: markers[index],
75+
index: index,
76+
palette: palette,
77+
activationPointDisplayMode: configuration.activationPointDisplayMode
78+
)
7479
}
7580
}
7681
.frame(width: renderSize.width, height: renderSize.height)
7782
.clipped()
7883
}
7984

80-
@ViewBuilder
81-
private func markerOverlay(at index: Int) -> some View {
82-
let marker = markers[index]
83-
ElementOverlay(
84-
index: index,
85-
shape: marker.shape,
86-
palette: palette
87-
)
88-
89-
if shouldShowActivationPoint(for: marker) {
90-
ActivationPointView(
91-
position: marker.activationPoint.cgPoint,
92-
color: palette.strokeColor(at: index)
93-
)
94-
}
95-
}
96-
9785
@ViewBuilder
9886
private func errorView(error: Error) -> some View {
9987
VStack {
@@ -145,17 +133,6 @@ public struct AccessibilitySnapshotView<Content: View>: View {
145133
parseError = error
146134
}
147135
}
148-
149-
private func shouldShowActivationPoint(for marker: AccessibilityMarker) -> Bool {
150-
switch configuration.activationPointDisplayMode {
151-
case .always:
152-
return true
153-
case .whenOverridden:
154-
return !marker.usesDefaultActivationPoint
155-
case .never:
156-
return false
157-
}
158-
}
159136
}
160137

161138
/// Backwards compatibility alias.
@@ -285,39 +262,54 @@ public struct PreParsedAccessibilitySnapshotView: View {
285262
.frame(width: renderSize.width, height: renderSize.height)
286263

287264
ForEach(markers.indices, id: \.self) { index in
288-
markerOverlay(at: index)
265+
markerOverlayView(
266+
marker: markers[index],
267+
index: index,
268+
palette: palette,
269+
activationPointDisplayMode: configuration.activationPointDisplayMode
270+
)
289271
}
290272
}
291273
.frame(width: renderSize.width, height: renderSize.height)
292274
.clipped()
293275
}
276+
}
294277

295-
@ViewBuilder
296-
private func markerOverlay(at index: Int) -> some View {
297-
let marker = markers[index]
298-
ElementOverlay(
299-
index: index,
300-
shape: marker.shape,
301-
palette: palette
302-
)
278+
// MARK: - Shared Overlay Helpers
303279

304-
if shouldShowActivationPoint(for: marker) {
305-
ActivationPointView(
306-
position: marker.activationPoint.cgPoint,
307-
color: palette.strokeColor(at: index)
308-
)
309-
}
280+
@available(iOS 16.0, *)
281+
@ViewBuilder
282+
func markerOverlayView(
283+
marker: AccessibilityMarker,
284+
index: Int,
285+
palette: ColorPalette,
286+
activationPointDisplayMode: AccessibilityContentDisplayMode
287+
) -> some View {
288+
ElementOverlay(
289+
index: index,
290+
shape: marker.shape,
291+
palette: palette
292+
)
293+
294+
if shouldShowActivationPoint(for: marker, displayMode: activationPointDisplayMode) {
295+
ActivationPointView(
296+
position: marker.activationPoint.cgPoint,
297+
color: palette.strokeColor(at: index)
298+
)
310299
}
300+
}
311301

312-
private func shouldShowActivationPoint(for marker: AccessibilityMarker) -> Bool {
313-
switch configuration.activationPointDisplayMode {
314-
case .always:
315-
return true
316-
case .whenOverridden:
317-
return !marker.usesDefaultActivationPoint
318-
case .never:
319-
return false
320-
}
302+
private func shouldShowActivationPoint(
303+
for marker: AccessibilityMarker,
304+
displayMode: AccessibilityContentDisplayMode
305+
) -> Bool {
306+
switch displayMode {
307+
case .always:
308+
return true
309+
case .whenOverridden:
310+
return !marker.usesDefaultActivationPoint
311+
case .never:
312+
return false
321313
}
322314
}
323315

‎Sources/AccessibilitySnapshot/Model/AccessibilityPrimitives.swift‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,23 @@ public enum AccessibilityPathElement: Hashable, Codable, Sendable {
7979
public enum AccessibilityShape: Hashable, Codable, Sendable {
8080
case frame(AccessibilityRect)
8181
case path([AccessibilityPathElement])
82+
83+
public var isFinite: Bool {
84+
switch self {
85+
case let .frame(rect):
86+
return rect.isFinite
87+
case let .path(elements):
88+
return elements.allSatisfy { element in
89+
switch element {
90+
case let .move(to): return to.isFinite
91+
case let .line(to): return to.isFinite
92+
case let .quadCurve(to, control): return to.isFinite && control.isFinite
93+
case let .curve(to, control1, control2): return to.isFinite && control1.isFinite && control2.isFinite
94+
case .closeSubpath: return true
95+
}
96+
}
97+
}
98+
}
8299
}
83100

84101
// MARK: - Wire-Compatible Codable

0 commit comments

Comments
 (0)