Skip to content

Commit

Permalink
Use iOS 16 APIs to rotate orientation (flutter#36874)
Browse files Browse the repository at this point in the history
  • Loading branch information
jmagman authored Oct 20, 2022
1 parent 791221c commit 69f2bc8
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 29 deletions.
66 changes: 46 additions & 20 deletions shell/platform/darwin/ios/framework/Source/FlutterViewController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"

#import <os/log.h>
#include <memory>

#include "flutter/fml/memory/weak_ptr.h"
Expand Down Expand Up @@ -1536,26 +1537,51 @@ - (void)onOrientationPreferencesUpdated:(NSNotification*)notification {
- (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"];

if (@available(iOS 16.0, *)) {
for (UIScene* scene in UIApplication.sharedApplication.connectedScenes) {
if (![scene isKindOfClass:[UIWindowScene class]]) {
continue;
}
UIWindowScene* windowScene = (UIWindowScene*)scene;
UIInterfaceOrientationMask currentInterfaceOrientation =
1 << windowScene.interfaceOrientation;
if (!(_orientationPreferences & currentInterfaceOrientation)) {
[self setNeedsUpdateOfSupportedInterfaceOrientations];
UIWindowSceneGeometryPreferencesIOS* preference =
[[UIWindowSceneGeometryPreferencesIOS alloc]
initWithInterfaceOrientations:_orientationPreferences];
[windowScene
requestGeometryUpdateWithPreferences:preference
errorHandler:^(NSError* error) {
os_log_error(OS_LOG_DEFAULT,
"Failed to change device orientation: %@",
error);
}];
}
}
} else {
UIInterfaceOrientationMask currentInterfaceOrientation =
1 << [[UIApplication sharedApplication] statusBarOrientation];
if (!(_orientationPreferences & currentInterfaceOrientation)) {
[UIViewController attemptRotationToDeviceOrientation];
// 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"];
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -888,22 +888,47 @@ - (void)orientationTestWithOrientationUpdate:(UIInterfaceOrientationMask)mask
currentOrientation:(UIInterfaceOrientation)currentOrientation
didChangeOrientation:(BOOL)didChange
resultingOrientation:(UIInterfaceOrientation)resultingOrientation {
id deviceMock = OCMPartialMock([UIDevice currentDevice]);
if (!didChange) {
OCMReject([deviceMock setValue:[OCMArg any] forKey:@"orientation"]);
id mockApplication = OCMClassMock([UIApplication class]);
id mockWindowScene;
id deviceMock;
if (@available(iOS 16.0, *)) {
mockWindowScene = OCMClassMock([UIWindowScene class]);
OCMStub([mockWindowScene interfaceOrientation]).andReturn(currentOrientation);
if (!didChange) {
OCMReject([mockWindowScene requestGeometryUpdateWithPreferences:[OCMArg any]
errorHandler:[OCMArg any]]);
} else {
OCMExpect([mockWindowScene
requestGeometryUpdateWithPreferences:[OCMArg checkWithBlock:^BOOL(
UIWindowSceneGeometryPreferencesIOS*
preferences) {
return preferences.interfaceOrientations == mask;
}]
errorHandler:[OCMArg any]]);
}
OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
OCMStub([mockApplication connectedScenes]).andReturn([NSSet setWithObject:mockWindowScene]);
} else {
OCMExpect([deviceMock setValue:@(resultingOrientation) forKey:@"orientation"]);
}
deviceMock = OCMPartialMock([UIDevice currentDevice]);
if (!didChange) {
OCMReject([deviceMock setValue:[OCMArg any] forKey:@"orientation"]);
} else {
OCMExpect([deviceMock setValue:@(resultingOrientation) forKey:@"orientation"]);
}

OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
OCMStub([mockApplication statusBarOrientation]).andReturn(currentOrientation);
}
FlutterViewController* realVC = [[FlutterViewController alloc] initWithEngine:self.mockEngine
nibName:nil
bundle:nil];
id mockApplication = OCMClassMock([UIApplication class]);
OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
OCMStub([mockApplication statusBarOrientation]).andReturn(currentOrientation);

[realVC performOrientationUpdate:mask];
OCMVerifyAll(deviceMock);
if (@available(iOS 16.0, *)) {
OCMVerifyAll(mockWindowScene);
} else {
OCMVerifyAll(deviceMock);
}
[deviceMock stopMocking];
[mockApplication stopMocking];
}
Expand Down

0 comments on commit 69f2bc8

Please sign in to comment.