From 3a32d9fe5cb30cadd6f7a2fb8f27497094b7e2e2 Mon Sep 17 00:00:00 2001 From: Josh <1541038+josh-ksr@users.noreply.github.com> Date: Thu, 31 Oct 2019 20:20:24 +0100 Subject: [PATCH] Force orientation change when current orientation not allowed on iOS (#13170) --- AUTHORS | 1 + .../framework/Source/FlutterViewController.mm | 34 ++- .../Source/FlutterViewControllerTest.m | 201 +++++++++++++++++- 3 files changed, 219 insertions(+), 17 deletions(-) diff --git a/AUTHORS b/AUTHORS index 12355a9457e43..d3f373b890f91 100644 --- a/AUTHORS +++ b/AUTHORS @@ -15,3 +15,4 @@ Simon Lightfoot Dwayne Slater Tetsuhiro Ueda shoryukenn +SOTEC GmbH & Co. KG \ No newline at end of file diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 7e00737db8ead..1af3dc648f34d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -828,14 +828,36 @@ - (void)onOrientationPreferencesUpdated:(NSNotification*)notification { if (update == nil) { return; } + [self performOrientationUpdate:update.unsignedIntegerValue]; + }); +} - NSUInteger new_preferences = update.unsignedIntegerValue; - - if (new_preferences != _orientationPreferences) { - _orientationPreferences = new_preferences; - [UIViewController attemptRotationToDeviceOrientation]; +- (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences { + if (new_preferences != _orientationPreferences) { + _orientationPreferences = new_preferences; + [UIViewController attemptRotationToDeviceOrientation]; + + UIInterfaceOrientationMask currentInterfaceOrientation = + 1 << [[UIApplication sharedApplication] statusBarOrientation]; + if (!(_orientationPreferences & currentInterfaceOrientation)) { + // Force orientation switch if the current orientation is not allowed + if (_orientationPreferences & UIInterfaceOrientationMaskPortrait) { + // This is no official API but more like a workaround / hack (using + // key-value coding on a read-only property). This might break in + // the future, but currently it´s the only way to force an orientation change + [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortrait) forKey:@"orientation"]; + } else if (_orientationPreferences & UIInterfaceOrientationMaskPortraitUpsideDown) { + [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortraitUpsideDown) + forKey:@"orientation"]; + } else if (_orientationPreferences & UIInterfaceOrientationMaskLandscapeLeft) { + [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeLeft) + forKey:@"orientation"]; + } else if (_orientationPreferences & UIInterfaceOrientationMaskLandscapeRight) { + [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeRight) + forKey:@"orientation"]; + } } - }); + } } - (BOOL)shouldAutorotate { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.m b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.m index b33e8cda6f56d..31c2bcc077706 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.m +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.m @@ -45,6 +45,10 @@ - (UIAccessibilityContrast)accessibilityContrast; @end #endif +@interface FlutterViewController (Tests) +- (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences; +@end + @implementation FlutterViewControllerTest - (void)testBinaryMessenger { @@ -252,6 +256,179 @@ - (void)testItReportsHighContrastWhenTraitCollectionRequestsIt { [mockTraitCollection stopMocking]; } +- (void)testPerformOrientationUpdateForcesOrientationChange { + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait + currentOrientation:UIInterfaceOrientationLandscapeLeft + didChangeOrientation:YES + resultingOrientation:UIInterfaceOrientationPortrait]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait + currentOrientation:UIInterfaceOrientationLandscapeRight + didChangeOrientation:YES + resultingOrientation:UIInterfaceOrientationPortrait]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait + currentOrientation:UIInterfaceOrientationPortraitUpsideDown + didChangeOrientation:YES + resultingOrientation:UIInterfaceOrientationPortrait]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown + currentOrientation:UIInterfaceOrientationLandscapeLeft + didChangeOrientation:YES + resultingOrientation:UIInterfaceOrientationPortraitUpsideDown]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown + currentOrientation:UIInterfaceOrientationLandscapeRight + didChangeOrientation:YES + resultingOrientation:UIInterfaceOrientationPortraitUpsideDown]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown + currentOrientation:UIInterfaceOrientationPortrait + didChangeOrientation:YES + resultingOrientation:UIInterfaceOrientationPortraitUpsideDown]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape + currentOrientation:UIInterfaceOrientationPortrait + didChangeOrientation:YES + resultingOrientation:UIInterfaceOrientationLandscapeLeft]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape + currentOrientation:UIInterfaceOrientationPortraitUpsideDown + didChangeOrientation:YES + resultingOrientation:UIInterfaceOrientationLandscapeLeft]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft + currentOrientation:UIInterfaceOrientationPortrait + didChangeOrientation:YES + resultingOrientation:UIInterfaceOrientationLandscapeLeft]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft + currentOrientation:UIInterfaceOrientationLandscapeRight + didChangeOrientation:YES + resultingOrientation:UIInterfaceOrientationLandscapeLeft]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft + currentOrientation:UIInterfaceOrientationPortraitUpsideDown + didChangeOrientation:YES + resultingOrientation:UIInterfaceOrientationLandscapeLeft]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight + currentOrientation:UIInterfaceOrientationPortrait + didChangeOrientation:YES + resultingOrientation:UIInterfaceOrientationLandscapeRight]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight + currentOrientation:UIInterfaceOrientationLandscapeLeft + didChangeOrientation:YES + resultingOrientation:UIInterfaceOrientationLandscapeRight]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight + currentOrientation:UIInterfaceOrientationPortraitUpsideDown + didChangeOrientation:YES + resultingOrientation:UIInterfaceOrientationLandscapeRight]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown + currentOrientation:UIInterfaceOrientationPortraitUpsideDown + didChangeOrientation:YES + resultingOrientation:UIInterfaceOrientationPortrait]; +} + +- (void)testPerformOrientationUpdateDoesNotForceOrientationChange { + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll + currentOrientation:UIInterfaceOrientationPortrait + didChangeOrientation:NO + resultingOrientation:0]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll + currentOrientation:UIInterfaceOrientationPortraitUpsideDown + didChangeOrientation:NO + resultingOrientation:0]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll + currentOrientation:UIInterfaceOrientationLandscapeLeft + didChangeOrientation:NO + resultingOrientation:0]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll + currentOrientation:UIInterfaceOrientationLandscapeRight + didChangeOrientation:NO + resultingOrientation:0]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown + currentOrientation:UIInterfaceOrientationPortrait + didChangeOrientation:NO + resultingOrientation:0]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown + currentOrientation:UIInterfaceOrientationLandscapeLeft + didChangeOrientation:NO + resultingOrientation:0]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown + currentOrientation:UIInterfaceOrientationLandscapeRight + didChangeOrientation:NO + resultingOrientation:0]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait + currentOrientation:UIInterfaceOrientationPortrait + didChangeOrientation:NO + resultingOrientation:0]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown + currentOrientation:UIInterfaceOrientationPortraitUpsideDown + didChangeOrientation:NO + resultingOrientation:0]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape + currentOrientation:UIInterfaceOrientationLandscapeLeft + didChangeOrientation:NO + resultingOrientation:0]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape + currentOrientation:UIInterfaceOrientationLandscapeRight + didChangeOrientation:NO + resultingOrientation:0]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft + currentOrientation:UIInterfaceOrientationLandscapeLeft + didChangeOrientation:NO + resultingOrientation:0]; + + [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight + currentOrientation:UIInterfaceOrientationLandscapeRight + didChangeOrientation:NO + resultingOrientation:0]; +} + +// Perform an orientation update test that fails when the expected outcome +// for an orientation update is not met +- (void)orientationTestWithOrientationUpdate:(UIInterfaceOrientationMask)mask + currentOrientation:(UIInterfaceOrientation)currentOrientation + didChangeOrientation:(BOOL)didChange + resultingOrientation:(UIInterfaceOrientation)resultingOrientation { + id engine = OCMClassMock([FlutterEngine class]); + + id deviceMock = OCMPartialMock([UIDevice currentDevice]); + if (!didChange) { + OCMReject([deviceMock setValue:[OCMArg any] forKey:@"orientation"]); + } else { + OCMExpect([deviceMock setValue:@(resultingOrientation) forKey:@"orientation"]); + } + + FlutterViewController* realVC = [[FlutterViewController alloc] initWithEngine:engine + nibName:nil + bundle:nil]; + id mockApplication = OCMClassMock([UIApplication class]); + OCMStub([mockApplication sharedApplication]).andReturn(mockApplication); + OCMStub([mockApplication statusBarOrientation]).andReturn(currentOrientation); + + [realVC performOrientationUpdate:mask]; + OCMVerifyAll(deviceMock); + [engine stopMocking]; + [deviceMock stopMocking]; + [mockApplication stopMocking]; +} + // Creates a mocked UITraitCollection with nil values for everything except accessibilityContrast, // which is set to the given "contrast". - (UITraitCollection*)fakeTraitCollectionWithContrast:(UIAccessibilityContrast)contrast { @@ -264,17 +441,19 @@ - (void)testWillDeallocNotification { XCTestExpectation* expectation = [[XCTestExpectation alloc] initWithDescription:@"notification called"]; id engine = [[MockEngine alloc] init]; - FlutterViewController* realVC = [[FlutterViewController alloc] initWithEngine:engine - nibName:nil - bundle:nil]; - id observer = - [[NSNotificationCenter defaultCenter] addObserverForName:FlutterViewControllerWillDealloc - object:nil - queue:[NSOperationQueue mainQueue] - usingBlock:^(NSNotification* _Nonnull note) { - [expectation fulfill]; - }]; - realVC = nil; + @autoreleasepool { + FlutterViewController* realVC = [[FlutterViewController alloc] initWithEngine:engine + nibName:nil + bundle:nil]; + id observer = + [[NSNotificationCenter defaultCenter] addObserverForName:FlutterViewControllerWillDealloc + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification* _Nonnull note) { + [expectation fulfill]; + }]; + realVC = nil; + } [self waitForExpectations:@[ expectation ] timeout:1.0]; }