background

By default, the pins provided by MapBox do not have the attributes related to the floor. If you cannot switch floors, only the pins of the corresponding floor are displayed. My colleague on the client side could not solve this problem, so he asked me to solve this problem on the SDK side, so I made relevant exploration (🤷♀️). Since I have not done map SDK development for some time, I have tried various pits as follows.

Try thinking

Based on the original classes and methods provided by MapBox; The mapBox original pin API code used by the client is not affected as much as possible.

Thinking a

Protocol oriented programming!

If you can add a new protocol, make mapBox original pin related class to comply with this protocol, and then implement the floor attribute, when using the floor attribute assignment, in the SDK internal logic judgment, it is good to achieve the function!

Think of this, can not help feeling, worthy of me! 😆

The following attempts were made:

New protocol with floorID4Annotation:

//MARK:protocol
@protocol HTMIndoorMapAnnotationViewAutoHide <NSObject>

/// Id of the floor where the pin is located
@property (nonatomic, assign) int floorID4Annotation;

@end
Copy the code

Make the class that requires invisible pins comply with the protocol to implement the floor property (@synthesize floorID4Annotation = _floorID4Annotation;) . Eg:

@interface HTMCustomPointAnnotation : MGLPointAnnotation<HTMIndoorMapAnnotationViewAutoHide>
@end

@implementation HTMCustomPointAnnotation
@synthesize floorID4Annotation = _floorID4Annotation;
@end
Copy the code

When used, assign a value to the floor property. Then in the related method of switching floors, traverse the map object pin array to determine whether the pin object responds to floorID4Annotation method. For the responding object, compare its floor attribute with the current display floor. If the floor attribute is inconsistent, it will be hidden, and if the floor attribute is consistent, it will be displayed. Related codes:

- (void)pmy_updateAnnotationsWithFloorId:(int)floorID {
    [self.mapView.annotations enumerateObjectsUsingBlock:^(
                                                           id
                                                           // 
      
       // must be annotated otherwise OBJ cannot get properties from other protocols
      
                                                           _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([obj respondsToSelector:@selector(floorID4Annotation)]) {
            int lFoorID =  [obj floorID4Annotation];
            MGLPointAnnotation *lP = (MGLPointAnnotation *)obj;
          
            // the MGLPointAnnotation class has no 'hidden' attribute!!
            lP.hidden = !(lFoorID = = floorID);
        }else{
            / / did not obey the HTMIndoorMapAnnotationViewAutoHide agreement, no matter}}]; }Copy the code

Property ‘hidden’ not found on object of type ‘MGLPointAnnotation *’, oh my God! 😳

Improvement ideas: first remove, add and display the same floor, or did not observe HTMIndoorMapAnnotationAutoHide agreement of pin (make the client can keep is not affected by floor switch pin display effect).

// Update pin conceals; First removed, then add the same as the show floor or did not follow HTMIndoorMapAnnotationAutoHide the pins of the agreement
- (void)pmy_updateAnnotationsWithFloorId:(int)floorID {
    NSArray *lArr = self.mapView.annotations;
    NSMutableArray *lArrM = @[].mutableCopy;
    
    [self.mapView.annotations enumerateObjectsUsingBlock:^(
                                                           id
                                                           // 
      
       // must be annotated otherwise OBJ cannot get properties from other protocols
      
                                                           _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([obj respondsToSelector:@selector(floorID4Annotation)]) {
            int lFoorID =  [obj floorID4Annotation];
            if (floorID = =lFoorID) { [lArrM addObject:obj]; }}else{
            / / not observe HTMIndoorMapAnnotationViewAutoHide agreement[lArrM addObject:obj]; }}]; [self.mapView removeAnnotations:lArr];
    [self.mapView addAnnotations:lArrM];
}
Copy the code

However, after operation, it is found that it is normal after switching the floor for 1 time; Switch floors again and there are no pins! And found that this logic doesn’t work! Each floor cut reduces the number of pins.

Annotations Again, what if I cache self.mapView.annotations? Again, it doesn’t work because the client can’t listen for self.mapView.annotation changes when it adds or deletes pins (it would be too cumbersome to have the client notify every time it adds or deletes a pin). The cache cannot be updated, causing the number of pins to grow! 🙃

