Problem with collision images, collision before touch - iphone

Hi everyone I'm french so scuse me for my english.My problem is the following: I have an image in the center of the screen called viewToRotate, then I have an image called flakeView that is created outside of the screen and then it is moving to the center, and every second a Timer does this(create a flakeView outside the screen and then move it to the center of the screen).
What I wanted to do was : if flakeView and viewToRotate collide reduce the alpha of viewToRotate to 0.5. But when flakeView appears on the screen the action of reducing the alpha is called without the collision of viewToRotate and flakeView, so they collide before they touches. I don't know why. How can I solve this please . Here is the code :
UIImageView* flakeView = [[[UIImageView alloc] initWithImage:flakeImage] autorelease];
// use the random() function to randomize up our flake attributes
int startY = round(random() % 320);
// set the flake start position
flakeView.center = CGPointMake(490, startY);
flakeView.alpha = 1;
// put the flake in our main view
[self.view addSubview:flakeView];
[UIView animateWithDuration:7
animations:^{
// set the postion where flake will move to
flakeView.center = viewToRotate.center;
}];
}
-(void)checkCollision{
if(CGRectIntersectsRect(flakeView.frame, viewToRotate.frame) == 1)
{
viewToRotate.alpha=0.5;
}
}

I would not suggest using a timer on viewDidLoad. I believe that you should set the timer at some other function. Even if you do not use viewDidLoad for anything else.
The other thing is the UIView animations. Even if you have a timer at every 60fps the attributes of the animated objects are not changed by the animation. So before you create the animation you set those properties once. And when you create the animation you set the same properties for the second time. And that's it. From now on while the animation is running if you do checks on the properties you will always get the second values.
To have a working code one option is to create the UIView animations (and perform the property checks) for every step at a certain time interval. Or use OpenGL.

Before getting too deep into this issue, please also make sure that your viewToRotate.frame is actually sized the way you expect it (see my comment).
Once you are certain about those attributes, you might want to check the collision on the current presentationLayer of your animated objects and not on their original state.
UIView.layer.presentationLayer
Returns a copy of
the layer containing all properties as
they were at the start of the current
transaction, with any active
animations applied.
So you might adapt your collision detection like this:
-(void)checkCollision
{
if(CGRectIntersectsRect(flakeView.layer.presentationLayer.frame, viewToRotate.layer.presentationLayer.frame) == 1)
{
viewToRotate.alpha=0.5;
}
}
Also consider using the CoreAnimation Layer method hitTest: instead of your handmade collision detection.
hitTest: Returns the farthest
descendant of the receiver in the
layer hierarchy (including itself)
that contains a specified point.

These are the following things you would want to check.
Dont init with image, use init with frame and then do
flakeView.image = ----; Because your image could be wide enough
initially , and hence colliding from the time you initialize.
Try alternatives to .center, like .frame.size.x , because I have had
problems with .center as well
You could probably NSLog the bounds of both ur imageviews before and
after reducing alpha

Related

How to animate part of a view transformation

