How define location of CALayer in the UIView during movement - ios

I have want to make an application where apples fall down from the tree and user can catch it with the basket. Basket starts moving when I touch the screen so it moves in that direction - but slowly. For this I use
-(void) touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
UITouch* t = [touches anyObject];
CGPoint p = [t locationInView:self.view];
if(p.y <self.view.frame.size.height-50)
{
p.y = self.view.frame.size.height-50;
}
CABasicAnimation *basketmovementAnimation = [CABasicAnimation animationWithKeyPath:#"position"];
[basketmovementAnimation setFromValue:[NSValue valueWithCGPoint:[[self.basketLayer presentationLayer]position]]];
[basketmovementAnimation setDuration:3.0];
[self.basketLayer setPosition:p];
[self.basketLayer addAnimation:basketmovementAnimation forKey:#"basket"];
}
So the question is how to get coordinates of "basket" during movement? I need it to compare with the coordinates of fallen apples.
Thanks in advance

CALayer has a method for this.
- (id)presentationLayer
From the CALayer Class Reference
The layer object returned by this method provides a close
approximation of the layer that is currently being displayed onscreen.
While an animation is in progress, you can retrieve this object and
use it to get the current values for those animations.

Related

Setting UIView position and size

When creating a new UIView or panning/swiping a view to a new position, whats the proper way of setting the new position and size. Currently, to move views resulting from a pan/swipe action, I'm using ratios based on the original position of the view that I'm moving and its relation to other views and items (navigation bar).
When moving a view due to a swipe I do the following:
CGFloat swipeUpDelta = -1 *(self.view.bounds.size.height - (self.view.bounds.size.height - recordView.frame.origin.y) - (self.navigationController.navigationBar.bounds.size.height + ([UIApplication sharedApplication].statusBarFrame.size.height)));
[UIView animateWithDuration:.1 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
recordView.frame = CGRectOffset(recordView.frame, 0, swipeUpDelta);
} completion:nil];
Or creating a view that renders a sineWave across my viewcontroller:
self.sineWave = [[OSDrawWave alloc] initWithFrame:CGRectMake(-1*self.view.bounds.size.width, (.75*self.view.bounds.size.height), 2*self.view.bounds.size.width, .3125*(2*self.view.bounds.size.width))];
Everything works fine, but I wanted to know if there is a better way of doing these things.
I'm not sure I understand the question. initWithFrame: or myView.frame is considered the correct way of positioning a UIView. Using animateWithDuration: with a new frame size is also pretty standard.
I'm unsure of what you mean as well. When you move a UIView you can move the view a variety of ways, it depends on your code and what you want out of it. The below is a valid example of code to move a CALayer inside of a UIView to follow touch positions. You can use this, you can animate your view, you can use explicit animations when changing the position of the layer or center of the view (By default iOS animates any changed property values of a CALayer... you can ride off of this and enable this feature for UIViews if you please). If you're moving a UIView, it's a good idea to move the view according to the center property. This is pretty much how SpriteKit works and any high functioning animation engine (avoid always resetting the frame... especially if you don't need to).
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *t = [touches anyObject];
CGPoint p = [t locationInView:self];
boxLayer.position = p;
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *t = [touches anyObject];
CGPoint p = [t locationInView:self];
[CATransaction begin];
[CATransaction setDisableActions:YES];
boxLayer.position = p;
[CATransaction commit];
}
As a note, note the comment in the following line of code
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
/*UITouch *t = [touches anyObject];
CGPoint p = [t locationInView:self]; // this causes the animations to appear sluggish as there is a consistant change in destination location. The above code corrects this.
[boxLayer setPosition:p];*/
This is valid, and may provide the results you want... it all depends on what you want.

Restrict movement of UIButton along a UIBezierPath path

