How to delete all Annotations on a MKMapView - objective-c

Is there a simple way to delete all the annotations on a map without iterating through all the displayed annotations in Objective-c?

Yes, here is how
[mapView removeAnnotations:mapView.annotations]
However the previous line of code will remove all map annotations "PINS" from
the map, including the user location pin "Blue Pin". To remove all map
annotations and keep the user location pin on the map, there are two
possible ways to do that
Example 1, retain the user location annotation, remove all pins, add
the user location pin back, but there is a flaw with this approach, it
will cause the user location pin to blink on the map, due to removing
the pin then adding it back
- (void)removeAllPinsButUserLocation1
{
id userLocation = [mapView userLocation];
[mapView removeAnnotations:[mapView annotations]];
if ( userLocation != nil ) {
[mapView addAnnotation:userLocation]; // will cause user location pin to blink
}
}
Example 2, I personally prefer to avoid removing the location user pin
in the first place,
- (void)removeAllPinsButUserLocation2
{
id userLocation = [mapView userLocation];
NSMutableArray *pins = [[NSMutableArray alloc] initWithArray:[mapView annotations]];
if ( userLocation != nil ) {
[pins removeObject:userLocation]; // avoid removing user location off the map
}
[mapView removeAnnotations:pins];
[pins release];
pins = nil;
}

Here is the simplest way to do that:
-(void)removeAllAnnotations
{
//Get the current user location annotation.
id userAnnotation=mapView.userLocation;
//Remove all added annotations
[mapView removeAnnotations:mapView.annotations];
// Add the current user location annotation again.
if(userAnnotation!=nil)
[mapView addAnnotation:userAnnotation];
}

Here's how to remove all annotations except the user location, written out explicitly because I imagine I will come looking for this answer again:
NSMutableArray *locs = [[NSMutableArray alloc] init];
for (id <MKAnnotation> annot in [mapView annotations])
{
if ( [annot isKindOfClass:[ MKUserLocation class]] ) {
}
else {
[locs addObject:annot];
}
}
[mapView removeAnnotations:locs];
[locs release];
locs = nil;

This is very similar to Sandip's answer, except that it doesn't re-add the user location so the blue dot doesn't blink on and off again.
-(void)removeAllAnnotations
{
id userAnnotation = self.mapView.userLocation;
NSMutableArray *annotations = [NSMutableArray arrayWithArray:self.mapView.annotations];
[annotations removeObject:userAnnotation];
[self.mapView removeAnnotations:annotations];
}

You do not need to save any reference to user location. All that is needed is:
[mapView removeAnnotations:mapView.annotations];
And as long as you have mapView.showsUserLocation set to YES, you will still have user location on the map. Settings this property to YES basically asks the map view to start updating and fetching user location, to to show it on the map. From the MKMapView.h comments:
// Set to YES to add the user location annotation to the map and start updating its location

Swift version:
func removeAllAnnotations() {
let annotations = mapView.annotations.filter {
$0 !== self.mapView.userLocation
}
mapView.removeAnnotations(annotations)
}

Swift 2.0
Simple and the best:
mapView.removeAnnotations(mapView.annotations)

Swift 3
if let annotations = self.mapView.annotations {
self.mapView.removeAnnotations(annotations)
}

To remove one type of subclass you can do
mapView.removeAnnotations(mapView.annotations.filter({$0 is PlacesAnnotation}))
where PlacesAnnotation is a subclass of MKAnnotation

Here is the function to remove all markers as well as all routes (if any) from MKMapView:
func removeAppleMapOverlays() {
let overlays = self.appleMapView.overlays
self.appleMapView.removeOverlays(overlays)
let annotations = self.appleMapView.annotations.filter {
$0 !== self.appleMapView.userLocation
}
self.appleMapView.removeAnnotations(annotations)
}
Cheers

Related

MKMapview how to check (probe) if map is loaded?

