How to reset an UIGestureRecognizer? - iphone

I have a subview inside a view. That view has a PanGestureRecognizer.
I want user could zoom in inside the webview, pan around, zoom out, etc.
But I want that, when the user reaches left edge of the webview, the gesturesrecognizers inside the webView to be ignored, and only the PanGestureRecognizer from the view get called.
Well, I have accomplished this, by that code:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
float coordY = scrollView.contentOffset.y;
float correctionOffsetRightSide = scrollView.contentSize.width-webView.bounds.size.width;
if(scrollView.contentOffset.x <= 0)
{
[delegate viewOnlyRecognizeItsPan];
[scrollView setContentOffset:CGPointMake(0, coordY)];
}
else if (scrollView.contentOffset.x >= correctionOffsetRightSide)
{
[delegate viewOnlyRecognizeItsPan];
[scrollView setContentOffset:CGPointMake(correctionOffsetRightSide, coordY)];
}
}
-(void)viewOnlyRecognizeItsPan
{
for (UIGestureRecognizer *gesture in [[[[arrayDeAbas objectAtIndex:currentViewTag] webView] scrollView] gestureRecognizers])
{
[gesture requireGestureRecognizerToFail:panRecognizer];
}
}
"arrayDeAbas" is an mutableArray that stores webviews.
My problem is: after user touchs a button, I need to reset "gestures", so that it doesn't requires panRecognizer to fail anymore.
How could I could this?
EDITED:
Ok, I've founded a solution. Instead of using requireGestureRecognizerToFail:, I can simply setEnablle to YES or NO.
-(void)turnPanON
{
for (UIGestureRecognizer *gesture in [[[[arrayDeAbas objectAtIndex:currentViewTag] webView] scrollView] gestureRecognizers])
{
gesture.enabled = NO;
}
}
-(void)turnPanOFF;
{
for (UIGestureRecognizer *gesture in [[[[arrayDeAbas objectAtIndex:currentViewTag] webView] scrollView] gestureRecognizers])
{
gesture.enabled = YES;
}
}

Related

viewdidLoad/ viewDidAppear issue

I m loading a view which gets data from webservices. So depending upon the response code from server I show the current view or redirect to another view (ErrorView) if responsecode is 400.But I'm not being redirected to ErrorView and find a message in console "Attempt to present <ErrorView: 0xe332f30> on ViewController while a presentation is in progress!"
After googling it out I found to place vieDidLoad code to viewDidAppear
After placing the entire code , it redirects to error view and works correctly.But in ios7 for Status bar issue I used the code
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if(responsecode==200)
{
//load current view
}
else
{
//Load anotherview(ErrorView)
}
float systemVersion=[[[UIDevice currentDevice] systemVersion] floatValue];
if(systemVersion>=7.0f)
{
CGRect tempRect;
for(UIView *sub in [[self view] subviews])
{
tempRect = [sub frame];
tempRect.origin.y += 20.0f;
[sub setFrame:tempRect];
}
}
}
First the statusbar appear on the top ,then after loading the entire viewdata , view gets adjusted and status bar dropsdown by 20px , which looks odd.Can I get it to be adjusted automatically before the viewloads completely ?
Any ideas/suggestions would be appreciable.
I think, you should seperate this code
-(void)viewDidLoad
{
float systemVersion=[[[UIDevice currentDevice] systemVersion] floatValue];
if(systemVersion>=7.0f)
{
CGRect tempRect;
for(UIView *sub in [[self view] subviews])
{
tempRect = [sub frame];
tempRect.origin.y += 20.0f;
[sub setFrame:tempRect];
}
}
}
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if(responsecode==200)
{
//load current view
}
else
{
//Load anotherview(ErrorView)
}
}

How to complete interactive UIViewController transition?

