Reputation: 421
In a custom control, how do you implement a sticky focus selection as seen in UISegmentedControl, UITabBar, UITableView, or UICollectionView? By 'sticky selection' I mean automatically diverting focus to the last focused/selected item when focusing into a container.
How do UISegmentedControl + others implement this themselves? I tried inspecting UISegmentedControl and discovered to my surprise that it is a single large focusable element, not a group of focusable segments. I was not able to find out how UISegmentedControl is able to switch focus between segments given its architecture.
So far the workaround I have is:
At the screen level, maintain each sticky selection. For example if the screen has two custom top menus, the screen maintains two active selections. Override shouldUpdateFocus to detect focus changes into a container/menu with a sticky selection. Return false + call setNeedsFocusUpdate combined with overriding preferredFocusEnvironments to redirect focus to the sticky/selected subview instead.
My solution works, but is unsatisfying for writing reusable custom UIs that maintain sticky selections automatically. Apple was able to make several such custom UIs, so there must be some method I am missing.
Upvotes: 0
Views: 205
Reputation: 421
I found a method that works for reusable controls:
Surround the control in a UIFocusGuide. Then simply use UIFocusGuide.preferredEnvironments to determine which subview receives focus when focus shifts to the container.
There is a caveat that if a container's subviews fill the container, the subviews will be hit instead of the container's surrounding focus guide. So overall, I have:
self.focusGuide = UIFocusGuide()
let focusGuideExpansion: CGFloat = 1
self.addLayoutGuide(focusGuide)
focusGuide.leadingAnchor.constraint(equalTo:self.leadingAnchor, constant:-focusGuideExpansion).isActive = true
focusGuide.trailingAnchor.constraint(equalTo:self.trailingAnchor, constant:focusGuideExpansion).isActive = true
focusGuide.topAnchor.constraint(equalTo:self.topAnchor, constant:-focusGuideExpansion).isActive = true
focusGuide.bottomAnchor.constraint(equalTo:self.bottomAnchor, constant:focusGuideExpansion).isActive = true
// default to focusing on the first subview
focusGuide.preferredFocusEnvironments = [subviews.first!]
Upvotes: 0