UIPercentDrivenInteractiveTransition cancelTransition turns screen black - iphone

I'm implementing custom navigation transitions, and using UIPercentDrivenInteractiveTransition to do the trick.
I've got the noninteractive transition working fine, and I'm using an animation block to handle the transition animation, but once I added in interactive transitions, the screen now goes black if I cancel the transition.
Here's my code that tracks the gesture:
-(void)userDidPan:(UIScreenEdgePanGestureRecognizer *)recognizer {
CGPoint location = [recognizer locationInView:nil];
if (recognizer.state == UIGestureRecognizerStateBegan) {
// We're being invoked via a gesture recognizer – we are necessarily interactive
self.hasActiveInteraction = YES;
self.interactiveTransition = [BXTPercentDrivenInteractiveTransition new];
[[BXTNavigationController sharedNavigationController] popViewControllerAnimated:YES];
}
else if (recognizer.state == UIGestureRecognizerStateChanged) {
CGFloat ratio = location.x / CGRectGetWidth(recognizer.view.frame);
NSLog(#"Percentage complete: %0.2f",ratio*100);
[self.interactiveTransition updateInteractiveTransition:ratio];
}
else if (recognizer.state == UIGestureRecognizerStateEnded) {
// Depending on our state and the velocity, determine whether to cancel or complete the transition.
CGFloat ratio = location.x / CGRectGetWidth(recognizer.view.frame);
if (ratio > 0.50) {
NSLog(#"Completing");
[self.interactiveTransition finishInteractiveTransition];
}
else {
NSLog(#"Canceling");
[self.interactiveTransition cancelInteractiveTransition];
}
}
}
...and here's an (abbreviated) look at my animation block when popping off the view controller:
[UIView animateWithDuration:kBXTNavigationTimingDuration
delay:0
options:animationOption
animations:^{
toVC.view.frame = destinationFrameForPoppedView;
fromVC.view.frame = CGRectOffset(fromVC.view.frame, fromVC.view.frame.size.width, 0);
} completion:^(BOOL finished) {
[darkeningView removeFromSuperview];
[_transition.secondaryTransitionViews enumerateObjectsUsingBlock:^(BXTTransitionView *transitionView, NSUInteger idx, BOOL *stop) {
[transitionView.view removeFromSuperview];
}];
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
Any ideas why the screen goes black when cancelling a swipe-from-the-left pop animation with a custom navigation transition?

Solved! I deleted all the removeFromSuperview calls in the animation completion block and it solved it (I had the same problem like you with almost the same code, see my comment to your q'). Try removing these lines from your code:
[darkeningView removeFromSuperview];
[_transition.secondaryTransitionViews enumerateObjectsUsingBlock:^(BXTTransitionView *transitionView, NSUInteger idx, BOOL *stop) {
[transitionView.view removeFromSuperview];
}];

Related

Cocos2D: Player controller algorithm glitches when jumping

I'm having some trouble with an algorithm for a player controller I'm writing. I'm using Cocos2D to make a platformer style game (think Super Mario Bros.). My controller has a left and right button, and you touch the right half of the screen to jump.
The problem I'm having is when you're holding the right or left button, then hold the screen to jump and let go of the directional button, the player will continue to go in that direction until the user lifts their finger off the screen.
The other problem is if the user holds the screen to jump, taps a directional button then lets go of the directional while still holding the screen, the player will continue in that direction. Half the time the player stops on release of the screen, the other half the player will continue going on release until the screen is tapped again.
This is my code to register which part of the controller are tapped:
- (void) ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch* touch in touches)
{
CGPoint point = [self convertTouchToNodeSpace: touch];
// Create a rect around right half of the window
CGSize winSize = [CCDirector sharedDirector].winSize;
CGRect jumpBox = CGRectMake(winSize.width / 2,
0,
winSize.width / 2,
winSize.height);
// left button
if (CGRectContainsPoint(leftButtonSprite.boundingBox, point))
{
self.isPressingLeftButton = YES;
self.isPressingRightButton = NO;
}
// right button
else if (CGRectContainsPoint(rightButtonSprite.boundingBox, point))
{
self.isPressingRightButton = YES;
self.isPressingLeftButton = NO;
}
// player's trying to jump
if (CGRectContainsPoint(jumpBox, point))
{
self.isTouchingScreen = YES;
}
}
}
- (void) ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch* touch in touches)
{
CGPoint point = [self convertTouchToNodeSpace: touch];
// Create a rect around right half of the window
CGSize winSize = [CCDirector sharedDirector].winSize;
CGRect jumpBox = CGRectMake(winSize.width / 2,
0,
winSize.width / 2,
winSize.height);
// left button
if (CGRectContainsPoint(leftButtonSprite.boundingBox, point))
{
// CCLOG(#"Pressing left");
self.isPressingLeftButton = YES;
self.isPressingRightButton = NO;
}
// right button
else if (CGRectContainsPoint(rightButtonSprite.boundingBox, point))
{
// CCLOG(#"Pressing right");
self.isPressingRightButton = YES;
self.isPressingLeftButton = NO;
}
// player's trying to jump
if (CGRectContainsPoint(jumpBox, point))
{
// CCLOG(#"Jumping");
self.isTouchingScreen = YES;
}
}
}
- (void) ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if (self.isTouchingScreen)
{
CCLOG(#"Stop jumping");
self.isTouchingScreen = NO;
}
else
{
CCLOG(#"Stop moving");
self.isPressingLeftButton = NO;
self.isPressingRightButton = NO;
}
}
This code is in my HUDLayer.m which is a child in my GameplayLayer.m. GameplayLayer uses update: to figure out which of the flags from HUDLayer is set and moves the player accordingly
Figured it out. Kind of a n00b mistake. In ccTouchesEnded:withEvent:, I had to convertTouchToNodeSpace and check if it intersects the rects of the directional buttons or jump half of the screen as done in ccTouchesBegan:withEvent:
Hope this helps someone some day. Happy coding :)

check if scrollview.contentset went over certain x value

I have a scrollview and above some image. When the scrollview scrollView.contentOffset.x is past a certain X my image above should animate.
I know how to animate. At the moment I'm doing this in the - (void)scrollViewDidScroll:(UIScrollView *)scrollView method.
if (scrollView.contentOffset.x == 160) {
//animate Image
}
but sometimes it gets the 160, but other times it passes over the 160. How can I solve this ?
Add an instance variable, set it to the offset that you've seen in the last invocation of scrollViewDidScroll:, and use it to decide if you would like to animate:
// Instance variable
CGPoint lastOffset;
...
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
...
if (lastOffset.x < 160 && scrollView.contentOffset.x >= 160) {
//animate Image
}
lastOffset = scrollView.contentOffset;
}
This would let you animate the image every time the scroll view crosses from below 160 to above 160.
Use >= 160 but also use a flag so you know if you have already done the animation:
if (scrollView.contentOffset.x == 160 && !self.animatedImage) {
self.animatedImage = YES;
...
}
I think you should add some flag to allow image animation,
and manage this flag during scrolling/after image animating
BOOL isCanAnimate_;
// some code here
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.x >= imageView.frame.size.width / 2 && isCanAnimate_)
{
isCanAnimate_ = FALSE;
[UIView animateWithDuration:2.0
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^
{
// Animation here
}
completion:^(BOOL finished)
{
isCanAnimate_ = TRUE;
}];
}
}