It turns out that there is a way to set the transparency of the Shape Annotation:

/** Returns the alpha value to use when rendering a shape annotation. A value of '0.0' results in a completely Transparent Shape. A value of '1.0', the default, results in a completely opaque shape. This method sets the opacity of an entire shape, inclusive of its stroke and fill. To independently set the values for stroke or fill, specify an alpha component in the color returned by `-mapView:strokeColorForShapeAnnotation:` or `-mapView:fillColorForPolygonAnnotation:`. @param mapView The map view rendering the shape annotation. @param annotation The annotation is being rendered. @return An alpha value between '0' and '1.0'. */
- (CGFloat)mapView:(MGLMapView *)mapView alphaForShapeAnnotation:(MGLShape *)annotation;
Copy the code

But it turns out that pins added via the addAnnotation method don’t trigger the above callback! 😐

Idea 2

Since the MGLPointAnnotation class does not have the hidden attribute, do any other classes have it? Here’s the MGLAnnotationView class:

@interface MGLAnnotationView : UIView <NSSecureCoding>
Copy the code

It inherits from UIView, so it has hidden property.

Therefore, improvement is made on the basis of idea 1:

@interface HTMCustomAnnotationView : MGLAnnotationView<HTMIndoorMapAnnotationViewAutoHide>
@end

@implementation HTMCustomAnnotationView
@synthesize floorID4Annotation = _floorID4Annotation;
@end
Copy the code

Update pin code in SDK:

