Siri Remote. Directional Arrows - uigesturerecognizer

I spotted one behaviour in Apple's default AVPlayerViewController on tvOS. If you call up timeline, where you can rewind or fast forward the video, and then if you put and leave your finger on the right side of touchpad don SiriRemote the "10" label appears next to current playback time
If you remove your finger without pressing on the remote, the "10" label disappears.
Same for touching left side of the remote, just the "10" label appears to the left of current playback time.
The question is, how can I receive callback for this event? The event of user putting the finger on the side of remote.
UPD
UITapGestureRecognizer with allowedPressTypes=UIPressTypeRightArrow will generate event after user releases the finger from touch surface. I'm interested in event that will be generated as soon as user touches edge of the surface (and probably leaves the finger resting)

After days of searching I concluded that UIKit does not report such event. But one can use GameController framework to intercept similar event.
Siri remote is represented as GCMicroGamepad. It has property
BOOL reportsAbsoluteDpadValues that should be set to YES. Than every time user touches the surface GCMicroGamepad will update it's values for dpad property. dpad property is represented with float x,y values that vary in range [-1,1] each. These values represent Carthesian coordinate system where (0,0) is the centre of the touch surface, (-1,-1) is the bottom left point near "Menu" button on the remote, (1,1) is the upper right point.
Putting all together we can have the following code to catch the event:
#import GameController;
[[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidConnectNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification * _Nonnull note) {
self.controller = note.object;
self.controller.microGamepad.reportsAbsoluteDpadValues = YES;
self.controller.microGamepad.dpad.valueChangedHandler =
^(GCControllerDirectionPad *dpad, float xValue, float yValue) {
if(xValue > 0.9)
{
////user currently has finger near right side of remote
}
if(xValue < -0.9)
{
////user currently has finger near left side of remote
}
if(xValue == 0 && yValue == 0)
{
////user released finger from touch surface
}
};
}];
Hope it helps somebody.

Related

SwiftUI DragGesture inconsistencies with location and start location

