Skip to content

Commit c2f8aef

Browse files
Plumb isSelectable into ApplyItemContentInfo
Add an `isSelectable` property to `ApplyItemContentInfo`, populated from the item's `selectionStyle.isSelectable`. This lets content views know when an item is interactive (`.tappable`, `.selectable`, `.toggles`) so they can represent themselves accordingly, e.g. by applying the `.button` accessibility trait for VoiceOver. TBHFUZZ-165 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent fd014a9 commit c2f8aef

4 files changed

Lines changed: 61 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
### Added
66

7+
- Added `isSelectable` to `ApplyItemContentInfo`, reflecting whether the item's `selectionStyle` is interactive (`.tappable`, `.selectable`, or `.toggles`). `ItemContent` implementations can read this to represent themselves as interactive, for example by applying the `.button` accessibility trait so VoiceOver users know the item responds to taps.
8+
79
### Removed
810

911
### Changed

ListableUI/Sources/Internal/PresentationState/PresentationState.ItemState.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ extension PresentationState
312312
cell.openTrailingSwipeActions()
313313
},
314314
isReorderable: self.model.reordering != nil,
315+
isSelectable: self.model.selectionStyle.isSelectable,
315316
environment: environment
316317
)
317318

ListableUI/Sources/Item/ItemContent.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,12 @@ public struct ApplyItemContentInfo
565565
/// If the item can be reordered.
566566
/// Use this property to determine if your `ItemContent` should display a reorder control.
567567
public var isReorderable : Bool
568-
568+
569+
/// If the item is selectable; that is, if its `selectionStyle` is `.tappable`, `.selectable`, or `.toggles`.
570+
/// Use this property to determine if your `ItemContent` should represent itself as interactive, for example
571+
/// by applying the `.button` accessibility trait so VoiceOver users know the item responds to taps.
572+
public var isSelectable : Bool = false
573+
569574
/// The environment of the containing list.
570575
/// See `ListEnvironment` for usage information.
571576
public var environment : ListEnvironment

ListableUI/Tests/Internal/Presentation State/PresentationState.ItemStateTests.swift

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,58 @@ class PresentationState_ItemStateTests : XCTestCase
318318
XCTAssertEqual(state.coordination.coordinator?.willDisplay_calls.count, 1)
319319
XCTAssertEqual(state.coordination.coordinator?.didEndDisplay_calls.count, 1)
320320
}
321+
322+
func test_applyTo_isSelectable()
323+
{
324+
XCTAssertEqual(appliedInfo(for: .tappable).isSelectable, true)
325+
XCTAssertEqual(appliedInfo(for: .notSelectable).isSelectable, false)
326+
}
327+
328+
/// Builds an `ItemState` for the given `selectionStyle`, applies it to a cell, and returns the `ApplyItemContentInfo` passed to the content.
329+
private func appliedInfo(for selectionStyle : ItemSelectionStyle) -> ApplyItemContentInfo
330+
{
331+
var applied : ApplyItemContentInfo?
332+
333+
let item = Item(CapturingContent { applied = $0 }, selectionStyle: selectionStyle)
334+
335+
let state = PresentationState.ItemState(
336+
with: item,
337+
dependencies: ItemStateDependencies(
338+
reorderingDelegate: ReorderingActionsDelegateMock(),
339+
coordinatorDelegate: ItemContentCoordinatorDelegateMock(),
340+
environmentProvider: { .empty }
341+
),
342+
updateCallbacks: UpdateCallbacks(.immediate, wantsAnimations: false),
343+
performsContentCallbacks: true
344+
)
345+
346+
state.applyTo(
347+
cell: ItemCell<CapturingContent>(frame: .zero),
348+
itemState: .init(isSelected: false, isHighlighted: false, isReordering: false),
349+
reason: .willDisplay,
350+
environment: .empty
351+
)
352+
353+
return applied!
354+
}
355+
}
356+
357+
358+
fileprivate struct CapturingContent : ItemContent
359+
{
360+
typealias ContentView = UIView
361+
362+
let onApply : (ApplyItemContentInfo) -> ()
363+
364+
var identifierValue : String { "" }
365+
366+
func isEquivalent(to other : CapturingContent) -> Bool { false }
367+
368+
func apply(to views : ItemContentViews<CapturingContent>, for reason : ApplyReason, with info : ApplyItemContentInfo) {
369+
onApply(info)
370+
}
371+
372+
static func createReusableContentView(frame : CGRect) -> UIView { UIView(frame: frame) }
321373
}
322374

323375

0 commit comments

Comments
 (0)