diff --git a/CHANGELOG.md b/CHANGELOG.md index c049608..cc251d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +#### [3.2.2] + +* [#67](https://github.com/neilang/NAMapKit/pull/67) - Added an example that show circle popup menu when `NAPinAnnotation` selected - [@troyz](https://github.com/troyz). #### [3.2.1](https://github.com/neilang/NAMapKit/tree/v3.2.1) (4/7/2016) diff --git a/Demo/Demo.xcodeproj/project.pbxproj b/Demo/Demo.xcodeproj/project.pbxproj index d2d9ea4..9ff823f 100644 --- a/Demo/Demo.xcodeproj/project.pbxproj +++ b/Demo/Demo.xcodeproj/project.pbxproj @@ -38,6 +38,10 @@ 3CB0E72218C8BB08009CE8DB /* NAMasterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3CB0E71818C8BB08009CE8DB /* NAMasterViewController.m */; }; 3CB0E72A18C8BC62009CE8DB /* NAMasterViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3CB0E72818C8BC62009CE8DB /* NAMasterViewController.xib */; }; 3CB0E72F18C8C9A4009CE8DB /* NAMapViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3CB0E72E18C8C9A4009CE8DB /* NAMapViewTests.m */; }; + 659BA77C1CD9C93E0035CEC9 /* NAPinAnnotationPopup.m in Sources */ = {isa = PBXBuildFile; fileRef = 659BA7771CD9C93E0035CEC9 /* NAPinAnnotationPopup.m */; }; + 659BA77D1CD9C93E0035CEC9 /* NAPinAnnotationPopupCircleCallOutView.m in Sources */ = {isa = PBXBuildFile; fileRef = 659BA7791CD9C93E0035CEC9 /* NAPinAnnotationPopupCircleCallOutView.m */; }; + 659BA77E1CD9C93E0035CEC9 /* NAPinAnnotationPopupMapView.m in Sources */ = {isa = PBXBuildFile; fileRef = 659BA77B1CD9C93E0035CEC9 /* NAPinAnnotationPopupMapView.m */; }; + 659BA7811CD9C9490035CEC9 /* NAPinAnnotationsPopupMenuDemoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 659BA7801CD9C9490035CEC9 /* NAPinAnnotationsPopupMenuDemoViewController.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -94,6 +98,14 @@ 3CB0E72B18C8BD61009CE8DB /* DemoTests-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "DemoTests-Prefix.pch"; sourceTree = ""; }; 3CB0E72E18C8C9A4009CE8DB /* NAMapViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NAMapViewTests.m; sourceTree = ""; }; 3EA578786DAD470F3EAEAF6F /* Pods-DemoTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DemoTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-DemoTests/Pods-DemoTests.release.xcconfig"; sourceTree = ""; }; + 659BA7761CD9C93E0035CEC9 /* NAPinAnnotationPopup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NAPinAnnotationPopup.h; sourceTree = ""; }; + 659BA7771CD9C93E0035CEC9 /* NAPinAnnotationPopup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NAPinAnnotationPopup.m; sourceTree = ""; }; + 659BA7781CD9C93E0035CEC9 /* NAPinAnnotationPopupCircleCallOutView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NAPinAnnotationPopupCircleCallOutView.h; sourceTree = ""; }; + 659BA7791CD9C93E0035CEC9 /* NAPinAnnotationPopupCircleCallOutView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NAPinAnnotationPopupCircleCallOutView.m; sourceTree = ""; }; + 659BA77A1CD9C93E0035CEC9 /* NAPinAnnotationPopupMapView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NAPinAnnotationPopupMapView.h; sourceTree = ""; }; + 659BA77B1CD9C93E0035CEC9 /* NAPinAnnotationPopupMapView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NAPinAnnotationPopupMapView.m; sourceTree = ""; }; + 659BA77F1CD9C9490035CEC9 /* NAPinAnnotationsPopupMenuDemoViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NAPinAnnotationsPopupMenuDemoViewController.h; sourceTree = ""; }; + 659BA7801CD9C9490035CEC9 /* NAPinAnnotationsPopupMenuDemoViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NAPinAnnotationsPopupMenuDemoViewController.m; sourceTree = ""; }; 98DB4ADA533041DD9D9E7647 /* libPods-DemoTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-DemoTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; D1E0B94B20F955D6CEF6E6B7 /* Pods-DemoTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DemoTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-DemoTests/Pods-DemoTests.debug.xcconfig"; sourceTree = ""; }; D2957D8BD1FB4630AD6F922F /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -162,6 +174,7 @@ 3CB0E6DC18C8B9E2009CE8DB /* Demo */ = { isa = PBXGroup; children = ( + 659BA7751CD9C92E0035CEC9 /* Popup */, 3C79CCB518D71AF500F6DF9F /* Maps */, 3CB0E72818C8BC62009CE8DB /* NAMasterViewController.xib */, 3CB0E70B18C8BB08009CE8DB /* NAAnimatedDemoViewController.h */, @@ -170,6 +183,8 @@ 3CB0E70E18C8BB08009CE8DB /* NAAppDelegate.m */, 3CB0E70F18C8BB08009CE8DB /* NAPinAnnotationsDemoViewController.h */, 3CB0E71018C8BB08009CE8DB /* NAPinAnnotationsDemoViewController.m */, + 659BA77F1CD9C9490035CEC9 /* NAPinAnnotationsPopupMenuDemoViewController.h */, + 659BA7801CD9C9490035CEC9 /* NAPinAnnotationsPopupMenuDemoViewController.m */, 3CB0E71118C8BB08009CE8DB /* NAInteractiveDemoViewController.h */, 3CB0E71218C8BB08009CE8DB /* NAInteractiveDemoViewController.m */, 3CB0E71318C8BB08009CE8DB /* NAInteractiveDemoViewController.xib */, @@ -235,6 +250,19 @@ name = Pods; sourceTree = ""; }; + 659BA7751CD9C92E0035CEC9 /* Popup */ = { + isa = PBXGroup; + children = ( + 659BA7761CD9C93E0035CEC9 /* NAPinAnnotationPopup.h */, + 659BA7771CD9C93E0035CEC9 /* NAPinAnnotationPopup.m */, + 659BA7781CD9C93E0035CEC9 /* NAPinAnnotationPopupCircleCallOutView.h */, + 659BA7791CD9C93E0035CEC9 /* NAPinAnnotationPopupCircleCallOutView.m */, + 659BA77A1CD9C93E0035CEC9 /* NAPinAnnotationPopupMapView.h */, + 659BA77B1CD9C93E0035CEC9 /* NAPinAnnotationPopupMapView.m */, + ); + name = Popup; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -437,10 +465,14 @@ files = ( 3CB0E72218C8BB08009CE8DB /* NAMasterViewController.m in Sources */, 3CB0E6E318C8B9E2009CE8DB /* main.m in Sources */, + 659BA77E1CD9C93E0035CEC9 /* NAPinAnnotationPopupMapView.m in Sources */, 3CB0E71E18C8BB08009CE8DB /* NAInteractiveDemoViewController.m in Sources */, 3CB0E71C18C8BB08009CE8DB /* NAAppDelegate.m in Sources */, + 659BA77C1CD9C93E0035CEC9 /* NAPinAnnotationPopup.m in Sources */, + 659BA77D1CD9C93E0035CEC9 /* NAPinAnnotationPopupCircleCallOutView.m in Sources */, 3CB0E71B18C8BB08009CE8DB /* NAAnimatedDemoViewController.m in Sources */, 3C9F354E18CA922C00EA1F22 /* NADotAnnotationDemoViewController.m in Sources */, + 659BA7811CD9C9490035CEC9 /* NAPinAnnotationsPopupMenuDemoViewController.m in Sources */, 3C7F86CD18CE609200F7091A /* NATiledImageDemoViewController.m in Sources */, 3CB0E72018C8BB08009CE8DB /* NALoadViaNIBDemoViewController.m in Sources */, 3CB0E71D18C8BB08009CE8DB /* NAPinAnnotationsDemoViewController.m in Sources */, diff --git a/Demo/Demo/NAMasterViewController.m b/Demo/Demo/NAMasterViewController.m index 5191f27..79e3571 100644 --- a/Demo/Demo/NAMasterViewController.m +++ b/Demo/Demo/NAMasterViewController.m @@ -13,6 +13,7 @@ #import "NAAnimatedDemoViewController.h" #import "NAInteractiveDemoViewController.h" #import "NATiledImageDemoViewController.h" +#import "NAPinAnnotationsPopupMenuDemoViewController.h" @implementation NAMasterViewController @@ -38,7 +39,19 @@ - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ - return 6; + if([self isRunningTests]) + { + return 6; + } + else + { + return 7; + } +} + +- (BOOL)isRunningTests +{ + return NSClassFromString(@"XCTest") != nil; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath @@ -70,6 +83,9 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N case 5: cell.textLabel.text = @"Tiled Map Demo"; break; + case 6: + cell.textLabel.text = @"Pin Annotations Popup Menu Demo"; + break; default: cell.textLabel.text = @"???"; break; @@ -98,6 +114,9 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath } else if (indexPath.row == 5){ NATiledImageDemoViewController *vc = [[NATiledImageDemoViewController alloc] initWithNibName:nil bundle:nil]; [self.navigationController pushViewController:vc animated:YES]; + } else if(indexPath.row == 6){ + NAPinAnnotationsPopupMenuDemoViewController *vc = [[NAPinAnnotationsPopupMenuDemoViewController alloc] initWithNibName:nil bundle:nil]; + [self.navigationController pushViewController:vc animated:YES]; } } diff --git a/Demo/Demo/NAPinAnnotationPopup.h b/Demo/Demo/NAPinAnnotationPopup.h new file mode 100644 index 0000000..3cfecdc --- /dev/null +++ b/Demo/Demo/NAPinAnnotationPopup.h @@ -0,0 +1,25 @@ +// +// NAPinAnnotationPopup.h +// NAMapKit +// +// Created by xdzhangm on 16/5/4. +// + +#import "NAPinAnnotation.h" + +// 弹出菜单样式 +typedef NS_ENUM(NSInteger, NAPopupMenuStyle) +{ + POP_UP_MENU_STYLE_DEFAULT = 0, + POP_UP_MENU_STYLE_CIRCLE = 1, // 圆形 +}; + +/** + * An annotation that looks like a pin. + */ +@interface NAPinAnnotationPopup : NAPinAnnotation + +@property (nonatomic, assign) NAPopupMenuStyle menuStyle; +@property (nonatomic, strong) NSArray *subTitleList; + +@end diff --git a/Demo/Demo/NAPinAnnotationPopup.m b/Demo/Demo/NAPinAnnotationPopup.m new file mode 100644 index 0000000..1b8ef2e --- /dev/null +++ b/Demo/Demo/NAPinAnnotationPopup.m @@ -0,0 +1,13 @@ +// +// NAPinAnnotation.m +// NAMapKit +// +// Created by xdzhangm on 16/5/4. +// + +#import "NAPinAnnotationPopup.h" + +@implementation NAPinAnnotationPopup + + +@end diff --git a/Demo/Demo/NAPinAnnotationPopupCircleCallOutView.h b/Demo/Demo/NAPinAnnotationPopupCircleCallOutView.h new file mode 100644 index 0000000..8323590 --- /dev/null +++ b/Demo/Demo/NAPinAnnotationPopupCircleCallOutView.h @@ -0,0 +1,50 @@ +// +// NAPinAnnotationPopupCircleCallOutView.h +// Pods +// +// Created by xdzhangm on 16/4/29. +// +// +/* + | + | + | + topLine + | + | + | + b1 b0 + + + b3 | CenterView | b2 + + + b5 b4 + | + + b6 + */ +#import + +#import "NAMapView.h" +#import "NAPinAnnotation.h" + +@protocol NAPinAnnotationPopupCircleCallOutViewDelegate +@required +- (NSInteger)numbersOfCircleForCallOutView; +@end + +@interface NAPinAnnotationPopupCircleCallOutView : UIView +- (id)initOnMapView:(NAMapView *)mapView; +- (void)showMenuAtPoint:(CGPoint)point; +- (void)hideMenu:(BOOL)animated; +- (void)updatePosition; +- (void)setTitle:(NSString *)title; + +// left side index: 1, 3, 5 +// right side index: 0, 2, 4 +- (UIButton *)menuAtIndex:(NSInteger)index; +@property (nonatomic, weak) NAMapView *mapView; +@property (nonatomic, weak) id delegate; +@property (readwrite, nonatomic, strong) NAPinAnnotation *annotation; +@end diff --git a/Demo/Demo/NAPinAnnotationPopupCircleCallOutView.m b/Demo/Demo/NAPinAnnotationPopupCircleCallOutView.m new file mode 100644 index 0000000..8712fca --- /dev/null +++ b/Demo/Demo/NAPinAnnotationPopupCircleCallOutView.m @@ -0,0 +1,489 @@ +// +// NAPinAnnotationPopupCircleCallOutView.m +// Pods +// +// Created by xdzhangm on 16/4/29. +// +// + +#import "NAPinAnnotationPopupCircleCallOutView.h" +#import + +#define VIEW_W_H 260 +#define CENTER_W_H 110 +#define BUTTON_W_H 44 +// distance between big circle and small circle +#define C_C_LENGTH (VIEW_W_H / 2.0 - BUTTON_W_H / 2.0) +#define C_C_LENGTH_SIN (sin(M_PI / 4.0) * C_C_LENGTH) +#define MAX_SMALL_CIRCLE 7 + +@interface NAPinAnnotationPopupCircleCallOutView() +{ + UILabel *centerView; + NSMutableDictionary *frameDict; + CGPoint mapAtPoint; + CGPoint atPoint; + BOOL isHiddening; + dispatch_group_t animateGroup; +} +@end + +@implementation NAPinAnnotationPopupCircleCallOutView +- (instancetype)init +{ + return [self initOnMapView:nil]; +} +- (id)initOnMapView:(NAMapView *)mapView +{ + self = [super init]; + if(self) + { + animateGroup = dispatch_group_create(); + + self.mapView = mapView; + [self initVariables]; + [self initViews]; + + self.hidden = YES; + [self hideMenu:NO]; + } + return self; +} + +- (void)initVariables +{ + frameDict = [[NSMutableDictionary alloc] init]; +} + +- (CGFloat)getMenuAngle:(NSInteger)i +{ + return M_PI * 0.25 * (i - 1); +} + +- (void)initViews +{ + self.frame = CGRectMake(0, 0, VIEW_W_H, VIEW_W_H); + self.layer.anchorPoint = CGPointMake(0.5, 0); + + centerView = [[UILabel alloc] init]; + centerView.font = [UIFont boldSystemFontOfSize:14]; + centerView.numberOfLines = 0; + centerView.textAlignment = NSTextAlignmentCenter; + centerView.textColor = [UIColor orangeColor]; + centerView.backgroundColor = [UIColor whiteColor]; + centerView.frame = CGRectMake((VIEW_W_H - CENTER_W_H) / 2.0, (VIEW_W_H - CENTER_W_H) / 2.0, CENTER_W_H, CENTER_W_H); + centerView.clipsToBounds = YES; + centerView.layer.cornerRadius = CENTER_W_H / 2.0; + [self addSubview:centerView]; + + UIImage *lineImage = [self createImageWithColor:[UIColor whiteColor] withFrame:CGRectMake(0, 0, VIEW_W_H / 2.0 - BUTTON_W_H, 1)]; + + UIImageView *lineView = [[UIImageView alloc] initWithImage:lineImage]; + lineView.frame = CGRectMake(VIEW_W_H / 2.0, 0, lineImage.size.height, VIEW_W_H / 2.0); + lineView.tag = 10; + [self addSubview:lineView]; + [self sendSubviewToBack:lineView]; + + for(NSInteger i = 0; i < MAX_SMALL_CIRCLE; i++) + { + CGFloat angle = [self getMenuAngle:i]; + + UIButton *btnView = [UIButton buttonWithType:UIButtonTypeCustom]; + btnView.tag = (i + 1); + [btnView setTitleColor:[UIColor orangeColor] forState:UIControlStateNormal]; + btnView.titleLabel.font = [UIFont systemFontOfSize:12.0]; + [btnView setTitle:[NSString stringWithFormat:@"b%zd", (i + 1)] forState:UIControlStateNormal]; + btnView.clipsToBounds = YES; + btnView.layer.cornerRadius = BUTTON_W_H / 2.0; + // btnView.layer.borderColor = RGBColor(0, 0, 0).CGColor; + // btnView.layer.borderWidth = 1.0; + btnView.frame = CGRectMake(VIEW_W_H / 2.0 + cos(angle) * C_C_LENGTH - BUTTON_W_H / 2.0, VIEW_W_H / 2.0 + sin(angle) * C_C_LENGTH - BUTTON_W_H / 2.0, BUTTON_W_H, BUTTON_W_H); + [btnView setBackgroundColor:[UIColor whiteColor]]; + [btnView setBackgroundImage:[self createImageWithColor:[UIColor orangeColor]] forState:UIControlStateHighlighted]; + [btnView setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted]; + [self addSubview:btnView]; + + UIImageView *lineView = [[UIImageView alloc] initWithImage:lineImage]; + lineView.frame = CGRectMake(VIEW_W_H / 2.0, VIEW_W_H / 2.0, lineImage.size.width, lineImage.size.height); + lineView.tag = (10 + btnView.tag); + lineView.transform = [self getTransformWithCenter:lineView.center withPoint:centerView.center withAngle:angle]; + [self addSubview:lineView]; + [self sendSubviewToBack:lineView]; + + [frameDict setObject:[NSValue valueWithCGPoint:btnView.center] forKey:@(btnView.tag)]; + } + + [self bringSubviewToFront:centerView]; +} + +- (CGAffineTransform) getTransformWithCenter:(CGPoint)center + withPoint:(CGPoint)point + withAngle:(CGFloat)angle +{ + CGFloat x = point.x - center.x; //计算(x,y)从(0,0)为原点的坐标系变换到(CenterX ,CenterY)为原点的坐标系下的坐标 + CGFloat y = point.y - center.y; //(0,0)坐标系的右横轴、下竖轴是正轴,(CenterX,CenterY)坐标系的正轴也一样 + + CGAffineTransform trans = CGAffineTransformMakeTranslation(x, y); + trans = CGAffineTransformRotate(trans,angle); + trans = CGAffineTransformTranslate(trans,-x, -y); + return trans; +} + +- (void)updatePosition +{ + if(self.mapView) + { + atPoint = [self.mapView zoomRelativePoint:mapAtPoint]; + atPoint.y -= 35; + } + self.layer.position = atPoint; + CGFloat angle = [self getAngleByPoint:atPoint center:[self screenCenterPoint]]; + [UIView animateWithDuration:0.3 animations:^{ + self.transform = CGAffineTransformMakeRotation(angle);//[self getTransformWithCenter:self.center withPoint:atPoint withAngle:angle]; + centerView.transform = CGAffineTransformMakeRotation(-angle); + for(NSInteger i = 0; i < MAX_SMALL_CIRCLE; i++) + { + UIButton *btnView = [self viewWithTag:i + 1]; + btnView.transform = CGAffineTransformMakeRotation(-angle); + } + }]; +} + +- (void)showMenuAtPoint:(CGPoint)point +{ + // 有可能当前hideMenu正在执行,需要等待hideMenu执行完毕后,才可执行show + dispatch_group_notify(animateGroup, dispatch_get_main_queue(), ^{ + isHiddening = NO; + mapAtPoint = point; + if(self.mapView) + { + atPoint = [self.mapView zoomRelativePoint:mapAtPoint]; + atPoint.y -= 35; + } + else + { + atPoint = point; + } + self.layer.position = atPoint; + + CGFloat angle = [self getAngleByPoint:atPoint center:[self screenCenterPoint]]; + for(NSInteger i = 0; i < MAX_SMALL_CIRCLE; i++) + { + UIButton *btnView = [self viewWithTag:i + 1]; + btnView.transform = CGAffineTransformIdentity; + btnView.transform = CGAffineTransformMakeRotation(-angle); + } + self.transform = CGAffineTransformMakeRotation(angle);//[self getTransformWithCenter:self.center withPoint:atPoint withAngle:angle]; + centerView.transform = CGAffineTransformMakeRotation(-angle); + self.hidden = NO; + [self showCenterView]; + }); +} + +- (CGPoint)screenCenterPoint +{ + CGPoint point = self.mapView.contentOffset; + point.x += self.mapView.bounds.size.width / 2.0; + point.y += self.mapView.bounds.size.height / 2.0; + return point; +} + +-(CGFloat)getAngleByPoint:(CGPoint)nowPoint center:(CGPoint)center +{ + if(nowPoint.y == center.y) + { + if(nowPoint.x >= center.x) + { + return M_PI; + } + else + { + return -M_PI; + } + } + if(nowPoint.x >= center.x && nowPoint.y >= center.y) + { + return M_PI - atan((nowPoint.x - center.x) / (nowPoint.y - center.y)); + } + else if(nowPoint.x >= center.x && nowPoint.y <= center.y) + { + return atan((nowPoint.x - center.x) / (center.y - nowPoint.y)); + } + else if(nowPoint.x <= center.x && nowPoint.y >= center.y) + { + return - (M_PI - atan((center.x - nowPoint.x) / (nowPoint.y - center.y))); + } + else + { + return - atan((center.x - nowPoint.x) / (center.y - nowPoint.y)); + } +} + +- (void)showMenu:(NSNumber *)num +{ + NSInteger i = [num integerValue]; + if(((i + 1) > (MAX_SMALL_CIRCLE + 1) / 2) || ((i + 1) > (([self menuCount] / 2) + ([self menuCount] % 2 == 0 ? 0 : 1)))) + { + return; + } + for(NSInteger _i = 0; _i < MAX_SMALL_CIRCLE; _i++) + { + if(_i == i || _i + i == MAX_SMALL_CIRCLE - 1) + { + if([self menuCount] % 2 != 0 && (i + 1) * 2 > [self menuCount] && _i != i) + { + continue; + } + POPSpringAnimation *animation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerSize]; + animation.springSpeed = 8; + + UIView *view = [self viewWithTag:_i + 11]; + animation.toValue = [NSValue valueWithCGSize:CGSizeMake(VIEW_W_H / 2.0 - BUTTON_W_H, 1.0)] ; + if(_i == i) + { + [self performSelector:@selector(showMenu:) withObject:@(i + 1) afterDelay:0.15]; + } + [view.layer pop_addAnimation:animation forKey:@"animation"]; + + [UIView animateWithDuration:0.25 animations:^{ + UIView *view = [self viewWithTag:_i + 1]; + view.center = [((NSValue *)[frameDict objectForKey:@(view.tag)]) CGPointValue]; + }]; + } + } +} + +- (void)hideMenu:(BOOL)animated +{ + if(animated) + { + [self hideMenuWithAnimate]; + } + else + { + [self hideMenuImediately]; + } +} + +- (void)hideMenuImediately +{ + self.hidden = YES; + isHiddening = NO; + for(NSInteger i = 0; i < MAX_SMALL_CIRCLE; i++) + { + UIView *view = [self viewWithTag:i + 11]; + view.bounds = CGRectMake(view.bounds.origin.x, view.bounds.origin.y, CENTER_W_H / 2.0 - BUTTON_W_H, 1.0); + } + for(NSInteger i = 0; i < MAX_SMALL_CIRCLE; i++) + { + UIView *view = [self viewWithTag:i + 1]; + view.transform = CGAffineTransformIdentity; + view.center = CGPointMake(VIEW_W_H / 2.0 + (CENTER_W_H / 2.0 - BUTTON_W_H / 2.0) * cos([self getMenuAngle:i]), VIEW_W_H / 2.0 + (CENTER_W_H / 2.0 - BUTTON_W_H / 2.0) * sin([self getMenuAngle:i])); + } + [self toggleShowSmallCircle:NO]; + UIView *topLineView = [self viewWithTag:10]; + topLineView.bounds = CGRectMake(VIEW_W_H / 2.0, 0, 0, 0); + topLineView.layer.position = CGPointMake(VIEW_W_H / 2.0, 0); + + centerView.layer.position = CGPointMake(VIEW_W_H / 2.0, 0); + // TODO: How to scale view WITHOUT animation????? + POPBasicAnimation *scaleAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerScaleXY]; + scaleAnimation.duration = 0; + scaleAnimation.toValue = [NSValue valueWithCGSize:CGSizeZero]; + [centerView.layer pop_addAnimation:scaleAnimation forKey:@"scaleAnimation"]; +} + +- (void)hideMenuWithAnimate +{ + if(isHiddening) + { + return; + } + dispatch_group_enter(animateGroup); + isHiddening = YES; + for(NSInteger i = 0; i < MAX_SMALL_CIRCLE; i++) + { + POPSpringAnimation *animation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerSize]; + animation.springSpeed = 3; + UIView *view = [self viewWithTag:i + 11]; + animation.toValue = [NSValue valueWithCGSize:CGSizeMake(CENTER_W_H / 2.0 - BUTTON_W_H, 1.0)]; + [view.layer pop_addAnimation:animation forKey:@"animation"]; + } + + [UIView animateWithDuration:0.3 animations:^{ + for(NSInteger i = 0; i < MAX_SMALL_CIRCLE; i++) + { + UIView *view = [self viewWithTag:i + 1]; + view.transform = CGAffineTransformIdentity; + view.center = CGPointMake(VIEW_W_H / 2.0 + (CENTER_W_H / 2.0 - BUTTON_W_H / 2.0) * cos([self getMenuAngle:i]), VIEW_W_H / 2.0 + (CENTER_W_H / 2.0 - BUTTON_W_H / 2.0) * sin([self getMenuAngle:i])); + } + } completion:^(BOOL finished) { + if(isHiddening) + { + [self hideCenterView]; + } + }]; +} + +- (void)hideCenterView +{ + [self toggleShowSmallCircle:NO]; + + UIView *topLineView = [self viewWithTag:10]; + + POPSpringAnimation *sizeAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerSize]; + sizeAnimation.toValue = [NSValue valueWithCGSize:CGSizeMake(topLineView.frame.size.width, 0)]; + + POPSpringAnimation *positionAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerPosition]; + positionAnimation.toValue = [NSValue valueWithCGPoint:topLineView.frame.origin]; + + [topLineView.layer pop_addAnimation:sizeAnimation forKey:@"sizeAnimation"]; + [topLineView.layer pop_addAnimation:positionAnimation forKey:@"positionAnimation"]; + + + POPSpringAnimation *scaleAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY]; + scaleAnimation.toValue = [NSValue valueWithCGSize:CGSizeZero]; + positionAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerPosition]; + positionAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(VIEW_W_H / 2.0, 0)]; + + [centerView.layer pop_addAnimation:scaleAnimation forKey:@"scaleAnimation"]; + [centerView.layer pop_addAnimation:positionAnimation forKey:@"positionAnimation"]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + self.hidden = YES; + isHiddening = NO; + dispatch_group_leave(animateGroup); + }); +} + +- (void)showCenterView +{ + UIView *topLineView = [self viewWithTag:10]; + + POPSpringAnimation *sizeAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerSize]; + sizeAnimation.toValue = [NSValue valueWithCGSize:CGSizeMake(1.0, VIEW_W_H / 2.0)]; + + POPSpringAnimation *positionAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerPosition]; + positionAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(VIEW_W_H / 2.0, VIEW_W_H / 4.0)]; + + [topLineView.layer pop_addAnimation:sizeAnimation forKey:@"sizeAnimation"]; + [topLineView.layer pop_addAnimation:positionAnimation forKey:@"positionAnimation"]; + + POPSpringAnimation *scaleAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY]; + scaleAnimation.springBounciness = 8; + scaleAnimation.springSpeed = 8; + scaleAnimation.toValue = [NSValue valueWithCGSize:CGSizeMake(1.0, 1.0)]; + + positionAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerPosition]; + positionAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(VIEW_W_H / 2.0, VIEW_W_H / 2.0)]; + + [centerView.layer pop_addAnimation:scaleAnimation forKey:@"scaleAnimation"]; + [centerView.layer pop_addAnimation:positionAnimation forKey:@"positionAnimation"]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self toggleShowSmallCircle:YES]; + [self showMenu:@(0)]; + }); +} + +- (void)toggleShowSmallCircle:(BOOL)isShow +{ + for(NSInteger i = 0; i < MAX_SMALL_CIRCLE; i++) + { + [self viewWithTag:(i + 1)].hidden = !isShow; + [self viewWithTag:(i + 11)].hidden = !isShow; + } +} + +- (NSInteger)menuCount +{ + if(_delegate && [_delegate respondsToSelector:@selector(numbersOfCircleForCallOutView)]) + { + return [_delegate numbersOfCircleForCallOutView]; + } + return 0; +} + +- (UIButton *)menuAtIndex:(NSInteger)index +{ + if(index >= MAX_SMALL_CIRCLE) + { + return nil; + } + NSInteger tag = (index % 2 == 0) ? (index / 2 + 1) : (MAX_SMALL_CIRCLE + 1 - ((index - 1) / 2 + 1)); + UIView *view = [self viewWithTag:tag]; + return [view isKindOfClass:[UIButton class]] ? (UIButton *)view : nil; +} + +/** + * 判断点击区域是否属于当前view + */ +- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event +{ + BOOL isInside = [super pointInside:point withEvent:event]; + if(isInside) + { + isInside = NO; + for(NSInteger i = 0; i < [self menuCount]; i++) + { + UIButton *btnView = [self menuAtIndex:i]; + CGPoint center = btnView.center; + CGFloat xOffset = center.x - point.x; + CGFloat yOffset = center.y - point.y; + if(sqrt(xOffset * xOffset + yOffset * yOffset) < BUTTON_W_H / 2.0) + { + NSLog(@"menu button tapped"); + return YES; + } + } + CGPoint center = centerView.center; + CGFloat xOffset = center.x - point.x; + CGFloat yOffset = center.y - point.y; + if(sqrt(xOffset * xOffset + yOffset * yOffset) < CENTER_W_H / 2.0) + { + NSLog(@"center view tapped"); + return YES; + } + } + return isInside; +} + +- (void)setTitle:(NSString *)title +{ + centerView.text = title; +} + +/* + // Only override drawRect: if you perform custom drawing. + // An empty implementation adversely affects performance during animation. + - (void)drawRect:(CGRect)rect { + // Drawing code + } + */ + +- (UIImage *)createImageWithColor:(UIColor *)color +{ + CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f); + return [self createImageWithColor:color withFrame:rect]; +} + +- (UIImage *)createImageWithColor:(UIColor *)color withFrame:(CGRect)frame +{ + UIGraphicsBeginImageContext(frame.size); + CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextSetFillColorWithColor(context, [color CGColor]); + CGContextFillRect(context, frame); + UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return theImage; +} + +- (void)setAnnotation:(NAPinAnnotation *)annotation +{ + _annotation = annotation; + [self showMenuAtPoint:annotation.point]; + [self updatePosition]; + [self setTitle:annotation.title]; +} +@end diff --git a/Demo/Demo/NAPinAnnotationPopupMapView.h b/Demo/Demo/NAPinAnnotationPopupMapView.h new file mode 100644 index 0000000..7adbd6b --- /dev/null +++ b/Demo/Demo/NAPinAnnotationPopupMapView.h @@ -0,0 +1,19 @@ +// +// NAPinAnnotationPopupMapView.h +// NAMapKit +// +// Created by xdzhangm on 16/5/4. +// + +#import "NAMapView.h" +#import "NAPinAnnotation.h" +#import "NAPinAnnotationPopup.h" +#import "NAPinAnnotationView.h" +#import "NAPinAnnotationCallOutView.h" + +/** + * A map view with pin annotations. + */ +@interface NAPinAnnotationPopupMapView : NAMapView + +@end diff --git a/Demo/Demo/NAPinAnnotationPopupMapView.m b/Demo/Demo/NAPinAnnotationPopupMapView.m new file mode 100644 index 0000000..f647e02 --- /dev/null +++ b/Demo/Demo/NAPinAnnotationPopupMapView.m @@ -0,0 +1,201 @@ +// +// NAPinAnnotationPopupMapView.m +// NAMapKit +// +// Created by xdzhangm on 16/5/4. +// + +#import "NAPinAnnotationPopupMapView.h" +#import "NAPinAnnotationPopupCircleCallOutView.h" +#import + +const CGFloat NAMapViewAnnotationCalloutAnimationDemoDuration = 0.1f; + +@interface NAPinAnnotationPopupMapView() + +@property (nonatomic, strong) NAPinAnnotationCallOutView *calloutView; +@property (nonatomic, strong) NAPinAnnotationPopupCircleCallOutView *circleCalloutView; + +- (IBAction)showCallOut:(id)sender; +- (void)hideCallOut; +@end + +@implementation NAPinAnnotationPopupMapView + +- (void)setupMap +{ + [super setupMap]; + + _calloutView = [[NAPinAnnotationCallOutView alloc] initOnMapView:self]; + [self addSubview:self.calloutView]; + + _circleCalloutView = [[NAPinAnnotationPopupCircleCallOutView alloc] initOnMapView:self]; + _circleCalloutView.delegate = self; + [self addSubview:_circleCalloutView]; +} + +- (void)addAnnotation:(NAAnnotation *)annotation animated:(BOOL)animate +{ + [super addAnnotation:annotation animated:animate]; + if ([annotation.view isKindOfClass:NAPinAnnotationView.class]) { + NAPinAnnotationView *annotationView = (NAPinAnnotationView *) annotation.view; + [annotationView addTarget:self action:@selector(showCallOut:) forControlEvents:UIControlEventTouchDown]; + } + [self bringSubviewToFront:self.calloutView]; + [self bringSubviewToFront:self.circleCalloutView]; +} + +- (void)selectAnnotation:(NAAnnotation *)annotation animated:(BOOL)animate +{ + if([annotation isKindOfClass:NAPinAnnotation.class]) { + [self showCalloutForAnnotation:(NAPinAnnotation *)annotation animated:animate]; + } + else{ + [self hideCallOut]; + } +} + +- (void)removeAnnotation:(NAAnnotation *)annotation +{ + [self hideCallOut]; + [super removeAnnotation:annotation]; +} + +- (IBAction)showCallOut:(id)sender +{ + if([sender isKindOfClass:[NAPinAnnotationView class]]) { + NAPinAnnotationView *annontationView = (NAPinAnnotationView *)sender; + + if ([self.mapViewDelegate respondsToSelector:@selector(mapView:tappedOnAnnotation:)]) { + [self.mapViewDelegate mapView:self tappedOnAnnotation:annontationView.annotation]; + } + + [self showCalloutForAnnotation:annontationView.annotation animated:YES]; + } +} + +- (void)showCalloutForAnnotation:(NAPinAnnotation *)annotation animated:(BOOL)animated +{ + NSLog(@"%f, %f", annotation.point.x, annotation.point.y); + + if(!self.circleCalloutView.hidden) + { + [self.circleCalloutView hideMenu:NO]; + } + else + { + [self hideCallOut]; + } + + if([annotation isKindOfClass:[NAPinAnnotationPopup class]] && ((NAPinAnnotationPopup *)annotation).menuStyle == POP_UP_MENU_STYLE_CIRCLE) + { + NAPinAnnotationPopup *annotationPopup = (NAPinAnnotationPopup *)annotation; + [self bringSubviewToFront:self.circleCalloutView]; + for(NSInteger i = 0; i < annotationPopup.subTitleList.count; i++) + { + NSString *title = annotationPopup.subTitleList[i]; + UIButton *btnView = [self.circleCalloutView menuAtIndex:i]; + [btnView setTitle:title forState:UIControlStateNormal]; + } + self.circleCalloutView.annotation = annotation; + } + else + { + self.calloutView.annotation = annotation; + + [self centerOnPoint:annotation.point animated:animated]; + + CGFloat animationDuration = animated ? NAMapViewAnnotationCalloutAnimationDemoDuration : 0.0f; + + self.calloutView.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.4f, 0.4f); + self.calloutView.hidden = NO; + + __weak typeof(self) weakSelf = self; + [UIView animateWithDuration:animationDuration animations:^{ + weakSelf.calloutView.transform = CGAffineTransformIdentity; + }]; + } +} + +- (void)hideCallOut +{ + self.calloutView.hidden = YES; + if(!self.circleCalloutView.hidden) + { + [self.circleCalloutView hideMenu:YES]; + } +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event +{ + if (!self.dragging) { + [self hideCallOut]; + } + + [super touchesEnded:touches withEvent:event]; +} + +- (void)updatePositions +{ + [self.calloutView updatePosition]; + [self.circleCalloutView updatePosition]; + [super updatePositions]; +} + +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView +{ + if(!self.circleCalloutView.hidden) + { + [self.circleCalloutView updatePosition]; + } + [self checkAnnotationViewVisible]; +} + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView +{ + if(!self.circleCalloutView.hidden) + { + [self.circleCalloutView updatePosition]; + } + [self checkAnnotationViewVisible]; +} + +- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView +{ + if(!self.circleCalloutView.hidden) + { + [self.circleCalloutView updatePosition]; + } + [self checkAnnotationViewVisible]; +} + +- (void)scrollViewDidZoom:(UIScrollView *)scrollView +{ + [super scrollViewDidZoom:scrollView]; + if(!self.circleCalloutView.hidden) + { + [self.circleCalloutView updatePosition]; + } + [self checkAnnotationViewVisible]; +} + +- (void)checkAnnotationViewVisible +{ + if(_circleCalloutView.hidden && _calloutView.hidden) + { + return; + } + UIView *view = _circleCalloutView.hidden ? _calloutView.annotation.view : _circleCalloutView.annotation.view; + CGRect screenBounds = [UIScreen mainScreen].bounds; + CGRect viewBounds = [view convertRect:view.bounds toView:nil]; + if(!CGRectIntersectsRect(viewBounds, screenBounds)) + { + [self hideCallOut]; + } +} + +- (NSInteger)numbersOfCircleForCallOutView +{ + return ((NAPinAnnotationPopup *)_circleCalloutView.annotation).subTitleList.count; +} +@end diff --git a/Demo/Demo/NAPinAnnotationsPopupMenuDemoViewController.h b/Demo/Demo/NAPinAnnotationsPopupMenuDemoViewController.h new file mode 100644 index 0000000..4b71133 --- /dev/null +++ b/Demo/Demo/NAPinAnnotationsPopupMenuDemoViewController.h @@ -0,0 +1,13 @@ +// +// NAPinAnnotationsPopupMenuDemoViewController.h +// Demo +// +// Created by xdzhangm on 16/5/4. +// Copyright © 2016年 neilang.com. All rights reserved. +// + +#import + +@interface NAPinAnnotationsPopupMenuDemoViewController : UIViewController + +@end diff --git a/Demo/Demo/NAPinAnnotationsPopupMenuDemoViewController.m b/Demo/Demo/NAPinAnnotationsPopupMenuDemoViewController.m new file mode 100644 index 0000000..4e1b63d --- /dev/null +++ b/Demo/Demo/NAPinAnnotationsPopupMenuDemoViewController.m @@ -0,0 +1,78 @@ +// +// NAPinAnnotationsPopupMenuDemoViewController.m +// Demo +// +// Created by xdzhangm on 16/5/4. +// Copyright © 2016年 neilang.com. All rights reserved. +// + +#import "NAPinAnnotationsPopupMenuDemoViewController.h" +#import "NAMapView.h" +#import "NAPinAnnotationPopupMapView.h" +#import "NAPinAnnotationPopup.h" + +@interface NAPinAnnotationsPopupMenuDemoViewController () + +@end + +@implementation NAPinAnnotationsPopupMenuDemoViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + NAMapView *mapView = [[NAPinAnnotationPopupMapView alloc] initWithFrame:self.view.bounds]; + + mapView.backgroundColor = [UIColor colorWithRed:0.000f green:0.475f blue:0.761f alpha:1.000f]; + mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + + mapView.minimumZoomScale = 0.5f; + mapView.maximumZoomScale = 1.5f; + + NSString *australia = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:@"/Maps/australia.png"]; + [mapView displayMap:[UIImage imageWithContentsOfFile:australia]]; + + [self.view addSubview:mapView]; + + NAPinAnnotationPopup *melbourne = [NAPinAnnotationPopup annotationWithPoint:CGPointMake(543.0f, 489.0f)]; + melbourne.title = @"Melbourne"; + melbourne.menuStyle = POP_UP_MENU_STYLE_CIRCLE; + melbourne.subTitleList = @[@"Menu 1", @"Menu 2", @"Menu 3"]; + + [mapView addAnnotation:melbourne animated:NO]; + + NAPinAnnotationPopup * perth = [NAPinAnnotationPopup annotationWithPoint:CGPointMake(63.0f, 379.0f)]; + perth.title = @"Perth"; + perth.menuStyle = POP_UP_MENU_STYLE_CIRCLE; + perth.subTitleList = @[@"Menu 1", @"Menu 2", @"Menu 3", @"Menu 4"]; + + [mapView addAnnotation:perth animated:NO]; + + NAPinAnnotation * brisbane = [NAPinAnnotation annotationWithPoint:CGPointMake(679.0f, 302.0f)]; + brisbane.title = @"Brisbane"; + brisbane.color = NAPinColorPurple; + + [mapView addAnnotation:brisbane animated:NO]; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation +{ + return (interfaceOrientation == UIInterfaceOrientationPortrait); +} + +- (void)didReceiveMemoryWarning { + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +/* +#pragma mark - Navigation + +// In a storyboard-based application, you will often want to do a little preparation before navigation +- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { + // Get the new view controller using [segue destinationViewController]. + // Pass the selected object to the new view controller. +} +*/ + +@end diff --git a/Demo/Podfile b/Demo/Podfile index 340c8fd..1612888 100644 --- a/Demo/Podfile +++ b/Demo/Podfile @@ -1,4 +1,5 @@ pod "NAMapKit", :path => "../namapkit.podspec" +pod "pop" target "DemoTests" do pod 'Specta', '~> 0.2.1' diff --git a/Demo/Podfile.lock b/Demo/Podfile.lock index dab7023..d8cdf48 100644 --- a/Demo/Podfile.lock +++ b/Demo/Podfile.lock @@ -9,6 +9,7 @@ PODS: - NAMapKit (3.2.1): - ARTiledImageView - SDWebImage + - pop (1.0.9) - SDWebImage (3.7.5): - SDWebImage/Core (= 3.7.5) - SDWebImage/Core (3.7.5) @@ -19,11 +20,12 @@ DEPENDENCIES: - EXPMatchers+FBSnapshotTest (~> 1.1.0) - FBSnapshotTestCase (~> 1.1) - NAMapKit (from `../namapkit.podspec`) + - pop - Specta (~> 0.2.1) EXTERNAL SOURCES: NAMapKit: - :path: "../namapkit.podspec" + :path: ../namapkit.podspec SPEC CHECKSUMS: ARTiledImageView: 8c6fd11d9e8459a853d94394adea3a5e8c9329ba @@ -31,6 +33,7 @@ SPEC CHECKSUMS: EXPMatchers+FBSnapshotTest: 3c48e8a29a445476fe2d72d7da45dfdd765b5f55 FBSnapshotTestCase: 3dc3899168747a0319c5278f5b3445c13a6532dd NAMapKit: 43b79013f2003216e0a301168b6b3603ed67687f + pop: f667631a5108a2e60d9e8797c9b32ddaf2080bce SDWebImage: 69c6303e3348fba97e03f65d65d4fbc26740f461 Specta: 15a276a6343867b426d5ed135d5aa4d04123a573 diff --git a/Demo/Screenshots/namapkit2.gif b/Demo/Screenshots/namapkit2.gif new file mode 100644 index 0000000..614cdee Binary files /dev/null and b/Demo/Screenshots/namapkit2.gif differ diff --git a/README.md b/README.md index 486d802..0d8563f 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,10 @@ The implementation of `NADotAnnotation` places a red semi-transparent dot on the You can find a complete custom annotation example of multi-colored pins in [NAPinAnnotation.h](NAMapKit/NAPinAnnotation.h)/[.m](NAMapKit/NAPinAnnotation.m). +You can find another custom annotation example of popup menus in [NAPinAnnotationsPopupMenuDemoViewController.h](Demo/Demo/NAPinAnnotationsPopupMenuDemoViewController.h)/[.m](Demo/Demo/NAPinAnnotationsPopupMenuDemoViewController.m). The screenshot is like below: + +![Circle popup Animated Demo](Demo/Screenshots/namapkit2.gif) + #### Delegates You can capture finger taps and zoom changes by registering a `mapViewDelegate` with the map. The delegate must implement the [NAMapViewDelegate](NAMapKit/NAMapViewDelegate.h) protocol.