- background
- Try ideas
- Train of thought 1
- Train of thought II
- Train of thought 3
- Idea 4
- integrate
- Code logic
- New custom class
- Add pin management controller
- Add and set pin picture material agent
- Create pin management controller inside SDK
- The determination of pin visibility and concealment is added in the SDK
- The display of the pin of the current floor is the same as that of the current floor
- Demo main controller test code
- Measured results
- summary
background
The pins provided by mapbox do not have floor related attributes by default. When switching floors cannot be realized, only the pins of the corresponding floors will be displayed. Colleagues on the client side can't solve this problem. I hope I can solve this problem on the SDK side, so I'll explore it( 🤷♀️). Since I haven't developed the map SDK for some time, I have made the following attempts to step on the pit.
Try ideas
Based on the original classes and methods provided by mapbox;
Try not to affect the code related to the original pin api of mapbox used by the client.
Train of thought 1
Idea source: Protocol oriented programming!
If you can add a new protocol to make mapbox's original pin related classes comply with this protocol, then realize the floor attribute, assign the floor attribute when using it, and make logical judgment within the SDK, you can realize the function!
Thinking of this, I can't help feeling that it's me! 😆
The following attempts were made:
Add a protocol with floor attribute (floorID4Annotation):
//MARK:protocol @protocol HTMIndoorMapAnnotationViewAutoHide <NSObject> ///Pin floor id @property (nonatomic, assign) int floorID4Annotation; @end
Let the classes that need explicit and implicit pins comply with the protocol and implement the floor attribute (@ synthesize floorID4Annotation = _floorID4Annotation;). eg:
@interface HTMCustomPointAnnotation : MGLPointAnnotation<HTMIndoorMapAnnotationViewAutoHide> @end @implementation HTMCustomPointAnnotation @synthesize floorID4Annotation = _floorID4Annotation; @end
When used, assign a value to the floor attribute. Then, in the relevant methods of switching floors, traverse the map object pin array to determine whether the pin object responds to the floorID4Annotation method. For the responding object, compare its floor attribute with the currently displayed floor to see whether it is consistent. If it is inconsistent, it will be hidden and if it is consistent, it will be displayed. Relevant codes:
- (void)pmy_updateAnnotationsWithFloorId:(int)floorID { [self.mapView.annotations enumerateObjectsUsingBlock:^( id // < mglannotation > / / must be annotated, otherwise obj cannot obtain the attributes in other protocols _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if ([obj respondsToSelector:@selector(floorID4Annotation)]) { int lFoorID = [obj floorID4Annotation]; MGLPointAnnotation *lP = (MGLPointAnnotation *)obj; //MGLPointAnnotation class has no 'hidden' attribute!!! lP.hidden = !(lFoorID == floorID); }else{ //Failure to comply with the htmiddoormapannotationviewautohide protocol, regardless } }]; }
Unfortunately, the compiler reported an error: Property 'hidden' not found on object of type 'MGLPointAnnotation *', oh my god, instantly confused! 😳
Improvement idea: first remove and then add pins that are the same as the display floor or do not comply with the htmindoormapnotionautohide protocol (so that the client can retain the pin display effect that is not affected by floor switching).
//Update the pin display; Remove first, and then add pins that are the same as the displayed floor or do not comply with the htmiddoormapannotation autohide protocol - (void)pmy_updateAnnotationsWithFloorId:(int)floorID { NSArray *lArr = self.mapView.annotations; NSMutableArray *lArrM = @[].mutableCopy; [self.mapView.annotations enumerateObjectsUsingBlock:^( id // < mglannotation > / / must be annotated, otherwise obj cannot obtain the attributes in 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{ //Failure to comply with the htmindoormapnotionviewautohide protocol [lArrM addObject:obj]; } }]; [self.mapView removeAnnotations:lArr]; [self.mapView addAnnotations:lArrM]; }
However, after the operation, it was found that after switching the floor once, it was normal; Switch floors again, pins are gone! So I found that this logic is not feasible! Each floor cut will reduce the number of pins.
Think again, if for self mapView. What about annotations for caching? Still not, because when the client adds or deletes pins, it cannot listen to self mapView. The change of annotation (if the client sends a notification every time it adds or deletes, it will be too troublesome to use). The cache cannot be updated, resulting in an increase in the number of pins! 🙃
Later, it was found that there are ways to set the transparency of 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 being rendered. @return An alpha value between `0` and `1.0`. */ - (CGFloat)mapView:(MGLMapView *)mapView alphaForShapeAnnotation:(MGLShape *)annotation;
However, the actual measurement shows that the pin added through the addAnnotation method will not trigger the above callback! 😐
Train of thought II
Since the MGLPointAnnotation class does not have the hidden attribute, do other classes have it? So we found the MGLAnnotationView class:
@interface MGLAnnotationView : UIView <NSSecureCoding>
It inherits from UIView, so it has hidden attribute.
Therefore, it is improved on the basis of idea 1:
@interface HTMCustomAnnotationView : MGLAnnotationView<HTMIndoorMapAnnotationViewAutoHide> @end @implementation HTMCustomAnnotationView @synthesize floorID4Annotation = _floorID4Annotation; @end
Update pin code in SDK:
- (void)pmy_updateAnnotationsWithFloorId:(int)floorID { [self.mapView.annotations enumerateObjectsUsingBlock:^( id // < mglannotation > / / must be annotated, otherwise obj cannot obtain the attributes in 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{ //Failure to comply with the htmiddoormapannotationviewautohide protocol, regardless } }else{ //Does not belong to the MGLAnnotationView class, regardless of } }]; }
It seems feasible, but (HA again), I found that the method of adding pins to mapbox is as follows:
/** 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 <a href="https://docs.mapbox.com/ios/maps/examples/annotation-models/"> Annotation models</a> and <a href="https://docs.mapbox.com/ios/maps/examples/line-geojson/"> Add a line annotation from GeoJSON</a> examples to learn how to add an annotation to an `MGLMapView` object. */ - (void)addAnnotation:(id <MGLAnnotation>)annotation;
Only classes that comply with the mglanotion protocol can be added, and mglanotionview does not comply with this protocol, so it cannot be added through the above method! Therefore, the code if ([obj isMemberOfClass:[MGLAnnotationView class]]) of the above for loop will never take effect!
When considering adding the MGLAnnotationView object as a child view to the mapview object, two problems will be involved:
-
The pin icon cannot be changed through the proxy method provided by mapbox (it does not meet the 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
-
When there are many sub views in the local map, it costs more performance
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.
When I explored here, I found that mapbox provided a new tutorial:
https://docs.mapbox.com/ios/maps/guides/markers-and-annotations/#using-the-annotation-extension-beta
Comparison of four methods of adding pins:
Effect example:
Wow, the effect of MGLCircleStyleLayer is cool!
Follow the tutorial and continue to explore.
Train of thought 3
Layer explicit and implicit method: create the corresponding MGLSymbolStyleLayer layer according to different floors (add a floor attribute to the classification or subclass); When switching floors, compare floors and control the display and hiding of layers.
When the pin needs to be changed, rebuild the MGLSymbolStyleLayer layer corresponding to the floor (no method to change the style through the data source is found).
Thinking of idea 4, I feel that I can realize the demand faster, so this idea has not been explored yet.
Method of adding non clickable pictures by layer method
Idea 4
Use 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. ⚠️
After checking the records of the lower library, it has existed in 2019, and the latest updated record was 6 months ago, a year and a half ago. Moreover, there is no big problem with the issue. It is relatively stable.
First, understand the main header file of this library and find that it has a key attribute:
/** 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;
This attribute means that you can display and hide the picture of the pin according to different floors.
The hunch is feasible, and the exploration process is as follows.
integrate
Create a Podfile with the following specification:
pod 'MapboxAnnotationExtension', '0.0.1-beta.2'
Run pod repo update && pod install and open the resulting Xcode workspace.
Code logic
New custom class
@interface HTMAutoVisibilityAnnotation : MGLSymbolStyleAnnotation @property (nonatomic,assign) int floorIdInt; @end
Add pin management controller
@property (nonatomic,strong) MGLSymbolAnnotationController *annotationAutoVisibiliyCtrl;
Add and set pin picture material agent
///The pin information that needs to be automatically displayed and hidden when registering to switch floors. key is the picture name and value is the corresponding UIImage * object. When this function is not required, return to @{} - (NSDictionary<NSString *,UIImage *> *)htmMapViewRegisterAnnoInfoOfAutoVisibilityWhenChangeFloor;
Create pin management controller inside SDK
- (void)setAnnotationVC{ MGLSymbolAnnotationController *lVC = [[MGLSymbolAnnotationController alloc] initWithMapView:self.mapView]; // lVC.iconAllowsOverlap = YES; lVC.iconIgnoresPlacement = YES; lVC.annotationsInteractionEnabled = NO; //Make the icon not obscure poi the original 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]; } }]; } }
The determination of pin visibility and concealment is added in the 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 transparency effect will not take effect until you add it again [self.annotationAutoVisibiliyCtrl removeStyleAnnotations:lArr]; [self.annotationAutoVisibiliyCtrl addStyleAnnotations:lArr]; }
The pin of the same floor as the currently displayed floor is displayed immediately
The effect is limited to pins of type htmatutovisibilityannotation * managed through the annotationAutoVisibiliyCtrl property.
Note: this method will be called automatically when switching floors 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 transparency effect will not take effect until you add it again [self.annotationAutoVisibiliyCtrl removeStyleAnnotations:lArr]; [self.annotationAutoVisibiliyCtrl addStyleAnnotations:lArr]; }
Demo main 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:@"2s Only the pins of the current floor will be displayed after!"]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [SVProgressHUD dismiss]; [self.indoorMapView showAnnotationsOfCurrentShownFloorImmediately]; }); }); }
Implement the new agent method (remember to add the picture corresponding to the picture name 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"], }; }
Measured results
Run the project, switch the building selector, and determine that the automatic hidden effect of the pin is feasible!
Examples of searching restrooms:
summary
When encountering troublesome requirements, the first time should be to find documents or whether there are ready-made open source solutions. If you do this at the beginning, you can save the time spent exploring ideas 1-2.
However, the result is OK, which not only solves the needs of colleagues who have been bothered for a long time, but also improves the further understanding of mapbox related classes.