Swapping images in IOS

I have to swap four images on a single screen . The images can be swapped only in left/right/upwards/downwards directions only and not diagonally . For example , the 1st image can be swapped with the images on its right and below it , the 2nd image can be swapped only with the one on the left and below it , and so on . Can anyone please help me about how to do it . Thanks
To add drag and drop functionality, assuming that is what you mean, you'll want to look into UIPanGestureRecognizer
Add a swipe gesture recognizer. When the user swipes determine which direction and handle the image swap.
[EDIT] - Imagine a square divided into four equal sections. The top left section has an index of zero, the top right an index of one, the bottom left an index of 2 and finally the bottom right an index of 3. The code below checks the current index and from that it determines if an image swap can be made, if not it does nothing.
This code is from the top of my head so there may be syntax errors but the logic is sound (I hope :D ).
- (void) viewDidLoad {
// turn on user interaction on the image view as its off by default.
[self.imageView setUserInteractionEnabled:TRUE];
UISwipeGestureRecognizer *recognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipe:)];
[recognizer setDirection:(UISwipeGestureRecognizerDirectionRight | UISwipeGestureRecognizerDirectionDown | UISwipeGestureRecognizerDirectionLeft | UISwipeGestureRecognizerDirectionUp)];
[self.imageView addGestureRecognizer:recognizer];
self.currentImageIndex = 0;
self.images = [NSArray arrayWithObjects:[UIImage imageNamed:#"top-left"],[UIImage imageNamed:#"top-right"],[UIImage imageNamed:#"bottom-left"],[UIImage imageNamed:#"top-right"],nil];
}
-(void)handleSwipeFrom:(UISwipeGestureRecognizer *)recognizer {
if (recognizer.direction == UISwipeGestureRecognizerDirectionRight) {
if (self.currentImageIndex == 0 || self.currentImageIndex == 2) self.currentImageIndex++; // change to image to the right
else return; // do nothing
}
else if (recognizer.direction == UISwipeGestureRecognizerDirectionLeft) {
if (self.currentImageIndex == 1 || self.currentImageIndex == 3) self.currentImageIndex--; // change to the image to the left
else return; // do nothing
}
else if (recognizer.direction == UISwipeGestureRecognizerDirectionUp) {
if (self.currentImageIndex == 2 || self.currentImageIndex == 3) self.currentImageIndex -= 2; // change to the above image
else return; // do nothing
}
else if (recognizer.direction == UISwipeGestureRecognizerDirectionDown) {
if (self.currentImageIndex == 0 || self.currentImageIndex == 1) self.currentImageIndex += 2; // change to the above image
else return; // do nothing
}
[UIView animationWithDuration:0.5 animations:^{
[self.imageView setAlpha:0];
} completion^(BOOL finished){
if (finished) {
[UIView animationWithDuration:0.5 animations:^{
[self.imageView setImage[self.images objectAtIndex:self.currentImageIndex]];
[self.imageView setAlpha:1];
}];
}
}];
}

Scroll effect with swipe gesture in iOS

I have a view which has two labels. When I swipe left I fill next content to label text. Similarly swiping right loads previous content. I want to give an effect to labels like they are scrolling from left or right.
I used a scrollview before but it had a memory problem. So I'm using one view, and swipe gesture loads next or previous content. I want to add scrollview's sliding effect to labels. How can I do that?
I'm not quite sure precisely what effect you're looking for, but you could do something like this, which creates a new, temporary label, puts it off screen, animates the moving it over the label you have on screen, and then when done, resets the old one and deletes the temporary label. This is what a non-autolayout implementation might look like:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
UISwipeGestureRecognizer *left = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(leftSwipe:)];
[left setDirection:UISwipeGestureRecognizerDirectionLeft];
[self.view addGestureRecognizer:left];
// if non-ARC, release it
// [release left];
self.label1.text = #"Mo";
}
- (void)leftSwipe:(UISwipeGestureRecognizer *)gesture
{
NSString *newText;
UILabel *existingLabel = self.label1;
// in my example, I'm just going to toggle the value between Mo and Curly
if ([existingLabel.text isEqualToString:#"Curly"])
newText = #"Mo";
else
newText = #"Curly";
// create new label
UILabel *tempLabel = [[UILabel alloc] initWithFrame:existingLabel.frame];
[existingLabel.superview addSubview:tempLabel];
tempLabel.text = newText;
// move the new label off-frame to the right
tempLabel.transform = CGAffineTransformMakeTranslation(tempLabel.superview.bounds.size.width, 0);
// animate the sliding of them into place
[UIView animateWithDuration:0.5
animations:^{
tempLabel.transform = CGAffineTransformIdentity;
existingLabel.transform = CGAffineTransformMakeTranslation(-existingLabel.superview.bounds.size.width, 0);
}
completion:^(BOOL finished) {
existingLabel.text = newText;
existingLabel.transform = CGAffineTransformIdentity;
[tempLabel removeFromSuperview];
}];
// if non-ARC, release it
// [release tempLabel];
}
This animation animates the label with respect to its superview. You may want to ensure that the superview is set to "clip subviews". This way, the animation will be constrained to the bounds of that superview, which yields a slightly more polished look.
Note, if using auto layout, the idea is the same (though the execution is more complicated). Basically configure your constraints so new view is off to the right, then, in animation block update/replace the constraints so the original label is off to the left and the new one is in the spot of the original label, and, finally, in the completion block reset the constraints of the original label and remove the temporary label.
By the way, this is all infinitely easier if you're comfortable with one of the built in transitions:
- (void)leftSwipe:(UISwipeGestureRecognizer *)gesture
{
NSString *newText;
UILabel *existingLabel = self.label1;
// in my example, I'm just going to toggle the value between Mo and Curly
if ([existingLabel.text isEqualToString:#"Curly"])
newText = #"Mo";
else
newText = #"Curly";
[UIView transitionWithView:existingLabel // or try `existingLabel.superview`
duration:0.5
options:UIViewAnimationOptionTransitionFlipFromRight
animations:^{
existingLabel.text = newText;
}
completion:nil];
}
If you prefer animation that behaves more like a scroll view (i.e., with continuous feedback on the gesture), it might look something like the following:
- (void)viewDidLoad
{
[super viewDidLoad];
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panHandler:)];
[self.view addGestureRecognizer:pan];
self.label1.text = #"Mo";
}
- (void)panHandler:(UIPanGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateBegan)
{
_panLabel = [[UILabel alloc] init];
// in my example, I'm just going to toggle the value between Mo and Curly
// you'll presumably set the label contents based upon the direction of the
// pan (if positive, swiping to the right, grab the "previous" label, if negative
// pan, grab the "next" label)
if ([self.label1.text isEqualToString:#"Curly"])
_newText = #"Mo";
else
_newText = #"Curly";
// set the text
_panLabel.text = _newText;
// set the frame to just be off screen
_panLabel.frame = CGRectMake(self.label1.frame.origin.x + self.containerView.frame.size.width,
self.label1.frame.origin.y,
self.label1.frame.size.width,
self.label1.frame.size.height);
[self.containerView addSubview:_panLabel];
_originalCenter = self.label1.center; // save where the original label originally was
}
else if (sender.state == UIGestureRecognizerStateChanged)
{
CGPoint translate = [sender translationInView:self.containerView];
if (translate.x > 0)
{
_panLabel.center = CGPointMake(_originalCenter.x - self.containerView.frame.size.width + translate.x, _originalCenter.y);
self.label1.center = CGPointMake(_originalCenter.x + translate.x, _originalCenter.y);
}
else
{
_panLabel.center = CGPointMake(_originalCenter.x + self.containerView.frame.size.width + translate.x, _originalCenter.y);
self.label1.center = CGPointMake(_originalCenter.x + translate.x, _originalCenter.y);
}
}
else if (sender.state == UIGestureRecognizerStateEnded || sender.state == UIGestureRecognizerStateFailed || sender.state == UIGestureRecognizerStateCancelled)
{
CGPoint translate = [sender translationInView:self.containerView];
CGPoint finalNewFieldLocation;
CGPoint finalOriginalFieldLocation;
BOOL panSucceeded;
if (sender.state == UIGestureRecognizerStateFailed ||
sender.state == UIGestureRecognizerStateCancelled)
{
panSucceeded = NO;
}
else
{
// by factoring in the velocity, we can capture a flick more accurately
//
// (by the way, I don't like iOS's velocity, because if you stop moving, it records the velocity
// prior to stopping the move rather than noting that you actually stopped, so I usually calculate my own,
// but I'll leave this as is for purposes of this example)
CGPoint velocity = [sender velocityInView:self.containerView];
if (translate.x < 0)
panSucceeded = ((translate.x + velocity.x * 0.5) < -(self.containerView.frame.size.width / 2));
else
panSucceeded = ((translate.x + velocity.x * 0.5) > (self.containerView.frame.size.width / 2));
}
if (panSucceeded)
{
// if we succeeded, finish moving the stuff
finalNewFieldLocation = _originalCenter;
if (translate.x < 0)
finalOriginalFieldLocation = CGPointMake(_originalCenter.x - self.containerView.frame.size.width, _originalCenter.y);
else
finalOriginalFieldLocation = CGPointMake(_originalCenter.x + self.containerView.frame.size.width, _originalCenter.y);
}
else
{
// if we didn't, then just return everything to where it was
finalOriginalFieldLocation = _originalCenter;
if (translate.x < 0)
finalNewFieldLocation = CGPointMake(_originalCenter.x + self.containerView.frame.size.width, _originalCenter.y);
else
finalNewFieldLocation = CGPointMake(_originalCenter.x - self.containerView.frame.size.width, _originalCenter.y);
}
// animate the moving of stuff to their final locations, and on completion, clean everything up
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
_panLabel.center = finalNewFieldLocation;
self.label1.center = finalOriginalFieldLocation;
}
completion:^(BOOL finished) {
if (panSucceeded)
self.label1.text = _newText;
self.label1.center = _originalCenter;
[_panLabel removeFromSuperview];
_panLabel = nil; // in non-ARC, release instead
}
];
}
}
Note, I have put both the original label, as well as the new label being panned on, in a container UIView (called containerView, surprisingly enough), so that I can clip the animation to that container.

CGRect not changing during animation

I'm animating an UIView and want to check if its frame intersects with another UIView's frame. This is how I "spawn" one of the UIViews:
- (void) spawnOncomer
{
oncomer1 = [[Oncomer alloc] initWithType:#"car"];
[self.view addSubview:oncomer1];
//make the oncomer race across the screen
[UIView beginAnimations:nil context:NULL]; {
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:3.0];
[UIView setAnimationDelegate:self];
CGRect f = oncomer1.frame;
f.origin.y = self.view.frame.size.height+oncomer1.frame.size.height;
oncomer1.frame = f;
[UIView setAnimationDidStopSelector:#selector(decountCar)];
}
[UIView commitAnimations];
}
So far so good. Now I want to check if this UIView and my other UIView collide by doing this:
- (void) checkCollision {
bool collision = CGRectIntersectsRect(car.frame, oncomer1.frame);
if (collision) {
NSLog(#"BOOOOOOOM");
} else {
NSLog(#"Oncomer: %#", NSStringFromCGRect(oncomer1.frame));
}
}
However, they never collide. Although I see oncomer1 moving across the screen, loggin oncomer1.frame never changes: it keeps outputting Oncomer: {{50, 520}, {30, 60}}(which are the post-animation values).
Does anyone know why this is?
P.s. Both methods are called directly or indirectly with a NSTimer and are thus performed in the background
UIView geometry updates apply immediately to their CALayer, even in an animation block. To get a version of a layer with animations applied, you can use -[CALayer presentationLayer], like this (warning - untested code):
- (void) checkCollision {
CGRect oncomerFrame = oncomer1.layer.presentationLayer.frame;
bool collision = CGRectIntersectsRect(car.frame, oncomerFrame);
if (collision) {
NSLog(#"BOOOOOOOM");
} else {
NSLog(#"Oncomer: %#", NSStringFromCGRect(oncomerFrame));
}
}
From your code:
CGRect f = oncomer1.frame;
f.origin.y = self.view.frame.size.height+oncomer1.frame.size.height;
oncomer1.frame = f;
A logical explanation of the frame never changing is that you are only changing the y of the frame, and you are always setting it to the same value determined by two heights.