How to delete the last annotation on a mapView - ios

I have added a button on my .xib file with which I want to delete the last annotation that has been added.
So on the Touch-Down action I have implemented this:
-(IBAction)DeleteAnnotation:(id)sender {
[mapview removeAnnotation:[mapview.annotations lastObject]];
}
and I have even tried it this way:
-(IBAction)DeleteAnnotation:(id)sender {
[self.mapview removeAnnotation:self.mapview.annotations.lastObject]];
}
where mapview is my MKMapView Outlet.
The problem I encounter with both ways is that I have to press this specific button quite a few times before an annotation is removed.
Furthermore, the annotations remove themselves in a quite random way.
Is there something I am doing wrong or is it a software and simulator matter?

The annotations property of MKMapView is not guaranteed to return the annotations in the same order that you added them.
Making the assumption that the annotations array property will return the annotations in the same order that you added them is most likely the reason for the "strange" behavior you see. Please see these related answers for some more details:
MKMapView annotations changing/losing order?
How to reorder MKMapView annotations array
To get the behavior you want (which I assume is simply "remove the last annotation that was added explicitly by my code"), here are three possible approaches (there may be others):
Simplest approach is to keep a reference in a strong property to the last annotation you add (update the reference when you call addAnnotation). When you want to remove the "last annotation added", pass that saved reference to removeAnnotation. For example:
//in the interface...
#property (nonatomic, strong) id<MKAnnotation> lastAnnotationAdded;
//in the implementation...
//when you add an annotation:
[mapview addAnnotation:someAnnotation];
self.lastAnnotationAdded = someAnnotation; //save the reference
//when you want to remove the "last annotation added":
if (self.lastAnnotationAdded != nil)
{
[mapview removeAnnotation:self.lastAnnotationAdded];
self.lastAnnotationAdded = nil;
}
Another option is to loop through the map view's annotations array and search for the "last" annotation (or whatever attribute you're interested in). Once you have a reference to the "last" one (which may not necessarily be the last object in the array), you can call removeAnnotation on it. This approach assumes you have some property in the annotation objects themselves that let you identify an annotation as the "last" one. This may not always be possible.
Another option is to keep your own array of annotations and add annotation objects to this array whenever you call addAnnotation. This is similar to keeping the single reference to just the "last annotation added" except you keep track of the entire list in an order you can rely on. To remove the "last" one, you would get lastObject from your array instead of the map view's (assuming you keep the array in that order). You have to make sure to keep your array in sync as you add/remove annotations from the map.

Related

iOS Swift: how to check if Object exists in an Array so I don't add it to it, or re-create a GMSMarker with it?