How can I check if a map is loaded into MKKapview ? I mean... asking the map view to be ready - and not waiting for notification mapViewDidFinishLoadingMap: which doesn't notify if it is already buffered ?
My usecase for better understanding the problem: My App should print a map which is created in the background, using MKMapview. Loading the map can take a while, so without checking the readyness, the map will be incomplete. This can clearly be seen in the preview. Using mapViewDidFinishLoadingMap: helps for the first time. But if I print again just with a slightly different region, I will not get the notification.
This is my code snippest
-(IBAction)print:(id)sender {
[self prepareClonedMap];
}
-(void) prepareClonedMap {
[self cloneMap:scaling];
// I would like here ... if ( mapIsReady ) [self executePrint];
}
-(void)mapViewDidFinishLoadingMap:(MKMapView *)mapView {
[self executePrint];
}
-(void)executePrint {
// make MKMapSnapshot
// create printoperation
.....
}
-(void)cloneMap:(CGFloat) factor {
NSRect myframe;
// expand to max factor
myframe=mapView.frame;
myframe.size.height = myframe.size.height * factor,
myframe.size.width = myframe.size.width * factor;
// reduce to max dimensions
myframe.size = [self maxDimensionWithRatio:myframe.size];
[cloneView setFrame:myframe];
[cloneView setRegion:[mapView region]];
}

Has anyone found **legal** overrides to customize drawing of NSTabView?