I recently stumbled upon a problem where I have a view that can be rotated by the user. During the rotation it should, however, be 10% larger (scaled up).
I want that scaling to be animated but the rotation to be be visible immediately since I set it without animation in the gesture recognizer's callback.
Question: Is it possible to update an CGAffineTransform's rotation without intercepting the scale being animated, or is there no way around creating a wrapping view that gets scaled instead?
Edit:
I think a wrapper view for scaling would be the least error prone way.
If you desperately want to avoid that, you could try to create the scaling animation manually with an NSTimer. Maybe if you query the current transform value first and then modify it instead of replacing it by an independently created one (for the rotation and the scale), it could work.
I think an implicit UIView animation calculates all subsequent values in the beginning, so that would mess up your rotation.
Another way to go would be to lock the rotation while the scaling occurs. The downside is, that scaling and rotation wouldn't be simultaneously. Anyway you could create an iVar or property let's say rotationLocked and do sth. like this:
- (void)handlePan:(UIPanGestureRecognizer *)gr
{
if (gr.state == UIGestureRecognizerStateBegan)
{
self.rotationLocked = YES;
[UIView animateWithDuration:.2 animations:^{
[self scaleView];
}completion:^{ self.rotationLocked = NO; };
}
if (gr.state == UIGestureRecognizerStateChanged)
{
if (!self.rotationLocked){
// do the rotation
}
if (gr.state == UIGestureRecognizerStateEnded)
{
// do something else
}

On iOS, why changing the frame of a CALayer didn't cause an animation?

I have some iOS sample code that if I tap on the main view and the tap handler changes the CALayer object's frame property, the layer object will animate to its position and size.
But if I put that animation inside ViewController's viewDidLoad:
UIImage *shipImage = [UIImage imageNamed:#"SpaceShip1.png"];
ship1 = [[CALayer alloc] init];
ship1.contents = (id) [shipImage CGImage];
ship1.frame = CGRectMake(50, 50, 100, 100);
[self.view.layer addSublayer:ship1];
ship1.frame = CGRectMake(0, 0, 300, 200);
then the animation won't happen. It will just use the (0, 0, 300, 200) with no animation. Why is that and how to make it work?
Several things.
First of all, the frame property is not animatable. Instead, you should be animating bounds (to change the size) and position (to move the layer).
To quote the docs:
Note: The frame property is not directly animatable. Instead you
should animate the appropriate combination of the bounds, anchorPoint
and position properties to achieve the desired result.
Second, you have to return to the event loop after adding a layer before changing an animatable property.
When you return and the system visits the event loop, the next time you change an animatable property, it creates an animation transaction to animate that change.
Since you created a layer, set it's frame, and then changed it, all before returning, your code simply adds the layer at it's final frame.
What you should do instead is to set up the layer, install it into it's parent layer, and return. You can use performSelector:withObject:afterDelay: to invoke the animation after the layer has been added:
- (void)viewDidLoad
{
UIImage *shipImage = [UIImage imageNamed:#"SpaceShip1.png"];
ship1 = [[CALayer alloc] init];
ship1.contents = (id) [shipImage CGImage];
ship1.frame = CGRectMake(50, 50, 100, 100);
[self.view.layer addSublayer:ship1];
[self performSelector: #selector(changeLayer)
withObject: nil
afterDelay: 0.0];
}
- (void) changeLayer
{
//Animate changes to bounds and position, not frame.
ship1.bounds = CGRectMake(0, 0, 300, 200);
//Note that position is based on the center of the layer by default, so you'll
//need to adjust your desired position.
ship1.position = CGPointMake(0,0);
}
You have specified no time delay or animation time. So the object will be moved before it is displayed, and you won't see it in the earlier position. You also need to return to the run loop for the display to show any animation.
There are 3 ways to trigger animations on iOS:
1. UIView Animation
You use a UIView.animateWithDuration method or other variant to directly specify that you want an animation. You can specify parameters such as duration, delay, a completion block and others.
2. Core Animation - Implicit Animation (DESIRED APPROACH)
As the name implies, this animation is triggered without an explicit command. In order to work though, you need to set the CALayer delegate, like this:
ship1.delegate = self;
All the parameters have default values until you say otherwise. For modifying these parameters, you need to call CATransaction:
[CATransition setAnimationDuration:0.2];
IMPORTANT: These CATransaction calls must be placed BEFORE the modifications of the layer's properties, because all animations happen asynchronous (in different runtime threads) and it will be too late for you if you put these calls after.
3. Core Animation - Explicit Animation
You create an CAAnimation (there are different CAAnimation subclass to choose from) and attach it to the layer:
CABasicAnimation *a = [CABasicAnimation alloc] init....
[ship1 addAnimation:a];
I hope this clarifies some doubts about the subject.

Removing a CAShapeLayer mask on a UIButton layer so that animating UIButton size change is unmasked

I have a UIButton of the custom button type, where I just set a background and border color on the underlying CALayer of the button. I then applied a mask to this underlying layer composed of a CAShapeLayer (to get two rounded corners) as described in this post.
So far so good. I animate the UIButton to grow in size. So far so good. See below (everything in the image is the _fusedBar, save the x-axis line snippet you see below it and the dollar amount text).
When I go to animate the UIButton to shrink using the same animation routine but different parameter values (affine translation with simultaneous frame height change), none of the UIButton (save for a sliver) is visible as the shrinking is taking place.
I've tried to have the CAShapeLayer that is the mask remove itself from its super layer, I've tried to set the mask property on the UIButton's layer to nil, etc., but none of these remove the mask such that I can see the UIButton animate (shrinking with affine translation and frame height change).
Here's the relevant animation code:
// Stackoverflow readers: Assume 'duration', 'scaledHeight' and 'delay'
// already defined.
void (^fusedBarChange)(void) = ^{
_fusedBar.transform = CGAffineTransformMakeTranslation(0, -scaledHeight);
[_fusedBar nd_setNewSizeHeight:scaledHeight];
};
[UIView animateWithDuration:duration
delay:delay
options:UIViewAnimationCurveEaseOut
animations:^(void) {
fusedBarChange();
}
completion:^(BOOL finished) {
// Other stuff not relevant to _fusedBar animation
}];
Before this animation gets called, I have the mask setup per the post cited above. That code is below. Note that '_fusedBar' is the UIButton at the heart of this discussion.
- (void)configureRoundedTop {
// YES: Start by creating the path (with only the top-left corner rounded)
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:_fusedBar.bounds
byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight
cornerRadii:CGSizeMake(4.0, 4.0)];
// Create the shape layer and set its path
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = _fusedBar.bounds;
maskLayer.path = maskPath.CGPath;
// Set the newly created shape layer as the mask for the image view's layer
_fusedBar.layer.mask = maskLayer;
}
When growing the _fusedBar from a sliver to a few hundred points, the growth is animated, and you can see it.
Prior to using this CAShapeLayer mask, when shrinking the _fusedBar back to a sliver, I could see the animation (same code above, just a different value for 'scaledHeight'). Now, I cannot see the shrinking animation.
So, I know I need to somehow shake-off the effects of the masking layer before animating the shrinking operation, but the obvious things (to me) haven't worked. Here's an example of various things I've tried:
// None of these do the trick. I call them just before
// the shrinking animation is invoked:
[_fusedBar setNeedsLayout];
[_fusedBar setNeedsDisplay];
[_fusedBar.layer.mask removeFromSuperlayer];
_fusedBar.layer.mask = nil;
Clearly, I'm missing some nugget of CALayers, masking and perhaps even presentationLayer semantics.
Any guidance much appreciated!
Coming back to it after a couple of days, I saw my mistake (not visible from the extracts posted above).
Essentially, after the animation to grow or shrink the "fusedBar", I was reapplying a mask based on the view's frame. Since this would effectively execute in concert with the animation block, it was going to have a mask that the view was shrinking into.
I needed to put that re-apply mask code in my animation completion block or suppress it if the fusedBar was shrinking.
Thanks to all those who took a look to help out!

Push a UIImageView with another UIImageView, possible?

Is it possible to pan a UIImageView and when it intersects with the frame of another UIImageView, have that other UIImageView be pushed around by the other UIImageView without just passing over it? I hope that makes sense!
If it is possible, could you give me some ideas on how I'd go about implementing this?
I imagine it would go something like...
If frame intersects frame from left or right on the x/y axis, have the other frame move in that same direction with same distance as the pushing frame. While that logic somewhat makes sense to me, I'm not sure how I'd implement that in code.
I'd really appreciate any advice you can offer.
So, you're only concerned with left / right panning...
Let's assume you have 2 UIImageViews, a & b, as subviews of some other UIView and defined as class members of some class.
You can get help detecting panning gestures (dragging touches) on each of these views by creating instances of UIPanGestureRecognizer and adding them to each UIImageView using method addGestureRecognizer:
When creating each UIPanGestureRecognizer, you need to designate a selector to receive the gesture events. Let say it's called didMove:
Now, some sample code:
- (void) didMove:(UIPanGestureRecognizer*)recognizer {
UIView* view = [recognizer view];
// Remember our UIImageViews are class members called, a & b
//
UIView* otherView = view == a ? b : a;
if (recognizer.state == UIGestureRecognizerStateBegin
|| recognizer.state == UIGestureRecognizerStateChanged) {
// Moving left will have a negative x, moving right positive
//
CGPoint translation = [recognizer translationInView:view.superview];
view.center = CGPointMake(view.center.x + translation.x, view.center.y);
// Reset so we always track the translation from the current view position
//
[recognizer setTranslation:CGPointZero inView:view.superview];
if (CGRectIntersectsRect(view.frame, otherView.frame)) {
// Translate by the same number of points to keep views in sync
//
otherView.center = CGPointMake(otherView.center.x + translation.x, otherView.center.y);
}
}
}
It may not be exactly what you need, but it should give you a good idea how it could be done.
Sounds like you'd better use something like Cocos2D with it's sprites, intersections and some basic physics...
You can do it like this,
First, Create an UIImageView and assign an UIImage to it.
Second, When the user swipes or makes any gesture just change the coordinates of the card by setting its frame again.
Since u want the card to slide away, you have to change the x coordinate to a negative value such that it slides off from the view.
The above said code of reframing the imageview should be given within a UIView setAnimation group of methods...with a required delay.
To achieve the push effect have a method called within the the same animation block of codes which creates a new imageview (having same name as that of first imageview) and keep it out of the view. That is beyond the width of the screen. (dont forget to release the card which was pushes away previously)
Then subsequently again make a new frame for this imageview such that it appears in the centre of the screen....When above three steps of code are put within the animation block of code for UIView or UIImageView with a delay time , u will achieve the required effect.
Hope this helps....
happy coding....

How can I redraw zoomed text on a transformed UIView?

I'm currently implementing an expanding timeline. When I pinch zoom into the timeline, I need my drawn text to stay at the same relative locations on the UIView they're drawn on inside the UIScrollView that handles the zooming. (Essentially like pins on GoogleMaps) However, I don't want to zoom vertically, so I apply a transform by overriding:
- (void)setTransform:(CGAffineTransform)newValue;
{
newValue.d = 1.0;
[super setTransform:newValue];
}
This works great in keeping the timeline fixed vertically and allowing it to expand horizontally. However, I am drawing my text labels as such in a method called during setNeedsDisplay:
for (int i = 1; i < 11; i++)
{
CGRect newFrame = CGRectMake(i * (512.0/11.0) - (512.0/11.0/2.0), self.frame.size.height - 16.0, 512.0/11.0, 32.0);
NSString *label = [NSString stringWithFormat:#"%d", i+1];
[label drawInRect:newFrame withFont:[UIFont systemFontOfSize:14.0]];
}
This draws my text at the correct position in the scrollview, and nearly works perfectly. However, because of my transform to keep the zooming view static vertically, the text expands horizontally and not vertically, and so stretches out horribly. I can't seem to get the text to redraw at the correct aspect ratio. Using UILabels works, however I am going to be rendering and manipulating upwards of 1,000 such labels, so I'd preferably like to draw static images in drawRect or something similar.
I've tried changing the CGRect I'm drawing the text in (was worth a shot), and applying CGAffineTransformIdentity isn't possible because I'm already transforming the view to keep it from zooming vertically. I've also tried drawing the text in various Views to no avail, and again, I'd rather not populate an obscene amount of objects if I can avoid it.
Thanks for any help!
Instead of applying a transform inside the 'setTransform:' method, I intercepts the scale at which it is being transformed, and resize the frame of the view being transformed. The code (roughly) follows:
- (void)setTransform:(CGAffineTransform)newValue;
{
// The 'a' value of the transform is the transform's new scale of the view, which is reset after the zooming action completes
// newZoomScale should therefore be kept while zooming, and then zoomScale should be updated upon completion
_newZoomScale = _zoomScale * newValue.a;
if (_newZoomScale < 1.0)
_newZoomScale = 1.0;
// Resize self
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, _originalFrame.size.width * _newZoomScale, self.frame.size.height);
}
As mentioned in the comments, the transform value of the CGAffineTransform is reset each time a new zooming action occurs (however, it is kept for the duration of the zooming action). So, I keep two instance variables in my UIView subclass (not sure if it's incredibly elegant, but it's not insanely terrible): the original frame the the view was instantiated with, and the "current" zoom scale of the view (prior to the current zooming action).
The _originalFrame is what is referenced in order to determine the proper sizing of the now zoomed frame, and the _zoomScale(the scale of the view prior to the current zooming action) is set to the value of _newZoomScale when the didFinishZooming callback is called in the UIScrollView containing this UIView.
All of this allows for the coordinate system of the UIView to not be transformed during zooming, so text, etc. may be drawn on the view without any distortion. Looking back at this solution, I'd wager a guess that you could also account for the transform and draw based on a stretched coordinate system. Not sure which is more effective. I had a slight concern by not calling super in setTransform:, but I haven't noticed any ill effects after about 6 months of use and development.