I'm currently creating a iPhone app that displays my speed heading location and a few other sensor readings for use on my bike. I've managed to get the tilt reading from the accelerator. what I'm trying to do is move an image either left or right depending on what the tilt reading is.
_______________________________
: === :
: = = :
: === :
-------------------------------
lets say the = signs is my image, I'm trying to get it to move either towards the left or right edge depending on the tilt angle.
any ideas? I've managed to move it by animations but I don't want it to continuously move if I'm leaning for a long period of time.
First I added the accelerator data using:
UIAccelerometer *theAccelerometer = [UIAccelerometer sharedAccelerometer];
theAccelerometer.updateInterval = 0.1; //in seconds
theAccelerometer.delegate = self;
Notice the timing is 0.1 seconds. You will want to match that timing to the animation below. I then set the animation up in the accelerometer:didAccelerate: delegate call:
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
NSInteger newX = 0;
newX = imageView.frame.size.width/2 + ((320-imageView.frame.size.width) * (acceleration.x+1)/2.0);
[UIView animateWithDuration:0.1 animations:^{
imageView.center = CGPointMake(newX, imageView.center.y);
}] ;
}
It is important to match the animation duration with the updateInterval of the UIAccelerometer so that you make sure to complete the animation before the next update comes through.
The important part here is probably the math to determine the newX position. I basically took half of the width of the image, because that is the left most absolute position. From there I knew I had a 320-imageView.frame.size.width amount of room that I could add. If I added the entire 320-imageView.frame.size.width then I would be at 320-imageView.frame.size.width/2 which is the right-most position. From there I just needed to break up the 320-imageView.frame.size.width chunk according to how much we have accelerated. The accelerator.x data is in the range -1 to 1, so my entire range is 2. So I added 1 to the accelerator.x to normalize it from 0-2. Then I divided by 2.0 so that I would have a range from 0-1. Now I had a multiplier that I could multiply my 320-imageView.frame.size.width range by. If it was 0 I would be at the left-most position. If it was 1 I would be at the right-most position, and anything in between would be broken up linearly. So the final term is:
imageView.frame.size.width/2 + ((320-imageView.frame.size.width) * (acceleration.x+1)/2.0);
to get the new x position of the image.
I hope that is what you are looking for! I tested it on a device and it is pretty smooth as long as you match up your durations.
If you are asking the question about animation. This is code apple recommnad to animate UIView. And you can change value for x and y coordinate.
imageFrame.origin.y = 200;
[UIView animateWithDuration:0.5
delay:0.0
options: UIViewAnimationCurveEaseOut
animations:^{
datePickerView.frame = imageFrame;
}
completion:^(BOOL finished){
self.datePickerViewFlag = YES;
}];
Related
My app is simple: I want to move a small uiimageview around a view of an iPad with the accelerometer (now Core Motion). So, I can move the uiimageview around with the accelerometer, but I am having trouble establishing working borders in landscape mode. So, here is my code. The problem is that the uiimageview tends to stick to the borders and some of the borders are not perfectly on the edge of the view. Here is my code, any help is greatly appreciated:
- (void)startMyMotionDetect
{
__block float stepMoveFactor = 15;
[self.motionManager
startAccelerometerUpdatesToQueue:[[NSOperationQueue alloc] init]
withHandler:^(CMAccelerometerData *data, NSError *error)
{
dispatch_async(dispatch_get_main_queue(),
^{
CGRect rect = self.sumbarine.frame;
float movetoY = rect.origin.x + (data.acceleration.x * stepMoveFactor);
float maxY = self.view.frame.size.width-rect.size.width;
float movetoX = (rect.origin.y + rect.size.height)
- (data.acceleration.y * stepMoveFactor);
float maxX = self.view.frame.size.height;
if ( movetoX >0 && movetoX < maxX ) {
rect.origin.x += (data.acceleration.y * stepMoveFactor);
};
if ( movetoY > 0 && movetoY < maxY ) {
rect.origin.y += (data.acceleration.x * stepMoveFactor);
};
[UIView animateWithDuration:0 delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:
^{
self.sumbarine.frame = rect;
}
completion:nil
];
}
);
}
];
}
The sticking issue comes from your conditions for moving. Right now, you say, if "moveToX/Y" is > 0, and less than the width/height, then add Acceleration X/Y.
What happens if origin is == or >greater than "maxX/Y"? Answer: Nothing. And thus, the submarine image acts like it's stuck to a board. Probably it can move on the alternate axis, until it's finally stuck at a condition where it no longer can move.
You'll want to play around with your conditional rules. I do not know what type of actions/input you're trying to implement for gameplay, so I can't tell you what to do next. I'm assuming the sub is supposed to go up/down depending on some user gesture/action.
One last piece of advice. Accelerometers are based on G-Force... What this means for you is even though acceleration on some axis is positive when you move say left... There's also an inverse curve when you need apply the force needed to stop. Try looking at the motiongraphs app (available in documentation) to help you get a better feel/sense of this stuff.
And remember, have fun!
I have two views: A and B. A is positioned at the top of the screen, B is positioned at the bottom of the screen.
When the user presses a button, view B animates upwards with a EaseInEaseOut bezier curve until it reaches y = 0. While B is on its way to its destination, it should push A up when it hits A. In other words, when B has passed a certain y coordinate (A's y origin + height) during its transition from bottom to top, A should stick to B so it seems B pushes A upwards.
What I have tried so far:
Register a target + selector to a CADisplayLink immediately after the user pressed the button. Inside this selector, request view B's y coordinate by accessing its presentationLayer and adjust A's y coordinate accordingly. However, this method turns out to be not accurate enough: the presentationLayer's frame is behind on B's current position on the screen (this is probably because -presentationLayer recalculates the position of the animating view on the current time, which takes longer than 1 frame). When I increase B's animation duration, this method works fine.
Register a target + selector to a CADisplayLink immediately after the user pressed the button. Inside this selector, calculate B's current y coordinate by solving the bezier equation for x = elapsed time / animation duration (which should return the quotient distance traveled / total distance). I used Apple's open source UnitBezier.h for this (http://opensource.apple.com/source/WebCore/WebCore-955.66/platform/graphics/UnitBezier.h). However, the results are not correct.
Any suggestions on what I can try next?
Two simple solutions:
Use animationWithDuration only: You can break your animation into two nested animations, using "ease in" to animate the moving of "B" up to "A", and then using "ease out" to animate the moving of "B" and "A" the rest of the way. The only trick here is to make sure the two duration values make sense so that the speed of the animation doesn't appear to change.
CGFloat animationDuration = 0.5;
CGFloat firstPortionDistance = self.b.frame.origin.y - (self.a.frame.origin.y + self.a.frame.size.height);
CGFloat secondPortionDistance = self.a.frame.size.height;
CGFloat firstPortionDuration = animationDuration * firstPortionDistance / (firstPortionDistance + secondPortionDistance);
CGFloat secondPortionDuration = animationDuration * secondPortionDistance / (firstPortionDistance + secondPortionDistance);
[UIView animateWithDuration:firstPortionDuration
delay:0.0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
CGRect frame = self.b.frame;
frame.origin.y -= firstPortionDistance;
self.b.frame = frame;
}
completion:^(BOOL finished) {
[UIView animateWithDuration:secondPortionDuration
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
CGRect frame = self.b.frame;
frame.origin.y -= secondPortionDistance;
self.b.frame = frame;
frame = self.a.frame;
frame.origin.y -= secondPortionDistance;
self.a.frame = frame;
}
completion:nil];
}];
You can let animateWithDuration handle the full animation of "B", but then use CADisplayLink and use presentationLayer to retrieve B's current frame and to adjust A's frame accordingly:
[self startDisplayLink];
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
CGRect frame = self.b.frame;
frame.origin.y = self.a.frame.origin.y;
self.b.frame = frame;
}
completion:^(BOOL finished) {
[self stopDisplayLink];
}];
where the methods to start, stop, and handle the display link are defined as follows:
- (void)startDisplayLink
{
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(handleDisplayLink:)];
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)stopDisplayLink
{
[self.displayLink invalidate];
self.displayLink = nil;
}
- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
CALayer *presentationLayer = self.b.layer.presentationLayer;
if (presentationLayer.frame.origin.y < (self.a.frame.origin.y + self.a.frame.size.height))
{
CGRect frame = self.a.frame;
frame.origin.y = presentationLayer.frame.origin.y - self.a.frame.size.height;
self.a.frame = frame;
}
}
You said that you tried the "animate B and use display link to update A" technique, and that it resulted in "A" lagging behind "B". You could theoretically animate a new view, "C", and then adjust B and A's frames accordingly in the display link, which should eliminate any lag (relative to each other).
You can try followin algorythm:
1)Put A and B in UIView(i.e UIview *gropedView)
2)Change B.y till it will be equal A.y+A.height(so B will be right under the A)
3)Animate groupedView
I'm stuck at the moment controlling UISlider in XCode. I've made a horizontal slider in Interface Builder and manage to code the controls in xcode. Right now I'm just not sure how to code the logic. If the user slides the nib to the left, I'd it like it to rotate my image counter-clockwise and if the user slides it to the right, rotate the image clockwise.
Minimum value of slider I've set is 0 and max is 100. Initial value is 50.
For transformation I'm using CGAffineTransformMakeRotation(myAngle). I've set myAngle to float. As for the condition, here's a snippet of code:
mySlider.value = myAngle; // This is for CGAffine
if(myAngle < 50) {
myAngle -= 0.1;
}
if(myAngle > 50) {
myAngle += 0.1;
}
When I slide the nib, it only rotates anti-clockwise. What's the best logic can I use? Hope someone can help. Thanks.
-Hakimo
Note that the Doc says about the angle as,
The angle, in radians, by which this matrix rotates the coordinate system axes. In iOS, a positive value specifies counterclockwise rotation and a negative value specifies clockwise rotation. In Mac OS X, a positive value specifies clockwise rotation and a negative value specifies counterclockwise rotation.
I think something like the following should work. I am not sure though. (Considering that the minimumValue is 0).
float middleValue = slider.maximumValue / 2;
float value = middleValue - slider.value;
float degrees = (middleValue / 180) * value;
float radians = degrees * (3.14/180);
If you want a fixed position on the slider to correspond to a fixed angle of rotation, then you can do something like this:
float degreesToRadians = M_PI / 180.0;
CGFloat angle = (mySlider.value - 50) * degreesToRadians;
myView.transform = CGAffineTransformMakeRotation(angle);
On the other hand, if want more complex behavior, such as that the slider snaps to the middle except when being dragged, and how far it's dragged determines the speed of rotation of your view, then I would suggest using a timer. Set up an NSTimer to call a given selector, and in that selector do something like this:
- (void)timerFired:(NSTimer *)aTimer {
CGFloat angleDelta = (mySlider.value - 50) / 500;
// I'll assume myAngle is an instance variable.
myAngle += angleDelta;
myView.transform = CGAffineTransformMakeRotation(myAngle);
}
And somewhere else you need to write a method to set mySlider.value = 50 when the user stops dragging it, which you can do by adding a target/action for UIControlEventTouchUpInside and ...Outside on the slider.
If you want, you can make the rotation appear very smooth by using UIView animations each time you set the transform, using an animation duration equal to the NSTimer interval. If you do that, then I would actually suggest using the animation callback in place of the timer method, to avoid any possible crossover between the two events.
Hello all I am working an iphone app where I need to show the current time in a watch That is there is a watch which displays the static time or static image . Then we need to find the current time then move all the hrs,mins ,seconds pins such tht they should set to their time.
Do u have any ideas to do this
Thanks Everyone
Here is my code:
//Calculate angles:
float minutesAngle = (0/60)*360;
float hoursAngle = (10/12)*360;
//Begin the animation block
[UIView beginAnimations:#"moveHands" context:nil];
[UIView setAnimationDuration:0.5];
minsImageView.transform = CGAffineTransformRotate(minsImageView.transform, minutesAngle);
hoursImageView.transform = CGAffineTransformRotate(hoursImageView.transform, hoursAngle);
[UIView commitAnimations];
where as minsImageView ,hoursImageView are the imageViews set in the XIB who shows the static image
But its not effecting at all..
Once you have individual time values (it sounds like you've figured this out already) you can position your hands as needed.
This means it's time to brush up on your math skills. I think this'll give you a start:
A circle has 360 degrees in it. This means that for whatever time unit we get, we divide by the maximum value of that time unit to get a fraction and multiply the result by 360 (instead of 100 like a percentage) to get our number of degrees.
So, if we had 23 seconds:
23/60 = 3.83333.8333 x 360 = 138 degrees
Now we know how many degrees to rotate our object. I would get a designer to make you some nice hand images and put them in UIImageViews. Then, you can rotate them around their origins like so:
secondHandImage.transform = CGAffineTransformMakeRotate(secondAngle);
Good luck :)
EDIT: Let's assume that our hands are on 10:10. If we wanted to move them to 01:00 we would do something like the following. Tweak it as needed.
//Calculate angles:
float minutesAngle = (0/60)*360;
float hoursAngle = (10/12)*360;
//Begin the animation block
[UIView beginAnimations:#"moveHands" context:nil];
[UIView setAnimationDuration:0.5];
minutesHand.transform = CGAffineTransformRotate(minutesHand.transform, minutesAngle);
hoursHand.transform = CGAffineTransformRotate(hoursHand.transform, hoursAngle);
[UIView commitAnimations];
If you're trying to show a digital clock, your life is SOOOO much easier than my previous answer related to analog clocks.
Create images with the numbers 0-9 and put them in your bundle. Have four UIImageViews in your UI in this configuration:
[][]:[][]
[ ] = UIImageVIew
Then, you can simply change the images in each slot to reflect the time. If you use transparent backgrounds and high resolution graphics, you can create really convincing effects.
Me again. I have a simple question. I have an UIImageView like the one shown below.
alt text http://img683.imageshack.us/img683/1999/volumen.png
That UIimageView is supposed to be the knob to control the volume of my iphone project. My question is, how to know the positions of bar on the UIImageView when it is rotated? Because the volume needs to be 0.5 when the little bar on the cercle is vertical.
I got a piece of code which is (in the touchMoved method):
float dx = locationT.x - imgVVolume.center.x;
float dy = locationT.y - imgVVolume.center.y;
CGFloat angleDif = 0.0f;
movedRotationAngle = atan2(dy,dx);
if (beganRotationAngle == 0.0) {
beganRotationAngle = movedRotationAngle;
initialTransform = imgVVolume.transform;
}
else {
angleDif = beganRotationAngle - movedRotationAngle;
CGAffineTransform newTrans = CGAffineTransformRotate(initialTransform, -angleDif);
imgVVolume.transform = newTrans;
}
Help please.
It depends on what input mechanism you want to use to control the rotation.
If the knob is to rotate based on a single finger touch dragging from side to side then you can create a UIPanGestureRecognizer and attach it to the knob UIImageView. The translationInView: method returns a CGPoint which is the amount of X and Y movement from the touch-down point. You can feed that into a formula like the one you post to get an angle of rotation. You'll want to keep track of delta from last position and also check for stop limits (like 0..360) to prevent over-rotation.
OTOH, if you're going to use two finger rotation then you'll want to use a UIRotationGestureRecognizer and look for the rotation value. Just feed that into a CGAffineTransformRotate and set it to the UIImageView transform. That takes care of all of the above for you. Again, you'll want to check for stop limits.