Fix test app issues, add tests (#2258)
This commit is contained in:
@@ -144,8 +144,12 @@ mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDi
|
||||
featureCollection.push_back(geoJsonObject);
|
||||
}
|
||||
const auto geojson = mbgl::GeoJSON{featureCollection};
|
||||
if(![self isCancelled] && self.rawSource) {
|
||||
self.rawSource->setTileData(mbgl::CanonicalTileID(self.z, self.x, self.y), geojson);
|
||||
|
||||
// Note: potential race condition with `cancel`
|
||||
if(![self isCancelled]) {
|
||||
if (auto *rawSource = self.rawSource) {
|
||||
rawSource->setTileData(mbgl::CanonicalTileID(self.z, self.x, self.y), geojson);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#import "MBXState.h"
|
||||
#import "MLNSettings.h"
|
||||
|
||||
#import "platform/ios/src/MLNMapView_Private.h"
|
||||
|
||||
#import "CustomStyleLayerExample.h"
|
||||
|
||||
#if MLN_DRAWABLE_RENDERER
|
||||
@@ -238,10 +240,26 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
BOOL _isTouringWorld;
|
||||
BOOL _contentInsetsEnabled;
|
||||
UIEdgeInsets _originalContentInsets;
|
||||
|
||||
NSLayoutConstraint* _firstMapLayout;
|
||||
NSArray<NSLayoutConstraint*>* _secondMapLayout;
|
||||
|
||||
NSDictionary* _pointFeatures;
|
||||
NSLock* _loadLock;
|
||||
}
|
||||
|
||||
// MARK: - Setup & Teardown
|
||||
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
_firstMapLayout = nil;
|
||||
_secondMapLayout = nil;
|
||||
_pointFeatures = nil;
|
||||
_loadLock = [NSLock new];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
@@ -830,22 +848,32 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
|
||||
// MARK: - Debugging Actions
|
||||
|
||||
- (NSDictionary*)loadPointFeatures
|
||||
{
|
||||
[_loadLock lock];
|
||||
if (!_pointFeatures) {
|
||||
NSData *featuresData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"points" ofType:@"geojson"]];
|
||||
if (featuresData) {
|
||||
id features = [NSJSONSerialization JSONObjectWithData:featuresData
|
||||
options:0
|
||||
error:nil];
|
||||
if ([features isKindOfClass:[NSDictionary class]])
|
||||
{
|
||||
_pointFeatures = (NSDictionary*)features;
|
||||
}
|
||||
}
|
||||
}
|
||||
[_loadLock unlock];
|
||||
return _pointFeatures;
|
||||
}
|
||||
|
||||
- (void)parseFeaturesAddingCount:(NSUInteger)featuresCount usingViews:(BOOL)useViews
|
||||
{
|
||||
[self.mapView removeAnnotations:self.mapView.annotations];
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
|
||||
{
|
||||
NSData *featuresData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"points" ofType:@"geojson"]];
|
||||
|
||||
id features = [NSJSONSerialization JSONObjectWithData:featuresData
|
||||
options:0
|
||||
error:nil];
|
||||
|
||||
if ([features isKindOfClass:[NSDictionary class]])
|
||||
if (auto *features = [self loadPointFeatures])
|
||||
{
|
||||
NSMutableArray *annotations = [NSMutableArray array];
|
||||
|
||||
for (NSDictionary *feature in features[@"features"])
|
||||
{
|
||||
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake([feature[@"geometry"][@"coordinates"][1] doubleValue],
|
||||
@@ -864,6 +892,7 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^
|
||||
{
|
||||
[self.mapView removeAnnotations:self.mapView.annotations];
|
||||
[self.mapView addAnnotations:annotations];
|
||||
[self.mapView showAnnotations:annotations animated:YES];
|
||||
});
|
||||
@@ -1131,34 +1160,59 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
|
||||
- (void)styleSymbolLayer
|
||||
{
|
||||
MLNSymbolStyleLayer *stateLayer = (MLNSymbolStyleLayer *)[self.mapView.style layerWithIdentifier:@"state-label-lg"];
|
||||
stateLayer.textColor = [NSExpression expressionForConstantValue:[UIColor redColor]];
|
||||
if (auto *stateLayer = (MLNSymbolStyleLayer *)[self.mapView.style layerWithIdentifier:@"state-label-lg"])
|
||||
{
|
||||
stateLayer.textColor = [NSExpression expressionForConstantValue:[UIColor redColor]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)styleBuildingLayer
|
||||
{
|
||||
MLNTransition transition = { 5, 1 };
|
||||
self.mapView.style.transition = transition;
|
||||
MLNFillStyleLayer *buildingLayer = (MLNFillStyleLayer *)[self.mapView.style layerWithIdentifier:@"building"];
|
||||
buildingLayer.fillColor = [NSExpression expressionForConstantValue:[UIColor purpleColor]];
|
||||
if (auto *buildingLayer = (MLNFillStyleLayer *)[self.mapView.style layerWithIdentifier:@"building"])
|
||||
{
|
||||
buildingLayer.fillColor = [NSExpression expressionForConstantValue:[UIColor purpleColor]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)styleFerryLayer
|
||||
{
|
||||
MLNLineStyleLayer *ferryLineLayer = (MLNLineStyleLayer *)[self.mapView.style layerWithIdentifier:@"ferry"];
|
||||
ferryLineLayer.lineColor = [NSExpression expressionForConstantValue:[UIColor redColor]];
|
||||
if (auto *ferryLineLayer = (MLNLineStyleLayer *)[self.mapView.style layerWithIdentifier:@"ferry"])
|
||||
{
|
||||
ferryLineLayer.lineColor = [NSExpression expressionForConstantValue:[UIColor redColor]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeParkLayer
|
||||
{
|
||||
MLNFillStyleLayer *parkLayer = (MLNFillStyleLayer *)[self.mapView.style layerWithIdentifier:@"park"];
|
||||
[self.mapView.style removeLayer:parkLayer];
|
||||
if (auto *parkLayer = (MLNFillStyleLayer *)[self.mapView.style layerWithIdentifier:@"park"])
|
||||
{
|
||||
[self.mapView.style removeLayer:parkLayer];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)loadStyleFromBundle:(NSString*)path
|
||||
{
|
||||
NSString *resourcePath = [[NSBundle mainBundle] pathForResource:path ofType:@"json"];
|
||||
if (NSURL* url = resourcePath ? [NSURL fileURLWithPath:resourcePath] : nil) {
|
||||
[self.mapView setStyleURL:url];
|
||||
return TRUE;
|
||||
} else {
|
||||
NSString *msg = [NSString stringWithFormat:@"Missing Style %@", path];
|
||||
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Style File" message:msg preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self presentViewController:alertController animated:YES completion:nil];
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)styleFilteredFill
|
||||
{
|
||||
// set style and focus on Texas
|
||||
[self.mapView setStyleURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"fill_filter_style" ofType:@"json"]]];
|
||||
if (![self loadStyleFromBundle:@"fill_filter_style"]) {
|
||||
return;
|
||||
}
|
||||
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(31, -100) zoomLevel:3 animated:NO];
|
||||
|
||||
// after slight delay, fill in Texas (atypical use; we want to clearly see the change for test purposes)
|
||||
@@ -1178,7 +1232,9 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
- (void)styleFilteredLines
|
||||
{
|
||||
// set style and focus on lower 48
|
||||
[self.mapView setStyleURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"line_filter_style" ofType:@"json"]]];
|
||||
if (![self loadStyleFromBundle:@"line_filter_style"]) {
|
||||
return;
|
||||
}
|
||||
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(40, -97) zoomLevel:5 animated:NO];
|
||||
|
||||
// after slight delay, change styling for all Washington-named counties (atypical use; we want to clearly see the change for test purposes)
|
||||
@@ -1199,7 +1255,9 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
- (void)styleNumericFilteredFills
|
||||
{
|
||||
// set style and focus on lower 48
|
||||
[self.mapView setStyleURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"numeric_filter_style" ofType:@"json"]]];
|
||||
if (![self loadStyleFromBundle:@"numeric_filter_style"]) {
|
||||
return;
|
||||
}
|
||||
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(40, -97) zoomLevel:5 animated:NO];
|
||||
|
||||
// after slight delay, change styling for regions 200-299 (atypical use; we want to clearly see the change for test purposes)
|
||||
@@ -1458,6 +1516,12 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
[self.mapView.style addLayer:rasterLayer];
|
||||
}
|
||||
|
||||
- (NSURL*)radarImageURL:(int)index
|
||||
{
|
||||
return [NSURL URLWithString:
|
||||
[NSString stringWithFormat:@"https://maplibre.org/maplibre-gl-js/docs/assets/radar%d.gif", index]];
|
||||
}
|
||||
|
||||
- (void)styleImageSource
|
||||
{
|
||||
MLNCoordinateQuad coordinateQuad = {
|
||||
@@ -1466,7 +1530,9 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
{ 37.936, -71.516 },
|
||||
{ 46.437, -71.516 } };
|
||||
|
||||
MLNImageSource *imageSource = [[MLNImageSource alloc] initWithIdentifier:@"style-image-source-id" coordinateQuad:coordinateQuad URL:[NSURL URLWithString:@"https://maplibre.org/maplibre-gl-js-docs/assets/radar0.gif"]];
|
||||
MLNImageSource *imageSource = [[MLNImageSource alloc] initWithIdentifier:@"style-image-source-id"
|
||||
coordinateQuad:coordinateQuad
|
||||
URL:[self radarImageURL:0]];
|
||||
|
||||
[self.mapView.style addSource:imageSource];
|
||||
|
||||
@@ -1478,17 +1544,31 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
selector:@selector(updateAnimatedImageSource:)
|
||||
userInfo:imageSource
|
||||
repeats:YES];
|
||||
|
||||
const CGFloat maximumPadding = 50;
|
||||
const CGSize frameSize = self.mapView.frame.size;
|
||||
const CGFloat yPadding = (frameSize.height / 5 <= maximumPadding) ? (frameSize.height / 5) : maximumPadding;
|
||||
const CGFloat xPadding = (frameSize.width / 5 <= maximumPadding) ? (frameSize.width / 5) : maximumPadding;
|
||||
[self.mapView setVisibleCoordinateBounds:MLNCoordinateBoundsMake(coordinateQuad.bottomLeft, coordinateQuad.topRight)
|
||||
edgePadding:UIEdgeInsetsMake(yPadding, xPadding, yPadding, xPadding)
|
||||
animated:YES
|
||||
completionHandler:nil];
|
||||
}
|
||||
|
||||
|
||||
- (void)updateAnimatedImageSource:(NSTimer *)timer {
|
||||
static int radarSuffix = 0;
|
||||
- (void)updateAnimatedImageSource:(NSTimer *)timer
|
||||
{
|
||||
MLNImageSource *imageSource = (MLNImageSource *)timer.userInfo;
|
||||
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"https://maplibre.org/maplibre-gl-js-docs/assets/radar%d.gif", radarSuffix++]];
|
||||
[imageSource setValue:url forKey:@"URL"];
|
||||
if (radarSuffix > 3) {
|
||||
radarSuffix = 0;
|
||||
if (![self.mapView.style sourceWithIdentifier:imageSource.identifier]) {
|
||||
// the source has been removed, probably by reloading the style, if we try to update
|
||||
// it now, we will crash with 'This source got invalidated after the style change'
|
||||
[timer invalidate];
|
||||
return;
|
||||
}
|
||||
|
||||
static int radarSuffix = 0;
|
||||
[imageSource setValue:[self radarImageURL:radarSuffix] forKey:@"URL"];
|
||||
radarSuffix = (radarSuffix + 1) % 5;
|
||||
}
|
||||
|
||||
-(void)toggleStyleLabelsLanguage
|
||||
@@ -1518,7 +1598,6 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
NSDictionary *sourceOptions = @{ MLNShapeSourceOptionLineDistanceMetrics: @YES };
|
||||
|
||||
MLNShapeSource *routeSource = [[MLNShapeSource alloc] initWithIdentifier:@"style-route-source" shape:routeLine options:sourceOptions];
|
||||
[self.mapView.style addSource:routeSource];
|
||||
|
||||
MLNLineStyleLayer *baseRouteLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"style-base-route-layer" source:routeSource];
|
||||
baseRouteLayer.lineColor = [NSExpression expressionForConstantValue:[UIColor orangeColor]];
|
||||
@@ -1526,7 +1605,6 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
baseRouteLayer.lineOpacity = [NSExpression expressionForConstantValue:@0.95];
|
||||
baseRouteLayer.lineCap = [NSExpression expressionForConstantValue:@"round"];
|
||||
baseRouteLayer.lineJoin = [NSExpression expressionForConstantValue:@"round"];
|
||||
[self.mapView.style addLayer:baseRouteLayer];
|
||||
|
||||
MLNLineStyleLayer *routeLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"style-route-layer" source:routeSource];
|
||||
routeLayer.lineColor = [NSExpression expressionForConstantValue:[UIColor whiteColor]];
|
||||
@@ -1547,6 +1625,12 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
// (mgl_interpolate:withCurveType:parameters:stops:($lineProgress, 'linear', nil, %@))
|
||||
NSExpression *lineGradientExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($lineProgress, 'linear', nil, %@)", stops];
|
||||
routeLayer.lineGradient = lineGradientExpression;
|
||||
|
||||
[self removeLayer:baseRouteLayer.identifier];
|
||||
[self removeLayer:routeLayer.identifier];
|
||||
[self removeSource:routeSource.identifier];
|
||||
[self.mapView.style addSource:routeSource];
|
||||
[self.mapView.style addLayer:baseRouteLayer];
|
||||
[self.mapView.style addLayer:routeLayer];
|
||||
}
|
||||
|
||||
@@ -1562,6 +1646,22 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)removeSource:(NSString*)ident
|
||||
{
|
||||
if (MLNSource *source = [self.mapView.style sourceWithIdentifier:ident])
|
||||
{
|
||||
[self.mapView.style removeSource:source];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeLayer:(NSString*)ident
|
||||
{
|
||||
if (MLNStyleLayer* layer = [self.mapView.style layerWithIdentifier:ident])
|
||||
{
|
||||
[self.mapView.style removeLayer:layer];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)styleRouteLine
|
||||
{
|
||||
CLLocationCoordinate2D coords[] = {
|
||||
@@ -1581,7 +1681,6 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
MLNPolylineFeature *routeLine = [MLNPolylineFeature polylineWithCoordinates:coords count:count];
|
||||
|
||||
MLNShapeSource *routeSource = [[MLNShapeSource alloc] initWithIdentifier:@"style-route-source" shape:routeLine options:nil];
|
||||
[self.mapView.style addSource:routeSource];
|
||||
|
||||
MLNLineStyleLayer *baseRouteLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"style-base-route-layer" source:routeSource];
|
||||
baseRouteLayer.lineColor = [NSExpression expressionForConstantValue:[UIColor orangeColor]];
|
||||
@@ -1589,7 +1688,6 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
baseRouteLayer.lineOpacity = [NSExpression expressionForConstantValue:@0.5];
|
||||
baseRouteLayer.lineCap = [NSExpression expressionForConstantValue:@"round"];
|
||||
baseRouteLayer.lineJoin = [NSExpression expressionForConstantValue:@"round"];
|
||||
[self.mapView.style addLayer:baseRouteLayer];
|
||||
|
||||
MLNLineStyleLayer *routeLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"style-route-layer" source:routeSource];
|
||||
routeLayer.lineColor = [NSExpression expressionForConstantValue:[UIColor whiteColor]];
|
||||
@@ -1597,16 +1695,24 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
routeLayer.lineOpacity = [NSExpression expressionForConstantValue:@0.8];
|
||||
routeLayer.lineCap = [NSExpression expressionForConstantValue:@"round"];
|
||||
routeLayer.lineJoin = [NSExpression expressionForConstantValue:@"round"];
|
||||
|
||||
[self removeLayer:baseRouteLayer.identifier];
|
||||
[self removeLayer:routeLayer.identifier];
|
||||
[self removeSource:routeSource.identifier];
|
||||
[self.mapView.style addSource:routeSource];
|
||||
[self.mapView.style addLayer:baseRouteLayer];
|
||||
[self.mapView.style addLayer:routeLayer];
|
||||
}
|
||||
|
||||
- (void)styleAddCustomTriangleLayer
|
||||
{
|
||||
CustomStyleLayerExample *layer = [[CustomStyleLayerExample alloc] initWithIdentifier:@"mbx-custom"];
|
||||
[self.mapView.style addLayer:layer];
|
||||
if (CustomStyleLayerExample *layer = [[CustomStyleLayerExample alloc] initWithIdentifier:@"mbx-custom"]) {
|
||||
[self.mapView.style addLayer:layer];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stylePolygonWithDDS {
|
||||
- (void)stylePolygonWithDDS
|
||||
{
|
||||
CLLocationCoordinate2D leftCoords[] = {
|
||||
{37.73081027834234, -122.49412536621094},
|
||||
{37.7566013348511, -122.49412536621094},
|
||||
@@ -1920,72 +2026,80 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
secondMapView.showsScale = YES;
|
||||
secondMapView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
secondMapView.tag = 2;
|
||||
|
||||
// Remove the main map bottom constraint
|
||||
for (NSLayoutConstraint *constraint in self.view.constraints)
|
||||
{
|
||||
if ((constraint.firstItem == self.mapView && constraint.firstAttribute == NSLayoutAttributeBottom) ||
|
||||
(constraint.secondItem == self.mapView && constraint.secondAttribute == NSLayoutAttributeBottom))
|
||||
{
|
||||
_firstMapLayout = constraint;
|
||||
[self.view removeConstraint:constraint];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Place the second map in the bottom half of the view
|
||||
_secondMapLayout = @[
|
||||
[NSLayoutConstraint constraintWithItem:self.mapView
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self.view
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
multiplier:1
|
||||
constant:0],
|
||||
[NSLayoutConstraint constraintWithItem:secondMapView
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self.view
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1
|
||||
constant:0],
|
||||
[NSLayoutConstraint constraintWithItem:secondMapView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self.view
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:1
|
||||
constant:0],
|
||||
[NSLayoutConstraint constraintWithItem:secondMapView
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self.view
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
multiplier:1
|
||||
constant:0],
|
||||
[secondMapView.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor]];
|
||||
|
||||
[self.view addSubview:secondMapView];
|
||||
[self.view addConstraints:@[
|
||||
[NSLayoutConstraint constraintWithItem:self.mapView
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self.view
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
multiplier:1
|
||||
constant:0],
|
||||
[NSLayoutConstraint constraintWithItem:secondMapView
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self.view
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1
|
||||
constant:0],
|
||||
[NSLayoutConstraint constraintWithItem:secondMapView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self.view
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:1
|
||||
constant:0],
|
||||
[NSLayoutConstraint constraintWithItem:secondMapView
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self.view
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
multiplier:1
|
||||
constant:0],
|
||||
[NSLayoutConstraint constraintWithItem:secondMapView
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self.view.safeAreaLayoutGuide.bottomAnchor
|
||||
attribute:NSLayoutAttributeBottom
|
||||
multiplier:1
|
||||
constant:0],
|
||||
]];
|
||||
[self.view addConstraints:_secondMapLayout];
|
||||
|
||||
secondMapView.styleURL = _mapView.styleURL;
|
||||
|
||||
__weak decltype(_mapView) weakMapView = _mapView;
|
||||
__weak decltype(secondMapView) weakSecondMap = secondMapView;
|
||||
auto moveComplete = ^{
|
||||
weakSecondMap.camera = [_mapView cameraByTiltingToPitch:weakMapView.camera.pitch];
|
||||
};
|
||||
|
||||
// Navigate the new map to the same view as the main one
|
||||
[secondMapView setCenterCoordinate:_mapView.centerCoordinate
|
||||
zoomLevel:_mapView.zoomLevel
|
||||
direction:_mapView.direction
|
||||
animated:YES
|
||||
completionHandler:moveComplete];
|
||||
|
||||
secondMapView.accessibilityIdentifier = @"Second Map";
|
||||
} else {
|
||||
NSMutableArray *constraintsToRemove = [NSMutableArray array];
|
||||
MLNMapView *secondMapView = (MLNMapView *)[self.view viewWithTag:2];
|
||||
for (NSLayoutConstraint *constraint in self.view.constraints)
|
||||
{
|
||||
if (constraint.firstItem == secondMapView || constraint.secondItem == secondMapView)
|
||||
{
|
||||
[constraintsToRemove addObject:constraint];
|
||||
}
|
||||
}
|
||||
[self.view removeConstraints:constraintsToRemove];
|
||||
|
||||
// Reset the layout to the original state
|
||||
[self.view removeConstraints:_secondMapLayout];
|
||||
[self.view addConstraint:_firstMapLayout];
|
||||
_firstMapLayout = nil;
|
||||
_secondMapLayout = nil;
|
||||
|
||||
[secondMapView removeFromSuperview];
|
||||
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.mapView
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self.view.safeAreaLayoutGuide.bottomAnchor
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2484,8 +2598,18 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
[self presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (BOOL)isUITesting {
|
||||
NSString* value = NSProcessInfo.processInfo.environment[@"UITesting"];
|
||||
return value && [value isEqual: @"YES"];
|
||||
}
|
||||
|
||||
- (void)saveCurrentMapState:(__unused NSNotification *)notification {
|
||||
|
||||
// saved changes to the settings can break UI tests, so always start from the defaults when testing
|
||||
if ([self isUITesting]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The following properties can change after the view loads so we need to save their
|
||||
// state before exiting the view controller.
|
||||
self.currentState.camera = self.mapView.camera;
|
||||
@@ -2502,6 +2626,11 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
}
|
||||
|
||||
- (void)restoreMapState:(__unused NSNotification *)notification {
|
||||
|
||||
if ([self isUITesting]) {
|
||||
return;
|
||||
}
|
||||
|
||||
MBXState *currentState = [MBXStateManager sharedManager].currentState;
|
||||
|
||||
self.mapView.camera = currentState.camera;
|
||||
@@ -92,7 +92,7 @@ MLN_PUBLIC_IOS_APP_SOPURCE = [
|
||||
"app/MBXState.m",
|
||||
"app/MBXStateManager.m",
|
||||
"app/MBXUserLocationAnnotationView.m",
|
||||
"app/MBXViewController.m",
|
||||
"app/MBXViewController.mm",
|
||||
"app/main.m",
|
||||
"app/MBXAnnotationView.h",
|
||||
"app/MBXAppDelegate.h",
|
||||
|
||||
@@ -15,6 +15,7 @@ swift_library(
|
||||
|
||||
ios_ui_test(
|
||||
name = "uitest",
|
||||
size = "large",
|
||||
minimum_os_version = "12.0",
|
||||
provisioning_profile = "xcode_profile",
|
||||
test_host = "//platform/ios:App",
|
||||
|
||||
@@ -14,6 +14,7 @@ class iosapp_UITests: XCTestCase {
|
||||
override func setUpWithError() throws {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
// UI tests must launch the application that they test.
|
||||
app.launchEnvironment.updateValue("YES", forKey: "UITesting")
|
||||
app.launch()
|
||||
|
||||
// In UI tests it is usually best to stop immediately when a failure occurs.
|
||||
@@ -38,7 +39,6 @@ class iosapp_UITests: XCTestCase {
|
||||
/// Turn on Debug tile boundaries, tile info and FPS ornaments
|
||||
/// Demonstrates how the Tile Boundaries look when rendered
|
||||
func testDebugBoundaryTiles() {
|
||||
|
||||
app.windows.children(matching: .other).element.children(matching: .other).element.children(matching: .other).element.doubleTap()
|
||||
|
||||
let mapSettingsButton = app.navigationBars["MapLibre Basic"].buttons["Map settings"]
|
||||
@@ -65,7 +65,174 @@ class iosapp_UITests: XCTestCase {
|
||||
sleep(1)
|
||||
add(screenshot(name: "Null Island, Zoom=0"))
|
||||
}
|
||||
|
||||
|
||||
var mapSettingsButton: XCUIElement { get { app.navigationBars["MapLibre Basic"].buttons["Map settings"] } }
|
||||
|
||||
/// Open and close the secondary map view a few times to ensure that the dynamic layout adjustment doesn't crash
|
||||
func testSecondMap() {
|
||||
let showTimeout = 1.0
|
||||
let hideTimeout = 10.0
|
||||
let iterations = 3
|
||||
|
||||
let secondMapQuery = app.otherElements["Second Map"]
|
||||
XCTAssert(!secondMapQuery.exists)
|
||||
|
||||
for _ in 0..<iterations {
|
||||
mapSettingsButton.tap()
|
||||
app.tables.staticTexts["Show Second Map"].tap()
|
||||
XCTAssert(secondMapQuery.waitForExistence(timeout: showTimeout))
|
||||
|
||||
sleep(1)
|
||||
|
||||
mapSettingsButton.tap()
|
||||
app.tables.staticTexts["Hide Second Map"].tap()
|
||||
|
||||
expectation(for: NSPredicate(format: "exists == 0"), evaluatedWith: secondMapQuery)
|
||||
waitForExpectations(timeout: hideTimeout, handler: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func testAll() {
|
||||
let allItems = [
|
||||
"Reset position",
|
||||
"Show tile boundaries",
|
||||
"Hide tile boundaries",
|
||||
"Show tile info",
|
||||
"Hide tile info",
|
||||
"Show tile timestamps",
|
||||
"Hide tile timestamps",
|
||||
"Show collision boxes",
|
||||
"Hide collision boxes",
|
||||
"Show overdraw visualization",
|
||||
"Hide overdraw visualization",
|
||||
"Show zoom level ornament",
|
||||
"Hide zoom level ornament",
|
||||
"Show frame time graph",
|
||||
"Hide frame time graph",
|
||||
"Show reuse queue stats",
|
||||
"Hide reuse queue stats",
|
||||
"Add 100 Views",
|
||||
//"Add 1,000 Views", // These cause the test to time out
|
||||
//"Add 10,000 Views",
|
||||
"Add 100 Sprites",
|
||||
//"Add 1,000 Sprites",
|
||||
//"Add 10,000 Sprites",
|
||||
"Animate an Annotation View",
|
||||
"Add Test Shapes",
|
||||
"Add 10x Test Shapes",
|
||||
"Add Point With Custom Callout",
|
||||
"Query Annotations",
|
||||
"Enable Custom User Dot",
|
||||
"Disable Custom User Dot",
|
||||
"Remove Annotations",
|
||||
"Select an offscreen point annotation",
|
||||
"Center selected annotation",
|
||||
"Add visible area polyline",
|
||||
"Add Building Extrusions",
|
||||
"Style Water With Function",
|
||||
"Style Roads With Function",
|
||||
"Add Raster & Apply Function",
|
||||
"Add Shapes & Apply Fill",
|
||||
"Style Symbol Color",
|
||||
"Style Building Fill Color",
|
||||
"Style Ferry Line Color",
|
||||
"Remove Parks",
|
||||
"Style Fill With Filter",
|
||||
"Style Lines With Filter",
|
||||
"Style Fill With Numeric Filter",
|
||||
"Query and Style Features",
|
||||
"Style Feature",
|
||||
"Style Dynamic Point Collection",
|
||||
"Update Shape Source: Data",
|
||||
"Update Shape Source: URL",
|
||||
"Update Shape Source: Features",
|
||||
"Style Vector Tile Source",
|
||||
"Style Raster Tile Source",
|
||||
"Style Image Source",
|
||||
"Add Route Line",
|
||||
"Dynamically Style Polygon",
|
||||
"Add Custom Lat/Lon Grid",
|
||||
"Style Route line with gradient",
|
||||
"Start World Tour",
|
||||
"Random Tour",
|
||||
"Show Second Map",
|
||||
"Hide Second Map",
|
||||
"Missing Icon",
|
||||
"Limit Camera Changes",
|
||||
"Unlimit Camera Changes",
|
||||
"Turn On Content Insets",
|
||||
"Turn Off Content Insets",
|
||||
"Lat Long bounds with padding",
|
||||
"Show Labels in Default Language",
|
||||
]
|
||||
|
||||
for label in allItems {
|
||||
mapSettingsButton.tap()
|
||||
app.tables.staticTexts[label].tap()
|
||||
sleep(1)
|
||||
}
|
||||
|
||||
// Only one of these will show up, run whichever one is there
|
||||
mapSettingsButton.tap()
|
||||
if let customLayer = staticItemIfExists("Add Custom Triangle Layer (OpenGL)") ??
|
||||
staticItemIfExists("Add Custom Triangle Layer (Metal)") {
|
||||
customLayer.tap()
|
||||
sleep(1)
|
||||
mapSettingsButton.tap()
|
||||
}
|
||||
|
||||
if let customLayer = staticItemIfExists("Add Custom Drawable Layer") {
|
||||
customLayer.tap()
|
||||
sleep(1)
|
||||
mapSettingsButton.tap()
|
||||
}
|
||||
|
||||
// See `bestLanguageForUser`
|
||||
for loc in [ "ar", "de", "en", "es", "fr", "ja", "ko", "pt", "ru", "zh", "zh-Hans", "zh-Hant" ] {
|
||||
if let name = NSLocale(localeIdentifier: loc).displayName(forKey: .identifier, value: loc) {
|
||||
if let item = staticItemIfExists("Show Labels in " + name) {
|
||||
item.tap()
|
||||
sleep(1)
|
||||
mapSettingsButton.tap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// These open another view controller that then needs to be closed
|
||||
|
||||
app.tables.staticTexts["View Route Simulation"].tap()
|
||||
XCTAssert(tapBackButton("MBXCustomLocationView", timeout: 5))
|
||||
|
||||
mapSettingsButton.tap()
|
||||
app.tables.staticTexts["Ornaments Placement"].tap()
|
||||
XCTAssert(tapBackButton("Ornaments", timeout: 5))
|
||||
|
||||
mapSettingsButton.tap()
|
||||
app.tables.staticTexts["Show Snapshots"].tap()
|
||||
XCTAssert(tapBackButton("MBXSnapshotsView", timeout: 5))
|
||||
|
||||
mapSettingsButton.tap()
|
||||
app.tables.staticTexts["Embedded Map View"].tap()
|
||||
XCTAssert(tapBackButton("MBXEmbeddedMapView", timeout: 5))
|
||||
}
|
||||
|
||||
private func staticItemIfExists(_ ident: String) -> XCUIElement? {
|
||||
let item = app.staticTexts[ident]
|
||||
return item.exists ? item : nil
|
||||
}
|
||||
|
||||
private func tapBackButton(_ label: String, timeout: TimeInterval) -> Bool {
|
||||
let bar = app.navigationBars[label]
|
||||
if (bar.waitForExistence(timeout: timeout)) {
|
||||
let button = bar.buttons["Back"]
|
||||
if (button.waitForExistence(timeout: 1)) {
|
||||
button.tap()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func testRecord() {
|
||||
/// Use recording to get started writing UI tests.
|
||||
/// Use `Editor` > `Start Recording UI Test` while your cursor is in this `func`
|
||||
|
||||
@@ -75,6 +75,8 @@ FOUNDATION_EXTERN MLN_EXPORT MLNExceptionName const _Nonnull MLNUnderlyingMapUna
|
||||
@property (nonatomic, readonly) BOOL enablePresentsWithTransaction;
|
||||
@property (nonatomic, assign) BOOL needsDisplayRefresh;
|
||||
|
||||
- (MLNMapCamera *_Nullable)cameraByTiltingToPitch:(CGFloat)pitch;
|
||||
|
||||
- (BOOL) _opaque;
|
||||
|
||||
@end
|
||||
|
||||
Reference in New Issue
Block a user