I have a circular UIBezierPath. I use the path to draw a circle on my view to create an outline of a 24 hr clock. I have a UIButton whose position depends on the current time. The button acts like an Hour hand. I want the users to be able to move the UIButton along the circular path. I call it "visit the future/past" feature. How do I restrict the buttons movement to the path I have?
Override touchesBegan: and touchesMoved: methods in your view
- (void)touchesBegan: (NSSet *)touches withEvent:(UIEvent *)event
{
if([[event touchesForView:button] count])
{
//User is trying to move the button set a variable to indicate this.
}
}
- (void)touchesMoved: (NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint *point = [[event anyObject] locationInView:self];
/*Compare x and y coordinates with the centre property of the button
If x or y are greater set center of button to next point in circle or previous
point if any of them are lesser.*/
}
Note that you will have to save all points in your circle in an array before attempting this or you will have to calculate the points on the circumference of the circle by knowing the radius.
The easiest way is in touchesMoved, you can check to ignore touch which is not in your circle view by using:
CGPoint point = [touch locationInView:circleView];
if (![circleView pointInside:point withEvent:event]) {
return;
}

UIView bringSubviewToFront: does *not* bring view to front

I am implementing a simple iOS solitaire game that allows the user to drag the cards around in the usual way. The cards are represented with the UIView subclass CardView. All the card view's are siblings which are subviews of SolitaireView. The following snippet tries to "bring a card to the front" so that it is above all the other views as it is being dragged:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
if (touch.view.tag == CARD_TAG) {
CardView *cardView = (CardView*) touch.view;
...
[self bringSubviewToFront:cardView];
...
}
}
Unfortunately, the card's z-order remains unchanged during the drag. In the images below, I am dragging the King. Notice how it is correctly on top the Nine in the left image, but is incorrectly under the Two (under the entire stack actually) in the right image:
I also tried alter the layer.zPosition property as well to no avail.
How can I bring the card view to the front during the drag? I am mystified.
Confirmed. bringSubviewToFront: causes layoutSubview to be invoked. Since my version of layoutSubviews sets the z-orders on all the views, this was undoing the z-order I was setting in the touchesBegan:withEvent code above. Apple should mention this side effect in the bringSubviewToFront documentation.
Instead of using a UIView subclass, I created a CALayer subclass named CardLayer. I handle the touch in my KlondikeView subclass as listed below. topZPosition is an instance var that tracks the highest zPosition of all cards. Note that modifying the zPosition is usually animated -- I turn this off in the code below:
-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self];
CGPoint hitTestPoint = [self.layer convertPoint:touchPoint
toLayer:self.layer.superlayer];
CALayer *layer = [self.layer hitTest:hitTestPoint];
if (layer == nil) return;
if ([layer.name isEqual:#"card"]) {
CardLayer *cardLayer = (CardLayer*) layer;
Card *card = cardLayer.card;
if ([self.solitaire isCardFaceUp:card]) {
//...
[CATransaction begin]; // disable animation of z change
[CATransaction setValue:(id)kCFBooleanTrue
forKey:kCATransactionDisableActions];
cardLayer.zPosition = topZPosition++; // bring to highest z
// ... if card fan, bring whole fan to top
[CATransaction commit];
//...
}
// ...
}
}

iPhone - Move object along touch and drag path

