iOS Affine rotation working just once - ios

I have a Button that I would like to use for open/close a view...
It is an arrow, when opening, the arrow will rotate 180°.
And when closing, it should rotate back 180°.
However, it only rotates once while opening, but then on closing won't rotate again. Further more if I open it for a second time, it won't rotate again even if it did the first time.
Here's the code :
- (id)initWithFrame:(CGRect)frame
{
mediaOpened = NO;
media = [UIButton buttonWithType:UIButtonTypeCustom];
[media retain];
[media setBackgroundImage:[ImageController getImageNamed:#"arrow.png"] forState:UIControlStateNormal];
[media addTarget:self action:#selector(toggleMedia) forControlEvents:UIControlEventTouchUpInside];
media.frame = CGRectMake(2, 10, 26, 17);
[self addSubview:media];
}
-(void) rotateMedia {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:0.3];
float angle = M_PI;
media.transform = CGAffineTransformMakeRotation(angle);
[UIView commitAnimations];
}
-(void) mediaClicked {
if (self.delegate != nil)
[self.delegate mediaClicked];
[self rotateMedia];
}
-(void) mediaUnclicked {
if (self.delegate != nil)
[self.delegate mediaUnClicked];
[self rotateMedia];
}
-(void) toggleMedia {
if (!mediaOpened)
[self mediaClicked];
else
[self mediaUnclicked];
mediaOpened = !mediaOpened;
}

Add the line:
media.transform = CGAffineTransformIdentity; // Add this line to reset from current angle
media.transform = CGAffineTransformMakeRotation(angle);
before applying the rotation.
This will make the rotation from the current position otherwise you are always starting from the same position. What you are doing is rotating to that angle and after it has gone to that angle the successive animation are trying to animate to a position it is already at thus, no animation.

Related

Animating custom UIView from off screen make its subviews not responding

I created a small custom UIView with a UILabel and a UIButton, this custom view is a banner to display at the top of the current view controller.
I load the view layout from a nib file and use a method from the custom view to display it with an animation, and the view will hide after a specific amount of time. Like this.
- (void)displayBannerInViewController:(UIViewController *)vc
{
CGFloat originY = 0;
if (vc.navigationController != nil) {
originY += 20 + vc.navigationController.navigationBar.bounds.size.height - self.bounds.size.height;
}
self.frame = CGRectMake(0,
originY,
[UIScreen mainScreen].bounds.size.width,
self.bounds.size.height);
if (vc.navigationController != nil) {
[vc.navigationController.view insertSubview:self atIndex:1];
} else {
[vc.view.window insertSubview:self atIndex:1];
}
[UIView animateWithDuration:0.3 animations:^{
self.frame = CGRectOffset(self.frame, 0, self.bounds.size.height);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 delay:self.duration options:0 animations:^{
self.frame = CGRectOffset(self.frame, 0, -self.bounds.size.height);
} completion:^(BOOL finished) {
if (finished) {
[self removeFromSuperview];
}
}];
}];
}
I set the action for the button inside the banner with this
[self.actionButton addTarget:self
action:#selector(executeActionBlock)
forControlEvents:UIControlEventTouchUpInside];
After animation showing the banner, and before is hidden, no matter how many times I tap on the button, the executeActionBlock method is never called.
I made a test setting the initial frame of the banner to origin (0, 0) and without animation and then the button worked fine. So, I don't know if a problem of the animation or because the original frame of the banner is in a non visible position. BTW, is important for the banner to not be visible because on the app is showing from under the navigation bar.
Thanks
You've written your code so that you run a series of animations, where the view is on-screen during the delay of the last animation. By default user interaction is disabled on views while they are being animated. Try passing UIViewAnimationOptionAllowUserInteraction in options for all your nested animations.
https://developer.apple.com/reference/uikit/uiviewanimationoptions/uiviewanimationoptionallowuserinteraction?language=objc
Well, it seems I should keep looking more because I found this question with the same problem. I tried the solution there and now is working.
The problem is that linking animations is not a good idea for user interaction so I used a NSTimer instead, like this.
- (void)displayBannerInViewController:(UIViewController *)vc
{
CGFloat originY = 0;
if (vc.navigationController != nil) {
originY += 20 + vc.navigationController.navigationBar.bounds.size.height - self.bounds.size.height;
}
self.frame = CGRectMake(0,
originY,
[UIScreen mainScreen].bounds.size.width,
self.bounds.size.height);
if (vc.navigationController != nil) {
[vc.navigationController.view insertSubview:self atIndex:1];
} else {
[vc.view.window insertSubview:self atIndex:1];
}
[UIView animateWithDuration:0.3 animations:^{
self.frame = CGRectOffset(self.frame, 0, self.bounds.size.height);
} completion:^(BOOL finished) {
[NSTimer scheduledTimerWithTimeInterval:self.duration target:self selector:#selector(hideBanner) userInfo:nil repeats:NO];
}];
}
- (void)hideBanner
{
[UIView animateWithDuration:0.3 animations:^{
self.frame = CGRectOffset(self.frame, 0, -self.bounds.size.height);
} completion:^(BOOL finished) {
if (finished) {
[self removeFromSuperview];
}
}];
}
And now works like a charm.

UIPickerView Animate on and off screen behavior

This is a continuation of this question
CGRectMake : How To Calculate X and Y For UIPickerView Animation?
answered by https://stackoverflow.com/users/2315974/danypata
I have a picker view that I want to animate on and off screen on a button press and using the code in the previous question/answer I have got it to animate on screen the first time the button is pressed.
the second tome the button is pressed it just disappears and then the third time it is pressed it animates to the wrong place...please help
the code
if (self.pickerSort.hidden == NO) {
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
self.pickerSort.frame = CGRectMake(0,self.view.frame.size.height
+ self.pickerSort.frame.size.height,-self.pickerSort.frame.size.width,-self.pickerSort.frame.size.height);
}
completion:^(BOOL finished) {
}];
self.pickerSort.hidden = YES;
// [self performSelector:#selector(hidePicker) withObject:nil afterDelay:1];
} else if (self.pickerSort.hidden == YES) {
self.pickerSort.hidden = NO;
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
self.pickerSort.frame = CGRectMake(0 ,
self.view.frame.size.height
- self.pickerSort.frame.size.height,
self.pickerSort.frame.size.width,
self.pickerSort.frame.size.height);
}
completion:^(BOOL finished) {
}];
[self.view bringSubviewToFront:self.pickerSort];
Behavior in images - button press animates onto screen beautifully
Second Press it disappears with no animation
Third Press it animates to here
Any help would be great...functionality I am looking for is how to put the picker view back on an animation so the 3rd press is the same as the first
Please try to use the below code.
self.pickerSort.hidden = NO;
NSTimeInterval duration = 0.5;
[UIView animateWithDuration:duration
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
CGRect pickerFrame = self.pickerSort.frame;
// currently picker is off the screen, show it.
if (pickerFrame.origin.y == self.view.frame.size.height) {
// set frame to show picker
pickerFrame.origin.y = self.view.frame.size.height - self.pickerSort.frame.size.height;
} else {
// set frame to hide picker
pickerFrame.origin.y = self.view.frame.size.height;
}
self.pickerSort.frame = pickerFrame;
}
completion:^(BOOL finished) {
self.pickerSort.hidden = YES;
}];
After playing around with this and learning the ins and outs of frames and CGRects, I achieved this by using the following code
if (pickerSort.hidden == YES) {
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
pickerSort.hidden = NO;
visualEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
[self.tableStations addSubview:visualEffectView];
pickerSort.frame = CGRectMake(0,0,originalPickerFrame.size.width,originalPickerFrame.size.height);
visualEffectView.frame = CGRectMake(0,0,originalPickerFrame.size.width,originalPickerFrame.size.height - 64);
}
completion:^(BOOL finished) {
self.navigationController.navigationItem.leftBarButtonItem.enabled = NO;
}];
}
else
{
visualEffectView.hidden = YES;
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
pickerSort.frame = CGRectMake(0,-originalPickerFrame.size.height,0,0);
}
completion:^(BOOL finished) {
self.navigationController.navigationItem.leftBarButtonItem.enabled = YES;
pickerSort.hidden = YES;
}];
}
I set the original frame size in viewDidLoad like so
pickerSort = [[UIPickerView alloc]initWithFrame:CGRectMake(0.0, 0.0, self.view.frame.size.width, self.view.frame.size.height)];
originalPickerFrame = pickerSort.frame;
pickerSort.frame = CGRectMake(0,-originalPickerFrame.size.height,0, 0);
Hope this can help somebody in the future

