UnityViewControllerBase+iOS.mm 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. #if PLATFORM_IOS
  2. #import "UnityViewControllerBase.h"
  3. #import "UnityAppController.h"
  4. #include "OrientationSupport.h"
  5. #include "Keyboard.h"
  6. #include "UnityView.h"
  7. #include "PluginBase/UnityViewControllerListener.h"
  8. #include "UnityAppController.h"
  9. #include "UnityAppController+ViewHandling.h"
  10. #include "Unity/ObjCRuntime.h"
  11. // when returning from presenting UIViewController we might need to update app orientation to "correct" one, as we wont get rotation notification
  12. @interface UnityAppController ()
  13. - (void)updateAppOrientation:(UIInterfaceOrientation)orientation;
  14. @end
  15. #ifndef __IPHONE_16_0
  16. @interface UIViewController ()
  17. - (void)setNeedsUpdateOfSupportedInterfaceOrientations;
  18. @end
  19. #endif
  20. @implementation UnityViewControllerBase (iOS)
  21. - (BOOL)shouldAutorotate
  22. {
  23. return YES;
  24. }
  25. - (BOOL)prefersStatusBarHidden
  26. {
  27. static bool _PrefersStatusBarHidden = true;
  28. static bool _PrefersStatusBarHiddenInited = false;
  29. if (!_PrefersStatusBarHiddenInited)
  30. {
  31. NSNumber* hidden = [[[NSBundle mainBundle] infoDictionary] objectForKey: @"UIStatusBarHidden"];
  32. _PrefersStatusBarHidden = hidden ? [hidden boolValue] : YES;
  33. _PrefersStatusBarHiddenInited = true;
  34. }
  35. return _PrefersStatusBarHidden;
  36. }
  37. - (UIStatusBarStyle)preferredStatusBarStyle
  38. {
  39. static UIStatusBarStyle _PreferredStatusBarStyle = UIStatusBarStyleDefault;
  40. static bool _PreferredStatusBarStyleInited = false;
  41. if (!_PreferredStatusBarStyleInited)
  42. {
  43. NSString* style = [[[NSBundle mainBundle] infoDictionary] objectForKey: @"UIStatusBarStyle"];
  44. if (style && [style isEqualToString: @"UIStatusBarStyleLightContent"])
  45. _PreferredStatusBarStyle = UIStatusBarStyleLightContent;
  46. _PreferredStatusBarStyleInited = true;
  47. }
  48. return _PreferredStatusBarStyle;
  49. }
  50. - (UIRectEdge)preferredScreenEdgesDeferringSystemGestures
  51. {
  52. UIRectEdge res = UIRectEdgeNone;
  53. if (UnityGetDeferSystemGesturesTopEdge())
  54. res |= UIRectEdgeTop;
  55. if (UnityGetDeferSystemGesturesBottomEdge())
  56. res |= UIRectEdgeBottom;
  57. if (UnityGetDeferSystemGesturesLeftEdge())
  58. res |= UIRectEdgeLeft;
  59. if (UnityGetDeferSystemGesturesRightEdge())
  60. res |= UIRectEdgeRight;
  61. return res;
  62. }
  63. - (BOOL)prefersHomeIndicatorAutoHidden
  64. {
  65. return UnityGetHideHomeButton();
  66. }
  67. @end
  68. @implementation UnityDefaultViewController
  69. // these will be updated in one place where we "sync" UI side orientation handling to unity side
  70. NSUInteger _supportedOrientations;
  71. - (id)init
  72. {
  73. if ((self = [super init]))
  74. {
  75. NSAssert(UnityShouldAutorotate(), @"UnityDefaultViewController should be used only if unity is set to autorotate");
  76. _supportedOrientations = EnabledAutorotationInterfaceOrientations();
  77. }
  78. return self;
  79. }
  80. - (void)updateSupportedOrientations
  81. {
  82. _supportedOrientations = EnabledAutorotationInterfaceOrientations();
  83. if (@available(iOS 16.0, *))
  84. [self setNeedsUpdateOfSupportedInterfaceOrientations];
  85. }
  86. - (NSUInteger)supportedInterfaceOrientations
  87. {
  88. return _supportedOrientations;
  89. }
  90. - (void)viewWillAppear:(BOOL)animated
  91. {
  92. ScreenOrientation currentOrientation = UIViewControllerOrientation(self);
  93. [GetAppController() updateAppOrientation: ConvertToIosScreenOrientation(currentOrientation)];
  94. [super viewWillAppear: animated];
  95. }
  96. - (void)viewDidAppear:(BOOL)animated
  97. {
  98. ScreenOrientation currentOrientation = UIViewControllerOrientation(self);
  99. [GetAppController() updateAppOrientation: ConvertToIosScreenOrientation(currentOrientation)];
  100. [super viewDidAppear: animated];
  101. }
  102. - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
  103. {
  104. // CODE ARCHEOLOGY: we were using UIViewControllerOrientation, but on showing view with "Requires full screen"
  105. // CODE ARCHEOLOGY: we will get the size/orientation *already* set, and the rotation logic would break
  106. const ScreenOrientation curOrient = GetAppController().unityView.contentOrientation;
  107. const ScreenOrientation newOrient = OrientationAfterTransform(curOrient, [coordinator targetTransform]);
  108. // in case of presentation controller it will take control over orientations
  109. // so to avoid crazy corner cases, make default view controller to ignore "wrong" orientations
  110. // as they will come only in case of presentation view controller and will be reverted anyway
  111. // NB: we still want to pass message to super, we just want to skip unity-specific magic
  112. NSUInteger targetMask = 1 << ConvertToIosScreenOrientation(newOrient);
  113. if (([self supportedInterfaceOrientations] & targetMask) != 0)
  114. {
  115. [UIView setAnimationsEnabled: UnityUseAnimatedAutorotation() ? YES : NO];
  116. [KeyboardDelegate StartReorientation];
  117. [GetAppController() interfaceWillChangeOrientationTo: ConvertToIosScreenOrientation(newOrient)];
  118. [coordinator animateAlongsideTransition: nil completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
  119. [self.view setNeedsLayout];
  120. [GetAppController() interfaceDidChangeOrientationFrom: ConvertToIosScreenOrientation(curOrient)];
  121. [KeyboardDelegate FinishReorientation];
  122. [UIView setAnimationsEnabled: YES];
  123. }];
  124. }
  125. [super viewWillTransitionToSize: size withTransitionCoordinator: coordinator];
  126. }
  127. @end
  128. @interface UnityFixedOrientationViewController ()
  129. {
  130. UIInterfaceOrientation _fixedOrientation;
  131. }
  132. @end
  133. @implementation UnityFixedOrientationViewController
  134. - (instancetype)initWithOrientation:(UIInterfaceOrientation)interfaceOrientation
  135. {
  136. self = [super init];
  137. if (self)
  138. {
  139. _fixedOrientation = interfaceOrientation;
  140. }
  141. return self;
  142. }
  143. - (NSUInteger)supportedInterfaceOrientations
  144. {
  145. return 1 << _fixedOrientation;
  146. }
  147. - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
  148. {
  149. return _fixedOrientation;
  150. }
  151. - (void)viewWillAppear:(BOOL)animated
  152. {
  153. [GetAppController() updateAppOrientation: _fixedOrientation];
  154. [super viewWillAppear: animated];
  155. }
  156. - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
  157. {
  158. // Adding a call to notify about changed orientation. In iOS16 there was a runtime change where view controller
  159. // is not fully set up as soon as we make the view controller change in AppController at -transitionToViewController:.
  160. // And instead iOS calls this method, in other cases this method is not called. So we want to call
  161. // didTransitionToViewController here as this is the place where we get updated orientation.
  162. //
  163. // NB: Look for additional explanation at UnityAppController+ViewHandling.mm method -transitionToViewController: before
  164. // call to same method.
  165. [GetAppController() didTransitionToViewController: self fromViewController: self];
  166. [super viewWillTransitionToSize: size withTransitionCoordinator: coordinator];
  167. }
  168. @end
  169. @implementation UnityPortraitOnlyViewController
  170. - (instancetype)init
  171. {
  172. self = [super initWithOrientation: UIInterfaceOrientationPortrait];
  173. return self;
  174. }
  175. @end
  176. @implementation UnityPortraitUpsideDownOnlyViewController
  177. - (instancetype)init
  178. {
  179. self = [super initWithOrientation: UIInterfaceOrientationPortraitUpsideDown];
  180. return self;
  181. }
  182. @end
  183. @implementation UnityLandscapeLeftOnlyViewController
  184. - (instancetype)init
  185. {
  186. self = [super initWithOrientation: UIInterfaceOrientationLandscapeLeft];
  187. return self;
  188. }
  189. @end
  190. @implementation UnityLandscapeRightOnlyViewController
  191. - (instancetype)init
  192. {
  193. self = [super initWithOrientation: UIInterfaceOrientationLandscapeRight];
  194. return self;
  195. }
  196. @end
  197. NSUInteger EnabledAutorotationInterfaceOrientations()
  198. {
  199. NSUInteger ret = 0;
  200. if (UnityIsOrientationEnabled(portrait))
  201. ret |= (1 << UIInterfaceOrientationPortrait);
  202. if (UnityDeviceSupportsUpsideDown() && UnityIsOrientationEnabled(portraitUpsideDown))
  203. ret |= (1 << UIInterfaceOrientationPortraitUpsideDown);
  204. if (UnityIsOrientationEnabled(landscapeLeft))
  205. ret |= (1 << UIInterfaceOrientationLandscapeRight);
  206. if (UnityIsOrientationEnabled(landscapeRight))
  207. ret |= (1 << UIInterfaceOrientationLandscapeLeft);
  208. // Handling unexpected case where autorotation is on and all the orientations are off by defaulting to current orientation.
  209. // Previously we returned 0 and iOS were handling it by keeping orientation as is. From iOS16 behaviour changed and the bug was raised.
  210. // Either way iOS requires us to provide non 0 value to supportedInterfaceOrientations.
  211. if (ret == 0)
  212. {
  213. NSLog(@"[Error] All orientations are off for autorotation. Preventing crash by using current orientation.");
  214. ret = (1 << [GetAppController() interfaceOrientation]);
  215. }
  216. return ret;
  217. }
  218. #endif // PLATFORM_IOS