Detecting Tap on MKPolygonView in MKMapView on iOS7 - objective-c

Based on what I found on this SO question (Touch events on MKMapView's overlays), I have implemented a way to intercept tap gesture on MKPolygon.
It was working fine in our app that was built using Xcode 4.6.3 against iOS 6. However things stopped working when I tried it on iOS 7 devices.
Specifically
CLLocationCoordinate2D coord = [neighborhoodMap_ convertPoint:point
toCoordinateFromView:neighborhoodMap_];
// We get view from MKMapView's viewForOverlay.
MKPolygonView *polygonView = (MKPolygonView*) view;
CGPoint polygonViewPoint = [polygonView pointForMapPoint:mapPoint];
BOOL mapCoordinateIsInPolygon = CGPathContainsPoint(polygonView.path,
NULL,
polygonViewPoint,
NO);
For some reason the call to CGPathContainsPoint no longer returns YES even the given coordinates is within the MKPolygonView. Not sure if anyone has hit this problem, but I would appreciate any insights you may have.
Thanks!

Since iOS 7 you need to use the MKOverlayRenderer:
BOOL tapInPolygon = NO;
MKOverlayRenderer * polygonRenderer = [mapView rendererForOverlay:polygonOverlay];
if ( [polygonRenderer isKindOfClass:[MKPolygonRenderer class]]) {
//Convert the point
CLLocationCoordinate2D coordinate = [self.mapView convertPoint:tapPoint
toCoordinateFromView:self.mapView];
MKMapPoint mapPoint = MKMapPointForCoordinate(coordinate);
CGPoint polygonViewPoint = [polygonRenderer pointForMapPoint:mapPoint];
// with iOS 7 you need to invalidate the path, this is not required for iOS 8
[polygonRenderer invalidatePath];
tapInPolygon = CGPathContainsPoint(polygonRenderer.path, NULL, polygonViewPoint, NO);
}

I had the same problem and just reading through the docs I found that MKPolygonView has been deprecated in iOS7 and should use MKPolygonRenderer instead.

I was having the same problem and was able to fix it with a workaround, but it definitely looks like a bug on apple's end. I noticed that the "path" property was not NULL right as the MKpolygonView was created, but was NULL whenever I wanted to reference it. The solution is to add another property to the MKPolygonView subclass as follows:
#property CGPathRef savedPath;
and then you have to assign it when it's not NULL:
polygonOverlay.savedPath = CGPathCreateCopy(polygonOverlay.path);
Then just check against self.savedPath whenever needed. Again this is not supposed to be a permanent solution, but will solve the issue of targeting apps to ios6 on ios7 devices.

Related

UICollectionView stuck in infinite loop in iOS 7 since I started to use the iOS 8 SDK

So I've been updating an app I'm maintaining for iOS 8 but I'm experiencing problems in iOS 7 that I did not have when I was using the iOS 7 SDK.
If I don't touch the code at all it ends op in a loop (90% CPU without stopping) when the code calls into layoutSubviews. It changes a bit when I play and pause but validateLayoutInRect: always seems to be there.
When I change layoutSubviews in layoutIfNeeded it passes but gets stuck in a very similar way after calling the following line:
[collectionView layoutAttributesForItemAtIndexPath:indexPath];
update: enabling and setting auto layouts on the parent view makes it pass layoutSubviews as well, that part of the problem might have to do with auto layouts in the xibs but not on the hosting view controller
To me it seems that it gets stuck calculating the size, changing something that triggers a recalculation and so on. It's weird that it doesn't happen with iOS 7 SDK -> iOS 7 phone and iOS 8 SDK -> iOS 8 phone nor iOS 7 SDK -> iOS 8 phone.
I tried to put NSLog messages at various points where it could surface in my code again but nothing is getting called in a loop. The Collection View is filled by reusing .xib files. I would like to rebuild it to use just storyboards but it's a very complex screen and at this point I am actually not sure if this really is my bug or a bug in the SDK.
When I compiled with the iOS 8.1 SDK in Xcode 6.1 things already got a lot better. But there was still one screen crashing. Eventually I figured out some cells ended up being MAXFLOAT high and I needed to add a sanity check before applying that height:
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
Class class = [[self dataSource] cellClassForIndexPath:indexPath];
NSString *classString = NSStringFromClass(class);
UICollectionViewCell *cell = [self sizingCells][classString];
if (cell == nil)
{
cell = [class loadInstanceFromNib];
[self sizingCells][classString] = cell;
}
[[self dataSource] configureCell:cell forIndexPath:indexPath excludeKeys:#[#"imageURL", #"image"]];
CGSize fittingSize = CGSizeMake([collectionView frameSizeWidth], MAXFLOAT);
CGSize size = [cell systemLayoutSizeFittingSize:fittingSize];
if (size.height > 10000) { // Needed for iOS 8 SDK on iOS 7 device bug AMA-827
size.height = cell.frame.size.height; // Ridiculous size detected, whipping
}
return CGSizeMake([collectionView frameSizeWidth], size.height);
}
I arbitrarily decided that anything above 10000 probably wasn't what I intended and put in the cell frame height instead. Works in this case everywhere I need it!
Most of it isn't my code, don't slap me for the old fashioned style of CGRects everywhere ;)
I noticed it got downvoted twice. Please tell me what is wrong with this working answer.

