UIView setAnimationTransition only working sometimes but not always? - ios

I'm writing a little card game where 4 cards are dealt to the screen and the user can tap each card to reveal (and again to hide) it.
Each card-front and card-back are stored in image views. A UIButton catches the user tap and should flip the card over.
I've added front and back of the card as subviews to a container view and I use the method UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight for the animation.
Please see the code below, I've removed the handling of the 4 different cards in the methods below for the sake of simplification & readability. Also I've replaced some array/filename-juggling for the cards with static image-names for front and back here.
The strange thing with this code now is the sometimes it works as expected 10 times in a row (i.e. the flipping animation is shown) but sometimes no animation is shown at all (i.e. the other card side is shown but without the flipping.). Then it's the other way round: Sometimes the card is shown without any animation for 7 or 8 times then suddenly the flipping-animation is shown.
It's driving me nuts as I can't see the reason for this strange behaviour.
Do you have any idea? I'm building for iOS 8 to iOS 10.
From the .h file:
#interface GameViewController : UIViewController
{
UIImageView *cardback1;
UIImageView *cardfront1;
UIView *containerView;
BOOL c1Flipped;
// much more...
}
And from the .m file:
-(void)flipCardButtonClicked:(id)sender
{
containerView = [[UIView alloc] initWithFrame: CGRectMake(25,420,220,300)];
[self.view addSubview:containerView];
c1Flipped = !c1Flipped;
cardback1 = [[UIImageView alloc] initWithFrame: CGRectMake(0,0,220,300)];
cardfront1 = [[UIImageView alloc] initWithFrame: CGRectMake(0,0,220,300)];
if (c1Flipped)
{
cardback1.image = [UIImage imageNamed:#"backside.png"];
cardfront1.image = [UIImage imageNamed:#"frontside.png"];
}
else
{
cardback1.image = [UIImage imageNamed:#"frontside.png"];
cardfront1.image = [UIImage imageNamed:#"backside.png"];
}
[containerView addSubview:cardfront1];
[containerView addSubview:cardback1];
[cardfront1 release];
[cardback1 release];
[self performSelector:#selector(flipSingleCard) withObject:nil afterDelay:0.0];
}
-(void)flipSingleCard
{
[containerView.layer removeAllAnimations];
[UIView beginAnimations:#"cardFlipping" context:self];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(flipDidStop:finished:context:)];
[UIView setAnimationDuration:0.5];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:containerView cache:YES];
[containerView exchangeSubviewAtIndex:0 withSubviewAtIndex:1];
[UIView commitAnimations];
}
-(void)flipDidStop:(NSString*)animationID finished:(BOOL)finished context:(void *)context
{
[containerView removeFromSuperview];
[containerView release];
}

My guess is that [self performSelector:#selector(flipSingleCard) withObject:nil afterDelay:0.0]; is the culprit. Seems like this could be a timing issue. Have you tried adding an actual delay to this? Say...0.1? I believe that doing this could fix this, OR just simply calling the method directly instead of using performSelector.

Looks like it indeed was a timing issue as suspected by BHendricks.
Adding a 0.1 delay solved the problem.
Thanks a lot.

Related

Tap Gesture Recognizer too slow

I am having difficulty getting a UITapGestureRecognizer to detect every tap that is occurring. For context, I am making a drumming app. I have gotten the tap gesture recognizer to work correctly, but the user should be able to tap very quickly to drum, and the recognizer is only picking up on let's say a tap every so often (if you try to tap as quickly as you can).
I am guessing this has to do with iOS categorizing very quick taps in a row as one gesture, but is there any way to get every time a finger goes down as a tap event?
I have tried using UILongPressGestureRecognizer with a small press duration.
I have tried setting the requiredNumberofTouches and requiredNumberofTaps to 1.
The code does not seem necessary to post in order to answer the question, but I will post it if you request it.
Any help would be most appreciated!
EDIT: I am adding my code, since it may help:
-(void)tap:(UITapGestureRecognizer *)tapGestureRecognizer {
CGPoint location = [tapGestureRecognizer locationInView:[self view]];
switch ([self validateTouchLocation:location]) {
case 0:
return;
case 1:
[[ASSoundManager sharedManager] playSoundNamed:#"SD0025" ofType:#"mp3"];
break;
case 2:
default:
[[ASSoundManager sharedManager] playSoundNamed:#"SD0010" ofType:#"mp3"];
break;
}
float tapWidth = 30;
ASDrumTapView *drumTapView = [[ASDrumTapView alloc] initWithFrame:CGRectMake(location.x - (tapWidth / 2), location.y - (tapWidth / 2), tapWidth, tapWidth)];
[drumTapView setBackgroundColor:[UIColor clearColor]];
[drumTapView setNeedsDisplay];
[[self view] addSubview:drumTapView];
float animDuration = 0.75;
CGRect frame = [drumTapView frame];
[UIView animateKeyframesWithDuration:animDuration
delay:0.0
options:0
animations:^{
[UIView addKeyframeWithRelativeStartTime:0
relativeDuration:animDuration
animations:^{
[drumTapView setFrame:CGRectInset(frame, -frame.size.width, -frame.size.height)];
}];
[UIView addKeyframeWithRelativeStartTime:0
relativeDuration:3*animDuration/5
animations:^{
[[drumTapView layer] setOpacity:0.0];
}];
}
completion:^(BOOL finished) {
[drumTapView removeFromSuperview];
}];
}
There could be many reason:
One possible reason could be fact that your audio engine cannot play next sound before previous sound has finished.
Check multipleTouchEnabled property on UIView
If your view is embedded in UIScrollView or any derived class, check delaysContentTouches property
While I appreciate everyone's input, I figured it out on my own.
In my code, I am running a keyframe animation with options set to 0. What this means is that during the animation, user interaction with the view is not enabled.
By setting options to UIKeyframeAnimationOptionAllowUserInteraction, I am able to pick up on taps even though one of the subviews is still in animation.
Hope this helps someone else!

Showing UIImageView on startup after a delay with close button

I want to show a UIImageView after 3 seconds from the view did load. This image view is kind of a static Ad, a static image loaded that will show up when the application starts as mentioned. This ismageView should have a close button on it so the user can close it just like the typical ad behavior. It's my first time dealing with this kind of situations so please help me out i'm totally lost.
Till now i got to animate a view containing an image view with fade in and out animations, which is perfect..but now haw can i add a close button to it, to make her dismiss only when that button is pressed? this is my code
UIImageView *wnn = [[UIImageView alloc]init];
wnn.frame = CGRectMake(100, 100, 300, 300);
[wnn setImage:[UIImage imageNamed:#"menu-icon.png"]];
UIView *jn = [[UIView alloc]init];
[jn addSubview:wnn];
[self.navigationController.view addSubview: jn];
[jn setAlpha:0.f];
[UIView animateWithDuration:2.f delay:0.f options:UIViewAnimationOptionCurveEaseIn animations:^{
[jn setAlpha:1.f];
} completion:^(BOOL finished) {
[UIView animateWithDuration:2.f delay:0.f options:UIViewAnimationOptionCurveEaseInOut animations:^{
[jn setAlpha:0.f];
} completion:nil];
}];
You can follow below steps:
Open Storyboard file with your viewcontroller.
Place UIView from library to your main view of viewcontroller.
Place your image and close button on new UIview placed.
Now set IBOutlet for this uiview.
Make hide/show this view instead of image and close button in your method.

Tweetbot like shrink view transition

Does anyone know how to achieve the present view controller's view to shrink and put another one over it with a transparency? It is achieved in Tweetbot 3 when you tap on your avatar in the top left on the navigation bar. Should I take a snapshot for example?
In order to achieve this effect you will have to rebuild your view stack from scratch.
So as there is no possibility to change the viewController.view's frame, you'll have to add a kind of container subview a little like this:
#implementation ViewController
#synthesize container;
- (void)viewDidLoad {
[super viewDidLoad];
container = [[UIView alloc] initWithFrame:self.view.frame];
[self.view addSubview:container];
// add all views later to this insted of self.view
// continue viewDidLoad setup
}
Now if you have that, you can animate the shrinking behavior like so:
[UIView animateWithDuration:.5 animations:^{
container.frame = CGRectMake(10, 17, self.view.frame.size.width-20, self.view.frame.size.height-34);
}];
Okay, I assume you are developing for iOS 7, so we'll make use of some new APIs here (for earlier versions there are alternative frameworks). Now since WWDC UIView's got a resizableSnapshotViewFromRect:(CGRect) afterScreenUpdates:(BOOL) withCapInsets:(UIEdgeInsets) method returning a single UIView object.
[UIView animateWithDuration:.5 animations:^{
container.frame = CGRectMake(10, 17, self.view.frame.size.width-20, self.view.frame.size.height-34);
} completion:^(BOOL finished) {
UIView *viewToBlur = [self.view resizableSnapshotViewFromRect:container.frame afterScreenUpdates:YES withCapInsets:UIEdgeInsetsZero];
}];
If you do not want to rewrite your view management, you can also first take a snapshot of your main view this way, set it to the container and then animate only the image. But remember, you can not interact with the captured view then.
When you've got that, you can download the two category files from this repo (They're from WWDC, so directly from Apple!). Basically, what they do is, they add some cool new methods to the UIView class, of which we'll use applyDarkEffect. I haven't tested this, maybe another method fits your needs better here.
Anyways if we implement that into the block and maybe also add a UIImageView to display the blurred overlay, it should look something like this:
[UIView animateWithDuration:.5 animations:^{
container.frame = CGRectMake(10, 17, self.view.frame.size.width-20, self.view.frame.size.height-34);
} completion:^(BOOL finished) {
UIView *viewToBlur = [self.view resizableSnapshotViewFromRect:container.frame afterScreenUpdates:YES withCapInsets:UIEdgeInsetsZero];
UIImage *image = [viewToBlur applyDarkEffect];
UIImageView *blurredView = [[UIImageView alloc] initWithFrame:self.view.frame];
[self.view addSubview:blurredView];
// optionally also animate this, to achieve an even smoother effect
[blurredView setImage:image];
}];
You can then add your SecondViewController's view on top of the stack, so that it's delegate methods will still be called. The bounce effect of the incoming account view can be achieved via the new UIView animation method animateWithDuration:(NSTimeInterval) delay:(NSTimeInterval) usingSpringWithDamping:(CGFloat) initialSpringVelocity:(CGFloat) options:(UIViewAnimationOptions) animations:^(void)animations completion:^(BOOL finished)completion (more on that in the documentation)
I hope that will help you with your project.

Choppy UIView animation

I use a UIView animation to randomly animate 5 squares (UIButtons) around the screen. Depending on a user selection, there are anywhere from 2 to 5 squares visible. When only 2 are visible, the other three's hidden values get set to YES, so they are actually still animating (right?), they just aren't visible. But when only 2 are visible, the animation is smooth, but when all five are visible, the animation gets choppy. I'm not really sure how to describe it, because the squares are still moving at the correct speed and moving to the correct points; the choppiness isn't terrible, just bad enough to be noticeable. Is there any way to get rid of it? This is the code I use to animate the squares:
Edit: changed animations to block:
[UIView animateWithDuration:animationSpeed
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
view.center = destPoint;
}
completion:^(BOOL finished){
if([view isEqual:squareThree])
[self moveBadGuys];
}
];
/*for(UIButton* button in squareArray) {
if(!shouldMove)
return;
[UIView beginAnimations:#"b" context:nil];
[UIView setAnimationDuration:animationSpeed];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
view.center = destPoint;
[UIView commitAnimations];
}*/
Edit: the view presenting this is the third in a stack of three UIViewController presented with
ViewController* controller = [[[ViewController alloc] init] autorelease];
[self presentModalViewController:controller animated:NO];
Does this way of presenting views eat up memory?
There are a few things that can cause this. It always comes down to how complex the content is. Also, simulator can be really bad about handling animation, so be sure you are testing on real hardware.
Are there large images on the buttons? Are the buttons casting shadows? Those things can slow it down.
Also- use block based animation. Not the old begin-commit methods.
Not exactly sure why it's slow, but have you tried nesting the thing differently?
if(!shouldMove)
return;
[UIView beginAnimations:#"b" context:nil];
[UIView setAnimationDuration:animationSpeed];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
for(UIButton* button in squareArray) {
view.center = destPoint;
}
[UIView commitAnimations];
does (almost - the logic is a bit different in the !shouldMove case, but that's a different story) the same, but in a cleaner way.

UIView transitionWithView funky results

I'm trying to write an app that uses custom animations to transition between view controllers in the view stack in a UINavigationController.
Right now, I'm adding multiple laters of the same view, just to get the mechanics up and running properly.
My visual goals are as follows:
1. Create a new "FirstVC".
2. Set the alpha value of [FirstVC view] to 0.
3. Set the transform value of [FirstVC view] to 25% in both directions (vertical & horizontal).
In the animations block, what I'd like to accomplish is:
1. Set the transform value of [OutgoingVC view] to 500% in both directions (blowing it up 5x).
2. Set the alpha value of [OutgoingVC view] to 0 (fading out to nothing).
3. Set the transform value of [FirstVC view] to 1 (bringing it back to its original size).
4. Set the alpha value of [FirstVC view] to 1 (fading in to full color).
The net effect of this animation should be that the "top page" blows up and fades out while the "next/bottom page" blows up (only to full screen) and fades in. The idea is of a user falling through a floor or something. "Going deeper."
I've slowed my animation down to a 10.0 sec interval so I can get a better handle of what's going on.
It appears that the animations taking place on "OutgoingVC" (i.e., the top view) are correct. The bottom view, however, seems to come in fine (100% of screen size and alpha=1.0), but then it keeps going and appears to blow up to either 4x/5x and also fade out to an alpha of 0. Once the screen has gone completely black, the new UIViewController (FirstVC) is correctly displayed on screen.
Does anyone see why my code wouldn't behave the way I want?
Thanks!
Stupid me! Here's the code:
- (IBAction)AddNewScreenPressed:(id)sender
{
FirstVC *newViewController = [[FirstVC alloc] init];
UIView *currentView = [self view];
UIView *newView = [newViewController view];
[newView setTransform:CGAffineTransformMakeScale(0.25, 0.25)];
[newView setAlpha:0];
[UIView transitionWithView:[self view]
duration:10.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
[[self view] addSubview:newView];
[currentView setAlpha:0];
[currentView setTransform:CGAffineTransformMakeScale(5.0, 5.0)];
[newView setAlpha:1.0];
[newView setTransform:CGAffineTransformMakeScale(1, 1)];
}
completion:^(BOOL finished){
[[self navigationController] pushViewController:newViewController animated:NO];
}
];
}
You have added newView as a subview of currentView, so perhaps the animations you are doing to currentView also apply to newView - it presumably scales up and changes the alpha of its subviews as well, including newView. Could you have the initial scale of newView to be 0.2, and drop the rescaling of newView in the animation block, so it would end up at 1.0 scale? Not sure what to do about the alpha, though...
EDIT after seeing your solution below - glad you've got it working, would it perhaps be cleaner (if you're grabbing images off the screen to handle your views anyway) to have a separate viewcontroller to manage the transition, passing it the two images so it can do the animations internally, so you push it to the front with no animation, perform your animation starting with the first view's image and ending with the second view's image (so you can just do standard animations on two imageViews instead of the whole view of the controller), and then push the second view controller once you are done?
After playing with it some more, I've gotten over one hump and met with another obstacle.
I'm upvoting jrturton's answer because the problem did indeed seem related to the fact that I was adding the new view to [self view].
What I did to get around this was add a buffer view ([self BackView]) that exists between [self view] and its subviews.
Then, then following code works to push a new view controller:
- (IBAction)AddNewScreenPressed:(id)sender
{
FirstVC *newViewController = [[FirstVC alloc] init];
UIView *newView = [newViewController view];
[newView setAlpha:0];
[newView setTransform:CGAffineTransformMakeScale(0.25, 0.25)];
[[self view] addSubview:newView];
[UIView transitionWithView:[self view]
duration:1.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
[[self BackView] setTransform:CGAffineTransformMakeScale(5, 5)];
[[self BackView] setAlpha:0];
[newView setTransform:CGAffineTransformMakeScale(1, 1)];
[newView setAlpha:1];
}
completion:^(BOOL finished){
[newView removeFromSuperview];
[[self navigationController] pushViewController:newViewController animated:NO];
[[self BackView] setTransform:CGAffineTransformIdentity];
[[self BackView] setAlpha:1];
}
];
}
Now, of course, I've run into another problem, namely, popping controllers.
When I pop them, the animation seems to work just fine, but when I get to the end (completion block) and do the following:
[[self navigationController] popViewControllerAnimated:NO];
or even:
[[self navigationController] popViewControllerAnimatedYES];
what I end up with is a blank white screen.
I'm assuming that something I'm doing during my "push animation" is causing this problem, but I can't figure it out! Help!
Thanks!

Resources