-
Notifications
You must be signed in to change notification settings - Fork 33
[iOS] Add Support for List Item Focus Area #602
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
johnnewman-square
merged 1 commit into
square:main
from
kavanbrandon:kavan/feat-ios-add-focus-area-support-for-list-items
Nov 4, 2025
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
127 changes: 127 additions & 0 deletions
127
Development/Sources/Demos/Demo Screens/KeyboardNavigationDemoViewController.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| // | ||
| // KeyboardNavigationDemoViewController.swift | ||
| // Development | ||
| // | ||
| // Created by Listable Demo on 12/30/24. | ||
| // | ||
|
|
||
| import UIKit | ||
| import ListableUI | ||
|
|
||
| final class KeyboardNavigationDemoViewController: ListViewController { | ||
|
|
||
| private var allowsFocus = true | ||
| private var selectionFollowsFocus = true | ||
|
|
||
| override func viewDidLoad() { | ||
| super.viewDidLoad() | ||
|
|
||
| self.title = "Keyboard Navigation Focus" | ||
|
|
||
| // Add toggle buttons to the navigation bar | ||
| let toggleFocusButton = UIBarButtonItem( | ||
| title: "Toggle Focus", | ||
| style: .plain, | ||
| target: self, | ||
| action: #selector(toggleAllowsFocus) | ||
| ) | ||
|
|
||
| let toggleSelectionButton = UIBarButtonItem( | ||
| title: "Toggle Selection", | ||
| style: .plain, | ||
| target: self, | ||
| action: #selector(toggleSelectionFollowsFocus) | ||
| ) | ||
|
|
||
| self.navigationItem.rightBarButtonItems = [toggleFocusButton, toggleSelectionButton] | ||
| } | ||
|
|
||
| @objc private func toggleAllowsFocus() { | ||
| allowsFocus.toggle() | ||
| self.reload(animated: true) | ||
| } | ||
|
|
||
| @objc private func toggleSelectionFollowsFocus() { | ||
| selectionFollowsFocus.toggle() | ||
| self.reload(animated: true) | ||
| } | ||
|
|
||
| override func configure(list: inout ListProperties) { | ||
|
|
||
| list.appearance = .demoAppearance | ||
| list.layout = .table() | ||
|
|
||
| if allowsFocus { | ||
| if selectionFollowsFocus { | ||
| list.behavior.focus = .selectionFollowsFocus(showFocusRing: true) | ||
| } else { | ||
| list.behavior.focus = .allowsFocus | ||
| } | ||
| } else { | ||
| list.behavior.focus = Behavior.FocusConfiguration.none | ||
| } | ||
|
|
||
| list.content.header = DemoHeader( | ||
| title: "Keyboard Navigation Demo\nFocus: \(allowsFocus ? "ON" : "OFF") | Selection Follows: \(selectionFollowsFocus ? "ON" : "OFF")" | ||
| ) | ||
|
|
||
| list.add { | ||
| Section("instructions") { | ||
| Item( | ||
| DemoItem(text: "Instructions:\n• Use Tab key to navigate between items\n• Use Arrow keys to navigate within the list\n• Press Return to select items\n• Space key works only with Full Keyboard Access enabled\n• Toggle settings with navigation buttons"), | ||
| selectionStyle: .none | ||
| ) | ||
| } header: { | ||
| DemoHeader(title: "How to Use") | ||
| } | ||
|
|
||
| Section("demo-items") { | ||
| // Create a few selectable items for focus testing | ||
| for i in 1...5 { | ||
| Item( | ||
| DemoItem(text: "Focusable Item \(i)"), | ||
| selectionStyle: .selectable(), | ||
| onSelect: { _ in | ||
| print("Selected Focusable Item \(i)") | ||
| } | ||
| ) | ||
| } | ||
| } header: { | ||
| DemoHeader(title: "Focusable Items") | ||
| } | ||
|
|
||
| Section("mixed-items") { | ||
| Item( | ||
| DemoItem(text: "Tappable Item (Focus + Select)"), | ||
| selectionStyle: .tappable, | ||
| onSelect: { _ in | ||
| print("Selected Tappable Item") | ||
| } | ||
| ) | ||
|
|
||
| Item( | ||
| DemoItem(text: "Toggle Item (Focus + Toggle)"), | ||
| selectionStyle: .toggles(), | ||
| onSelect: { _ in | ||
| print("Toggled Toggle Item") | ||
| } | ||
| ) | ||
|
|
||
| Item( | ||
| DemoItem(text: "Non-selectable Item (No Focus)"), | ||
| selectionStyle: .none | ||
| ) | ||
|
|
||
| Item( | ||
| DemoItem(text: "Another Selectable Item"), | ||
| selectionStyle: .selectable(), | ||
| onSelect: { _ in | ||
| print("Selected Another Selectable Item") | ||
| } | ||
| ) | ||
| } header: { | ||
| DemoHeader(title: "Mixed Selection Styles") | ||
| } | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed that after toggling this item, the focus selection is reset the next time I use the arrow keys for navigation. If we wanted the focused item to be preserved, is this behavior that needs to be configured by the client, or could that be something internal to our UICollectionView implementation within Listable?
(I did not have full keyboard access enabled during this flow.)
ScreenRecording_10-27-2025.10-16-24_1.mov
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is interesting! It's hard to tell from the documentation but I wonder if this is expected UIKit behavior. When the toggle item deselects, UICollectionView loses the current focus anchor and re-evaluates from the beginning of the focus group. I think one potential solution is leveraging focusgroupidentifier and focusgrouppriority and updating a cell's focusgrouppriority if it's deselected.
Curious if you're ok with keeping the current changes for now and we could add focus preservation logic for toggles in a follow-up? I can create a ticket to track it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it! Increasing the priority when deselected sounds like a good idea to try out. Interestingly, when using full keyboard access, the toggled item stays focused between toggle states (video below). So this issue seems to be isolated to standard focus and will impact a smaller subset of users. I'm on board with merging if you'd like to move toggle focus preservation to a followup ticket!
ScreenRecording_10-31-2025.14-11-25_1.mov
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made a ticket here: https://block.atlassian.net/browse/UI-9566
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@johnnewman-square Would you be able to merge on your end? I don't have the option to do so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure thing! I'll kick off one more set of test actions and merge.