How to make a custom pop-up when clicking on a tag in iOS cards

  • Tutorial
This post is addressed primarily to beginner (and not so) iOS developers.
Often in applications, you need to place a map with labels on some places. By standard means, you can change the picture of the label, in the pop-up window when you click on the label, you can change the title, subtitle, image, and also add a button on the right or something else.
At the same time, all these elements can only be of standard size and will look something like the picture on the right.

But what if you want to create a custom pop-up window in which you can place anything you want (well, almost anything) like in the picture on the left?

If you are too lazy to read further and immediately want to download an example, then below is a link to the github. You can download and see for yourself.

Those who have already faced such a task, probably know about . Based on this example, my solution is built. The flaw in the nfarina solution is that its example shows how to use its own popup for a single label. And how to display a lot of labels is not considered in the example. For some reason, he also deduced 2 cards and placed one label on each and wrote all this in one place. This can be a bit confusing. Well, there is no description in Russian.

Step-by-step instructions for use

To begin with, you will need to add two standard frameworks to the project - MapKit and QuartzCore. (The second one is optional, but it is used in my example)

Next, add 2 files to the project - SMCalloutView.h and SMCalloutView.m. This class will form pop-up windows. I borrowed it from nfarina. Most likely, you will not have to edit anything in it, therefore we will not consider it.

The main attention will be paid to the controller that displays the card and the labels on it.

As in the nfarina example, in the file containing the controller that displays the map, there will also be 2 classes MapAnnotation and CustomPinAnnotationView. Maybe this is not very correct, but in this case it seemed to me a more elegant and simple solution, because these classes are inextricably linked with the controller and are unlikely to be used outside it.

In the controller file, import the MapKit / MapKit.h library and the SMCalloutView.h file. My controller will inherit from UIViewController and will comply with MKMapViewDelegate and SMCalloutViewDelegate protocols. Also, the class must contain a variable that must store all the information about the displayed objects. In my case it will be stocks like NSArray. There will be something else in yours.

Full code of my header for the NewMapViewController.h controller file:
#import "SMCalloutView.h"
@interface NewMapViewController : UIViewController 
@property (nonatomic, strong) NSArray *stocks;
@interface MapAnnotation : NSObject 
@property (nonatomic, copy) NSString *title, *subtitle;
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
@property int idAnn;
@interface CustomPinAnnotationView : MKPinAnnotationView
@property (strong, nonatomic) SMCalloutView *calloutView;
@property int idAnn;

An idAnn variable of type int will store the id of the label (or some other object of yours) , the pop-up window of which is currently displayed. Those. clicked on the label, a window popped up, idAnn has changed. Consider the implementation of the controller in more detail.

In my case, the QuartzCore library is imported in the NewMapViewController.m file and the following local variables are declared for the class:
#import "NewMapViewController.h"
@implementation NewMapViewController{
    SMCalloutView *calloutView;
    MKMapView *mapView;
    NSMutableArray *pins;
    int idAnn;

calloutView - pop-up window, mapView - map, pins - an array of displayed labels (not to be confused with stocks) , idAnn - id of the label that displays the pop-up window.

In my case, the stocks array is initialized in the init file, which will contain information about the displayed labels (picture, title, price, coordinates) .
- (id)init
    self = [super init];
    if (self)
        pins = [NSMutableArray array];
        self.stocks = @[
            @{@"img" : @"1.jpg", @"title" : @"Бургеры, салаты, курочка и многое другое в сети ресторанов быстрого питания «DAL's Burger» и «Gold'n'Brown». Скидка 50%", @"price" : @"50% за 99 тг.", @"lat" : @"43.20138", @"lng" : @"76.90597"},
            @{@"img" : @"6.jpg", @"title" : @"Две пиццы по цене одной в итальянском ресторане «Del Papa». Скидка 50%", @"price" : @"3000 тг. - 50% = 1499 тг.", @"lat" : @"43.25417", @"lng" : @"76.94035"}
    return self;

In your case, you can get this information from any other place (json, xml, other classes, etc.).

My viewDidLoad contains only 2 methods:
- (void)viewDidLoad
    [super viewDidLoad];
    [self setMap];
    [self drawMarkers];

setMap - initializes the map and shows the desired area. drawMarkers - draws labels on the map. You will need to change these methods to your needs. (I won’t give all the code below. If necessary, you can always download the finished example at the bottom of the github link and see)

The setMap method also indicates that all the pop-up windows will have a button on the right, which, when clicked, should trigger an event associated with it with this label:
   UIButton *bottomDisclosure = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
   [bottomDisclosure addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(goToMarker)]];
   calloutView.rightAccessoryView = bottomDisclosure;

The goToMarker method you must change to suit your needs. In my case, a UIAlertView just pops up with information about the pressed label:
- (void)goToMarker
    // Change this for your case
    NSDictionary *item = [self.stocks objectAtIndex:idAnn];
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Ура!"
                                                    message:[item objectForKey:@"title"]
    [alert show];

To change the design of your popup, you will need to change the popupMapCalloutView function

- (void)popupMapCalloutView {
    UIView *customView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 260, 88)];
    NSDictionary *item = [self.stocks objectAtIndex:idAnn];
    UIImageView *photo = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, 88.0, 88.0)];
    photo.image = [UIImage imageNamed:[item objectForKey:@"img"]];
    photo.contentMode = UIViewContentModeScaleAspectFit;
    photo.layer.cornerRadius = 15.0;
    photo.layer.masksToBounds = YES;
    [customView addSubview:photo];

In my case, the data will be taken from the stocks array. Which element is shown is stored by the idAnn variable.


Of course, I don’t think that I did something ingenious and very different from the nfarina example , but nevertheless, my manual is intended for Russian speakers (and there is very little information about development for iOS in Russian) . And my example extends the functionality of the original example. (Also, I did not consider some of the differences between my controller methods and the original nfarina methods, which you will not have to edit. Or you may have to)

I ask the developers to point out my errors (where without them?) , And also share other solutions to this problem (if any) online) . And discuss everything in the comments.

You can download my example on the github from this link -

Also popular now: