Animating while resizing a view - ios

I have a view containing many bubbles of various sizes. I am trying to create an animation that will zoom into a bubble, making it the same size every time, and center it in my view. This would cause all the bubbles to zoom until the bubble in question is the right size. The bubbles should all maintain the same ratio throughout the process so that if one bubble moves, all the others move the same amount and in the same direction. If one grows, they all grow in proportion.
I have gotten as far as creating the animation to center the bubble and all the other bubbles move proportionally. The problems arise when I try to make the bubbles grow. Once I add the line bellow which does successfully make the bubbles grow in proportion, the code to move the bubble to center the bubbles no longer moves the bubble to the right point. I cannot figure out how to make the bubble both grow and move to the right position.
To move the bubbles (Needs to be 22p from the top and centered horizontally, and 64p by 64p in size), this successfully moves the bubbles to the right position without growing them to the correct size:
float distanceFromTop = 22 - bubble.frame.origin.y;
float distanceFromLeft = self.frame.size.width / 2 - bubble.center.x;
[UIView animateWithDuration:1 animations:^{
self.currentKarmaView.frame = CGRectMake(distanceFromLeft, distanceFromTop, self.currentKarmaView.frame.size.width, self.currentKarmaView.frame.size.height);
}];
To make the bubbles grow, I added the following line, which should make the bubbles grow and move them to the right position. With growth added, the bubbles still move, just not to the right position.
float distanceFromTop = 22 - bubble.frame.origin.y;
float distanceFromLeft = self.frame.size.width / 2 - bubble.center.x;
[UIView animateWithDuration:1 animations:^{
self.currentKarmaView.frame = CGRectMake(distanceFromLeft, distanceFromTop, self.currentKarmaView.frame.size.width, self.currentKarmaView.frame.size.height);
self.currentKarmaView.transform = CGAffineTransformScale(CGAffineTransformIdentity, scaleFactor, scaleFactor);
}];
I have tried many many variations of this, but cannot get the growth and positioning to work in unison. If anyone can help, I would really appreciate it.

Related

Creating randomly generated horizontal flying labels

I would like to add a feature to my app that involves labels with different keywords flying horizontally across the view. Ideally, I would like this to be done with an array of words, that are randomly selected to fly across the screen at different velocities and in 3-4 different positions (vertically) on the screen.
I have been doing some research, and maybe I am searching for the wrong thing, but I can't find much advice to get an idea of where I should start. I am only able to find how to move sprites to random areas of a view.
I would like to have more to show, but unfortunately the only code I have so far is as follows:
func generateWords() {
let keyWordArray = ["Word 1", "Word 2", "Word 3", "Word 4", "Word 5"]
for _ in 1...5 {
let randomIndexWords = Int(arc4random_uniform(UInt32(keyWordArray.count)))
}
}
How can I randomly generate multiple labels, at different vertical positions, that fly at different speeds across the screen and then disappear? This would need to happen infinitely until the view is closed by the user.
Thanks!
You want to first get the screen height:
CGFloat height;
height = [[UIScreen mainScreen] bounds].size.height;
You can then decide on the vertical position yourself i.e if you have ten labels divide the height by ten then increment up. To animate the labels you need to place the frame off the screen on one side and then place it on the opposite side. You can animate a UIView, in this example the initial label frame is (someXOffTheScreenOnTheRight, someY, widthOfLabel, HeightOfLabel) :
[UIView animateWithDuration:0.8 delay:0 options:0 animations:^{
[self.view addSubview:label];
label.frame=CGRectMake(-10, someY, widthOfLabel, heightOfLabel);
} completion:^(BOOL finished){
[label removeFromSuperview];
}];
This will animate the label from right to left. You can tweak the speed by changing the duration.
Of course if you want to use random Y coords then use the arc4random method.
That should be enough to get you going..

Trouble with anchor point in CGAffineTransformScale

