Can't set desired view to focus when multiple views in area of focus change - focus

I'm having a hard time finding a solution to this so I thought I might as well ask to see if anyone has a better solution to this than I have. So to frame the question say I have a UI layout like so.
Where one focusable view is much larger than the other. And contains multiple valid focusable views underneath it when the user changes focus downward. Something like this.
My question is what is a good way to enforce that the button to the far left is focused as apposed to the other buttons.
So far the only solution I can think of is to override canBecomeFocused in a custom button subclass, and return the value of a settable variable.
class CustomButton: UIButton {
var isFocusable = true
canBecomeFocused: Bool {
return isFocusable
}
}
Then change the value of isFocusable based on where I want the focus to go next. It works but I'm not super happy with it. Just wondering if maybe someone else can think of a better solution.

You can check out UIKitCatalogtvOSCreatingandCustomizingUIKitControls example from Apple at https://developer.apple.com/library/archive/samplecode/UICatalogFortvOS/Introduction/Intro.html
Check out FocusGuide behaviour or shouldFocusAtItem and canFocusAtItem to get your preferredFocus behaviour.

Related

iOS 14 setup the UIBarButttonItem menu before it is opened

As iOS14 offers an easy way to setup the menu on the UIBarButtonItem, I wonder do I miss that there is no easy way to setup its items as they may depend on the current context.
I see that there is UIDeferredMenuElement, but this doesn't seem to be a good use case for it?
Also I see that there is primaryAction, but there doesn't seem to be a trivial way to call the menu from there.
And the third option seems to be reshuffling the menu on each change that may affect it, but that doesn't seem like a great idea.
I assume that there is something I miss out.
Basically for a simple bar button item there's no such provision. A UIControl like a UIButton has an event .menuActionTriggered, and it is perfectly reasonable, at that moment, to construct and assign the menu to the control, thus implementing a dynamic menu that is constructed just in the instant before the menu appears.
But you can't do that with a bar button item — unless it is a custom view bar button item with a UIButton in it.
What you'd have to do otherwise is watch the surrounding conditions via some other mechanism and just change the bar button item's menu each time the conditions change. It sounds like a pain in the butt but it's probably your only option. (I take it that that's exactly what you mean when you say "And the third option seems to be reshuffling the menu on each change that may affect it, but that doesn't seem like a great idea.")
I think what you're asking for is reasonable and it seems sort of silly that there's no provision for it; if you have a good use case, I'd recommend filing an enhancement request with Apple.
Here is the Swift 5 version of implementation, inspired by #matt 's answer.
private func setButton() {
button.menu = UIMenu() // Here must be a placeholder menu. As the menu won't show if button.menu is nil.
button.showsMenuAsPrimaryAction = true
button.addTarget(self, action: #selector(createMenu(_:)), for: .menuActionTriggered)
}
#objc private func createMenu(_ button:UIButton) {
...
}
This is the only solution so far. As func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) doesn't called every time unless you hold for a while when a button is set to showsMenuAsPrimaryAction = true.
----edited----
If my answer was correct, so matt's. As my code was based on his idea. So I didn't think that matt was wrong at the first place. I just thought that the questioner had misunstanding with matt's answer.

Highlight UIButton when not selecting the button itself

I have a custom class here that consists of a UIButton inside of a UIView.
The goal of this class is to allow users to have a greater area (being the UIView) to select the button than just the buttons frame itself.
Now when a user taps on the view I want the buttons highlighted image to show... But the problem is, it does not.
I've read many possible solutions to this issue For Example:
Calling: [btnObject sendActionsForControlEvents:UIControlEventTouchUpInside]
This however did not change the buttons highlight.
I also tried just settings the button.highlighted = YES;
But that did not work either.
I have the images for different states properly setup (Normal and Highlighted), I'm sure of that.
I also have the gestureRecognizer working properly as the functionality is great except for the lack of highlight.
Does anybody know if I'm missing any thing special that needs to be done in order to pull off this seemingly very simple task? Surely it's been done many times.
Thank you
You were on the right track. -[UIButton setHighlighted:] is just a flag. What you need to do is call setNeedsDisplay on that button right after you change the highlighted property.
I solved my problem a little while ago and I'm not sure if Kevin Low's answer would've worked too, it very well might have.
But for some reason, a UITapGesture doesn't work well with highlighting buttons as a view transitions (That might be because I didn't call setNeedsDisplay). The gesture that ended up working was the UILongPressGesture with a 0.0 sec for minimum duration.