CGAffineTransformMakeRotation again won't work

I create a UIView and set it's layer's contents a image. And I added a tap gesture to this view. When I tap the view, I want it to rotate 180 degree, and when I click close button, I want it to rotate to it's original degree. But it won't work when I try to rotate it back.
- (void)viewDidLoad
{
[super viewDidLoad];
UIImage *image = [UIImage imageNamed:#"menuArray"];
self.menuView.layer.contents = (__bridge id)image.CGImage;
self.menuView.userInteractionEnabled = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tap)];
[self.menuView addGestureRecognizer:tap];
}
//rotate it 180 degree
- (void)tap
{
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionCurveEaseIn animations:^{
self.menuView.layer.affineTransform = CGAffineTransformMakeRotation(M_PI);
} completion:^(BOOL finished) {
}];
}
//rotate it back. this won't work. I have change the value from M_PI to M_PI_2, M_PI_4. it's not working.
- (IBAction)close:(id)sender
{
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionCurveEaseIn animations:^{
self.menuView.layer.affineTransform = CGAffineTransformMakeRotation(-M_PI);
} completion:^(BOOL finished) {
}];
}
To undo an affineTransorm you should set it to the constant affineTransformIdentity , which is tranform of nil so to speak, a matrix of all zeros..
So in your second method where you want to rotate back set self.menuView.layer.affineTransform = CGAffineTransformIdentity

