How can I check if a polyline has already been added to the map?
I've tried the following code, but it doesnt seem to work
for (MKPolyline *feature1 in self.mapView.overlays) {
NSLog(#"feature1.title: %#", feature1.title);
NSLog(#"Polu.title: %#", polu.title);
if (![feature1.title isEqualToString:polu.title]) {
NSLog(#"NOT");
[self.mapView addOverlay:polu];
}
else {
NSLog(#"Already added");
}
}
}
I've also tried this:
if (![self.mapView.overlays containsObject:polu]) {
NSLog(#"NOT");
[self.mapView addOverlay:polu];
}
The current for loop assumes the overlay exists or doesn't exist as soon as it finds one other overlay whose title doesn't match.
But at that point, the for loop might not have checked the remaining overlays (one of which might be the overlay you're looking for).
For example:
Assume there are already 4 overlays on the map with the titles A, B, C, and D.
Assume the overlay you want to check for (polu) has the title C.
The first overlay checked is A. Since A does not match C, the existing code immediately adds another overlay named C.
Then the for loop continues and looks at B. Again, since B does not match C, the existing code adds another overlay named C.
Then the loop continues, looks at C, and logs "already added".
Then the loop continues, looks at D, sees it doesn't match C, and adds another overlay.
Instead, you want to stop the loop when a matching title is found and if the loop ends with no match found, then add the overlay.
Example:
BOOL poluExists = NO;
for (MKPolyline *feature1 in self.mapView.overlays) {
NSLog(#"feature1.title: %#", feature1.title);
NSLog(#"Polu.title: %#", polu.title);
//STOP looping if titles MATCH...
if ([feature1.title isEqualToString:polu.title]) {
poluExists = YES;
break;
}
}
//AFTER the loop, we know whether polu.title exists or not.
//If it existed, loop would have been stopped and we come here.
//If it didn't exist, loop would have checked all overlays and we come here.
if (poluExists) {
NSLog(#"Already added");
}
else {
NSLog(#"NOT");
[self.mapView addOverlay:polu];
}
In the second example in the question, containsObject: will only work if polu was the original object that was given the first time addOverlay was called because in this case, containsObject: will compare pointer addresses and not the title property of the overlays.
Related
I am having a heck of a time getting around some very annoying behavior in MapKit. Specifically, I want to show a popover annotation view after the map has animated to a certain visible region.
I can use mapView:regionDidChangeAnimated: to get a callback after the map transition (which I will call the "zoom"), but this only fires if the visible region actually changes.
You see the zoom happens in my app when an annotation (or its counterpart in a table view) is selected: I want to zoom the map to essentially focus on the selected annotation.
The problem is that if the map is already in the correct region, I will get no regionDidChange, and therefore no signal to show my popover view.
I am using variants of setVisibleMapRect: to actually perform the zoom. So I thought I was being smart by comparing the new and old MKMapRects to see if they are equal, and if so manually calling my callback.
The problem is it doesn't work! Even if I determine that the two MKMapRects are not equal, by means of MKMapRectEqualToRect, MapKit just sometimes decides it won't fire the regionDidChange event! Perhaps it has to be over a certain delta or something, I don't know.
So my question is: what the heck is the expected best practice for getting a guaranteed completion from setVisibleMapRect:?
Update: I'm now thinking this might have to do with the edgePadding argument which I am also using:
- (BOOL)vivoSetVisibleMapRect:(MKMapRect)mapRect edgePadding:(UIEdgeInsets)insets animated:(BOOL)animate {
BOOL equal = MKMapRectEqualToRect(self.visibleMapRect, mapRect);
NSLog(#"Map rects are: %#", equal ? #"equal" : #"NOT equal");
NSLog(#"%#\n\n%#", MKStringFromMapRect(self.visibleMapRect), MKStringFromMapRect(newRect));
[self setVisibleMapRect:mapRect edgePadding:insets animated:animate];
return !equal;
}
As you can see, the edge padding is not taken into account in the comparison, yet it is most likely having an effect on the finally computed mapRect.
Does anyone know how I can perform this test to properly take into account edgePadding?
Update 2: It's now looking like MKMapRectEqualToRect is wrong and therefore completely useless. Check out this log statement:
2016-03-16 17:06:30.841 Mobile[70089:6240786] Map rects are: NOT equal
{{42403042.3, 91858289.9}, {14878.4, 12832.6}}
{{42403042.3, 91858289.9}, {14878.4, 12832.6}}
They look pretty darn equal to me!! đ
MapRects are defined using Doubles, and comparing Doubles can give unexpected behavior. My recommendation is to define your own comparison that compares to your desired tolerance. For instance, compare to the nearest integer value.
In Swift:
public extension Double {
func roundToInt() -> Int {
let value = Int(self)
return self - Double(value) < 0.5 ? value : value + 1
}
}
public func == (lhs: MKMapRect, rhs: MKMapRect) -> Bool {
if lhs.origin.x.roundToInt() != rhs.origin.x.roundToInt() {return false}
if lhs.origin.y.roundToInt() != rhs.origin.y.roundToInt() {return false}
if lhs.size.width.roundToInt() != rhs.size.width.roundToInt() {return false}
if lhs.size.height.roundToInt() != rhs.size.height.roundToInt() {return false}
return true
}
You can then do a comparison by typing
if mapRect1 == mapRect 2 {
...
}
It turns out MKMapRectEqualToRect is completely broken (at least on the simulator). Don't use it!!
I changed it to instead do a comparison on the strings returned by MKStringFromMapRect, as well as adjusting the map rect with mapRectThatFits:edgePadding: before the comparison and it now appears to be working correctly:
- (BOOL)vivoSetVisibleMapRect:(MKMapRect)mapRect edgePadding:(UIEdgeInsets)insets animated:(BOOL)animate {
MKMapRect newRect = [self mapRectThatFits:mapRect edgePadding:insets];
BOOL equal = [MKStringFromMapRect(self.visibleMapRect) isEqualToString:MKStringFromMapRect(newRect)];
[self setVisibleMapRect:newRect animated:animate];
return !equal;
}
I've setup CEMarkerGroup's according to my data, and have successfully displayed them. According to Citymaps' documentation, they indicate the following:
USING A MARKER GROUP
Marker groups allow you to organize your markers and perform functions on all markers in the group simultaneously, and also perform certain operations which you would need to implement yourself otherwise.
However, there don't appear to be any exposed class or instance methods that allow action on a particular group. Below, I've setup code
CEMarkerGroup *grpCondo = [self.mapView markerGroupWithName:#"grpCondo"];
CEMarkerGroup *grpRental = [self.mapView markerGroupWithName:#"grpRental"];
CEMarkerGroup *grpCoOp = [self.mapView markerGroupWithName:#"grpCoOp"];
CEMarkerGroup *grpCondop = [self.mapView markerGroupWithName:#"grpCondop"];
Later, as I loop through the list of markers I'm adding, I specify the group based on a category (cat) value.
if ([cat isEqualToString:#"Condo"]) {
[grpCondo addMarker:marker];
}
if ([cat isEqualToString:#"Condop"]) {
[grpCondop addMarker:marker];
}
if ([cat isEqualToString:#"Rental Unit"]) {
[grpRental addMarker:marker];
}
if ([cat isEqualToString:#"Co-op"]) {
[grpCoOp addMarker:marker];
}
These groups, already associated with my map object, display fine, but I cannot find any way to act on these individual groups (e.g., hide a group, show a group, etc.)
Any thoughts out there?
Thanks!
I am a developer at Citymaps.
CEMarkerGroup is rather bare, and for the most part just a way to organize where your objects are.
The only batch operation we have a on a marker group right now is to remove all markers in that group from the map. We also have the collision detection feature, which I saw your other post on.
CEMarkerGroup does provide readonly access to its markers, if you want to perform some action to each CEMarker in the group.
EDIT: To answer your comment, here is a code sample of how to toggle markers in a marker group.
// This would be your toggled value
BOOL showRentals = YES;
for(CEMarker *rentalMarker in grpRental.markers) {
// This property is not yet exposed. This would have the marker automatically fade in or out based on fadeTime.
//rentalMarker.hidden = !showRentals;
// You can use this as a proof of concept
rentalMarker.alpha = showRentals ? 1.f : 0.f;
}
Is there a method that gets called every time a node is deleted? or added?
Along the terms of touchesBegan a method that gets called every time a touch on the screen happens.
or touchesMoved a method that gets called when a node gets touched and moved.
So is there such a method that gets called when a node gets removeFromParent?
I am currently using the update method to detect if a node is still present like:
SKNode *n = [self childNodeWithName: #"n"];
if (!n)
{
// no n
if (life != 0)
{
[self addChild:_n);
life--;
}
}
Now when ever that node is missing I remove 1 life, and then add the node again.
The problem is the update method is running too fast that 2 lives are removed every time the node is removed. I believe this is because adding the node back is kinda slower that the update method. So it takes 2 loops on the update method before it detects that the node is present again.
There is no such method. But you can create one.
Create a subclass of (for example) SKSpriteNode and override all "remove" methods (or just those you are using). Within that method send a message to whichever object needs to receive the removal event, or send a notification and register other objects to receive that notification. Don't forget to call the super implementation of the overridden method.
if you run this lines inside update
SKNode *n = [self childNodeWithName: #"n"];
if (!n)
that statement will run forever until you stop the game or your node is not equal to nil
to correct that behaviour you need to create a BOOL value
BOOL _shouldProceed;
change the name if you want
when you create your node set BOOL value to true or yes
- (void)createNode {
_shouldProceed = YES;
// Your implementation for the node
}
and put this inside update
SKNode *n = [self childNodeWithName:#"n"];
if (!n && _shouldProceed) {
_shouldProceed = NO;
if (life != 0) {
[self createNode];
life--;
}
}
if your node is equal to nil and BOOL value is equal to true/yes
you need to set BOOL value to false/NO
with this approach this statement only will happens once
when you create a new node with
[self createNode];
the BOOL value is set to true/YES
and that should solve your problems
Good Luck!!
I kind of understand recursion. We start on one method and it calls itself until it reaches its base case. Please help me understand how this code works. I know we are popping off an object off stack each time it is called we are returning a double at the end when the base case is reached.Is stack being modified each time it is called? For example: 3 (Enter) 5 (Enter) * would obviously equal 15. However based on the method when I look at it I follow that it goes into multiply if statement sends stack (3,5) returns 5 then on the second call sends(3,5) and returns 5 again? Why is the second one being sent stack(3) instead of (3,5)?
+ (double)popOperandOffProgramStack:(NSMutableArray *)stack
{
double result = 0;
id topOfStack = [stack lastObject];
if (topOfStack) [stack removeLastObject];
if ([topOfStack isKindOfClass:[NSNumber class]])
{
result = [topOfStack doubleValue];
}
else if ([topOfStack isKindOfClass:[NSString class]])
{
NSString *operation = topOfStack;
if ([operation isEqualToString:#"+"]) {
result = [self popOperandOffProgramStack:stack] +
[self popOperandOffProgramStack:stack];
} else if ([#"*" isEqualToString:operation]) {
result = [self popOperandOffProgramStack:stack] *
[self popOperandOffProgramStack:stack];
} else if ([operation isEqualToString:#"-"]) {
double subtrahend = [self popOperandOffProgramStack:stack];
result = [self popOperandOffProgramStack:stack] - subtrahend;
} else if ([operation isEqualToString:#"/"]) {
double divisor = [self popOperandOffProgramStack:stack];
if (divisor) result = [self popOperandOffProgramStack:stack] / divisor;
}
}
return result;
}
You write:
I know we are popping off an object off stack each time it is called we are returning a double at the end when the base case is reached.Is stack being modified each time it is called?
So you know an object is popped off the stack, which means the stack changes. So why would you think the stack as not modified?
Are you maybe confusing the contents of the variable stack, with the object it references? The contents of the variable stack do not change - that contents is a reference to an object (or type NSMutableArray). However the internal state of the referenced object change, each time removeLastObject is called.
Think of it like a house - which has a street address (the reference to the object), and some people in it (the internal state of the object) - as people enter and leave the house (items pushed and popped from the stack) then the internal state keeps changing, but the street address of the house never changes.
HTH.
it goes into multiply if statement sends stack (3,5) returns 5 then on the second call sends(3,5) and returns 5 again?
No. After the first call the stack is reduced. On the second call it sends only (3). Relevant code modifying the stack:
if (topOfStack) [stack removeLastObject];
I have a GPSTestViewController class that has a MKMapView with added MKAnnotations (stored in a class called Bases). I'm trying to continuously update the coordinates for the MKAnnotations (with the updateBaseInformation method in Bases) so the bases is moving on the map. The update is invoked from the GPSTestViewController method locationUpdate (since it's called every second):
- (void)locationUpdate:(CLLocation *)location {
NSLog(#"locationUpdate");
self.cachedLocation = location;
[self centerTo:cachedLocation.coordinate];
//Trying to update the coordinates every second
[bases updateBaseInformation]; <--Program received signal: âEXC_BAD_ACCESSâ
return;
}
But then I get the following message:
[bases updateBaseInformation]; Program received signal: âEXC_BAD_ACCESSâ
Bases.m contains the following code (and even when some code is commented it crashes):
- (void)updateBaseInformation {
NSLog(#"Updating base information");
for(MyAnnotation *a in bases)
{
//CLLocationCoordinate2D c;
if([a.type compare:#"friend"] == 0)
{
//c.latitude = a.coordinate.latitude+0.001;
//c.longitude = a.coordinate.longitude+0.001;
//a.coordinate = c;
}
else if([a.type compare:#"enemy"] == 0)
{
//[a setCoordinate:CLLocationCoordinate2DMake(a.coordinate.latitude+0.002, a.coordinate.longitude+0.0012)];
}
}
}
My guess is that I'm accessing the objects that are already accessed somewhere else and that causes the EXC_BAD_ACCESS. I have spent many hours on searching, but without results. Why do I get this error and how should I do in order to make the Annotations move around on the map?
I have uploaded the complete project to (link removed).
Solution
The problem is now solved. The problem was that the array that holds the Annotations was autoreleased. So I changed the allocation from
bases = [NSMutableArray array];
to
bases = [[NSMutableArray array] retain];
Thanks in advance.
My guess is that I'm accessing the objects that are already accessed
somewhere else
You are probably accessing an object that no longer exists in memory - You need to check you are not deallocating an object which you are subsequently accessing. You should turn on Zombies for your scheme which will allow you to see when you are accessing a deallocated object by effectively keeping an object's memory under watch after the object has been deallocated. You can switch them on here: Scheme/edit scheme/enable zombie objects.