I'm trying to get a UIButton to scale but remain at its original center point, and I'm getting perplexing results with CGAffineTransformScale.
Here's the function in my UIButton subclass:
-(void)shrink {
self.transform = CGAffineTransformMakeScale(0.9,0.9);
}
With this code, the button scales to the top-left corner, but when I add code to try to set the anchor point (either of the following lines), the button gets relocated off screen somewhere:
[self.layer setAnchorPoint:CGPointMake(self.frame.size.width/2, self.frame.size.height/2)];
//same result if I use bounds instead of frame
[self.layer setAnchorPoint:self.center];
Interestingly, this line causes the view to move down and to the right some distance:
[self.layer setAnchorPoint:CGPointMake(0, 0)];
Sorry, I know there are many posts on this topic already but I honestly read and tried at least a dozen and still couldn't get this to work. I'm probably just missing something incredibly simple.
The default anchor point is 0.5, 0.5. That is why setting it to 0, 0 moves the view down and to the right. Also, if you don't want the center to move, you need to re-adjust the center after scaling it.
Just readjust its position after shrinking it.
-(void)shrink {
CGPoint centerPoint = self.center;
self.transform = CGAffineTransformMakeScale(0.9,0.9);
self.center = centerPoint;
}

Why am I unable to detect when my UIView I push off screen using UIKit Dynamics is no longer visible?

