MKMapView regionDidChangeAnimated not always called! - iphone

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:)

Related

Assertion failure in UIQueuingScrollView didScrollWithAnimation:force:

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

Detecting a finger being held on an object

I am trying to have an image that when the user touches it, it wiggles and as soon as the user lifts their finger it stops.
Is there a gesture that I can use to detect when the finger is down, not just on the initial touch, or when the user moves there finger?
I have tried a LongPress gesture, but that does not get called the entire time the finger is on the view. Can anyone help me with the best way to active this. Right now i am doing it using touchesBegin, touchesMoved, touchesEnd, but i was wondering if there is a better way.
Any suggestions are greatly appreciated.
Thanks
EDIT
Based on the comments, I slightly misunderstood the original question, so I edit my answer to a different solution, which hopefully is a bit more clear (and answers the actual question - not the one that was in my head).
A LongPress gesture is continuous (where a tap gesture is not). That means, the recognizer callback will continue to be invoked until the gesture is complete - which does not happen until the "longpress" is released. So, the following should do what you want. NOTE: I think you want to "start shaking" a view when the long-press is recognized, then "stop shaking" the view when the fingers are released. I just pretended you have functions for that. Substitute appropriately.
- (void)handleLongPress:(UILongPressGestureRecognizer*)gestureRecognizer
{
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
StartShakingView(gestureRecognizer.view);
} else if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
StopShakingView(gestureRecognizer.view);
}
}
The Apple Touches sample includes code that demonstrate using both UIResponder and UIGestureRecognizer methods.
Either should work for what you're doing.
Simple answer - you could make the image a UIButton, and start the wiggle on TouchDown, and stop it on TouchUpInside or TouchUpOutside
It sounds like you want to subclass UIGestureRecognizer, which, as I recall, gets the touchesBegan:... and associated methods. Read the notes on subclassing in the UIGestureRecognizer reference. Or use a UIButton as SomaMan suggests.

UIPanGestureRecognizer in iOS 4.3 not working

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!

MKMapview with UIGestureRecognizers

So I have a UIView as my Root view on my iPad. I add some subviews to it, amongst which there's also an MKMapView.
The thing I am trying to achieve ist to detect a 3-finger swipe across the screen so I can react to it. Furthermore I need to distinguish between 3-finger swipe to the left or to the right.
Before I added the mapview, I was experimenting with touchesMoved etc. Since I found out this to be inaccurate, I moved to using UISwipegestureRecognizer which worked well.
Anyway, once I added the Map, it ate all my touches. So I kept looking for answers.
This one seemed promising:
Intercepting/Hijacking iPhone Touch Events for MKMapView
as well as subclassing UIWindow and intercepting the touches.
Well, it turns out, none of them work well for me, since in both cases I end up either in
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
or in the situation to recognize the touch type etc.
This is, what I was trying to do in first place!
So my question is: Is there a way to use UIGestureRecognizers the way I described above to maintain my controls while keeping the functionality of the Mapview?
Have I decribed my problem accurately enough?
Greetz.
This answer describes subclassing a gesture recogniser to ensure that it does not interfere with other recognisers.
You could add a custom recogniser to MKMapView that intercepts three finger swipe (and reacts appropriately) but then allows all other gestures to be processed as normal by the MKMapView.
The link above give the example adding simple finger press by subclassing UIGestureRecognizer. For a three finger swipe I would subclass UISwipeGestureRecognizer.
I have had success using this method to add two finger rotate to a MKMapView without messing up pan and zoom etc.
The map should not "eat" all your touches. There's something wrong with that - I have successfully added long press and tap recognizers to the MKMapView, and they worked as expected.
Make sure to assign your controller the delegate of all the gesture recognizers you create, and be sure to implement the method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer :(UIGestureRecognizer *)otherGestureRecognizer;
You can gain further control by implementing
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
After that, it's very much up to the details of your implementation to make the gesture recognizer work with the map view. In my experience you can add your own, and it should not be interfering with your recognizers.
Some other thoughts:
- Overlay an invisible user interaction enabled view over the MKMapView to take events from it. Depending on how much interaction you need on the map view you could turn this on and off, etc.
- Add another swipe recognizer to the map view itself (in addition to the one on the parent view).
You could also play around with UIView hitTest method but for me that was always too low level.
question is old, but actual. my two cents, especially for OSX
If You need to detect pinch from region changes by code:
(I set an ivar to detect in in orther places of controller code)
func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
guard let view = mapView.subviews.first else{ return }
self.regionChangeIsFromUserInteraction = false
#if os(iOS)
guard let recognizers = view.gestureRecognizers else{ return }
#elseif os(OSX)
// on OSX they are in maps..
let recognizers = mapView.gestureRecognizers
#endif
for recognizer : AimGestureRecognizer in recognizers{
let state = recognizer.state
if state == .began || state == .ended {
self.regionChangeIsFromUserInteraction = true
break
}
}
#if DEBUG
print("regionChangeIsFromUserInteraction ", self.regionChangeIsFromUserInteraction)
#endif
}
Would removing the swipe gesture recognizer from your MkMapView resolve the issue?
UISwipeGestureRecognizer *leftSwipeGesture = [[UISwipeGestureRecognizer alloc] init];
UISwipeGestureRecognizer *rightSwipeGesture = [[UISwipeGestureRecognizer alloc] init];
leftSwipeGesture.direction = UISwipeGestureRecognizerDirectionLeft;
rightSwipeGesture.direction = UISwipeGestureRecognizerDirectionRight;
[YourMKMapView removeGestureRecognizer:leftSwipeGesture];
[YourMKMapView removeGestureRecognizer:rightSwipeGesture];
That way, MKMapView will not respond to swipe gestures and resigns responder to the container view controller of the MKMapView.

Difficulty activating 3 touch gesture recognizer in iOS app

I have an app that uses gesture recognizers quite a bit. From the studying I have done, I have found that there is the touchesBegan method of recognizing a gesture, and then there are gesture recognizers, which should be more slick.
The problem I am running into is that the gesture recognizers aren't nearly as responsive or accurate as the touchesBegan method, but are a lot easier to implement, which is obviously why I am using them. If I want to have a 3 finger gesture detected with a gesture recognizer, it is quite difficult because I have to press down my 3 fingers at the EXACT same time, or else it won't fire. This is in contrast to the touchesBegan method that just knows how many fingers you have down at any point.
Am I missing something with the implementation of this seemingly nice gesture feature that is making it not very responsive? I have set the max and min touches to 3, is that incorrect?
Please help. Thanks!!
The reason you need to press at the exact same time is because, by default, only one gesture recognizer can be recognized at a time. So once you press one finger down that recognizer automatically blocks the other two.
Try implementing the UIGestureRecognizerDelegate and using:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
I'm not sure if this will solve the issue or not but it might.
Cheers.