- (void)pmy_updateAnnotationsWithFloorId:(int)floorID {
    [self.mapView.annotations enumerateObjectsUsingBlock:^(
                                                           id
// 
      
       // must be annotated otherwise OBJ cannot get properties from other protocols
      
                                                           _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([obj isMemberOfClass:[MGLAnnotationView class]]) {
            if ([obj respondsToSelector:@selector(floorID4Annotation)]) {
                int lFoorID =  [obj floorID4Annotation];
                MGLAnnotationView *lV = (MGLAnnotationView *)obj;
                lV.hidden = !(lFoorID = = floorID);
            }else{
                / / did not obey the HTMIndoorMapAnnotationViewAutoHide agreement, no matter}}else{
            // does not belong to the MGLAnnotationView class, regardless}}]; }Copy the code

It looks like it might work, but here’s how mapBox adds pins:

/** Adds an annotation to the map view. @note `MGLMultiPolyline`, `MGLMultiPolygon`, `MGLShapeCollection`, and `MGLPointCollection` objects cannot be added to the map view at this time. Any multipoint, multipolyline, multipolygon, shape or point collection object that is specified is silently ignored. @param annotation The annotation object to add to the receiver. This object must conform to the `MGLAnnotation` protocol. The map view retains the annotation object. #### Related examples See the  Annotation models and  Add a line annotation from GeoJSON  examples to learn how to add an annotation to an `MGLMapView` object. */
- (void)addAnnotation:(id <MGLAnnotation>)annotation;
Copy the code

Classes that adhere to the MGLAnnotation protocol can only be added, and MGLAnnotationView does not adhere to the MGLAnnotation protocol, so it cannot be added! If ([obj isMemberOfClass:[MGLAnnotationView class]]))

If you consider adding an MGLAnnotationView object as a subview to a MapView object, two problems arise:

  • Unable to change pin icon via proxy method provided by MapBox (not meeting business requirements)

    /** If you want to mark a particular point annotation with a static image instead, omit this method or have it return nil for that annotation, then implement -mapView:imageForAnnotation: instead. */

    – (MGLAnnotationView *)mapView:(MGLMapView *)mapView viewForAnnotation:(id)annotation

  • Compare performance of local graph subviews when there are many

    Using many MGLAnnotationViews can cause slow performance, so if you need to add a large number of annotations, consider using more performant MGLStyleLayers instead, detailed below.

    Style layers are more performant compared to UIView-based annotations. You will need to implement your own gesture recognizers and callouts, but it is the most powerful option if you need to create rich map data visualizations within your app.

While exploring this, I stumbled upon a new tutorial from Mapbox:

Docs.mapbox.com/ios/maps/gu…

Four ways to add pins

Sample effect diagram:

Wow, **MGLCircleStyleLayer** has a cool effect!

Follow the tutorial and continue exploring.

Thought three

Layer explicit implicit method, according to the different floor, create the corresponding MGLSymbolStyleLayer layer (classification or subclass add a floor attribute); When switching floors, compare the layers and make the control layer fade. When you need to change the pin, rebuild the floor corresponding to the MGLSymbolStyleLayer layer (did not find a way to change style via data source).

Since I have thought of the fourth idea, I feel that it can realize the demand faster, so THIS idea has not been explored.

Layer method adds an unclickable image method

Thinking four

Using existing wheels: MapboxAnnotationExtension

The Mapbox Annotation Extension is a lightweight library you can use with the Mapbox Maps SDK for iOS to quickly add basic shapes, icons, and other annotations to a map.

This extension leverages the power of runtime styling with an object oriented approach to simplify the creation and styling of annotations.

⚠️ This product is currently in active beta development, is not intended for production usage. ⚠️

I checked the records of the library and found that it already existed in 2019. The latest update was 6 months ago and 1 and a half years ago. And there is no big problem in reading the issue, it has been relatively stable.

Let’s start by looking at the library’s main header file, which has one key property:

/** The opacity of the symbol style annotation's icon image. Requires `iconImageName`. Defaults to `1`. This property corresponds to the `icon-opacity` property in the style [Mapbox Style Specification](https://docs.mapbox.com/mapbox-gl-js/style-spec/#paint-symbol-icon-opacity). */
@property (nonatomic, assign) CGFloat iconOpacity;
Copy the code

This property means that the pin image can be hidden depending on the floor.

The hunch is that it works, and the process is as follows.

integration

Create a Podfile with the following specification:

Pod 'MapboxAnnotationExtension', '0.0.1 - beta. 2'Copy the code

Run pod repo update && pod install and open the resulting Xcode workspace.

Code logic

Create a custom class
@interface HTMAutoVisibilityAnnotation : MGLSymbolStyleAnnotation
@property (nonatomic,assign) int floorIdInt;
@end
Copy the code
Add a pin management controller
@property (nonatomic,strong) MGLSymbolAnnotationController *annotationAutoVisibiliyCtrl;
Copy the code
Added setting pin picture material agent
/// Register to switch floors require automatic implicit pin information. Key is the image name and value is the corresponding UIImage* object. When this function is not required, return @{}
- (NSDictionary<NSString *,UIImage* >*)htmMapViewRegisterAnnoInfoOfAutoVisibilityWhenChangeFloor;
Copy the code
The SDK creates a pin management controller internally
- (void)setAnnotationVC{
    MGLSymbolAnnotationController *lVC = [[MGLSymbolAnnotationController alloc] initWithMapView:self.mapView];
// lVC.iconAllowsOverlap = YES;
    lVC.iconIgnoresPlacement = YES;
    lVC.annotationsInteractionEnabled = NO;
    
    // The icon does not block the original POI icon
    lVC.iconTranslation = CGVectorMake(0.-26);
    self.annotationAutoVisibiliyCtrl = lVC;
    
    if ([self.delegateCustom respondsToSelector:@selector(htmMapViewRegisterAnnoInfoOfAutoVisibilityWhenChangeFloor)]) {
        NSDictionary<NSString *,UIImage* >*lDic = [self.delegateCustom htmMapViewRegisterAnnoInfoOfAutoVisibilityWhenChangeFloor];
        [lDic enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, UIImage * _Nonnull obj, BOOL * _Nonnull stop) {
            if (key.length > 0
                && nil ! = obj) {
                [self.mapView.style setImage:obj forName:key]; }}]; }}Copy the code
Add pin implicit decision inside SDK
- (void)pmy_updateAnnotationsWithFloorId:(int)floorID {
    NSArray *lArr = self.annotationAutoVisibiliyCtrl.styleAnnotations;
    [lArr enumerateObjectsUsingBlock:^(MGLStyleAnnotation * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([obj isKindOfClass:[HTMAutoVisibilityAnnotation class]]) {
            HTMAutoVisibilityAnnotation *lAnno = (HTMAutoVisibilityAnnotation *)obj;
            if (lAnno.floorIdInt = = floorID) {
                lAnno.iconOpacity = 1;
            }else{
                lAnno.iconOpacity = 0; }}}];// The opacity effect will only take effect if you add it again
    [self.annotationAutoVisibiliyCtrl removeStyleAnnotations:lArr];
    [self.annotationAutoVisibiliyCtrl addStyleAnnotations:lArr];
}	
Copy the code
Immediately display pins on the same floor as the currently displayed floor

Effect only through annotationAutoVisibiliyCtrl property management HTMAutoVisibilityAnnotation * type of pin.

Note: This method is called automatically when floors are switched automatically or manually.

- (void)showAnnotationsOfCurrentShownFloorImmediately{
    [self pmy_updateAnnotationsWithFloorId:self.floorModelMapShowing.floorID];
}

- (void)pmy_updateAnnotationsWithFloorId:(int)floorID {
    NSArray *lArr = self.annotationAutoVisibiliyCtrl.styleAnnotations;
    [lArr enumerateObjectsUsingBlock:^(MGLStyleAnnotation * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([obj isKindOfClass:[HTMAutoVisibilityAnnotation class]]) {
            HTMAutoVisibilityAnnotation *lAnno = (HTMAutoVisibilityAnnotation *)obj;
            if (lAnno.floorIdInt = = floorID) {
                lAnno.iconOpacity = 1;
            }else{
                lAnno.iconOpacity = 0; }}}];// The opacity effect will only take effect if you add it again
    [self.annotationAutoVisibiliyCtrl removeStyleAnnotations:lArr];
    [self.annotationAutoVisibiliyCtrl addStyleAnnotations:lArr];
}
Copy the code
Demo Master controller test code
- (void)pmy_upateSymbolAnnosWithPoisArr:(NSArray<HTMPoi* >*)poiArr{
    NSMutableArray *lArrM = @[].mutableCopy;
    [poiArr enumerateObjectsUsingBlock:^(HTMPoi *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        HTMAutoVisibilityAnnotation *lAnno = [[HTMAutoVisibilityAnnotation alloc] initWithCoordinate:(CLLocationCoordinate2DMake(obj.lat, obj.lng)) iconImageName:@"poiAnno"];
        lAnno.iconOpacity = 0.5;
        lAnno.floorIdInt = obj.floorId.intValue;
        [lArrM addObject:lAnno];
    }];
    
    [self.indoorMapView.annotationAutoVisibiliyCtrl removeStyleAnnotations:self.indoorMapView.annotationAutoVisibiliyCtrl.styleAnnotations];
    [self.indoorMapView.annotationAutoVisibiliyCtrl addStyleAnnotations:lArrM];
    

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{[SVProgressHUD showWithStatus:@"After 2s only display current display floor pin!"];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{[SVProgressHUD dismiss];
            [self.indoorMapView showAnnotationsOfCurrentShownFloorImmediately];
        });
    });
}
Copy the code

Implement the new proxy method (remember to add the corresponding picture to the project):

- (nonnull NSDictionary<NSString *,UIImage* >*)htmMapViewRegisterAnnoInfoOfAutoVisibilityWhenChangeFloor {
    return@ {@"route_icon_start": [UIImage imageNamed:@"route_icon_start"],
             @"route_icon_end": [UIImage imageNamed:@"route_icon_end"],
             @"poiAnno": [UIImage imageNamed:@"poiAnno"]}; }Copy the code
The measured results

Run the project, switch the building selector, make sure the pin automatic display effect is feasible!

Search for restroom examples:

conclusion

The first place to look for a cumbersome requirement is in the documentation, or if an open source solution is already in place. If you do this at first, you can save time on exploring ideas 1-2.

However, the result is ok, solving a nagging need of colleagues and improving the understanding of mapBox related classes.