I'm using UIKit Dynamics to push a UIView off screen, similar to how Tweetbot performs it in their image overlay.
I use a UIPanGestureRecognizer, and when they end the gesture, if they exceed the velocity threshold it goes offscreen.
[self.animator removeBehavior:self.panAttachmentBehavior];
CGPoint velocity = [panGestureRecognizer velocityInView:self.view];
if (fabs(velocity.y) > 100) {
self.pushBehavior = [[UIPushBehavior alloc] initWithItems:#[self.scrollView] mode:UIPushBehaviorModeInstantaneous];
[self.pushBehavior setTargetOffsetFromCenter:centerOffset forItem:self.scrollView];
self.pushBehavior.active = YES;
self.pushBehavior.action = ^{
CGPoint lowestPoint = CGPointMake(CGRectGetMinX(self.imageView.bounds), CGRectGetMaxY(self.imageView.bounds));
CGPoint convertedPoint = [self.imageView convertPoint:lowestPoint toView:self.view];
if (!CGRectIntersectsRect(self.view.bounds, self.imageView.frame)) {
NSLog(#"outside");
}
};
CGFloat area = CGRectGetWidth(self.scrollView.bounds) * CGRectGetHeight(self.scrollView.bounds);
CGFloat UIKitNewtonScaling = 5000000.0;
CGFloat scaling = area / UIKitNewtonScaling;
CGVector pushDirection = CGVectorMake(velocity.x * scaling, velocity.y * scaling);
self.pushBehavior.pushDirection = pushDirection;
[self.animator addBehavior:self.pushBehavior];
}
I'm having an immense amount of trouble detecting when my view actually completely disappears from the screen.
My view is setup rather simply. It's a UIScrollView with a UIImageView within it. Both are just within a UIViewController. I move the UIScrollView with the pan gesture, but want to detect when the image view is off screen.
In the action block I can monitor the view as it moves, and I've tried two methods:
1. Each time the action block is called, find the lowest point in y for the image view. Convert that to the view controller's reference point, and I was just trying to see when the y value of the converted point was less than 0 (negative) for when I "threw" the view upward. (This means the lowest point in the view has crossed into negative y values for the view controller's reference point, which is above the visible area of the view controller.)
This worked okay, except the x value I gave to lowestPoint really messes everything up. If I choose the minimum X, that is the furthest to the left, it will only tell me when the bottom left corner of the UIView has gone off screen. Often times as the view can be rotating depending on where the user pushes from, the bottom right may go off screen after the left, making it detect it too early. If I choose the middle X, it will only tell me when the middle bottom has gone off, etc. I can't seem to figure out how to tell it "just get me the absolute lowest y value.
2. I tried CGRectIntersectsRect as shown in the code above, and it never says it's outside, even seconds after it went shooting outside of any visible area.
What am I doing wrong? How should I be detecting it no longer being visible?
If you take a look on UIDynamicItem protocol properties, you can see they are center, bounds and transform. So UIDynamicAnimator actually modifies only these three properties. I'm not really sure what happens with the frame during the Dynamics animations, but from my experience I can tell it's value inside the action block is not always reliable. Maybe it's because the frame is actually being calculated by CALayer based on center, transform and bounds, as described in this excellent blog post.
But you for sure can make use of center and bounds in the action block. The following code worked for me in a case similar to yours:
CGPoint parentCenter = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds));
self.pushBehavior.action = ^{
CGFloat dx = self.imageView.center.x - parentCenter.x;
CGFloat dy = self.imageView.center.y - parentCenter.y;
CGFloat distance = sqrtf(dx * dx + dy * dy);
if(distance > MIN(parentCenter.y + CGRectGetHeight(self.imageView.bounds), parentCenter.x + CGRectGetWidth(self.imageView.bounds))) {
NSLog(#"Off screen!");
}
};

iOS - squish an image vertically

I have a round image that I want to "squish" vertically so that it looks more like a horizontal line, then expand it back to the original shape. I thought this would work by setting the layer's anchor point to the center and then animating the frame via UIViewAnimation with the height of the frame = 1.
[self.imageToSquish.layer setAnchorPoint:CGPointMake(0.5, 0.5)];
CGRect newFrame = CGRectMake(self.imageToSquish.frame.origin.x, self.imageToSquish.frame.origin.y, self.imageToSquish.frame.size.width, 1 );
[UIView animateWithDuration:3
animations:^{self.imageToSquish.frame = newFrame;}
completion:nil];
But the image shrinks toward the top instead of around the center.
You’re giving it a frame that has its origin—at the top left—in the same position as it started. You probably want to do something more like this, adding half the image’s height:
CGRect newFrame = CGRectMake(self.imageToSquish.frame.origin.x, self.imageToSquish.frame.origin.y + self.imageToSquish.frame.size.height / 2, self.imageToSquish.frame.size.width, 1);
Alternatively—and more efficiently—you could set the image view’s transform property to, say, CGAffineTransformMakeScale(1, 0.01) instead of messing with its frame. That’ll be centered on the middle of the image, and you can easily undo it by setting the transform to CGAffineTransformIdentity.

How to compose Core Animation CATransform3D matrices to animate simultaneous translation and scaling

I want to simultaneously scale and translate a CALayer from one CGrect (a small one, from a button) to a another (a bigger, centered one, for a view). Basically, the idea is that the user touches a button and from the button, a CALayer reveals and translates and scales up to end up centered on the screen. Then the CALayer (through another button) shrinks back to the position and size of the button.
I'm animating this through CATransform3D matrices. But the CALayer is actually the backing layer for a UIView (because I also need Responder functionality). And while applying my scale or translation transforms separately works fine. The concatenation of both (translation, followed by scaling) offsets the layer's position so that it doesn't align with the button when it shrinks.
My guess is that this is because the CALayer anchor point is in its center by default. The transform applies translation first, moving the 'big' CALayer to align with the button at the upper left corner of their frames. Then, when scaling takes place, since the CALayer anchor point is in the center, all directions scale down towards it. At this point, my layer is the button's size (what I want), but the position is offset (cause all points shrank towards the layer center).
Makes sense?
So I'm trying to figure out whether instead of concatenating translation + scale, I need to:
translate
change anchor point to upper-left.
scale.
Or, if I should be able to come up with some factor or constant to incorporate to the values of the translation matrix, so that it translates to a position offset by what the subsequent scaling will in turn offset, and then the final position would be right.
Any thoughts?
You should post your code. It is generally much easier for us to help you when we can look at your code.
Anyway, this works for me:
- (IBAction)showZoomView:(id)sender {
[UIView animateWithDuration:.5 animations:^{
self.zoomView.layer.transform = CATransform3DIdentity;
}];
}
- (IBAction)hideZoomView:(id)sender {
CGPoint buttonCenter = self.hideButton.center;
CGPoint zoomViewCenter = self.zoomView.center;
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DTranslate(transform, buttonCenter.x - zoomViewCenter.x, buttonCenter.y - zoomViewCenter.y, 0);
transform = CATransform3DScale(transform, .001, .001, 1);
[UIView animateWithDuration:.5 animations:^{
self.zoomView.layer.transform = transform;
}];
}
In my test case, self.hideButton and self.zoomView have the same superview.

Resources