How to enforce synchronization between UISlider value and thumb position? - ios

I'm using a custom UISlider. I save the value after user input using slider.value. After a while, I need to restore the position of the slide to its saved value using [slider setValue:savedValue].
But I noticed some discrepancies between the position at save time and the position after restoration.
After a while I found out that if the user moves the thumb very quickly, it goes further than the actual value (Some kind of momentum/inertia effect). How can I make sure that one value corresponds to one thumb position?

Assuming you're catching UIControlEventTouchUpInside to dictate 'finished', use UIControlEventEditingDidEnd instead.
Failing that, you could key-value observe value, at each change cancelling future saves and, if tracking is false, scheduling a new for 0.5 seconds from now. E.g.
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(storeValue) object:nil];
if(!slider.tracking)
{
[self performSelector:#selector(storeValue) withObject:nil afterDelay:0.5];
}
(or explicitly store whether a save is scheduled if you find any performance problem with that, I guess, but don't overcomplicate it unless you have to)

Are you treating the value as an integer?
Because it (the UISlider object) stores a float, treating it as an integer can cause this behaviour..
Perhaps re-set it in the action where you process the change, I've found this necessary before once or twice..
Eg (this hooked up to the sliders (value changed) controlEvent....
-(IBAction) sliderMoved:(id)sender{
UISlider *sld = (UISlider *)sender;
CGFloat val = sld.value;
val += 0.49; /// rounder...
int result = (int) val;
[sld setValue:(CGFLoat)result animated:YES];
///// etc process entry...
}
This kind of treatment turns the slider into a multi-position "switch", it snaps to whole number values.. You could be doing it with fabsf(...) function if you want to remain in the float territory too...

Related

Animate a custom property's value?

I can easily animate something like the position or size of a UIView. But how can I animate a "custom" variable (such as experiencePoints) to achieve interpolation of values that are not associated with a UIView.
// The variable being animated
CGFloat experiencePoints = 0;
// Pseudo-code
[experiencePoints animateTo:200 duration:2 timingFunction:someTimingFunction];
With this code, if I accessed experiencePoints while it was animating, I would get a value between 0 and 200 based on how long the animation has been going.
Bonus question: Is it possible to use a CAAnimation to do what I want?
You can go with Presentation layer with CADisplayLink and by adding observer on animation status you can increment the variable upto your final one. Observer will observe current status of Animation which will eventually provide you a way to get current value of that VAR.
Ended up using Facebook's POP animation library, which allows the animation of any property on an object. I recommend it!
Here is a quote from the readme, explaining how to do it:
The framework provides many common layer and view animatable properties out of box. You can animate a custom property by creating a new instance of the class. In this example, we declare a custom volume property:
prop = [POPAnimatableProperty propertyWithName:#"com.foo.radio.volume" initializer:^(POPMutableAnimatableProperty *prop) {
// read value
prop.readBlock = ^(id obj, CGFloat values[]) {
values[0] = [obj volume];
};
// write value
prop.writeBlock = ^(id obj, const CGFloat values[]) {
[obj setVolume:values[0]];
};
// dynamics threshold
prop.threshold = 0.01;
}];
anim.property = prop;
"Animating" something is simply moving a value from a starting position to an ending position over time. To "animate" a float from one value to another over a duration you could just run a timer and incrementally change it. Or possibly you could store the begin and end time of the "animation" and whenever the value is accessed, compare those to the actual time to come up with a value.

Cocos2d - move a sprite from point A to point B in a sine wave motion

What would be the best way to do this? I see the CCEaseSineInOut action but it doesn't look like that could be used to do this.
I need to move from one side of the screen to the other. The sprite should move in a sine-wave pattern across the screen.
I always like to have complete control over CCNode motion. I only use CCActions to do very basic things. While your case sounds simple enough to possibly do with CCActions, I will show you how to move a CCNode according to any function over time. You can also change scale, color, opacity, rotation, and even anchor point with the same technique.
#interface SomeLayer : CCLayer
{
CCNode *nodeToMove;
float t; // time elapsed
}
#end
#implementation SomeLayer
// Assumes nodeToMove has been created somewhere else
-(void)startAction
{
t = 0;
// updateNodeProperties: gets called at the framerate
// The exact time between calls is passed to the selector
[self schedule:#selector(updateNodeProperties:)];
}
-(void)updateNodeProperties:(ccTime)dt
{
t += dt;
// Option 1: Update properties "differentially"
CGPoint velocity = ccp( Vx(t), Vy(t) ); // You have to provide Vx(t), and Vy(t)
nodeToMove.position = ccpAdd(nodeToMove.position, ccpMult(velocity, dt));
nodeToMove.rotation = ...
nodeToMove.scale = ...
...
// Option 2: Update properties non-differentially
nodeToMove.position = ccp( x(t), y(t) ); // You have to provide x(t) and y(t)
nodeToMove.rotation = ...
nodeToMove.scale = ...
...
// In either case, you may want to stop the action if some condition is met
// i.e.)
if(nodeToMove.position.x > [[CCDirector sharedDirector] winSize].width){
[self unschedule:#selector(updateNodeProperties:)];
// Perhaps you also want to call some other method now
[self callToSomeMethod];
}
}
#end
For your specific problem, you could use Option 2 with x(t) = k * t + c, and y(t) = A * sin(w * t) + d.
Math note #1: x(t) and y(t) are called position parameterizations. Vx(t) and Vy(t) are velocity parameterizations.
Math note #2: If you have studied calculus, it will be readily apparent that Option 2 prevents accumulation of positional errors over time (especially for low framerates). When possible, use Option 2. However, it is often easier to use Option 1 when accuracy is not a concern or when user input is actively changing the parameterizations.
There are many advantages to using CCActions. They handle calling other functions at specific times for you. They are kept track of so that you can easily pause them and restart them, or count them.
But when you really need to manage nodes generally, this is the way to do it. For complex or intricate formulas for position, for example, it is much easier to change the parameterizations than to figure out how to implement that parameterization in CCActions.

How do I calculate the speed of which a user drags an image?

I have an image that the user can drag to the right and it will spring back when the user releases it. I want to execute some code when a user drags it quickly and releases it. Now I have a very awkward requirement that the user can drag the image, then keep it still for any length of time (for example 5 seconds), then drag it quickly and release it. As long as the image is moving above a certain speed when it is released, it will execute the code. If it falls below the minimum speed, it executes some different code. So that means I can't calculate the length of time between the beginning of the gesture and the end and execute the code depending on the length of time. What can I do? I guess I somehow need to know the speed at which the image is moving in it's last 500 milliseconds before the gesture ends. However I've hit a brick wall figuring out how to do that. Any help would be greatly appreciated.
Can you please include an explanation and possible example code with your answer as that would be a great help.
If you get the start X,Y coordinates of when the image is dragged, and the X,Y coordinates for when the mouse is released, you can use pythagoras' theorm to calculate the distance between the two points: http://en.wikipedia.org/wiki/Pythagorean_theorem
Also, if you start a timer when the mouse is moved (and mouse button is down), and stop it in the mouseup event, you can calculate the speed using the time and distance (speed = distance / time)
edit following comments:
point delayedMousePos;
point previousMousePos;
bool secondDrag = false;
bool isStopped = false;
var timeFirstStopped;
var positionCount = 0;
array previousMousePositions[3];
// timer which monitors mouse position (set to an interval of say, 10ms)
function timerMonitorMousePos_Elapsed() {
point currentMousePos = getMousePos();
if (isStopped == false) {
if (positionCount >= 2) {
array_shift(previousMousePositions); // remove the first element of the array and move everything down to reindex numerical array to start counting from zero
positionCount = 2; // keep positionCount within array bounds
}
previousMousePositions[positionCount] = currentMousePos; // add the new position to the end of the 'stack'
positionCount++;
}
if (currentMousePos == previousMousePos) { // start check for stationary
isStopped = true;
if (timeFirstStopped == null) {
timeFirstStopped = NOW();
} else {
if (NOW() - timeFirstStopped >= 500) { // we have been stopped for at least 500ms (assumes time is counted in milliseconds)
secondDrag = true;
// previousMousePositions[0] = the mouse position 30ms before the mouse stopped
}
}
} else {
isStopped = false;
timeFirstStopped = null;
}
previousMousePos = currentMousePos;
}
I wouldn't use a timer. I would just save the starting date/time along with x,y position when the dragging starts.
When the dragging has ended, save the ending date/time and position. From those information, I can calculate the distance in pixel and duration in milliseconds.
After searching some more on the internet, I finally answered my own question.
I worked out what I needed to do:
My UIPanGestureRecognizer:
- (IBAction)handlePan3:(UIPanGestureRecognizer *)recognizer3
Get the velocity of the users finger moving across the screen:
CGPoint vel = [recognizer velocityInView:self.myView];
Then:
if (vel.x > /*value*/) {
// Your code
}
I was about to give up, but no! I got there in the end. Thanks for everyones help. I've upvoted one or two answers because they were helpful. bobnoble actually gave the suggestion to use velocityInView and I found this other stack overflow question which gave me the info I needed: iOS - Making sense of velocityInView on UIPanGesture

iOS, trigger UIViewAnimation once at slider value n?

I have a set of sliders, I'm using Value Changed to feed a number to a % indicator. I'm also using this value to check if the slider is below a certain point. If it is, I want to run a UIViewAnimation (which I am, it's all working fine). However, the animation gets called constantly if the slider is moved below the threshold, meaning the items being animated go from point a to point b then back again over and over. So, can I trigger the animation once only at the threshold point?
This is how I get my value in pixels:
_sizeSliderRange = _sizeSlider.frame.size.width - _sizeSlider.currentThumbImage.size.width;
_sizeSliderOrigin = _sizeSlider.frame.origin.x + (_sizeSlider.currentThumbImage.size.width / 4.0);
_sizeSliderValueToPixels = (_sizeSlider.value * _sizeSliderRange) + _sizeSliderOrigin;
And I use a conditional inside the linked Value Changed IBAction function to checkt he value and do the work:
if (_sizeThumbX < 85) { //if within 60px of the left margin we animate the label to sit float left
[UIView transitionWithView:_sizeLabel duration:0.25f options:UIViewAnimationCurveEaseInOut animations:^(void) { etc etc
Thanks.
Like #Luis said, just use a BOOL property like this:
if (_sizeThumbX < 85) { //if within 60px of the left margin we animate the label to sit float left
if (!self.passedBelowThreshold) {
[UIView transitionWithView:_sizeLabel duration:0.25f options:UIViewAnimationCurveEaseInOut animations:^(void) { /* ... */ }
}
}
self.passedBelowThreshold = _sizeThumbX < 85;
Your code is working according to your logic that is everytime the slider value is changed and it is below 85 the animation will be invoked .You can have animation triggered only once in the follwing way :-
1>YOu can keep an absolute value at which animation occurs.Something like _sizeThumbX == 85
2>or you canhave counter of how many times the value changes . In a different function count and store the value of slider change.If the slider value lies in the 85 range do not increase the counter value and in your animation part check the counter flag and slider's current position if slider is still in below 85 range do not invoke animation if counter value is already 1 that is animation is already fired else invoke and increase the animation counter.
3>I am not ware of you conditions as you have not mentioned clearly but ithink you want to invoke the animation again if your slider goes beyond the range and comes back again , in that case make the count to zero (slider crosses the specified range) .

Bad to Set slider.value When User has Changed Slider Position?

In the online Stanford CS193p iPhone Application Development course, lecture 6, an application is built which has a slider as input and a custom view as output.
When the slider is changed, the view controller sets the slider value again.
Important bits of the view controller in Happiness 2.zip:
#implementation HappinessViewController
#synthesize happiness;
- (void)updateUI
{
// assignment-loop when called from happinessChanged:?
self.slider.value = self.happiness; // sets slider to model's (corrected) value
[self.faceView setNeedsDisplay];
}
- (void)setHappiness:(int)newHappiness
{
if (newHappiness < 0) newHappiness = 0; // limit value
if (newHappiness > 100) newHappiness = 100;
happiness = newHappiness;
[self updateUI]; // changed happiness should update view
}
- (IBAction)happinessChanged:(UISlider *)sender // called by changed slider
{
self.happiness = sender.value; // calls setter setHappiness:
}
Doesn't this result in a loop (slider changed -> model updated -> change slider -> ?)?
Or is this even good practice?
If the slider is updated from code, rather than by the user, it presumably doesn't sent the valueChanged action. So you don't get an infinite loop.
This can be used to "correct" the value selected by the user, or to force the slider onto regular tick marks instead of a smooth scale.

Resources