I have a UIImageView subclass and I need to have a pan gesture so I added the following code:
UIPanGestureRecognizer * panRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(handlePan)];
[self addGestureRecognizer:panRecognizer];
but my handlePan selector never gets called.
Is there something else i need to do?
Thanks
If your object is UIImageView subclass, you have to enable user interaction. It is set to NO by default for UIImageView.
self.userInteractionEnabled = YES;
I had EXACTLY the same problem using the StoryBoard - I created a sub-view in my main view, dropped a Pan Gesture Recognizer on it, created an action and joined the pan to it, and it didn't work.
My sub-view had User Interaction Enabled on it and it didn't work. Frustrated, I deleted my Pan Gesture, added it back, everything seemed hooked up and it still didn't work.
Finally, I looked at the SUPERVIEW and its User Interaction Enabled was checked off. Checking it on and it worked.
So as a caveat, if it's not working, look at the parent views too!
Related
I added ICarousel to my IOS project and it worked fine. I could scroll pictures.
Then my view contain another data. So I needed to add a UISCrollView wich cover all my view. So, now, I have some elements (labels, textViews, and the UIVIew for ICarousel) in my ScrollView.
The ScrollView works fine. But now, the ICarousel doesn't switch pictures. Pictures are loaded (I see the first one and a part of the second one) but the carousel doesn't work anymore.
Did someone have the same problem? How to solve it?
Edit:
After #Wain advices, I tried this:
- (void)viewDidLoad
{
UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(pan:)];
[self.view addGestureRecognizer:panRecognizer];
panRecognizer.delegate = self;
}
- (void)pan:(id)sender {
NSLog(#"Pan");
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
It doesn't work, but I checked with a breakpoint, it ran shouldRecognizeSimultaneouslyWithGestureRecognizer method when I tried a horizontal scroll.
Where am I wrong?
Looks like a clash between gesture recognisers because, by default, only one can be active at any one time.
Not sure why your scroll view covers everything, but you should be able to make your controller the delegate of the gestures and implement gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: to allow them to recognise concurrently.
UIScrollView has a panGestureRecognizer property that you can access to set yourself as the delegate.
iCarousel is a bit different as it doesn't make the gesture available publicly so if setting the delegate on the scroll view doesn't work you can edit the carousel (which sets itself as the delegate) to implement the delegate method.
Whilst developing an app I have come up against a problem with having too many pan gesture recognisers.
My first pan gesture recogniser is on the MainViewController which is a parent of the RecipeSearchVC. This gesture recogniser slides the whole view left or right.
My second pan gesture recogniser in in the RecipeSearchParametersVC which is a parent of a Page View Controller.
The third pan gesture gesture recogniser is added to a UIControl Wheel nested inside of a view controller which is represented by the PageViewController.
I know this sounds insane and it could be argued that this is poor design. However, I believe that is this worked cohesively it would be fine.
When trying to rotate the wheel it will rotate for a second or two before the gesture is overtaken by either the PageViewController or the MainViewController. More often than not it is the MainViewController that takes over. What techniques could I employ to clearly separate each of these gesture recognisers?
EDIT:
Apologies for the vagueness of my description when it comes to the pan gesture recognisers.
The MainViewController has it's own UIPanGestureRecpgniser to allow it to move everything left or right.
The RecipeSearchParametersVC only has a UIPanGestureRecogniser because of the UIPageViewController it contains. It does not add the gesture recogniser itself, but simply takes them from the the pageViewController.
The UIControl's gesture recognisers allows it to track the rotation it should undergo.
In taking the advice given, I may remove the gestures from the page view controller and substitue them with buttons. I only intended this to work like the images (which can be scrolled to reveal more images) found in iBooks, and so I thought that it would work fine.
UIControl UIPanGestureRecogniser Code
/**
* sent to the control when a touch related to the given event enters the control’s bounds
*
* #param touch uitouch object that represents a touch on the receiving control during tracking
* #param event event object encapsulating the information specific to the user event
*/
- (BOOL)beginTrackingWithTouch:(UITouch *)touch
withEvent:(UIEvent *)event
{
[super beginTrackingWithTouch:touch withEvent:event];
CGPoint touchPoint = [touch locationInView:self];
// filter out touchs too close to centre of wheel
CGFloat magnitudeFromCentre = [self calculateDistanceFromCentre:touchPoint];
if (magnitudeFromCentre < 40) return NO;
// calculate distance from centre
CGFloat deltaX = touchPoint.x - _container.center.x;
CGFloat deltaY = touchPoint.y - _container.center.y;
// calculate the arctangent of the opposite (y axis) over the adjacent (x axis) to get the angle
_deltaAngle = atan2(deltaY, deltaX);
_startTransform = _container.transform;
// selection in limbo so set all sector image's to minimum value by changing current one
[self getSectorByValue:_currentSector].alpha = kMinimumAlpha;
return YES;
}
Unfortunately due to the nature of my controller hierarchy I was forced to rethink the design of my app.
The MainViewController with the UIPanGestureRecogniser has stayed as is.
The UIPageViewController with the UIControl has moved to a separate static view controller.
This works far better but is not yet ideal. The UIPageViewController steals any horizontal panning, however this can probably be fixed by implementing buttons as an alternative to the scrolling.
The UIControl did not have a gesture recogniser, but I override the beginTrackingWithTouch: and other methods to track the touches.
I suppose the answer should be: if you are layering too many gestures, you're doing it wrong.
You will need to add a container to the wheel, and then you can do something like that, if I am not missing something, this code must work.
UIPanGestureRecognizer* pan1 = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(pan1:)];
[self.view addGestureRecognizer:pan1];
UIPanGestureRecognizer* pan2 = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(pan2:)];
[view2 addGestureRecognizer:pan2];
UIPanGestureRecognizer* pan3 = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(pan3:)];
[view3 addGestureRecognizer:pan3];
- (void) pan1:(UIPanGestureRecognizer*)sender{
NSLog(#"%#",sender);
}
- (void) pan2:(UIPanGestureRecognizer*)sender{
NSLog(#"%#",sender);
}
- (void) pan3:(UIPanGestureRecognizer*)sender{
NSLog(#"%#",sender);
}
I believe you would have defined 3 different UIPanGesture objects and attached it to the appropriate views.
While technically this is correct it could cause some confusion, for example, if you're having several overlapping views (you have 3 here) and need a touch to be sent to a view that's not at the top of the view stack what would happen? The gesture could end up being confusing.
Instead, it's possible to attach a single gesture recognizer to the superview of several target views and delegate the gesture to the correct view based on the coordinates of where the user is touching. For that you need to normalize the touch coordinates originating from any of the subviews to the superview where the UIPanGesture is defined. That way you can know where the pan happend on the wheel or elsewhere.
PS: This being said, I strongly feel this is a design gotcha and it will hurt you. I have done things like this but eventually you would stumble on some corner case where the user interaction would be horrid. If this is the main view of your app I suggest you rethink the design.
What techniques could I employ to clearly separate each of these gesture recognisers?
You should look into the UIGestureRecognizerDelegate method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
Asks the delegate if two gesture recognizers should be allowed to recognize gestures simultaneously.
This method is called when recognition of a gesture by either gestureRecognizer or otherGestureRecognizer would block the other gesture recognizer from recognizing its gesture. Note that returning YES is guaranteed to allow simultaneous recognition; returning NO, on the other hand, is not guaranteed to prevent simultaneous recognition because the other gesture recognizer's delegate may return YES.
I believe this will solve your problem of gestures being 'overtaken' by other gestures.
I've got a UIPageViewController set up paging my ImageViewController.
The ImageViewController contains a UIScrollView with a UIImageView inside. Nothing else.
I'm testing at the moment with 3 "items" in my datasource for the UIPageViewController (i.e. three pages).
It all works fine and I can scroll and zoom and then page for about 30 seconds and then suddenly I get this warning...
*** Assertion failure in -[_UIQueuingScrollView _didScrollWithAnimation:force:], /SourceCache/UIKit/UIKit-2372/_UIQueuingScrollView.m:778
I've got no idea where to start debugging it though as it doesn't point to any of my code and there isn't any of my code in the stack or anything.
Can someone give me a pointer as to where to start debugging this.
EDIT
I've done a bit more testing. It seems to happen if the scrollView is decelerating (i.e. after a flick) and then I try to transition the PageViewController to another ViewController as the scrollview is still moving.
The app crashes about 20% of the way through the transition to the next page.
EDIT 2
The error seems to stop on the line _cache_getImp (not sure if that's a capital i or lowercase L).
EDIT 3
This gets better. I just downloaded Apple's PhotoScroller sample app to see if they got round the problem a different way. Well, no, they didn't. The sample app crashes in exactly the same way mine does! You have to zoom and scroll and transition pages at the same time to make it more likely to crash but it happens on it's own too it just might take longer to happen.
Came up with a solution! In my case I have a UIPageViewController with UIPageViewControllerTransitionStyleScroll as well as next buttons that allow the user to advance through my viewpager by tapping. I am getting this crash when the user presses a next button and drags around slightly before releasing their finger (still within the frame of the button). It appears that the dragging within the button is interfering with UIPageViewController's pan gesture recognizer in this case, and that's what causes the crash.
While it's pretty unlikely that the user will get in this state, I've come up with a relatively simple solution the prevents my app from crashing if it does happen. I have a boolean that represents if the user is in a valid state to move to the next screen and set it to YES on touch down, then to NO if the user drags anywhere inside my button. Then, on touchUp (nextPressed) I check the boolean before moving my UIPageViewController programatically.
- (IBAction)touchDown:(id)sender
{
self.shouldAdvanceToNextScreen = YES;
}
- (IBAction)touchDragInside:(id)sender
{
self.shouldAdvanceToNextScreen = NO;
}
- (IBAction)nextPressed:(id)sender
{
if (self.shouldAdvanceToNextScreen) {
UIViewController *initialViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"TutorialScreen2"];
NSArray *viewControllers = [NSArray arrayWithObject:initialViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
}
}
The downside is that nothing will happen even though the user still released their finger within the button frame. However, I prefer this over a crash and see this as a pretty rare edge case regardless. I'd expect the user would just tap again - this time without a tap & drag - and move forward successfully.
I'd welcome any ideas on taking this a step further and preventing the clash between the touch drag and the UIPageViewController altogether.
Have you tried disabling bouncing in the UIScrollView? That worked for me and solved my other problem too alluded to in my comment above.
I have been fighting with this all day.
My Conclusions:
If you have a scrollview as the showing ViewController and you are delegate of the scrolls: you're in trouble. Even with the PageViewController configured with Horizontal scrolling, a vertical scrolling on your view will trigger an event. -> this does not cause trouble if: you scroll back to the top of your view before (Not sure how to fix this).
There are good StackOverflow threads like this one.
Basically the solution is:
1 [yourView setViewControllers:yourControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
2 Use UIPageViewControllerTransitionStylePageCurl as the transition Style.
This fixed most of my problems.
If someone has a solution for the delegation problems with the scrolling, will be most wellcome
I had a similar problem. My setup was a UIPageViewController and I was loading view controllers with an UIImageView inside. When interacting while the UIPageViewController was scrolling I got the same crash log.
I fixed it by creating a UILongPressGestureRecognizer and add to the UIPageViewController's scroll view.
Created my own subclass of UIPageViewController (Solution is specific for my case but can easily used as a generic solution)
Find the inner UIScrollView of the UIPageViewController
- (UIScrollView *)findScrollView
{
UIScrollView *scrollView;
for (id subview in self.view.subviews)
{
if ([subview isKindOfClass:UIScrollView.class])
{
scrollView = subview;
break;
}
}
return scrollView;
}
Add the long tap gesture recognizer to the inner scroll view and point its action to a method or nil
/**
* On tap-hold the page view controller crashes as soon as it pages to a new view controller.
* Setting a long press gesture to ignore the hold.
*/
- (void)listenForLongPressGestureOnScrollView:(UIScrollView *)scrollView
{
UILongPressGestureRecognizer *longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:nil];
[longPressGestureRecognizer setMinimumPressDuration:0.5];
[scrollView addGestureRecognizer:longPressGestureRecognizer];
}
It surely looks like a bug in iOS 8 and 9.
(and the "answers" down below attempt to work around that)
Feel free to file a ticket on bugreport.apple.com
Do be sure to specify how to crash PhotoScroller
Include all iOS versions you've seen it crash on
To date I've seen:
Assertion failure in -[XXX.TrackingPageViewController queuingScrollView:didEndManualScroll:toRevealView:direction:animated:didFinish:didComplete:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3512.60.12/UIPageViewController.m:2028
Assertion failure in -[_UIQueuingScrollView _didScrollWithAnimation:force:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3512.60.12/_UIQueuingScrollView.m:785
(lldb)
I'll keep the list updated (Despite the downvoting, however massive).
Stay tuned.
It does not look like our bug.
UPD 2016 09 27 (on Xcode 8 now):
Assertion failure in -[XXX.TrackingPageViewController queuingScrollView:didEndManualScroll:toRevealView:direction:animated:didFinish:didComplete:], /SourceCache/UIKit/UIKit-3347.44.2.2/UIPageViewController.m:1875
awesome
Recently I had a problem. How to disable scrolling in a particular area of a UIScrollView, particularly the area occupied by a UIView or subview.
I've readen a lot about subclassing and other long approaches to solve this.
But recently I solved this problem in a simplier manner without subclassing:
UIPanGestureRecognizer *panrecognizer = [[UIPanGestureRecognizer alloc] init];
and then
[panrecognizer setCancelsTouchesinView:NO];
[mySubViewInScroll addGestureRecognizer:panrecognizer];
I created the UIPanGestureRecognizer without an Action passed to it and then added the recognizer to the view in the scroller. In such way the gestures on the view will be captured but expressly not handled by the view or by the superviews because we passed to the object no Action.
The question is this. Is this a correct approach to handle this type of prolem or it's better to do otherwise. I mean Apple will accept this kind of application with this approach ?
In effect I think that this, even if not the best is the most practical solution..since messing around with the classes and subclassing to achieve only a partial scroll lock of the screen seems to be very odd. So let-s see if Apple will accept this kind of solution...
try rewrite ScrollView's
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
to return chi
This is frustrating me!!!
It will be called most of the time but then it stops responding to the pinches. It will be called on a screen rotate and a double tap. Not to a pinch!
Help!
I was working on some code that had the same issue and turns out the problem was a subview with a UIGestureRecognizer had been added as a subview to MKMapView, and sometimes, they would cause some delegate methods not to fire.
So make sure you aren't adding subviews or anything to the MKMapView.
Hope this helps.
I was moving the map in code and then it appears I needed to call
[mapView setNeedsDisplay];
After!
I think this problem may have something to do with multi-threading.
I had the same problem this morning. I use a gesture recognizer to capture long press event and then add a pin to the mapview. If works well but after a few rounds, the region did change method stop being called.
I tried a few solutions here but none works. Then I recalled some other issue I had before with multi-threading nature of actions. So I try to moved the code that controls the mapview in long press action to a block that runs in main thread. And the problem is solved.
I managed to solve this problem by disabling the gesture recognizer within the touchesBeganCallback
self.tapInterceptor.touchesBeganCallback = ^(NSSet *touches, UIEvent *event) {
self.tapInterceptor.enabled = NO;
// do something
};
and reenabling it in the regionDidChangeAnimated delegate method
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
self.tapInterceptor.enabled = YES;
// do something
}
Whenever a tap gesture recognizer added to the mapview, setting
recognizer.cancelsTouchesInView = NO;
takes care of the problem if your business logic allows for double processing if touches on mapview (by MKMapView AND the gesture recognizer
that was most recently interfering with the region[Will,Did]ChangeAnimated:)