I've been working with gmaps for iOS sdk and have this problem: each time the map pans or zooms and finishes(Im using the Delegate idleAtCameraPosition) in my serverside I compare the coordinates of the center of the map, in order to mark pins of places around it. I save the places in an array of "Places" objects, when they are close enough(1km) from the center coordinates.
My problem is that I haven't been able to avoid adding repeated objects to the array, so every time I do pan the map but don't change the camera position that much, the existing markers in the screen are re-added to the Places array. I tried using this code, but it doesn't work and the array can grow infinitely until crashing the app:
if(!arrayProp.contains(place)){
arrayProp.append(place)
}
but it doesn't work.
I also have another array of GMSMarkers that feeds itself from the Places array; tried this code to avoid re-creating markers in the map with Places objects that already existed:
for place in arrayProp {
let pinPlace = PlaceMarker(propied: place)//custom GMSMarker class
if(self.markerArray.contains(pinPlace) == false ) {
self.markerArray.append(pinPlace)
pinPlace.map = self.mapaGoogle
} else {
pinPlace.map = nil
}
}
Any help would be greatly appreciated; thank you
Probably your Place is using a default definition of Equatable which is not what you expect. You'll need something like this for contains() to work as expected:
struct Place: Equatable {
let there: Bool
}
func ==(lhs: Place, rhs: Place) -> Bool {
return lhs.there == rhs.there
}
Check Place's definition, or create one if it's using a default that checks only the necessary fields.
Have you tried using a Set? Swift supports a Set collection type that handles uniqueness for you. The type you store in the set just needs to implement Hashable, which you can implement on your GMSMarker class. (Or, if it's not your class, you can add an extension to implement the Hashable protocol)
See Apple's documentation on Sets.
If you're loading all of the new objects anyways, then you can just empty out the existing array before adding the new values. self.markerArray.removeAll() then add your new objects.

Undoing last drawn Polyline

I have been working on a polyline overlay and have been helped very well when I was stuck, I am hoping for some more assistance again.
In my project I am drawing polylines on a map from touches began to touches moved. the coordinates of where my finger is dragged is added to my array and displayed.
When I want to clear my lines i simply empty the array, this is fine
However, If i want to remove/undo the last line drawn. I was under the impression that I could just minus the last value from my array, but while thinking more deeply I realized that I may have a problem. Each element in the array is a coordinate where my finger last touched.
I havent tried this yet, but I am imagining if I only minus the last value of the array, I am going to spend a long time "erasing" a line which I drew.
I was looking for a method, by which when i press "Undo" it erases the entire line i drew. Even if that line extends from North America to England.
The OP wishes to be able to undo the last line that was drawn. The problem he imagines is that removing a single element from the array would visually only remove a part of the line drawn by the user.
What he wishes to do is to remove all the elements within the array that make up the line.
I haven't done a lot on drawing but my solution to your problem should work.
Do the following, create an enumerated data type that would create three possible states for each coordinate drawn to the screen/added to the array.
This data type would look something like this:
typedef NS_ENUM(NSInteger, PKLinePointState) {
PKLinePointStart,
PKLinePointMiddle,
PKLinePointEnd
};
The idea here is that you will use this data type to determine which elements in your array are the starting points and which elements in your array are the ending points. This way you can make determine the lines in your array, and so therefor be able to determine how many elements to delete until you hit a coordinate that has status of PKLinePointStart.
You don't want to store just coordinates in your array, instead you want to store a data model which will hold more information so that you can do the sexy things you wish to accomplish.
The first data model will be used to store the start and ending coordinates and elements within the array. This will make it easier work with your coordinates. This is what it would look like:
LinePointModel.h
typedef NS_ENUM(NSInteger, PKLinePointState) {
PKLinePointStart,
PKLinePointMiddle,
PKLinePointEnd
};
#interface LinePointModel : NSObject
#property (assign) CGPoint linePointCoordinate;
#property (assign) PKLinePointState linePointState;
#end
//you may want to create a custom initialiser so that the coordinate and state are initialised straight away.
//It would look something like this -(instanceType)initWithLinePointCoordinate:(CGPoint)coordinate andLinePointState:(PKLinePointState)linePointstate;
LinePointModel.m
//I'll let you complete the initialiser method in the implementation file.
Now that you've created your data model that will store your coordinates properly in the coordinates array, here's how I would add them to the array.
The logic Phase 1:
When the user taps on a screen - the touchesBegan method gets called, you would want to create an instance of the LinePointModel and have its coordinate value set to the first coordinate the user taps on the screen and would set the state to PKLinePointStart which would mark the beginning of the line. Add this element to the array.
Then, when the user drags across the screen, you will receive multiple coordinates from the touchesMoved. Here you will create an instance of the same data model again setting the coordinate but this time the state will be stored as PKLinePointMiddle, we dont really care about these points, since we wont be checking against this state, but its still good to give it a value so that we know that any elements within the array that have this state only servers to form a point, points which eventually make a whole line. Here these instances will keep getting added to the array as many times as it needs until the user finally stops dragging - drawing the current line - on the screen.
Soon as the the touchesEnded method gets called - signifying the end of the line - the user lifting up his finger, this is where you create your last instance of the the data model, set the coordinate accordingly, and finally the state would be the PKLinePointEnd state. Again, you would add this instance of within the array too.
Now you have a meaningful custom array that keeps track of all the coordinates but lets you know which element has the starting state, midde and ending state.
Every starting state is coupled with the ending state with as many middle elements needed to make up the line.
If you want to stop here, you simply run the for loop, and determine the latest start and end elements added within your array. :)
The logic Phase 2 - Improving the algorithm:
Now you dont wan't to be looping through thousands of elements in an array just to determine how far in your array you have to go back to just to delete a line, that would be inefficient. This is where your second data model will be useful. This second data model will keep track of your lines and store which elements in your array make up its line by storing the start and end index or even better, store the array of the coordinates which is what we will do.
LineArrayModel.h
#interface LineArrayModel : NSObject
#property (nonatomic, retain) NSMutableArray *arrayOfLinePoints; //What matters.
//#property (assign) int lineNumber;
//#property (assign) CGFloat lineThickness;
//#property (strong, nonatomic) UIColor *lineColor;
#end
Using this data model, you would then create a temporary array whenever a user draws a line on the screen, and as soon as they are done drawing a line, you would grab the array and store it in an instance of LineArrayModel; and it's this instance that you would use to store in a new array called, myLinesArray. This is the array that you would then use to draw your lines on your screen.
Then to simply undo a line you just remove the last instance of LineArrayModel in the array that stores all your lines, and then simply redraw your lines if necessary.
Let me know how you get on.

