iOS - Recreate UIActionSheet / UIAlertController drag to highlight - ios

How does the UIActionSheet's hit detection work? When a user selects an option and then moves it's finger to another option, the other option is highlighted as seen in the GIF's below. The detection also knows when a user is scrolling.

So this is achieved by listening for multiple UIControlEvents. Chances are you're used to listening for touchUpInside as this is standard for UIButton behaviour. But there are plenty more besides that. A full list and documentation can be found here.
In your case, you want to listen to touchDragEnter and touchDownInside, making the callback from these invoke some code that changes the background colour of your button.
You should also listen for touchDragExit and touchUpInside to return the background colour to normal.
Additionally, you should run the action code in touchUpInside.
I hope this clears things up!

UIAlertController contains a stack of items in subview with type UIStackView, this stack view is placed on the view with type _UIInterfaceActionRepresentationsSequenceView that has three gesture recognizers with types:
• UIScrollViewDelayedTouchesBeganGestureRecognizer
• UIScrollViewPanGestureRecognizer
• _UIDragAutoScrollGestureRecognizer
You can inspect it with Xcode built-in tool: User Interface Inspector.
I think that custom handlers of these recognisers provide this drag-and-highlight function.
Internal logic of UIAlertController handle touches and hit test subview in stack and set highlighted boolean property to YES for item under user's finger and to NO for others.

Related

make UIAccessibilityTrait adjustable ignore double-tap (like a button) swift

I have a custom control to increment and decrement values. Now that I've added support for voice over, I've stumbled upon a problem.
My customView has the accessibility trait .adjustable and I implemented the correct methods for increasing and decreasing the values.
However, the voice over user can also double tap on that view to activate it. The problem is, that this triggers a gesture which is irrelevant to voice over users.
Is there a way to prevent an adjustable accessibility view from being activated so that the element is only adjustable, not double-tappable like a button?
There are two important properties to know when a double-tap occurs:
accessibilityActivate.
accessibilityActivationPoint.
In your case, you could just return true by overriding accessibilityActivate and if it's not enough, provide as well a CGPoint coordinate that triggers nothing (depends of your custom control and its neighborhood).
Otherwise, use the accessibilityElementIsFocused instance method to know wether you can trigger actions as this complete example shows up.
I ended up using UIAccessibility.isVoiceOverRunning to stop any tasks which would be triggered by a doubletap on that specific element.

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 react to a "touch up inside" event in UIAccessibilityElement subclass?

I have a map with drawn items. The map handles touch events and determines the touched items as if they were buttons.
I made the map a container and implemented the methods to return accessibleElements. For each item I create one instance of a UIAccessibilityElement subclass.
It seems UIAccessibilityAction protocol has no callback for a "tap" or "button pressed" event.
How would I mimic the effect of a UIButton with UIAccessibilityElement then?
Assuming you are running under iOS 5 or iOS 6, consider the following workaround. It is not optimal, but will work until there is a better way:
Create a dummy view that is not, itself, an accessibility element.
On this view, implement your "tap" handling in -touchesEnded:withEvent:.
Set your accessibility element's accessibilityActivationPoint to a value that falls within this dummy view.
Your dummy view will receive touch events when the corresponding accessibility element is activated. Make sure to ignore touch handling in your dummy view if VoiceOver and other assistive technologies are not running.
EDIT: Another less hacky approach is to implement a tap gesture recognizer on the view you're concerned with, convert the coordinate from -touchesEnded:withEvent: to screen coordinates, and manually hit test the point against the frames of your accessibility elements.

Intercepting touches in static UIPickerView

I have a 5 components UIPickerView which needs to be disabled to normal user interaction (ie: no spinning / clicking around to spin them). I need however to detect touches inside them and react according to which component was pressed.
I tried UIGestureRecognizer and UITapGestureRecognizer with cancelsTouchesInView set to YES: It fires up and blocks the single taps but not the dragging around. I tried didSelectRow:inComponent but it's only firing up when the pickers spinned which is a behavior I don't want.. the reels must never spin on a user interaction as mentioned above.
Solved this by adding a UIGestureRecognizer to the background image (with user interaction enabled) and looking at the locations of the taps to determine if the taps were within the region of a UIPickerView component.

Using a UIView as a button

I'm trying to use a UIView I've created in Storyboard as a button. I assumed it would be possible to use a UIButton, setting the type to custom. However I was unable to add subviews to a custom UIButton in Storyboard.
As such I've just spent the last hour reinventing the wheel by making my own custom gesture recoginizers to reimplement button functionality.
Surely this isn't the best way of doing it though, so my question - to more experienced iOS developers than myself - is what is the best way to make a custom button?
To be clear it needs to:
Use the UIView I've created as it's hittable area.
Be able to show a
different state depending on whether is currently highlighted or not
(i.e. touch down).
Perform some action when actually tapped.
Thank you for your help.
You can use a UIButton, set the type to custom, and then programmatically add your subviews...
Change your UIView into a UIControl in the storyboard. Then use the method [controlViewName addTarget:self action:#selector(*click handler method*) forControlEvents:UIControlEventTouchDown];. click handler method is a placeholder for the method name of your handler. Use this method except change out the UIControlEventTouchDown for UIControlEventTouchInside and UIControlEventTouchDragExit to call a method when the user finishes their click and drags their finger out of the view respectively. I used this for something I'm working on now and it works great.
In Touch down you will want to: highlight all subviews
In Touch up inside you will want to: unhighlight all subviews and perform segue or do whatever the button is supposed to do
In Touch Drag Exit you will want to: unhighlight all subviews
See second answer by LiCheng in this similiar SO post.
Subclass UIControl instead. You can add subviews to it and it can respond to actions
Why are you implementing your own GestureRecognizer? I recommend using the UIView so you can add subviews in the interface builder and adding a UITapGestureRecognizer. You can even do this graphically since you don't care about IOS4 support.

Resources