From 0542ece0bf2456db3ca7f0140bb6ac43c4697a97 Mon Sep 17 00:00:00 2001 From: Fabian Guerra Soto Date: Wed, 10 Jan 2018 14:28:55 -0600 Subject: [ios] Fix the layout constraints on iOS 11 for iPhone X (#10858) * [ios] Fix the layout constraints on iOS 11 for iPhone X for the bottom ornaments. * WIP adding tests for layout of map view attribution logo and other ornaments * Rename * First pass at tests for mapview ornament layout * Additional code changes from call with Fabian and Jason * [ios] Fix ornaments insets margin space. * [ios] Fix map view layout test for pre iOS 11. * [ios] Refactor MGLMapView ornaments layout constraints for iOS 11. * [ios] Remove NSLogs from MGLMapViewLayoutTests --- platform/ios/ios.xcodeproj/project.pbxproj | 4 ++ platform/ios/src/MGLMapView.mm | 56 +++++++-------- platform/ios/test/MGLMapViewLayoutTests.m | 108 +++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 32 deletions(-) create mode 100644 platform/ios/test/MGLMapViewLayoutTests.m (limited to 'platform/ios') diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index c405e92d35..52f33a83b6 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 071BBB031EE76146001FB02A /* MGLImageSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 071BBAFC1EE75CD4001FB02A /* MGLImageSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; 071BBB041EE76147001FB02A /* MGLImageSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 071BBAFC1EE75CD4001FB02A /* MGLImageSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; 071BBB071EE77631001FB02A /* MGLImageSourceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 071BBB051EE7761A001FB02A /* MGLImageSourceTests.m */; }; + 16376B491FFEED010000563E /* MGLMapViewLayoutTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 16376B481FFEED010000563E /* MGLMapViewLayoutTests.m */; }; 1753ED421E53CE6F00A9FD90 /* MGLConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 1753ED411E53CE6F00A9FD90 /* MGLConversion.h */; }; 1753ED431E53CE6F00A9FD90 /* MGLConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 1753ED411E53CE6F00A9FD90 /* MGLConversion.h */; }; 1F06668A1EC64F8E001C16D7 /* MGLLight.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F0666881EC64F8E001C16D7 /* MGLLight.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -580,6 +581,7 @@ 071BBAFC1EE75CD4001FB02A /* MGLImageSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLImageSource.h; sourceTree = ""; }; 071BBAFD1EE75CD4001FB02A /* MGLImageSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLImageSource.mm; sourceTree = ""; }; 071BBB051EE7761A001FB02A /* MGLImageSourceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLImageSourceTests.m; path = ../../darwin/test/MGLImageSourceTests.m; sourceTree = ""; }; + 16376B481FFEED010000563E /* MGLMapViewLayoutTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLMapViewLayoutTests.m; sourceTree = ""; }; 1753ED411E53CE6F00A9FD90 /* MGLConversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLConversion.h; sourceTree = ""; }; 1F0666881EC64F8E001C16D7 /* MGLLight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight.h; sourceTree = ""; }; 1F0666891EC64F8E001C16D7 /* MGLLight.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLLight.mm; sourceTree = ""; }; @@ -1365,6 +1367,7 @@ DA0CD58F1CF56F6A00A5F5A5 /* MGLFeatureTests.mm */, DA2E885C1CC0382C00F24E7B /* MGLGeometryTests.mm */, DA5DB1291FABF1EE001C2326 /* MGLMapAccessibilityElementTests.m */, + 16376B481FFEED010000563E /* MGLMapViewLayoutTests.m */, 35E208A61D24210F00EC9A46 /* MGLNSDataAdditionsTests.m */, 1F95931C1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm */, DAE7DEC11E245455007505A6 /* MGLNSStringAdditionsTests.m */, @@ -2311,6 +2314,7 @@ DA0CD5901CF56F6A00A5F5A5 /* MGLFeatureTests.mm in Sources */, 556660D81E1D085500E2C41B /* MGLVersionNumber.m in Sources */, 4031ACFF1E9FD29F00A3EA26 /* MGLSDKTestHelpers.swift in Sources */, + 16376B491FFEED010000563E /* MGLMapViewLayoutTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index c02446609a..0849cbd12f 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -753,7 +753,7 @@ public: toItem:viewController.topLayoutGuide attribute:NSLayoutAttributeBottom multiplier:1.0 - constant:5.0]]; + constant:8.0]]; } [self.compassViewConstraints addObject: [NSLayoutConstraint constraintWithItem:self.compassView @@ -762,7 +762,7 @@ public: toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 - constant:5.0 + self.contentInset.top]]; + constant:8.0 + self.contentInset.top]]; [self.compassViewConstraints addObject: [NSLayoutConstraint constraintWithItem:self @@ -771,7 +771,7 @@ public: toItem:self.compassView attribute:NSLayoutAttributeTrailing multiplier:1.0 - constant:5.0 + self.contentInset.right]]; + constant:8.0 + self.contentInset.right]]; [containerView addConstraints:self.compassViewConstraints]; @@ -788,7 +788,7 @@ public: toItem:viewController.topLayoutGuide attribute:NSLayoutAttributeBottom multiplier:1.0 - constant:5.0]]; + constant:8.0]]; } [self.scaleBarConstraints addObject: [NSLayoutConstraint constraintWithItem:self.scaleBar @@ -797,7 +797,7 @@ public: toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 - constant:5.0 + self.contentInset.top]]; + constant:8.0 + self.contentInset.top]]; [self.scaleBarConstraints addObject: [NSLayoutConstraint constraintWithItem:self.scaleBar attribute:NSLayoutAttributeLeft @@ -831,7 +831,7 @@ public: toItem:self.logoView attribute:NSLayoutAttributeBaseline multiplier:1 - constant:8 + self.contentInset.bottom]]; + constant:8.0 + self.contentInset.bottom]]; [self.logoViewConstraints addObject: [NSLayoutConstraint constraintWithItem:self.logoView attribute:NSLayoutAttributeLeading @@ -855,7 +855,7 @@ public: toItem:self.attributionButton attribute:NSLayoutAttributeBaseline multiplier:1 - constant:8 + self.contentInset.bottom]]; + constant:8.0 + self.contentInset.bottom]]; } [self.attributionButtonConstraints addObject: [NSLayoutConstraint constraintWithItem:self @@ -864,7 +864,7 @@ public: toItem:self.attributionButton attribute:NSLayoutAttributeBaseline multiplier:1 - constant:8 + self.contentInset.bottom]]; + constant:8.0 + self.contentInset.bottom]]; [self.attributionButtonConstraints addObject: [NSLayoutConstraint constraintWithItem:self @@ -873,29 +873,20 @@ public: toItem:self.attributionButton attribute:NSLayoutAttributeTrailing multiplier:1 - constant:8 + self.contentInset.right]]; + constant:8.0 + self.contentInset.right]]; [containerView addConstraints:self.attributionButtonConstraints]; } - (void)updateConstraints { - -// If compiling with the iOS 11+ SDK -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 // If safeAreaLayoutGuide API exists - if ( [self respondsToSelector:@selector(safeAreaLayoutGuide)] ) { - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpartial-availability" + if (@available(iOS 11.0, *)) { UILayoutGuide *safeAreaLayoutGuide = self.safeAreaLayoutGuide; -#pragma clang diagnostic pop + // compass view [self removeConstraints:self.compassViewConstraints]; [self.compassViewConstraints removeAllObjects]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpartial-availability" - [self.compassViewConstraints addObject:[self.compassView.topAnchor constraintEqualToSystemSpacingBelowAnchor:safeAreaLayoutGuide.topAnchor multiplier:1]]; -#pragma clang diagnostic pop + [self.compassViewConstraints addObject:[self constraintForYAxisAnchor:self.compassView.topAnchor belowAnchor:safeAreaLayoutGuide.topAnchor]]; [self.compassViewConstraints addObject:[safeAreaLayoutGuide.rightAnchor constraintEqualToAnchor:self.compassView.rightAnchor constant:8.0 + self.contentInset.right]]; [self addConstraints:self.compassViewConstraints]; @@ -903,10 +894,7 @@ public: // scale bar view [self removeConstraints:self.scaleBarConstraints]; [self.scaleBarConstraints removeAllObjects]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpartial-availability" - [self.scaleBarConstraints addObject:[self.scaleBar.topAnchor constraintEqualToSystemSpacingBelowAnchor:safeAreaLayoutGuide.topAnchor multiplier:1]]; -#pragma clang diagnostic pop + [self.scaleBarConstraints addObject:[self constraintForYAxisAnchor:self.scaleBar.topAnchor belowAnchor:safeAreaLayoutGuide.topAnchor]]; [self.scaleBarConstraints addObject:[self.scaleBar.leftAnchor constraintEqualToAnchor:safeAreaLayoutGuide.leftAnchor constant:8.0 + self.contentInset.left]]; [self addConstraints:self.scaleBarConstraints]; @@ -914,8 +902,7 @@ public: // logo view [self removeConstraints:self.logoViewConstraints]; [self.logoViewConstraints removeAllObjects]; - [self.logoViewConstraints addObject:[safeAreaLayoutGuide.bottomAnchor constraintEqualToAnchor:self.logoView.bottomAnchor - constant:8.0 + self.contentInset.bottom]]; + [self.logoViewConstraints addObject:[self constraintForYAxisAnchor:safeAreaLayoutGuide.bottomAnchor belowAnchor:self.logoView.bottomAnchor]]; [self.logoViewConstraints addObject:[self.logoView.leftAnchor constraintEqualToAnchor:safeAreaLayoutGuide.leftAnchor constant:8.0 + self.contentInset.left]]; [self addConstraints:self.logoViewConstraints]; @@ -923,21 +910,26 @@ public: // attribution button [self removeConstraints:self.attributionButtonConstraints]; [self.attributionButtonConstraints removeAllObjects]; - [self.attributionButtonConstraints addObject:[safeAreaLayoutGuide.bottomAnchor constraintEqualToAnchor:self.attributionButton.bottomAnchor - constant:8.0 + self.contentInset.bottom]]; + [self.attributionButtonConstraints addObject:[self constraintForYAxisAnchor:safeAreaLayoutGuide.bottomAnchor belowAnchor:self.attributionButton.bottomAnchor]]; [self.attributionButtonConstraints addObject:[safeAreaLayoutGuide.rightAnchor constraintEqualToAnchor:self.attributionButton.rightAnchor constant:8.0 + self.contentInset.right]]; [self addConstraints:self.attributionButtonConstraints]; } else { [self updateConstraintsPreiOS11]; } -#else - [self updateConstraintsPreiOS11]; -#endif [super updateConstraints]; } +- (NSLayoutConstraint *)constraintForYAxisAnchor:(NSLayoutYAxisAnchor *)yAxisAnchor belowAnchor:(NSLayoutYAxisAnchor *)anchor +{ + if (@available(iOS 11.0, *)) { + return [yAxisAnchor constraintEqualToSystemSpacingBelowAnchor:anchor multiplier:1]; + } else { + return nil; + } +} + - (BOOL)isOpaque { return _opaque; diff --git a/platform/ios/test/MGLMapViewLayoutTests.m b/platform/ios/test/MGLMapViewLayoutTests.m new file mode 100644 index 0000000000..a41e7695f9 --- /dev/null +++ b/platform/ios/test/MGLMapViewLayoutTests.m @@ -0,0 +1,108 @@ +#import +#import "MGLMapView.h" +#import "MGLMapViewDelegate.h" +#import "MGLAccountManager.h" + + +@interface MGLMapViewLayoutTests : XCTestCase + +@property (nonatomic) UIView *superView; +@property (nonatomic) MGLMapView *mapView; +@property (nonatomic) XCTestExpectation *styleLoadingExpectation; + +@end + +@implementation MGLMapViewLayoutTests + +- (void)setUp { + [super setUp]; + + [MGLAccountManager setAccessToken:@"pk.feedcafedeadbeefbadebede"]; + NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; + + self.superView = [[UIView alloc] initWithFrame:UIScreen.mainScreen.bounds]; + + self.mapView = [[MGLMapView alloc] initWithFrame:UIScreen.mainScreen.bounds styleURL:styleURL]; + self.mapView.delegate = self; + + [self.superView addSubview:self.mapView]; + + UIView *mapView = self.mapView; + NSDictionary *bindings = NSDictionaryOfVariableBindings(mapView); + NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[mapView]-0-|" options:0 metrics:nil views:bindings]; + NSArray *horizonatalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[mapView]-0-|" options:0 metrics:nil views:bindings]; + + [self.superView addConstraints:[verticalConstraints arrayByAddingObjectsFromArray:horizonatalConstraints]]; + + self.styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."]; + [self waitForExpectationsWithTimeout:1 handler:nil]; + + //set zoom and heading so that scale bar and compass will be shown + [self.mapView setZoomLevel:4.5 animated:NO]; + [self.mapView.camera setHeading:12.0]; + + //invoke layout + [self.superView setNeedsLayout]; + [self.superView layoutIfNeeded]; + +} + +- (void)mapView:(MGLMapView *)mapView didFinishLoadingStyle:(MGLStyle *)style { + XCTAssertNotNil(mapView.style); + XCTAssertEqual(mapView.style, style); + + [self.styleLoadingExpectation fulfill]; +} + +- (void)tearDown { + self.styleLoadingExpectation = nil; + self.mapView = nil; + + [super tearDown]; +} + +- (void)testOrnamentPlacement { + + CGFloat margin = 8.0; + CGFloat bottomSafeAreaInset = 0.0; + double accuracy = 0.01; + + if ( [self.mapView respondsToSelector:@selector(safeAreaInsets)] ) { + bottomSafeAreaInset = self.mapView.safeAreaInsets.bottom; + } + + //compass + UIImageView *compassView = self.mapView.compassView; + + CGFloat expectedCompassOriginX = CGRectGetMaxX(self.mapView.bounds) - margin - CGRectGetWidth(compassView.frame); + CGFloat expectedCompassOriginY = margin; + + XCTAssertEqualWithAccuracy(CGRectGetMinX(compassView.frame), expectedCompassOriginX, accuracy); + XCTAssertEqualWithAccuracy(CGRectGetMinY(compassView.frame), expectedCompassOriginY, accuracy); + + //scale bar + UIView *scaleBar = self.mapView.scaleBar; + + XCTAssertEqualWithAccuracy(CGRectGetMinX(scaleBar.frame), margin, accuracy); + XCTAssertEqualWithAccuracy(CGRectGetMinY(scaleBar.frame), margin, accuracy); + + //attribution button + UIButton *attributionButton = self.mapView.attributionButton; + + CGFloat expectedButtonOriginX = CGRectGetMaxX(self.mapView.bounds) - margin - CGRectGetWidth(attributionButton.frame); + CGFloat expectedButtonOriginY = CGRectGetMaxY(self.mapView.bounds) - margin - bottomSafeAreaInset - CGRectGetHeight(attributionButton.frame); + + XCTAssertEqualWithAccuracy(CGRectGetMinX(attributionButton.frame), expectedButtonOriginX, accuracy); + XCTAssertEqualWithAccuracy(CGRectGetMinY(attributionButton.frame), expectedButtonOriginY, accuracy); + + //mapbox logo + UIImageView *logoView = self.mapView.logoView; + + CGFloat expectedLogoOriginX = margin; + CGFloat expectedLogoOriginY = CGRectGetMaxY(self.mapView.bounds) - margin - bottomSafeAreaInset - CGRectGetHeight(logoView.frame); + + XCTAssertEqualWithAccuracy(CGRectGetMinX(logoView.frame), expectedLogoOriginX, accuracy); + XCTAssertEqualWithAccuracy(CGRectGetMinY(logoView.frame), expectedLogoOriginY, accuracy); +} + +@end -- cgit v1.2.1