MKPointAnnotation tag

Simple Question: Seems I cannot find the tag attribute for MKPointAnnotation class. It return error;
MKPointAnnotation *annotation = [[HCIAnnotationViewController alloc]
initwithHouse:house];
The following returns error (Property tag not found ob object of type "MKPointAnnotation"
NSLog(#"%d",annotation.tag);
My question is, if Im not allowed to set the tag, How am I supposed to detect which annotation was clicked.
The other approaches I followed are
Setting tag for MkAnnotationView, However in this what I found out is that the last annotation when added doesn't immediately call for viewForAnnotation (Might be because I'm adding around 1000 MkPointAnnotations to a small map, so it only calls when it comes to view.).
Please tell me how to resolve this?
Since MKPointAnnotation is not a subclass of UIView, has not a property called tag. Bu you have the property coordinate. I assume you have different coordinates for all of your annotations. So you can detect which one.
You just need to compare the coordinates.

change annotation image after its created

I have a custom annotation that sets its image based on the type of the annotation using the viewForAnnotation delegate method. Im only using 1 annotation that represents a car moving and want to change the image for when the car is detected to be moving and stopped. How could I go about this besides removing my annotation and re-adding it which causes a blink?
Wherever you detect that the car's state has changed, retrieve the annotation's current view using the MKMapView instance method viewForAnnotation:. This is not the same as the mapView:viewForAnnotation: delegate method.
After getting the current view for the annotation, you can modify its properties including image.
Also make sure the mapView:viewForAnnotation: delegate method has the same exact condition to set image based on the state of the car annotation. You may want to put the logic in a common method called from both places (where the state changes and the delegate method) so the code isn't duplicated.
For example, where the state changes, you might have:
//carAnnotation is your id<MKAnnotation> object
MKAnnotationView *av = [mapView viewForAnnotation:carAnnotation];
if (carAnnotation.isMoving)
av.image = [UIImage imageNamed:#"moving.png"];
else
av.image = [UIImage imageNamed:#"stopped.png"];
The if statement (or whatever logic you have to set image) is the part that should also be in the viewForAnnotation delegate method.

How to record all of the user's touches in iPhone app

note: This is an expansion (and clarification) of a question I asked yesterday.
I am conducting a research project where I want to record all of the user's touches in the iPhone app. After the experiment, I will be able to download the data and process it in either Excel or (more likely) Matlab and determine how many times they clicked on certain buttons, when they clicked certain buttons, etc. To do this, I would need to know:
a) When they touched
b) Where they touched
c) Which view they touched
The first two are easy, but the third I am having trouble with. I know I can do this to get the reference to the UIView that was touched:
CGPoint locationPoint = [[touches anyObject] locationInView:self];
UIView* viewYouWishToObtain = [self hitTest:locationPoint withEvent:event];
However, that will just give me a pointer to the view, not the name of the view that was touched. I could assign each view a tag, but then every time I create a new view I would need to remember to tag it (or, alternatively, log the address of each view when initialized and log it when the view is touched). Subclassing UIView and adding an automatic tag isn't really an option since I'm creating other UIButtons and UISliders and would need to subclass those also, which doesn't seem like a very good solution.
Does anyone know of a clean, easy way to do this?
For "Which view they touched", what information do you need?
Perhaps you could use a category to add a method to UIView. This method would generate a string containing information about the view. Such as:
its type e.g. UIButton etc.
its size and position
the title of the view, if it has one (e.g. the button title)
the parent view type and title
other stuff e.g. is the view enabled, what state it is in. anything you like.
For example: "Type:UIButton Title:"Back" Rect:{3,5,40,25}" or some such string.
This is very clean and gives you quite a lot of information to be going with.
You could add a category to UIView which would then be inherited by all UIView descended objects, although I'm not sure its any more efficient than tagging. Since a category can override methods then you could override init methods for automatic tagging I suppose.
http://macdevelopertips.com/objective-c/objective-c-categories.html
I'm not sure what you mean by the "name" of the view. If you mean the view name in Interface Builder, I don't believe it includes that in the instantiated objects. You could use the Tag attribute which is included, but that's just a number and not a name.

Resources