I've been dabbling with the new iOS 7 custom transition API and looked through all the tutorials/documentation I could find but I can't seem to figure this stuff out for my specific scenario.
So essentially what I'm trying to implement is a UIPanGestureRecognizer on a view where I would swipe up and transition to a VC whose view would slide up from the bottom while the current view would slide up as I drag my finger higher.
I have no problem accomplishing this without the interaction transition, but once I implement the interaction (the pan gesture) I can't seem to complete the transition.
Here's the relevant code from the VC that conforms to the UIViewControllerTransitionDelegate which is needed to vend the animator controllers:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"Swipe"]) {
NSLog(#"PREPARE FOR SEGUE METHOD CALLED");
UIViewController *toVC = segue.destinationViewController;
[interactionController wireToViewController:toVC];
toVC.transitioningDelegate = self;
toVC.modalPresentationStyle = UIModalPresentationCustom;
}
}
#pragma mark UIViewControllerTransition Delegate Methods
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController: (UIViewController *)presented presentingController: (UIViewController *)presenting sourceController:(UIViewController *)source {
NSLog(#"PRESENTING ANIMATION CONTROLLER CALLED");
SwipeDownPresentationAnimationController *transitionController = [SwipeDownPresentationAnimationController new];
return transitionController;
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
NSLog(#"DISMISS ANIMATION CONTROLLER CALLED");
DismissAnimatorViewController *transitionController = [DismissAnimatorViewController new];
return transitionController;
}
- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator {
NSLog(#"Interaction controller for dimiss method caled");
return interactionController.interactionInProgress ? interactionController:nil;
}
NOTE: The interaction swipe is only for the dismissal of the VC which is why it's in the interactionControllerForDismissal method
Here's the code for the animator of the dismissal which works fine when I tap on a button to dismiss it:
#import "DismissAnimatorViewController.h"
#implementation DismissAnimatorViewController
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
return 1.0;
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
NSTimeInterval duration = [self transitionDuration:transitionContext];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
CGRect initialFrameFromVC = [transitionContext initialFrameForViewController:fromVC];
UIView *containerView = [transitionContext containerView];
CGRect screenBounds = [[UIScreen mainScreen] bounds];
NSLog(#"The screen bounds is :%#", NSStringFromCGRect(screenBounds));
toVC.view.frame = CGRectOffset(initialFrameFromVC, 0, screenBounds.size.height);
toVC.view.alpha = 0.2;
CGRect pushedPresentingFrame = CGRectOffset(initialFrameFromVC, 0, -screenBounds.size.height);
[containerView addSubview:toVC.view];
[UIView animateWithDuration:duration
delay:0
usingSpringWithDamping:0.6
initialSpringVelocity:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
fromVC.view.frame = pushedPresentingFrame;
fromVC.view.alpha = 0.2;
toVC.view.frame = initialFrameFromVC;
toVC.view.alpha = 1.0;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
#end
Here's the code for the UIPercentDrivenInteractiveTransition subclass which serves as the interaction controller:
#import "SwipeInteractionController.h"
#implementation SwipeInteractionController {
BOOL _shouldCompleteTransition;
UIViewController *_viewController;
}
- (void)wireToViewController:(UIViewController *)viewController {
_viewController = viewController;
[self prepareGestureRecognizerInView:_viewController.view];
}
- (void)prepareGestureRecognizerInView:(UIView*)view {
UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
gesture.minimumNumberOfTouches = 1.0;
[view addGestureRecognizer:gesture];
}
- (CGFloat)completionSpeed {
return 1 - self.percentComplete;
NSLog(#"PERCENT COMPLETE:%f",self.percentComplete);
}
- (void)handleGesture:(UIPanGestureRecognizer*)gestureRecognizer {
// CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view.superview];
CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view.superview];
switch (gestureRecognizer.state) {
case UIGestureRecognizerStateBegan:
// 1. Start an interactive transition!
self.interactionInProgress = YES;
[_viewController dismissViewControllerAnimated:YES completion:nil];
break;
case UIGestureRecognizerStateChanged: {
// 2. compute the current position
CGFloat fraction = fabsf(translation.y / 568);
NSLog(#"Fraction is %f",fraction);
fraction = fminf(fraction, 1.0);
fraction = fmaxf(fraction, 0.0);
// 3. should we complete?
_shouldCompleteTransition = (fraction > 0.23);
// 4. update the animation controller
[self updateInteractiveTransition:fraction];
NSLog(#"Percent complete:%f",self.percentComplete);
break;
}
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: {
// 5. finish or cancel
NSLog(#"UI GESTURE RECOGNIZER STATE CANCELED");
self.interactionInProgress = NO;
if (!_shouldCompleteTransition || gestureRecognizer.state == UIGestureRecognizerStateCancelled) {
[self cancelInteractiveTransition];
NSLog(#"Interactive Transition is cancled.");
}
else {
NSLog(#"Interactive Transition is FINISHED");
[self finishInteractiveTransition];
}
break;
}
default:
NSLog(#"Default is being called");
break;
}
}
#end
Once again, when I run the code now and I don't swipe all the way to purposefully cancel the transition, I just get a flash and am presented with the view controller I want to swipe to. This happens regardless if the transition completes or is canceled.
However, when I dismiss via the button I get the transition specified in my animator view controller.
I can see a couple of issues here - although I cannot be certain that these will fix your problem!
Firstly, your animation controller's UIView animation completion block has the following:
[transitionContext completeTransition:YES];
Whereas it should return completion based on the result of the interaction controller as follows:
[transitionContext completeTransition:![transitionContext transitionWasCancelled]]
Also, I have found that if you tell the UIPercentDrivenInteractiveTransition that a transition is 100% complete, it does not call the animation controller completion block. As a workaround, I limit it to ~99.9%
https://github.com/ColinEberhardt/VCTransitionsLibrary/issues/4
I've created a number of example interaction and animation controllers here, that you might find useful:
https://github.com/ColinEberhardt/VCTransitionsLibrary
I had this same problem. I tried the fixes above and others, but nothing worked. Then I stumbled upon https://github.com/MrAlek/AWPercentDrivenInteractiveTransition, which fixed everything.
Once you add it to your project, just replace UIPercentDrivenInteractiveTransition with AWPercentDrivenInteractiveTransition.
Also, you have to set the animator before starting an interactive transition. In my case, I use the same class for UIViewControllerAnimatedTransitioning and UIViewControllerInteractiveTransitioning, so I just did it in init():
init() {
super.init()
self.animator = self
}

handle taps in two different points at a same time via UIGestureRecognizer

I have two labels in two different positions, when both labels are tapped at the same time i want another label to show a success message.
How do I accomplish this? I can recognize a single tap or double tap with one or more finger touches but this is a different scenario. Please help. I tried this, but it does not work.
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
tapRecognizer.numberOfTapsRequired = 1;
tapRecognizer.numberOfTouchesRequired = 2;
tapRecognizer.delegate = self;
[self.view addGestureRecognizer:tapRecognizer];
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if (touch.view == tap2 && touch.view == tap1)
{
result.text = #"success";
}
return YES;
}
Thanks in advance.
What you're trying to detect isn't really a single gesture.
I'd suggest adding a tap gesture recogniser to each button. The handler would:
Store the time of the tap (at the moment that the handler is called)
Compare this time with the time that the other button was last
tapped. If the times are very similar (perhaps 0.25 secs apart),
consider that they've both been tapped simultaneously and react
accordingly.
Play with the time interval on a real device to find the ideal amount.
UPDATE:
A code snippet that obviously hasn't been tested in any way:
- (void)handleButton1Tap:(UITapGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateEnded)
{
self.button1TapTime = CACurrentMediaTime();
[self testForSimultaneousTap];
}
}
- (void)handleButton2Tap:(UITapGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateEnded)
{
self.button2TapTime = CACurrentMediaTime();
[self testForSimultaneousTap];
}
}
- (void)testForSimultaneousTap
{
if (fabs(self.button1TapTime - self.button2TapTime) <= 0.2)
{
// Do stuff
}
}
where self.button1TapTime and self.button2TapTime are member variables (doubles).
Tim
Formally I had accepted termes's answer first and that worked too, but I have found a more simpler solution to this process. There is no need for two gesture recognizers, it is achievable with a simple tap gesture recognizer with number of touches count to two. Here is the code:
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
tapRecognizer.numberOfTapsRequired = 1;
tapRecognizer.numberOfTouchesRequired = 2;
tapRecognizer.delegate = self;
[self addGestureRecognizer:tapRecognizer];
Now, in the handle tap method we can easily get the two touch points by "locationOfTouch:inView:", a instance method of UIGestureRecognizer class. So in the handleTap: method we need to check if the two touch points are in the desired location. Here is the code:
-(void)handleTap:(UITapGestureRecognizer*)recognizer
{
if (recognizer.state == UIGestureRecognizerStateEnded)
{
CGPoint point1 = [recognizer locationOfTouch:0 self];
CGPoint point2 = [recognizer locationOfTouch:1 self];
if ([self validateTapIn:point1 and:point2])
{
resultLabel.text = #"success";
}
}
}
-(BOOL)validateTapIn:(CGPoint)point1 and:(CGPoint)point2
{
return
(CGRectContainsPoint(label1.frame, point1) && CGRectContainsPoint(label2.frame,:point2)) ||
(CGRectContainsPoint(label1.frame, point2) && CGRectContainsPoint(label2.frame, point1));
}

Paged UIScrollview with lazy delayed loading

I need help with the uiscrollview implementation with the following requirements:
1) pagingEnabled = true.
2) lazy pages loading.
3) pages are loading in the background. So i need at first run loading the page X, then get the notification that the page is fully loaded and only then allow the user to scroll to it.
4) ability to change the current page.
My first attempt was to override the scrollViewDidEndDeacelerating and scrollViewDidScroll, but I had troubles with stucking on half of pages (when you stop the scroll on the half of the page and then wait for new page to add to the scroll) and empty pages (when the user scrolled too fast).
My second attempt was to override the layoutSubviews method of the UIScrollView and do all calculations there. But it seems to be very sofisticated.
So, I'd love to find any examples of similar implementations.
Now I have the code like this:
I've implemented scrollViewWillBeginDragging and scrollViewDidEndDecelerating:
- (void)scrollViewWillBeginDragging:(UIScrollView *)aScrollView
{
isScrolling = YES;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)aScrollView
{
isScrolling = NO;
// Here we load the page that was prepared when the user was scrolling
if (needDisplay > -1) {
//NSLog(#"Loading queued page %d", needDisplay);
[self loadScrollViewWithPage:needDisplay forceAddToSuperview:NO animated:YES];
needDisplay = -1;
}
// minLoadedPageIndex - index of the first loaded page.
int selectedIndex = MIN(floor((aScrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1 + minLoadedPageIndex, [photoItems count] - 1);
[self loadScrollViewWithPage:selectedIndex - 1 forceAddToSuperview:NO animated:YES];
[self loadScrollViewWithPage:selectedIndex forceAddToSuperview:NO animated:YES];
[self loadScrollViewWithPage:selectedIndex + 1 forceAddToSuperview:NO animated:YES];
}
In loadScrollViewWithPage I create the page view controller which loads the data from the server in the background. I don't add the view to the scrollview until it loads the data from the server.
- (void)loadScrollViewWithPage:(int)page forceAddToSuperview:(BOOL)value animated:(BOOL)animated
{
DetailController *controller = page >= viewControllers.count ? [NSNull null] :[viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null])
{
controller = [[DetailController alloc] initWithNibName:#"DetailController" bundle:nil];
controller.delegate = self;
controller.view.hidden = NO; //this will call viewDidLoad.
if (page >= viewControllers.count) {
[viewControllers addObject:controller];
}
else {
[viewControllers replaceObjectAtIndex:page withObject:controller];
}
[controller release];
}
// add the controller's view to the scroll view
if (controller.view && controller.view.superview == nil && (controller.isLoadedOrFailed || value)) {
[self setNumberOfVisiblePages:visiblePagesCount+1];
if (page < selectedIndex) {
// We are adding the page to the left of the current page,
// so we need to adjust the content offset.
CGFloat offset = (int)scrollView.contentOffset.x % (int)scrollView.frame.size.width;
offset = scrollView.frame.size.width * (selectedIndex - minLoadedPageIndex) + offset;
[scrollView setContentOffset:CGPointMake(offset, 0.0) animated:animated];
}
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * (page - minLoadedPageIndex);
frame.origin.y = 0;
controller.view.frame = frame;
[scrollView addSubview:controller.view];
[controller viewWillAppear:NO];
}
}
Also I have a detailControllerDidFinishDownload method which is called when data for the page view controller has been loaded.
- (void)detailControllerDidFinishDownload:(DetailController *)controller
{
... //here I calculate new minLoadedPageIndex value
// reset pages frames
if (minLoadedPageIndex < oldMinPage) {
for(int i=oldMinPage;i < [viewControllers count]; i++) {
DetailController *detailsController = [viewControllers objectAtIndex:i];
if ((NSNull *)detailsController != [NSNull null]) {
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * (i - minLoadedPageIndex);
frame.origin.y = 0;
[detailsController.view setFrame:frame];
}
}
}
// load the page now or delay load until the scrolling will be finished
if (!isScrolling) {
[self loadScrollViewWithPage:[photoItems indexOfObject:controller.photoItem] forceAddToSuperview:NO animated:NO];
}
else {
needDisplay = [photoItems indexOfObject:controller.photoItem];
//NSLog(#"pageControlUsed is used!!! %d", [photoItems indexOfObject:controller.photoItem]);
}
}
The problem I have now is that sometimes the scroll stucks on the middle (or somewhere near to middle) of the pages and it won't go to the nearest page bounce until I slightly more it. My tests showed that this situation happens if I scroll out of content view frame (the scroll view have bounces on) and wait for the new page to load. In 1 of the 10 times the scroll stucks.
Thanks, Mikhail!
There are a lot of things you are requiring at the same time. I suggest you have a look at this example.
http://ykyuen.wordpress.com/2010/05/22/iphone-uiscrollview-with-paging-example/
It's a very good starting point.

cocos2d-iOS - Gesture recognisers

Has anyone managed to get the gesture recognition working in cocos-2d?
I have read a post here that claimed to have achieved it, here: http://www.cocos2d-iphone.org/forum/topic/8929
I patched from the git hub here: https://github.com/xemus/cocos2d-GestureRecognizers/blob/master/README
I made a subclass of CCSprite (which is a subclass of CCNode):
-(id) initWithTexture:(CCTexture2D*)texture rect:(CGRect)rect {
if( (self=[super initWithTexture:texture rect:rect]) )
{
CCGestureRecognizer* recognizer;
recognizer = [CCGestureRecognizer
CCRecognizerWithRecognizerTargetAction:[[[UITapGestureRecognizer alloc]init] autorelease]
target:self
action:#selector(tap:node:)];
[self addGestureRecognizer:recognizer];
}
return self;
}
Delegate method:
- (void) swipe:(UIGestureRecognizer*)recognizer node:(CCNode*)node
{
NSLog(#" I never get called :( ");
}
My tap event never gets called.
Has anyone got this working? How difficult is it to do gesture recognition manually for swipe detection?
You need to attach the gesture recognizer to something "up the chain". Don't attach them to the individual nodes; attach them to the UIView (i.e., [[CCDirector sharedDirector] openGLView]).
Here's what I did:
- (UIPanGestureRecognizer *)watchForPan:(SEL)selector number:(int)tapsRequired {
UIPanGestureRecognizer *recognizer = [[[UIPanGestureRecognizer alloc] initWithTarget:self action:selector] autorelease];
recognizer.minimumNumberOfTouches = tapsRequired;
[[[CCDirector sharedDirector] openGLView] addGestureRecognizer:recognizer];
return recognizer;
}
- (void)unwatch:(UIGestureRecognizer *)gr {
[[[CCDirector sharedDirector] openGLView] removeGestureRecognizer:gr];
}
This particular code is used in a superclass for scene controllers, so the target for the selector is hard-coded to "self", but you could easily abstract that to a passed-in object. Also, you could extrapolate the above to easily create gesture recognizers for taps, pinches, etc.
In the subclass for the controller, then, I just do this:
- (MyController *)init {
if ((self = [super init])) {
[self watchForPan:#selector(panning:) number:1];
}
return self;
}
- (void)panning:(UIPanGestureRecognizer *)recognizer {
CGPoint p;
CGPoint v;
switch( recognizer.state ) {
case UIGestureRecognizerStatePossible:
case UIGestureRecognizerStateBegan:
p = [recognizer locationInView:[CCDirector sharedDirector].openGLView];
(do something when the pan begins)
break;
case UIGestureRecognizerStateChanged:
p = [recognizer locationInView:[CCDirector sharedDirector].openGLView];
(do something while the pan is in progress)
break;
case UIGestureRecognizerStateFailed:
break;
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
(do something when the pan ends)
(the below gets the velocity; good for letting player "fling" things)
v = [recognizer velocityInView:[CCDirector sharedDirector].openGLView];
break;
}
}
If you don't want to handle everything manually I created a simple category that will add gesture recognizers to any cocos2d version
read more at:
http://www.merowing.info/2012/03/using-gesturerecognizers-in-cocos2d/
or grab it from github
https://github.com/krzysztofzablocki/CCNode-SFGestureRecognizers