iOS 7 photos app like custom UIViewController Transition

I am a bit new to iOS development. I am working on an app that has about 5,000 visual data points, organized in categories. I want to present them in a UICollectionView with very tiny UICollectionViewCells. When a user taps on something in a category, the category zooms in with the selected cell in focus. Pretty much like how the "Photos" tab of the iOS 7 Photos app works: Years > Collections > Moments.
How do I go about implementing a custom transition like that? Are there any open-source libraries already written for accomplishing this?
If you can't find any library, try to play with this code I wrote for custom animations. You can specify a start point, an end point, start scale and end scale of a view that you want to zoom and scale in or out. See below an example how I use it. The point is to use a fake view to push it with animation, then push and pop your real view without animation. viewobj is set to fade in from alpha 0 to alpha 1, zoomableView will scale from the point/scale you give as parameter to the final position you set it in your storyboard/xib. Hope it will help.
# you can create a category vor UIView and add these methods
- (void) addSubView:(UIView*)viewObj animateFromPoint:(CGPoint)point zoomableView:(UIView*)view minScale:(CGSize)scale completion:(void (^)(void))completionBlock{
CGPoint center = view.center;
[view setTransform:CGAffineTransformMakeScale(scale.width, scale.height)];
viewObj.alpha = 0;
view.center = point;
[self addSubview:viewObj];
[self addSubview:view];
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
view.center = center;
[view setTransform:CGAffineTransformMakeScale(1.0, 1.0)];
viewObj.alpha = 1;
}
completion:^(BOOL fin){
if(completionBlock)
completionBlock();
}];
}
- (void) removeFromSuperviewAnimateToPoint:(CGPoint)point zoomableView:(UIView*)view minScale:(CGSize)scale completion:(void (^)(void))completionBlock{
CGRect startFrame = view.frame;
self.alpha = 1;
[self.superview addSubview:view];
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
view.center = point;
[view setTransform:CGAffineTransformMakeScale(scale.width, scale.height)];
self.alpha = 0;
}
completion:^(BOOL fin){
[self removeFromSuperview];
[view removeFromSuperview];
[view setTransform:CGAffineTransformMakeScale(1, 1)];
view.frame = startFrame;
[self addSubview:view];
if(completionBlock)
completionBlock();
}];
}
And to use them:
# your did select item at index path:
self.itemDetails = [self.storyboard instantiateViewControllerWithIdentifier:#"ItemDetailsVC"];
ItemDetailsVC* itd = [self.storyboard instantiateViewControllerWithIdentifier:#"ItemDetailsVC"];
__weak UIViewController* wself = self;
[self.view addSubView:self.itemDetails.view animateFromPoint:self.zoomedFrom zoomableView:self.itemDetails.viewZoomable minScale:CGSizeMake(0.371134, 0.371134) completion:^{
[wself.navigationController pushViewController:itd animated:NO];
}];
self.addSubviewIsUp = YES;
# back button of the view you added:
[self.navigationController popViewControllerAnimated:NO];
# viewdidapear of your main screen
if(self.addSubviewIsUp){
[self.addVc.view removeFromSuperviewAnimateToPoint:CGPointMake(160, 75) zoomableView:self.addVc.zoomableView minScale:CGSizeMake(0.01, 0.01) completion:^{
}];
}
self.addSubviewIsUp = NO;

How to create an animation by changing images after a certain amount of time

Hello guys I am new to xcode and I am trying to animate balloons on touch by changing its images: this is my code:
now the problem i am facing is its not animating images means the animation timer is not working: please guide me what should i do to animate the images with time: if I am not doing it properly then please guide me that how can i do it by NSTimer?
-(void)baloonbursting:(UIButton *)button withEvent:(UIEvent *)event{
if ([[UIImage imageNamed:#"redbaloons.png"] isEqual:button.currentImage]) {
NSLog(#"em redbaloons.png");
UIImage *bubbleImage3 = [UIImage imageNamed:#"redburst.png"];
[button setImage:bubbleImage3 forState:UIControlStateNormal];
}
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView animateWithDuration:1.0f animations:^(){
// define animation
if ([[UIImage imageNamed:#"redburst.png"] isEqual:button.currentImage]) {
NSLog(#"em redbaloons.png");
UIImage *bubbleImage3 = [UIImage imageNamed:#"redburst2.png"];
[button setImage:bubbleImage3 forState:UIControlStateNormal];
}
}
completion:^(BOOL finished){
// after the animation is completed call showAnimation again
[UIView animateWithDuration:1.0 delay:0.0 options:UIViewAnimationOptionCurveEaseOut|UIViewAnimationOptionAllowUserInteraction animations:^{
} completion:^(BOOL finished){
if (finished) {
[button removeFromSuperview];
}}];
}];
}
I want you to give you a solution to show you the right direction to think. That is, why I developed a small test project in Xcode and I controlled that this code really works.
First: forget NSTimers for this animation!
The idea is, that you "play" around with subviews, because you can change their alpha properties from 0.0 (invisible) to 1.0 (fully opaque), which are supported by the SDK's view animations.
Please change the image names according to your own file names (I've used my own here).
The following method checks, if the button's image is that one that shall invoke an animation - exactly what you did before. If this condition is met, it visually animates the change of the button's image to another image:
- (IBAction)balloonBursting:(UIButton *)sender
{
BOOL doAnimate = NO;
UIImageView *ivOldBubbleImage;
UIImageView *ivNewBubbleImage;
if ([[UIImage imageNamed:#"BalloonYellow.png"] isEqual:sender.currentImage]) {
NSLog(#"will animate");
doAnimate = YES;
UIImage *newImage = [UIImage imageNamed:#"BalloonPurple.png"];
ivNewBubbleImage = [[UIImageView alloc] initWithImage:newImage];
ivNewBubbleImage.alpha = 0.0;
ivOldBubbleImage = sender.imageView;
[sender addSubview:ivNewBubbleImage];
}
if (doAnimate) {
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView animateWithDuration:1.0f animations:^(){
// define animation
ivOldBubbleImage.alpha = 0.0;
ivNewBubbleImage.alpha = 1.0;
}
completion:^(BOOL finished){
[sender setImage:ivNewBubbleImage.image forState:UIControlStateNormal];
[ivNewBubbleImage removeFromSuperview];
}];
}
}

Resources