UIKeyInput with All Caps Keyboard

I have created a custom text input view and am trying to force the keyboard to show all caps on the keyboard; similar to a UITextField. Here is what I have tried, but it isn't working for me:
class CustomInput: UIView, UIKeyInput {
// UIKeyInput inherits this property
var autocapitalizationType: UITextAutocapitalizationType {
get { return .AllCharacters }
set { }
}
}
I hoped that overriding the variable and only allowing .AllCharacters would force the keyboard to all caps, but that isn't the case. Any ideas how I can get the keyboard to all caps?
I have this same problem in an app I'm currently working on. Have spent a fair amount of time trying to figure this out, especially since autocorrectionType and keyboardType both seem to work fine.
My current work-around is to make my custom view implement UITextInput instead of UIKeyInput, with dummy code for all unused properties and functions. Extremely ugly work-around but it's the only way I can get it to work, so I'm running with it.
Would be interested to see if anyone has insight into the underlying issue here.

Prevent UISegmentedControl segment selection on focus on tvOS

I'm working on a simple UI on a tvOS app and I'm facing a strange problem.
When a UISegmentedControl get focused you can move your focus around and it automatically changes the selected segment. But what I'm looking for is a way to limit the segment selection only when the user taps the segment, not when he focused it.
Any idea?
Thanks in advance.
You need to have your own internal variable for the selected segment and only change its value when the select button is pressed (which you can get using a gesture recognizer). When the segment loses focus (detectable in didUpdateFocus function) you assign the value of your internal variable to the selected index of the segment control.
You need to subclass UISegmentedControl then override didUpdateFocusInContext. In the "Custom Class" field in IB use the name of your custom class.
You can subclass UISegmentedControl and disable the behavior by defining:
#objc func _selectFocusedSegment(){
print ("select focused segment")
}
Beware that this solution is a hack. As far as I know there is no good, clean way to accomplish what you want short of steering clear of UISegmentedControl.
Also know that when a UISegmentedControl 'changes focus' between segments, it does not actually change focus. So hooking into focus updates like Nostradamus is suggesting will not work. To the focus engine UISegmentedControl behaves like a single large focusable element, not like a group of focusable segments. You can see this for yourself by debug inspecting a UIFocusUpdateContext on focusing towards or away from a UISegmentedControl.
I stumbled onto _selectFocusedSegment by defining a UISegmentedControl subclass and debug logging the various NSObject.perform methods, among others. My intent was to reverse engineer how UISegmentedControl retains a sticky last focused item, which is quite difficult to do on Apple TV. I was not able to find out exactly how UISegmentedControl manages focus, but I was able to find the answer to your question along the way.

How to force VoiceOver to not read the "heading" word for view controller title

Is There any way to force VoiceOver to not read view controller title "heading" word after reading title/accessibilityLabel?
Currently the behaviour is default. If I set title to "My Panel" voice over will read something like this:
"My Panel"...."Heading"
I believe this is the result of accessibility trait UIAccessibilityTraitHeader set on some of the UI element but I don't know exacly which element has this trait. My view controller in nested inside navigation controller. Please don't ask why I need to remove this one speaking word. That is the requirement and I need to get rid of it.
David's comment is the correct answer. You should use the UIElement classes semantically. In other words, if the text "My Panel" is not a heading for the view, then what is it semantically? Once you have the answer for that question, then you can redesign your view to use that type of element, styled appropriately.
However, from all the information you have given, it appears as though it is semantically a heading and therefore the screen reader is doing the appropriate thing.
Whoever gave you that requirement may not know what they are talking about. This type of requirement often comes from people who are new to accessible UI/UX design.
You can customise this headerview, and implement the accessibilityElementDidBecomeFocused method in your custom view, when this view did become focused and post UIAccessibilityLayoutChangedNotification to VoiceOver and focus to itself, and then return the traits UIAccessibilityTraitNone
- (void)accessibilityElementDidBecomeFocused {
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, self);
}
- (UIAccessibilityTraits)accessibilityTraits {
return UIAccessibilityTraitNone;
}

Resources