Camera Rotation and OverlayView in iOS8

I am experiencing an issue when using the iPad Camera in iOS 8. I've seen some older questions and a thread on the Apple Developer Forums from during the beta but still haven't find a solution.
There seems to be two parts to this issue.
1) The camera itself rotates when the device orientation rotates, eg the world is on its side
2) When opening the camera in Landscape, the overlay does not appear. When opened in Portrait it is fine.
It is an app using iOS7 as the Base SDK, problem only occurs when running the app on a device that has been upgraded to iOS8. The app is not using storyboards, it is using nibs.
I'm hoping to push out a fix for this with Xcode 5.1.1 before moving onto the iOS8 specific fixes and using it as a Base SDK in the next version.
Here is my code to bring up the camera:
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera] == YES) {
// Create Camera
imagePicker = [[UIImagePickerController alloc] init];
imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
imagePicker.delegate = self;
imagePicker.showsCameraControls = NO;
// Set up custom controls view
[[NSBundle mainBundle] loadNibNamed:#"OverlayView" owner:self options:nil];
self.overlayView.frame = imagePicker.cameraOverlayView.frame;
imagePicker.cameraOverlayView = self.overlayView;
self.overlayView = nil;
// Show Camera
[self presentViewController:imagePicker animated:NO completion:nil];
[imagePicker release];
}
I have also tried
And the Layout of the Toolbar (sitting at the bottom) of the OverlayView:
If I change that to sit "at the top" it appears in both portrait and landscape! So it must have to do with the view/window/something size, though it's strange how its behaviour would change when the layout has stayed the same.
I have tried it with both showsCameraControls = YES and hashing out the OverlayView block, and problem #1 persists so it's not to do with the overlay at app.
I'm hoping someone has found an answer to this, it seems like quite a common problem.
Please let me know if you need any further details.
Edit 1: Fixed the Overlay (Issue #2)
It wasn't applying the orientation to the OverlayView, fixed it like this:
// Grab the window frame and adjust it for orientation - from http://stackoverflow.com/a/15707997/520902
UIView *rootView = [[[UIApplication sharedApplication] keyWindow]
rootViewController].view;
CGRect originalFrame = [[UIScreen mainScreen] bounds];
CGRect screenFrame = [rootView convertRect:originalFrame fromView:nil];
...
self.overlayView.frame = imagePicker.cameraOverlayView.frame;
I suspect that it's related to the camera not realising it's orientated too, will keep searching for a fix for Problem #1.
Edit 2: Update on Issue #1
Looks like the camera rotating might be an Apple issue. On iOS8 if you open up the Contacts app, edit a contact and choose 'Take Photo', the exact same issue occurs - in a default Apple app!
I still can't find a fix so I am just destroying and recreating the imagePicker on each orientation change for now, it's ugly but will suffice until Apple release a fix or a better solution pops up.
Apple fixed this problem in iOS 8.1.

Autoresizing issue of UICollectionViewCell contentView's frame in Storyboard prototype cell (Xcode 6, iOS 8 SDK) happens when running on iOS 7 only

I'm using Xcode 6 Beta 3, iOS 8 SDK. Build Target iOS 7.0 using Swift. Please refer to my problem step by step with screenshots below.
I have a UICollectionView in Storyboard. 1 Prototype UICollectionViewCell which contains 1 label in the centre (no autoresizing rule). Purple background was to mark a contentView that is generated in runtime by the Cell I guess. That view will be resized properly base on my UICollectionViewLayoutDelegate eventually, but not on iOS 7. Notice that I'm using Xcode 6 and the problem only happens on iOS 7.
When I build the app on iOS 8. Everything is okay.
Note: Purple is the contentView, Blue is my UIButton with rounded corner.
However, on iOS 7, all the subViews inside the Cell suddenly shrink to the frame of (0,0,50,50) and never conforms to my Autoresizing rule anymore.
I assume this is a bug in iOS 8 SDK or Swift or maybe Xcode?
Update 1: This problem still exists in the official Xcode 6.0.1 ! The best work around is like what KoCMoHaBTa suggested below by setting the frame in cellForItem of the cell (You have to subclass your cell though). It turned out that this is a incompatibility between iOS 8 SDK and iOS 7 (check ecotax's answer below quoted from Apple).
Update 2:
Paste this code at the beginning of your cellForItem and things should be okay:
/** Xcode 6 on iOS 7 hot fix **/
cell.contentView.frame = cell.bounds;
cell.contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
/** End of Xcode 6 on iOS 7 hot fix **/
contentView is broken. It can be also fixed in awakeFromNib
ObjC:
- (void)awakeFromNib {
[super awakeFromNib];
self.contentView.frame = self.bounds;
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
Swift3:
override func awakeFromNib() {
super.awakeFromNib()
self.contentView.frame = self.bounds
self.contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
I encountered the same problem and asked Apple DTS for help. Their reply was:
In iOS 7, cells’ content views sized themselves via autoresizing
masks. In iOS 8, this was changed, cells stopped using the
autoresizing masks and started sizing the content view in
layoutSubviews. If a nib is encoded in iOS 8 and then decode it on iOS
7, you’ll have a content view without an autoresizing mask and no
other means by which to size itself. So if you ever change the frame
of the cell, the content view won’t follow.
Apps being deploying back to iOS 7 will have to work around this by
sizing the content view itself, adding autoresizing masks, or adding
constraints.
I guess this means that it's not a bug in XCode 6, but an incompatibility between the iOS 8 SDK and iOS 7 SDK, which will hit you if you upgrade to Xcode 6, because it will automatically start using the iOS 8 SDK.
As I commented before, the workaround Daniel Plamann described works for me. The ones described by Igor Palaguta and KoCMoHaBTa look simpler though, and appear to make sense giving Apple DTS' answer, so I'll try those later.
I encountered the same issue and hope that Apple will fix this with the next Xcode version. Meanwhile I use a workaround. In my UICollectionViewCell subclass I've just overridden layoutSubviews and resize the contentView manually in case the size differs from collectionViewCell size.
- (void)layoutSubviews
{
[super layoutSubviews];
BOOL contentViewIsAutoresized = CGSizeEqualToSize(self.frame.size, self.contentView.frame.size);
if( !contentViewIsAutoresized) {
CGRect contentViewFrame = self.contentView.frame;
contentViewFrame.size = self.frame.size;
self.contentView.frame = contentViewFrame;
}
}
Another solution is to set the contentView's size and autoresizing masks in -collectionView:cellForItemAtIndexPath: like the following:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellID = #"CellID";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellID forIndexPath:indexPath];
// Set contentView's frame and autoresizingMask
cell.contentView.frame = cell.bounds;
cell.contentView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin |UIViewAutoresizingFlexibleTopMargin |UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleBottomMargin;
// Your custom code goes here
return cell;
}
This works with Auto Layout too, since auto resizing masks are translated to constraints.
In Xcode 6.0.1 contentView for UICollectionViewCell is broken for iOS7 devices.
It can be also fixed by adding proper constraints to UICollectionViewCell and its contentView in awakeFromNib or init methods.
UIView *cellContentView = self.contentView;
cellContentView.translatesAutoresizingMaskIntoConstraints = NO;
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[cellContentView]|"
options:0
metrics:0
views:NSDictionaryOfVariableBindings(cellContentView)]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[cellContentView]|"
options:0
metrics:0
views:NSDictionaryOfVariableBindings(cellContentView)]];
This will not work correctly without any of the other mentioned workarounds because of a bug in Xcode 6 GM with how Xcode compiles xib files into the nib format. While I cannot say for 100% certainty it is Xcode related and not having to do with runtime, I'm very confident - here's how I can show it:
Build+Run the application in Xcode 5.1.
Go to the simulator application's directory and copy the compiled .nib file for the xib you are having issues with.
Build+Run the application in Xcode 6 GM.
Stop the application.
Replace the .nib file in the newly built application's simulator folder with the .nib file created using Xcode 5.1
Relaunch the app from the simulator, NOT from Xcode.
Your cell loaded from that .nib should work as expected.
I hope everyone who reads this question will file a Radar with Apple. This is a HUGE issue and needs addressing before the final Xcode release.
Edit: In light of ecotax's post, I just wanted to update this to say it is now confirmed behavior differences between building in iOS 8 vs iOS 7, but not a bug. My hack fixed the issue because building on iOS 7 added the autoresizing mask to the content view needed to make this work, which Apple no longer adds.
The answers in this post work, what I never understood is why it works.
First, there are two "rules":
For views created programmatically (Ex. [UIView new]), the property translatesAutoresizingMaskIntoConstraints is set to YES
Views created in interface builder, with AutoLayout enabled, will have the property translatesAutoresizingMaskIntoConstraints set to NO
The second rule does not seem to apply to top-level views for which you do not define constraints for. (Ex. the content view)
When looking at a Storyboard cell, notice that the cell does not have its contentView exposed. We are not "controlling" the contentView, Apple is.
Deep dive into storyboard source code and see how contentView cell is defined:
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
Now the cell's subviews (notice the translatesAutoresizingMaskIntoConstraints="NO"):
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="NaT-qJ-npL" userLabel="myCustomLabel">
The contentView does not have it's translatesAutoresizingMaskIntoConstraints set to NO. Plus it lacks layout definition, maybe because of what #ecotax said.
If we look into the contentView, it does have an autoresizing mask, but no definition for it:
<autoresizingMask key="autoresizingMask"/>
So there are two conclusions:
contentView translatesAutoresizingMaskIntoConstraints is set to YES.
contentView lacks definition of a layout.
This leads us to two solutions which have been talked about.
You can set the autoresizing masks manually in awakeFromNib:
self.contentView.frame = cell.bounds;
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
Or you can set the contentView translatesAutoresizingMaskIntoConstraints to NO in awakeFromNib and define constraints in - (void)updateConstraints.
This is the Swift version of #Igor's answer which is accepted and thanks for your nice answer mate.
First Goto your UICollectionViewCell Subclass and paste the following code as it is inside the class.
override func awakeFromNib() {
super.awakeFromNib()
self.contentView.frame = self.bounds
self.contentView.autoresizingMask = [.FlexibleHeight, .FlexibleWidth]
}
By the way I am using Xcode 7.3.1 and Swift 2.3. Solution is tested on iOS 9.3 which is working flawlessly.
Thanks, Hope this helped.
In swift, place the following code in the collection view cell subclass:
override var bounds: CGRect {
didSet {
// Fix autolayout constraints broken in Xcode 6 GM + iOS 7.1
self.contentView.frame = bounds
}
}
I have found that there are also issues with contentView sizing in iOS 8. It tends to get laid out very late in the cycle, which can cause temporary constraint conflicts. To solve this, I added the following method in a category of UICollectionViewCell:
- (void)fixupContentView
{
#if __IPHONE_OS_VERSION_MAX_ALLOWED < 80100
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
if (NSFoundationVersionNumber <= NSFoundationVersionNumber_iOS_7_1) {
self.contentView.frame = self.bounds;
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin |UIViewAutoresizingFlexibleTopMargin |UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleBottomMargin;
} else {
[self layoutIfNeeded];
}
#endif
#endif
}
This method should be called after dequeuing the cell.
I fixed doing this:
override func layoutSubviews() {
contentView.superview?.frame = bounds
super.layoutSubviews()
}
see: here
Just make sure you check the check box "Autoresize subviews" in the nib for that collection view cell. It will work fine on both iOS 8 and iOS 7.

MKMapView in reverse, when compass mode using in my iphone app project

I am developing an iphone application,in that app i am showing the client Parking places in different locations on MKMapView. Now I need to use compass mode in MKMapView to show the client parking places in particular direction (SE,SW,NE,NW), for that I run this below code.
-(void)updateHeading:(CLHeading *) newHeading
{
NSLog(#"New magnetic heading: %f", newHeading.magneticHeading);
NSLog(#"New true heading: %f", newHeading.trueHeading);
double rotation = newHeading.magneticHeading * 3.14159/-180;
[mapView setTransform:CGAffineTransformMakeRotation(-rotation)];
[[mapView annotations] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
MKAnnotationView * view = [mapView viewForAnnotation:obj];
[view setTransform:CGAffineTransformMakeRotation(rotation)];
}];
}
Everthing works fine in MKMapView, but my MKMapView shows in reverse order, when the device starts rotating, I am facing this problem in ios5 and ios6 also.
NOTE: when I test this app in America, map shows correctly, while I test the app in my location (India) Map turns into reverse.
`
Thanks in advance for any help.
Is the map intended to be centered on the user while rotating? If so you could just set the MKMapView's userTrackingMode to MKUserTrackingModeFollowWithHeading.
If not then how about telling us what you see in magneticHeading, trueHeading and rotation when things go right and when they go wrong.

Disable double tap zoom in MKMapView (iOS 6)

in ios 5 i was able to disable the double tap zoom by just overriding it with a new double tap gesture. But it seems that the double tap gesture is no longer in the gesturerecognizer array that comes with the mkmapview.
NSArray *gestureRecognizers = [_mapView gestureRecognizers];
for (UIGestureRecognizer *recognizer in gestureRecognizers) {
NSLog(#"%#", recognizer);
}
returns nothing in ios 6, where in ios 5 it would return 2 recognizers, one for single tap and one for double tap.
I'd look through the gesture recognizers of MKMapView's subviews. It's probably still there somewhere.
Of course, messing around with another view's GRs is slightly dubious and will likely break the next time Apple changes something about MKMapView...
EDIT: For the benefit of anyone else reading this, please check that it's a UITapGestureRecognizer and that numberOfTapsRequired == 2 and numberOfTouchesRequired == 1.
Also, instead of disabling double-taps on the map entirely, consider adding a double-tap GR on the annotation and then do [mapDoubleTapGR requireGestureRecognizerToFail:annotationDoubleTapGR]. Again, hacky — don't blame me if it breaks on the next OS update!
This worked for me:
[_mapView.subviews[0] addGestureRecognizer:MyDoubleTapOverrider];
Do you want to let the user do anything with the view? If not, it sufficient to set userInteractionEnabled to NO. If so, what specific interactions do you need to allow? Everything but double-tapping? Why disable that one interaction?
The more we know about your use case, the better the answers we can provide.
This works for me:
//INIT the MKMapView
-(id) init{
...
[self getGesturesRecursive:mapView];
...
}
And then let the recursive function loop through the subviews and find the GR:s.
-(void)getGesturesRecursive:(UIView*)v{
NSArray *gestureRecognizers = [v gestureRecognizers];
for (UIGestureRecognizer *recognizer in gestureRecognizers) {
if ([recognizer isKindOfClass:[UITapGestureRecognizer class]]) {
[v removeGestureRecognizer:recognizer];
}
}
for (UIView *v1 in v.subviews){
[self getGesturesRecursive:v1];
}
}
This example removes all tap-GR:s. But I guess you can specify to remove whatever you'd like.
You can use a long tap gesture instead, that works.