I am developing a simple animation where an UIImageView moves along a UIBezierPath, now I want to provide user interation to the moving UIImageView so that user can guide the UIImageView by touching the UIImageView and drag the UIImageview around the screen.
Change the frame of the position of the image view in touchesMoved:withEvent:.
Edit: Some code
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
UITouch *touch = [[event allTouches] anyObject];
if ([touch.view isEqual: self.view] || touch.view == nil) {
return;
}
lastLocation = [touch locationInView: self.view];
}
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
UITouch *touch = [[event allTouches] anyObject];
if ([touch.view isEqual: self.view]) {
return;
}
CGPoint location = [touch locationInView: self.view];
CGFloat xDisplacement = location.x - lastLocation.x;
CGFloat yDisplacement = location.y - lastLocation.y;
CGRect frame = touch.view.frame;
frame.origin.x += xDisplacement;
frame.origin.y += yDisplacement;
touch.view.frame = frame;
lastLocation=location;
}
You should also implement touchesEnded:withEvent: and touchesCanceled:withEvent:.
So you want the user to be able to touch an image in the middle of a keyframe animation along a curved path, and drag it to a different location? What do you want to happen to the animation at that point?
You have multiple challenges.
First is detecting the touch on the object while a keyframe animation is "in flight".
To do that, you want to use the parent view's layer's presentation layer's hitTest method.
A layer's presentation layer represents the state of the layer at any given instant, including animations.
Once you detect touches on your view, you will need to get the image's current location from the presentation layer, stop the animation, and take over with a touchesMoved/touchesDragged based animation.
I wrote a demo application that shows how to detect touches on an object that's being animated along a path. That would be a good starting point.
Take a look here:
Core Animation demo including detecting touches on a view while an animation is "in flight".
Easiest way would be subclassing UIImageView.
For simple dragging take a look at the code here (code borrowed from user MHC):
UIView drag (image and text)
Since you want to drag along Bezier path you'll have to modify touchesMoved:
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *aTouch = [touches anyObject];
//here you have location of user's finger
CGPoint location = [aTouch locationInView:self.superview];
[UIView beginAnimations:#"Dragging A DraggableView" context:nil];
//commented code would simply move the view to that point
//self.frame = CGRectMake(location.x-offset.x,location.y-offset.y,self.frame.size.width, self.frame.size.height);
//you need some kind of a function
CGPoint calculatedPosition = [self calculatePositonForPoint: location];
self.frame = CGRectMake(calculatedPosition.x,calculatedPosition.y,self.frame.size.width, self.frame.size.height);
[UIView commitAnimations];
}
What exactly you would like to do in -(CGPoint) calculatePositionForPoint:(CGPoint)location
is up to you. You could for example calculate point in Bezier path that is the closest to location. For simple test you can do:
-(CGPoint) calculatePositionForPoint:(CGPoint)location {
return location;
}
Along the way you're gonna have to decide what happens if user wonders off to far from your
precalculated Bezier path.

rotate an image with touch

I making an app which sets the sleep timer with a clock .Basically it is clock which has a single hand which user can move to set his sleep time .I tried to rotate the image with uitouch but it rotates from middle but i want it to rotate from the tip.Secondly i want that the image only rotates when user is touching the tip of the image but in my project the image is also rotating when use touches any part of the screen.Also i want to rotate the image in both directions but in my project it moves only clockwise due to this method
image.transform = CGAffineTransformRotate(image.transform, degreesToRadians(1));
Can anybody give me hints or solutions about how can it be done?
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
//
touch = [[event allTouches] anyObject];
touchLocation = [touch locationInView:touch.view];
NSLog(#"began");
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
// get touch event
[image.layer setAnchorPoint:CGPointMake(0.0,0.0)];
if ([touch view] == image) {
image.transform = CGAffineTransformRotate(image.transform, degreesToRadians(1));
//image.center = touchLocation;
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(#"end");
}
In detail, you can custom a rotateView,then:
1: In the delegate method of "touchesBegan", get initialPoint of finger and initialAngle.
2: During "touchesMoved",get the newPoint of finger:
CGPoint newPoint = [[touches anyObject] locationInView:self];
[self pushTouchPoint:thePoint date:[NSDate date]];
double angleDif = [self angleForPoint:newPoint] - [self angleForPoint:initialPoint];
self.angle = initialAngle + angleDif;
[[imageView layer] setTransform:CATransform3DMakeRotation(angle, 0, 0, 1)];
3: At last, in "touchesEnded" you can calculate final AngularVelocity.
If anything being confused, for more detail, you can write back.
To rotate it the other way you just use:
image.transform = CGAffineTransformRotate(image.transform, degreesToRadians(1));
And to rotate form the tip you should use setAnchorPoint (like you used in your code, but i think you should do: [image setAnchorPoint]).
more on the subject: setAnchorPoint for UIImage?
I tried to rotate the image with uitouch but it rotates from middle but i want it to rotate from the tip
I dont know any way in SDK to rotate an element on its extreme end. If you want to have that effect take the clock handle image you have twice in length with half portion transparent. It is not a direct solution, but a workaround.
Also i want to rotate the image in both directions but in my project it moves only clockwise due to this method
As per iOS developer documentation, a positive angle value specifies counterclockwise rotation and a negative value specifies clockwise rotation.

Resources