BGHUDAppKit BGHUDTabView _drawThemeTab private API override now broken
For years, I have been using code originally based off of BGHUDAppKit, and found replacements for all of the private API that BGHUDAppKit overrides.
Except for one that I could not find a way to replace...
-[NSTabView _drawThemeTab:withState:inRect:]
(Note: I also use venerable PSMTabBarControl in many circumstances, so if all else fails I'll convert all my tab views to PSMTabBarControl)
Apple has now added the dark NSAppearance in 10.14 Mojave (so in ~10 years I can use it once we stop supporting High Sierra).
Whichever selfish dev at Apple writes NSTabView does not believe in making his view customizable, unlike all of the other NSControls which are customizable.
Here is part of the hackish overrides for custom drawing of NSTabView:
// until we can eliminate private API _drawThemeTab:, return nil for new NSAppearance
- (id) appearance { return nil; }
- (id) effectiveAppearance { return nil; }
-(void)_drawThemeTab:(id) tabItem withState:(NSUInteger) state inRect:(NSRect) aRect {
NSInteger idx = [self indexOfTabViewItem: tabItem];
int gradientAngle = 90;
NSBezierPath *path = nil;
aRect = NSInsetRect(aRect, 0.5f, 0.5f);
if([self tabViewType] == NSLeftTabsBezelBorder) {
gradientAngle = 0;
} else if([self tabViewType] == NSRightTabsBezelBorder) {
gradientAngle = 180;
}
NSColor *specialFillColor = [tabItem color];
NSColor *outlineColor = nil;
NSString *name = [specialFillColor description];
// MEC - added new prefix 12/15/17 to fix white border around last segment in High Sierra
if ( [name hasPrefix:#"NSNamedColorSpace System"] || [name hasPrefix:#"Catalog color: System controlColor"])
specialFillColor = nil;
else if ( [name isEqualToString: #"NSCalibratedWhiteColorSpace 0 1"] )
[specialFillColor set];
else
{
outlineColor = specialFillColor;
specialFillColor = nil;
}
... etc ...
It's probably preferrable to completely disable NSTabView's drawing (setting its tabViewType to NSNoTabsNoBorder), and create a custom segmented bar view to draw the selection separately (as a sibling view). This allows you to completely control the appearance, layout, and sizing of that custom implementation rather than relying on any details of NSTabView.
Looking at the view hierarchy of an NSTabViewController, you can see that it has this same approach by using an NSSegmentedControl as a separate subview managing selection from the NSTabView.

Showing a button when all 'enemy' ccsprites have been removed from scene

I am using SpriteBuilder to make a game. The objective is to destroy some CCSprites. I have 3 sprites on screen and are destroyed by another sprite, so the code must have something to do with when there are no more 'enemy' sprites remaining a next button must show. I have looked on the internet and are inexperienced with Cocos2D coding. Here is the code I have used to get rid of the 'enemy'
-(void)ccPhysicsCollisionPostSolve:(CCPhysicsCollisionPair *)pair danald:(CCNode *)nodeA wildcard:(CCNode *)nodeB {
float energy = [pair totalKineticEnergy];
if (energy > 5000.f) {
[self danaldRemoved:nodeA];
}
}
If the object is hit with a certain speed it will call the method below
- (void)danaldRemoved:(CCNode *)Danald {
CCParticleSystem *explosion = (CCParticleSystem *)[CCBReader load:#"Explosion"];
explosion.autoRemoveOnFinish = TRUE;
explosion.position = Danald.position;
[Danald.parent addChild:explosion];
[Danald removeFromParent];
}
Thanks in an advanced, sorry if this question has been asked before but I cannot find it
Well I would suggest this method:
Create a variable where you store the number of sprites left. For example:
int spritesLeft;
And then initialize it to 0:
-(void) didLoadFromCCB{
//REST OF CODE
spritesLeft=3; //3 because you said there are only 3.
}
Now when you call danaldRemoved: method, just subtract 1 to spritesLeft, and check if spritesLeft is equal to 0. If it's true, just call your method to make a button appear:
- (void)danaldRemoved:(CCNode *)Danald {
spritesLeft--; //substract 1
CCParticleSystem *explosion = (CCParticleSystem *)[CCBReader load:#"Explosion"];
explosion.autoRemoveOnFinish = TRUE;
explosion.position = Danald.position;
[Danald.parent addChild:explosion];
[Danald removeFromParent];
//check if game is over.
if (spritesLeft == 0){
[self printButton];
}
}
Now create the method printButton, but before go to SpriteBuilder, create the button and place it where you want. Now uncheck 'Visible' value, and then go to code connections, and select 'Doc root var' (under custom class) and write a name for the button, for example: nextButton. At the selector value write: changeLevel and target: document root
Now declare it at the top of your .m file as you did with any other objects:
CCButton *nextButton;
Method for button (just set visibility ON)
-(void) printButton{
nextButton.visible = YES;
}
And now your method to change level:
-(void) changeLevel{
CCScene *nextLevel = [CCBReader loadAsScene:#"YOUR LEVEL"];
[[CCDirector sharedDirector] replaceScene:nextLevel];
}
Hope this helps!
EDIT: HOW TO DETECT WHEN A SPRITE GOES OFF THE SCREEN
As I said, create any kind of physic object in spritebuilder. For example, I use CCNodeColor. Then make it a rectangle and place it at left of the screen. Now go to physics, enable physics, polygon type and static. Now in connections, select doc root var and call it _leftNode. Now repeat with top,right and bottom and call them _topNode, etc.
Now go to code, declare your new nodes: CCNode *_leftNode; and so...
Now let's make a collision type:
_bottomNode.physicsBody.collisionType = #"_bound";
_leftNode.physicsBody.collisionType = #"_bound";
_rightNode.physicsBody.collisionType = #"_bound";
_topNode.physicsBody.collisionType = #"_bound";
And do the same with your sprite, but I think you have done that before. Let's make an example:
spritename.physicsBody.collisionType = #"_sprite";
So now implement the method:
-(void)ccPhysicsCollisionPostSolve:(CCPhysicsCollisionPair *)pair _sprite:(CCNode *)nodeA _bound:(CCNode *)nodeB {
[_physicsNode removeChild:nodeA cleanup:YES];
}
And that's all.

Check if mapView already contains an annotation

I have a method of adding secondary nearby annotations (ann2) when I tap on another annotation (ann1). But when I deselect and re-select the exact same annotation (ann1) the ann2 re-creates it self and is getting added again. Is there a way to check if the annotation already exists on the map and if yes then do nothing otherwise add the new annotation. I have already checked this: Restrict Duplicate Annotation on MapView but it did not help me.. Any advice is appreciated. This is what I have so far:
fixedLocationsPin *pin = [[fixedLocationsPin alloc] init];
pin.title = [NSString stringWithFormat:#"%#",nearestPlace];
pin.subtitle = pinSubtitle;
pin.coordinate = CLLocationCoordinate2DMake(newObject.lat, newObject.lon);
for (fixedLocationsPin *pins in mapView.annotations) {
if (MKMapRectContainsPoint(mapView.visibleMapRect, MKMapPointForCoordinate (pins.coordinate))) {
NSLog(#"already in map");
}else{
[mapView addAnnotation:pin];
}
In this case I get the log already on map but I also get the drop animation of the annotation adding to the map. Any ideas?
Thank you in advance..
Your for loop isn't checking if the annotation is on the screen, it is checking if the coordinates of the pin are currently within the visible area. Even if it was checking if the pin object was already in the mapView.annotations it would never be true, because you've only just created pin a few lines earlier, it can't possibly be the same object as on in the mapView.annotations. It might though have the same coordinates and title, and that's what you need to check:
bool found = false;
for (fixedLocationsPin *existingPin in mapView.annotations)
{
if (([existingPin.title isEqualToString:pin.title] &&
(existingPin.coordinate.latitude == pin.coordinate.latitude)
(existingPin.coordinate.longitude == pin.coordinate.longitude))
{
NSLog(#"already in map");
found = true;
break;
}
}
if (!found)
{
[mapView addAnnotation:pin];
}
Annotations array exist in map object so you just have to check
if ( yourmap.annotations.count==0)
{
NSLog(#"no annotations");
}
NSNumber *latCord = [row valueForKey:#"latitude"];
NSNumber *longCord = [row valueForKey:#"longitude"];
NSString *title = [row valueForKey:#"name"];
CLLocationCoordinate2D coord;
coord.latitude = latCord.doubleValue;
coord.longitude = longCord.doubleValue;
MapAnnotation *annotation = [[MapAnnotation alloc]initWithCoordinate:coord withTitle:title];
if([mkMapView.annotations containsObject:annotation]==YES){
//add codes here if the map contains the annotation.
}else {
//add codes here if the annotation does not exist in the map.
}
if (sampleMapView.annotations.count > 0) {
sampleMapView.removeAnnotation(detailMapView.annotations.last!)
}
Following my comment on Craig's answer, I think the solution could look like something like this :
import MapKit
extension MKMapView {
func containsAnnotation(annotation: MKAnnotation) -> Bool {
if let existingAnnotations = self.annotations as? [MKAnnotation] {
for existingAnnotation in existingAnnotations {
if existingAnnotation.title == annotation.title
&& existingAnnotation.coordinate.latitude == annotation.coordinate.latitude
&& existingAnnotation.coordinate.longitude == annotation.coordinate.longitude {
return true
}
}
}
return false
}
}
This code allows you to check if a mapView contains a given annotation. Use this in a "for" loop on all your annotations:
for annotation in annotations {
if mapView.containsAnnotation(annotation) {
// do nothing
} else {
mapView.addAnnotation(annotation)
}
PS: this works well if you need to add new annotations to a mapView. But if you need also to remove entries, you may have to do the opposite: check that each existing annotation exists in the new array of annotations ; if not, remove it.
Or you could remove everything and add everything again (but then you will have the change animated ...)

Skip to Previous AVPlayerItem on AVQueuePlayer / Play selected Item from queue

I am playing a Tv-show that has been sliced to different chapters on my project using an AVQueuePlayer.
I also want to offer the possibility to skip to the previous/next chapter or to select a different chapter on the fly, while the AVQueuePlayer is already playing.
Skipping to next Item is no problem with the advanceToNextItem provided by AVQueuePlayer, but there is nothing alike for skipping back or playing a certainitem from the queue.
So I am not quite sure what would be the best approach here:
Using an AVPlayer instead of AVQueuePlayer, invoke replaceCurrentItemWithPlayerItem: at actionAtItemEnd to play the nextItem and just use 'replaceCurrentItemWithPlayerItem' to let the User select a certain Chapter
or
reorganise the queue or the current player by using 'insertItem:afterItem:' and 'removeAllItems'
Additional information:
I store the Path to the different videos in the order they should appear in a NSArray
The user is supposed to jump to certain chapters by pressing buttons that represent the chapter. The Buttons have tags, that are also the indexes of the corresponding videos in the array.
Hope I could make myself clear?
Anyone having any experience with this situation?
If anyone knows where to buy a good IOS VideoPlayerFramework which provides the functionality, I would also appreciate the link.
If you want your program can play previous item and play the selected item from your playeritems(NSArray),you can do this.
- (void)playAtIndex:(NSInteger)index
{
[player removeAllItems];
for (int i = index; i <playerItems.count; i ++) {
AVPlayerItem* obj = [playerItems objectAtIndex:i];
if ([player canInsertItem:obj afterItem:nil]) {
[obj seekToTime:kCMTimeZero];
[player insertItem:obj afterItem:nil];
}
}
}
edit:
playerItems is the NSMutableArray(NSArray) where you store your AVPlayerItems.
The first answer removes all items from AVQueuePlayer, and repopulates queue starting with iteration passed as index arg. This would start the newly populated queue with previous item(assuming you passed correct index) as well the rest of the items in existing playerItems array from that point forward, BUT it does not allow for multiple reverses, e.g. you are on track 10 and want to go back and replay track 9, then replay track 5, with above you cannot accomplish. But here you can...
-(IBAction) prevSongTapped: (id) sender
{
if (globalSongCount>1){ //keep running tally of items already played
[self resetAVQueue]; //removes all items from AVQueuePlayer
for (int i = 1; i < globalSongCount-1; i++){
[audioQueuePlayer advanceToNextItem];
}
globalSongCount--;
[audioQueuePlayer play];
}
}
The following code allows you to jump to any item in your. No playerhead advancing. Plain and simple. playerItemList is your NSArray with AVPlayerItem objects.
- (void)playAtIndex:(NSInteger)index
{
[audioPlayer removeAllItems];
AVPlayerItem* obj = [playerItemList objectAtIndex:index];
[obj seekToTime:kCMTimeZero];
[audioPlayer insertItem:obj afterItem:nil];
[audioPlayer play];
}
djiovann created a subclass of AVQueuePlayer that provides exactly this functionality.
You can find it on github.
I haven't tested it yet but from browsing through the code it seems to get the job done. Also the code is well documented, so it should at least serve as a good reference for a custom implementation of the functionality (I suggest using a category instead of subclassing though).
This should be the responsability of the AVQueuePlayer object and not your view controller itself, thus you should make it reusable and expose other answers implementations through an extension and use it in a similar way of advanceToNextItem() :
extension AVQueuePlayer {
func advanceToPreviousItem(for currentItem: Int, with initialItems: [AVPlayerItem]) {
self.removeAllItems()
for i in currentItem..<initialItems.count {
let obj: AVPlayerItem? = initialItems[i]
if self.canInsert(obj!, after: nil) {
obj?.seek(to: kCMTimeZero, completionHandler: nil)
self.insert(obj!, after: nil)
}
}
}
}
Usage (you only have to store an index and a reference to initial queue player items) :
self.queuePlayer.advanceToPreviousItem(for: self.currentIndex, with: self.playerItems)
One way of maintaining an index is to observe the AVPlayerItemDidPlayToEndTime notification for each of your video items :
func addDidFinishObserver() {
queuePlayer.items().forEach { item in
NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying), name: Notification.Name.AVPlayerItemDidPlayToEndTime, object: item)
}
}
func removeDidFinishObserver() {
queuePlayer.items().forEach { item in
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: item)
}
}
#objc func playerDidFinishPlaying(note: NSNotification) {
if queuePlayer.currentItem == queuePlayer.items().last {
print("last item finished")
} else {
print("item \(currentIndex) finished")
currentIndex += 1
}
}
This observation can also be really useful for other use cases (progress bar, current video timer reset ...).
Swift 5.2
var playerItems = [AVPlayerItem]()
func play(at itemIndex: Int) {
player.removeAllItems()
for index in itemIndex...playerItems.count {
if let item = playerItems[safe: index] {
if player.canInsert(item, after: nil) {
item.seek(to: .zero, completionHandler: nil)
player.insert(item, after: nil)
}
}
}
}
#saiday's answer works for me, here is swift version of his answer
func play(at index: Int) {
queue.removeAllItems()
for i in index..<items.count {
let obj: AVPlayerItem? = items[i]
if queue.canInsert(obj!, after: nil) {
obj?.seek(to: kCMTimeZero, completionHandler: nil)
queue.insert(obj!, after: nil)
}
}
}
If you want to play a song from any index using AVQueuePlayer.Then this below code can help to.
NSMutableArray *musicListarray (add song that you want to play in queue);
AVQueuePlayer *audioPlayer;
AVPlayerItem *item;
-(void) nextTapped
{
nowPlayingIndex = nowPlayingIndex + 1;
if (nowPlayingIndex > musicListarray.count)
{
}
else
{
[self playTrack];
}
}
-(void) playback
{
if (nowPlayingIndex < 0)
{
}
else
{
nowPlayingIndex = nowPlayingIndex - 1;
[self playTrack];
}
}
-(void) playTrack
{
#try
{
if (musicArray.count > 0)
{
item =[[AVPlayerItem alloc] initWithURL: [NSURL URLWithString:musicListarray
[nowPlayingIndex]]];
[audioPlayer replaceCurrentItemWithPlayerItem:item];
[audioPlayer play];
}
}
#catch (NSException *exception)
{
}
}
-(void) PlaySongAtIndex
{
//yore code...
nowPlayingIndex = i (stating index from queue)[audioPlayer play];
[self playTrack];
}
Here PlaySongAtIndex call when you want to play a song.