UnityViewControllerBase+iOS.mm 10 KB

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