I'm building a SwiftUI macOS app.
I've got a basic Rectangle shape with a drag gesture on it.
In the onEnded handler, I am wanting to determine if the user has effectively tapped on the object. I do this by checking that the width and height of the translation are both zero.
(There are reasons I'm not using a tap gesture).
Rectangle()
.size(.init(width:50, height: 50))
.fill(Color.blue.opacity(0.01))
.gesture(DragGesture(minimumDistance:0)
.onChanged { gesture in
// Ommited
}
.onEnded { gesture in
print("startLocation", gesture.startLocation)
print("start", gesture.location)
print("translation", gesture.translation)
if gesture.translation == .zero {
print("tap")
}
print()
}
)
I'm getting issues where translations are being reported with unexpected values.
The values reported differ based on where I click in the rectangle.
Here's a set of groups of individual clicks. The translation is derived from the startLocation and location fields.
You can see variation between the startLocation and the location fields. If it was a very small variation I could debounce, however the fact that sometimes I get a value of 3 makes me wonder why such a variation could happen (I'm being over the top careful to execute the click without movement).
Does anyone know why this variation is creeping in?
startLocation (263.5149841308594, 144.3092803955078)
start (263.51495361328125, 144.30926513671875)
translation (-3.0517578125e-05, -1.52587890625e-05)
startLocation (276.2882995605469, 144.43479919433594)
start (276.288330078125, 144.434814453125)
translation (3.0517578125e-05, 1.52587890625e-05)
startLocation (274.3827209472656, 162.3402557373047)
start (274.38275146484375, 162.34027099609375)
translation (3.0517578125e-05, 1.52587890625e-05)
startLocation (264.81805419921875, 167.47662353515625)
start (264.81805419921875, 167.47662353515625)
translation (0.0, 0.0)
tap
startLocation (254.5931396484375, 135.4690399169922)
start (254.5931396484375, 135.46905517578125)
translation (0.0, 1.52587890625e-05)
startLocation (259.1647033691406, 140.26919555664062)
start (259.16473388671875, 140.26919555664062)
translation (3.0517578125e-05, 0.0)
Edit
As pointed out below, the value of 3 is actually 3e-05 = 0.00003 which I missed at the time of writing. However, still looking for information as to why the tap gesture will have zero translation on repeated clicks in some points of the Rectangle, but have a non zero translation in others.
How about this?
if gesture.translation.width/UIScreen.main.bounds.width < 0.05 &&
gesture.translation.height/UIScreen.main.bounds.height < 0.05
{
print("tap") // 0.01 ~ 0.05
}
This way works, regardless of the size of the device and width & height, it is always constant as it falls in percentages.

How to scrollRowToVisible without animations

On a Mac, Mail and Finder have a solid looking scroll on their table views when the up or down arrow is held. The row highlight sits flush with the top or bottom of the column and the rows step through with no animation.
8 years ago it seems that it was hard to not do this. Now I can't seem to stop scrollRowToVisible on an NSOutlineView animating.
I have tried wrapping the call with NSAnimationContext.beginGrouping() or CATransaction.begin() etc to set any animation duration to 0.0 but no luck.
Is there anyway to make this call snap - or should I be using something a little lower level?
EDIT
Here is my code. The duration has no effect here. There are always a few frames of scroll animation, and the endpoint of the animation is slightly irregular (i.e. the bottom edge of the scrolled to view is not always aligned with the bottom edge).
if selectedRows != outlineView.selectedRowIndexes {
outlineView.selectRowIndexes(selectedRows, byExtendingSelection: false)
// I would love this not to animate like in mail, but it cannot be stopped!!!
if selectedRows.one {
NSAnimationContext.beginGrouping()
NSAnimationContext.current.allowsImplicitAnimation = false
NSAnimationContext.current.duration = 0
outlineView.scrollRowToVisible(selectedRows.first!)
NSAnimationContext.endGrouping()
}
}
Using runAnimationGroup has the same result:
NSAnimationContext.runAnimationGroup( { current in
current.allowsImplicitAnimation = false
current.duration = 0
outlineView.scrollRowToVisible(selectedRows.first!)
}, completionHandler: nil)
I have variable height rows in my table but I don't see why this would make a difference. From the above code, the change in selection is always highlighted before any movement in the table, further indication that the scroll animation is not being removed.
I had this problem myself, and solved it by subclassing NSClipView and overriding func scroll(to newOrigin: NSPoint) like this:
override func scroll(to newOrigin: NSPoint) {
super.setBoundsOrigin(newOrigin)
}
This should disable smooth scroll entirely, which is the animation effect you are describing, for the scroll view that houses your subclassed clip view.

Override the Autolayout center values of UIButton in a Subview # viewDidLayoutSubviews

I use Autolayout for a fairly complex Menu and really need it. All Buttons, UIViews etc. of my Menu are in a separate UIView called "menuSubview".
If the user presses a button the whole "menuSubview" shifts to another position to reveal other parts of the menu. Sometimes the buttons in the menuSubview move as well. I always save the "Menu State" (with Userdefaults in the get-set variable "lastMenu") and have a function to set the alphas and centers according to the saved "Menu State".
I tried calling the "openLastMenu" function in viewDidAppear, viewDidLayoutSubview - all the "viewDid" functions of the ViewController. The "menuSubview" center and alphas of the buttons always behave as expected... but the centers of the buttons simply won't - no matter what "viewDid" I call the function in.
(the code is a lot more complex - I boiled it down to debug and state my point)
override func viewDidAppear(animated: Bool) {
if lastMenu != nil {openLastMenu()}
}
func openLastMenu(){
menuSubview.center.x = view.center.x //works
menuSubview.center.y = view.center.y + 200 //works
button1.center.x = view.center.x - 50 //why you no behave???
button2.center.x = view.center.x + 50 //why you no behave???
button3.alpha = 0 //works
button4.alpha = 0 //works
}
For debugging I even made a button Subclass to fetch the "center" values with a "didSet" if they change. Seems like after taking the correct values they change once more to their Autolayout-Position.
...oh and ignoring the constraints with "translatesAutoresizingMaskIntoConstraints" on the buttons always fucks up the whole menu. I'm starting to get crazy here :)
If you position views using autolayout, any changes to the frame, like what you do here with the center property, will be ignored.
What you need to do is identify the constraints that are you need to change to move the views in the desired position. Example:
You want to move button1 50 points to the left of view.center. Assuming view is the superview of menuSubview, you would
1) deactivate the the constraint responsible for button1's horizontal placement. How you do this mainly depends on whether you created the constraints in code or Interface Builder. The latter will require you to create outlets for some of the constraints.
2) create a new constraint between button1's centerX anchor and view's centerX anchor with a constant of -50, like so (iOS 9 code)
button1.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor, constant: -50.0).active = true

Time Machine style Navigation

I've been doing some programming for iPhone lately and now I'm venturing into the iPad domain. The concept I want to realise relies on a navigation that is similar to time machine in osx. In short I have a number of views that can be panned and zoomed, as any normal view. However, the views are stacked upon each other using a third dimension (in this case depth). the user will the navigate to any view by, in this case, picking a letter, whereupon the app will fly through the views until it reaches the view of the selected letter.
My question is: can somebody give the complete final code for how to do this? Just kidding. :) What I need is a push in the right direction, since I'm unsure how to even start doing this, and whether it is at all possible using the frameworks available. Any tips are appreciated
Thanks!
Core Animation—or more specifically, the UIView animation model that's built on Core Animation—is your friend. You can make a Time Machine-like interface with your views by positioning them in a vertical line within their parent view (using their center properties), having the ones farther up that line be scaled slightly smaller than the ones below (“in front of”) them (using their transform properties, with the CGAffineTransformMakeScale function), and setting their layers’ z-index (get the layer using the view’s layer property, then set its zPosition) so that the ones farther up the line appear behind the others. Here's some sample code.
// animate an array of views into a stack at an offset position (0 has the first view in the stack at the front; higher values move "into" the stack)
// took the shortcut here of not setting the views' layers' z-indices; this will work if the backmost views are added first, but otherwise you'll need to set the zPosition values before doing this
int offset = 0;
[UIView animateWithDuration:0.3 animations:^{
CGFloat maxScale = 0.8; // frontmost visible view will be at 80% scale
CGFloat minScale = 0.2; // farthest-back view will be at 40% scale
CGFloat centerX = 160; // horizontal center
CGFloat frontCenterY = 280; // vertical center of frontmost visible view
CGFloat backCenterY = 80; // vertical center of farthest-back view
for(int i = 0; i < [viewStack count]; i++)
{
float distance = (float)(i - offset) / [viewStack count];
UIView *v = [viewStack objectAtIndex:i];
v.transform = CGAffineTransformMakeScale(maxScale + (minScale - maxScale) * distance, maxScale + (minScale - maxScale) * distance);
v.alpha = (i - offset > 0) ? (1 - distance) : 0; // views that have disappeared behind the screen get no opacity; views still visible fade as their distance increases
v.center = CGPointMake(centerX, frontCenterY + (backCenterY - frontCenterY) * distance);
}
}];
And here's what it looks like, with a couple of randomly-colored views:
do you mean something like this on the right?
If yes, it should be possible. You would have to arrange the Views like on the image and animate them going forwards and backwards. As far as I know aren't there any frameworks for this.
It's called Cover Flow and is also used in iTunes to view the artwork/albums. Apple appear to have bought the technology from a third party and also to have patented it. However if you google for ios cover flow you will get plenty of hits and code to point you in the right direction.
I have not looked but would think that it was maybe in the iOS library but i do not know for sure.

how to set up multiple expandable sections in a scrollview

I need a layout similar to IB’s Inspectors, where there are multiple expandable sections, expanded by disclosure triangles, all of which are contained within a scrollview.
If only one expandable section were needed, I’d be there already: I put the expandable section in an NSBox, give the box and everything above it a top strut but no bottom strut, and give everything below it a bottom strut but no top strut. Then I set up the disclosure triangle’s action to show/hide the box and to adjust the frame size of the scrollview’s document view.
But there doesn’t seem to be a way to set the struts for multiple boxes. Either closing the disclosure triangles leaves gaps, or the boxes slide on top of each other.
I did take a look at NSOutlineView, but that’s a table; it can’t have subviews like comboboxes and buttons. (Or maybe it can, if I make custom cells, something I haven’t done yet — but I suspect those are not suited for full-featured layout.)
Can somebody point me in the right direction?
In case anybody else runs into this design challenge, I’ll post the IBAction I came up with.
This scheme uses regular, unflipped views. That is, the origin is at the lower left-hand corner. When the docSize is changed, space is added or removed from the top.
While for a single disclosure triangle, some controls need top struts and some need bottom struts, for this scheme, all controls must have both top and bottom struts. Otherwise they adjust themselves automatically, throwing everything off.
As noted at the end, there’s a considerable challenge involved when fully scrolled to the bottom. But that’s another chapter…
/**
Action called upon clicking a disclosure triangle.
Hides or discloses the box associated with the disclosure triangle.
*/
- (IBAction) discloseBox:(id)sender {
// Determine which box is governed by this disclosure triangle.
NSBox *boxTarget;
switch ([sender tag]) {
case kDT_Source:
boxTarget = self.boxSourceInfo;
break;
case kDT_Tweak:
boxTarget = self.boxTweak;
break;
case kDT_Series:
boxTarget = self.boxSeries;
break;
case kDT_Abbrevs:
boxTarget = self.boxAbbreviations;
break;
case kDT_Flag:
boxTarget = self.boxFlaggingAndComments;
break;
default:
break;
}
// Get size info on the content with and without the box.
NSView *docView = [self.svEditorMain documentView];
NSSize docSize = [docView frame].size;
CGFloat fHeightChange = [boxTarget frame].size.height;
// Before actually changing the content size, record what point is currently at the top of the window.
CGFloat dropFromTop_preChange = [self getCurrentDropFromTop];
// If the state is now on, make the box visible.
// If the state is now off, hide the box and make the height change negative.
switch ([sender state]) {
case NSOnState:
[boxTarget setHidden:NO];
break;
case NSOffState:
[boxTarget setHidden:YES];
fHeightChange *= -1;
break;
default:
break;
}
// Use the height change to prepare the adjusted docSize, but don't apply it yet.
NSSize adjustedDocSize = NSMakeSize(docSize.width, (docSize.height + fHeightChange));
// Make sure the adjustees array is populated.
[self populateVerticalAdjusteesArray];
// If the height change is positive, expand the content size before adjusting the origins, so that the origins will have space to move up into. (Space will be added at top.)
if (fHeightChange > 0)
[docView setFrameSize:adjustedDocSize];
// Get the current, pre-change Y origin of the target box.
CGFloat boxOriginY_preChange = [boxTarget frame].origin.y;
// Loop through the adjustees, adjusting their height.
NSControl *control;
CGFloat originX;
CGFloat originY;
for (NSUInteger ui = 0; ui < [self.carrVerticalAdjustees count]; ++ui) {
control = [self.carrVerticalAdjustees objectAtIndex:ui];
originY = [control frame].origin.y;
// Adjust all controls that are above the origin Y of the target box (but do nothing to the target box itself).
// Since coordinate system places origin at lower left corner, higher numbers are higher controls.
if (originY > boxOriginY_preChange) {
originX = [control frame].origin.x; // get originX just so you can assemble a new NSPoint
originY += fHeightChange;
[control setFrameOrigin:NSMakePoint(originX, originY)];
}
// Since the array was assembled in order from top down, once a member is encountered whose origin is below the target's, we're done.
else
break;
}
// If the height change is negative, contract the content size only now, after the origins have all been safely adjusted downwards. (Space will be removed at top.)
if (fHeightChange < 0)
[docView setFrameSize:adjustedDocSize];
// Left to its own devices, the scroller will maintain the old distance from the bottom, so whatever is under the cursor will jump up or down. To prevent this, scroll the content to maintain the old distance from the TOP, as recorded above.
// (This won't work if user is already scrolled to the bottom and then collapses a box. The only way to maintain the scroll position then would be to add blank space at the bottom, which would require moving the origin of all the content up. And then you would want to reverse those changes as soon as the blank space scrolled back out of view, which would require adding some trackers and monitoring NSViewBoundsDidChangeNotification.)
[self scrollMainTo:dropFromTop_preChange];
}
Check out InspectorKit. If you're using Xcode 4, however, keep in mind it no longer supports IBPlugins, so you'd have to use InspectorKit in code (and do without the drag-and-drop convenience of the Interface Builder plug-in).

Resources