UnityAppController.mm 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. #import "UnityAppController.h"
  2. #import "UnityAppController+ViewHandling.h"
  3. #import "UnityAppController+Rendering.h"
  4. #import "iPhone_Sensors.h"
  5. #import <CoreGraphics/CoreGraphics.h>
  6. #import <QuartzCore/QuartzCore.h>
  7. #import <QuartzCore/CADisplayLink.h>
  8. #import <Availability.h>
  9. #import <AVFoundation/AVFoundation.h>
  10. #import <OpenGLES/EAGL.h>
  11. #import <OpenGLES/EAGLDrawable.h>
  12. #import <OpenGLES/ES2/gl.h>
  13. #import <OpenGLES/ES2/glext.h>
  14. #include <mach/mach_time.h>
  15. // MSAA_DEFAULT_SAMPLE_COUNT was moved to iPhone_GlesSupport.h
  16. // ENABLE_INTERNAL_PROFILER and related defines were moved to iPhone_Profiler.h
  17. // kFPS define for removed: you can use Application.targetFrameRate (30 fps by default)
  18. // DisplayLink is the only run loop mode now - all others were removed
  19. #include "CrashReporter.h"
  20. #include "UI/OrientationSupport.h"
  21. #include "UI/UnityView.h"
  22. #include "UI/Keyboard.h"
  23. #include "UI/SplashScreen.h"
  24. #include "Unity/InternalProfiler.h"
  25. #include "Unity/DisplayManager.h"
  26. #include "Unity/EAGLContextHelper.h"
  27. #include "Unity/GlesHelper.h"
  28. #include "Unity/ObjCRuntime.h"
  29. #include "PluginBase/AppDelegateListener.h"
  30. #include <assert.h>
  31. #include <stdbool.h>
  32. #include <sys/types.h>
  33. #include <unistd.h>
  34. #include <sys/sysctl.h>
  35. #import "IOSPlatformSDK.h"
  36. // we assume that app delegate is never changed and we can cache it, instead of re-query UIApplication every time
  37. UnityAppController* _UnityAppController = nil;
  38. UnityAppController* GetAppController()
  39. {
  40. return _UnityAppController;
  41. }
  42. // we keep old bools around to support "old" code that might have used them
  43. bool _ios81orNewer = false, _ios82orNewer = false, _ios83orNewer = false, _ios90orNewer = false, _ios91orNewer = false;
  44. bool _ios100orNewer = false, _ios101orNewer = false, _ios102orNewer = false, _ios103orNewer = false;
  45. bool _ios110orNewer = false, _ios111orNewer = false, _ios112orNewer = false;
  46. bool _ios130orNewer = false;
  47. // was unity rendering already inited: we should not touch rendering while this is false
  48. bool _renderingInited = false;
  49. // was unity inited: we should not touch unity api while this is false
  50. bool _unityAppReady = false;
  51. // see if there's a need to do internal player pause/resume handling
  52. //
  53. // Typically the trampoline code should manage this internally, but
  54. // there are use cases, videoplayer, plugin code, etc where the player
  55. // is paused before the internal handling comes relevant. Avoid
  56. // overriding externally managed player pause/resume handling by
  57. // caching the state
  58. bool _wasPausedExternal = false;
  59. // should we skip present on next draw: used in corner cases (like rotation) to fill both draw-buffers with some content
  60. bool _skipPresent = false;
  61. // was app "resigned active": some operations do not make sense while app is in background
  62. bool _didResignActive = false;
  63. // was startUnity scheduled: used to make startup robust in case of locking device
  64. static bool _startUnityScheduled = false;
  65. bool _supportsMSAA = false;
  66. #if UNITY_SUPPORT_ROTATION
  67. // Required to enable specific orientation for some presentation controllers: see supportedInterfaceOrientationsForWindow below for details
  68. NSInteger _forceInterfaceOrientationMask = 0;
  69. #endif
  70. @implementation UnityAppController
  71. @synthesize unityView = _unityView;
  72. @synthesize unityDisplayLink = _displayLink;
  73. @synthesize rootView = _rootView;
  74. @synthesize rootViewController = _rootController;
  75. @synthesize mainDisplay = _mainDisplay;
  76. @synthesize renderDelegate = _renderDelegate;
  77. @synthesize quitHandler = _quitHandler;
  78. #if UNITY_SUPPORT_ROTATION
  79. @synthesize interfaceOrientation = _curOrientation;
  80. #endif
  81. - (id)init
  82. {
  83. if ((self = _UnityAppController = [super init]))
  84. {
  85. // due to clang issues with generating warning for overriding deprecated methods
  86. // we will simply assert if deprecated methods are present
  87. // NB: methods table is initied at load (before this call), so it is ok to check for override
  88. NSAssert(![self respondsToSelector: @selector(createUnityViewImpl)],
  89. @"createUnityViewImpl is deprecated and will not be called. Override createUnityView"
  90. );
  91. NSAssert(![self respondsToSelector: @selector(createViewHierarchyImpl)],
  92. @"createViewHierarchyImpl is deprecated and will not be called. Override willStartWithViewController"
  93. );
  94. NSAssert(![self respondsToSelector: @selector(createViewHierarchy)],
  95. @"createViewHierarchy is deprecated and will not be implemented. Use createUI"
  96. );
  97. }
  98. return self;
  99. }
  100. - (void)setWindow:(id)object {}
  101. - (UIWindow*)window { return _window; }
  102. - (void)shouldAttachRenderDelegate {}
  103. - (void)preStartUnity {}
  104. - (void)startUnity:(UIApplication*)application
  105. {
  106. NSAssert(_unityAppReady == NO, @"[UnityAppController startUnity:] called after Unity has been initialized");
  107. UnityInitApplicationGraphics();
  108. // we make sure that first level gets correct display list and orientation
  109. [[DisplayManager Instance] updateDisplayListCacheInUnity];
  110. UnityLoadApplication();
  111. Profiler_InitProfiler();
  112. [self showGameUI];
  113. [self createDisplayLink];
  114. UnitySetPlayerFocus(1);
  115. AVAudioSession* audioSession = [AVAudioSession sharedInstance];
  116. [audioSession setActive: YES error: nil];
  117. [audioSession addObserver: self forKeyPath: @"outputVolume" options: 0 context: nil];
  118. UnityUpdateMuteState([audioSession outputVolume] < 0.01f ? 1 : 0);
  119. [IOSPlatformSDK sharedInstance];
  120. }
  121. extern "C" void UnityDestroyDisplayLink()
  122. {
  123. [GetAppController() destroyDisplayLink];
  124. }
  125. extern "C" void UnityRequestQuit()
  126. {
  127. _didResignActive = true;
  128. if (GetAppController().quitHandler)
  129. GetAppController().quitHandler();
  130. else
  131. exit(0);
  132. }
  133. extern void SensorsCleanup();
  134. extern "C" void UnityCleanupTrampoline()
  135. {
  136. // Unity view and viewController will not necessary be destroyed right after this function execution.
  137. // We need to ensure that these objects will not receive any callbacks from system during that time.
  138. [_UnityAppController window].rootViewController = nil;
  139. [[_UnityAppController unityView] removeFromSuperview];
  140. // Prevent multiple cleanups
  141. if (_UnityAppController == nil)
  142. return;
  143. [KeyboardDelegate Destroy];
  144. SensorsCleanup();
  145. Profiler_UninitProfiler();
  146. [DisplayManager Destroy];
  147. UnityDestroyDisplayLink();
  148. _UnityAppController = nil;
  149. }
  150. #if UNITY_SUPPORT_ROTATION
  151. - (NSUInteger)application:(UIApplication*)application supportedInterfaceOrientationsForWindow:(UIWindow*)window
  152. {
  153. // No rootViewController is set because we are switching from one view controller to another, all orientations should be enabled
  154. if ([window rootViewController] == nil)
  155. return UIInterfaceOrientationMaskAll;
  156. // Some presentation controllers (e.g. UIImagePickerController) require portrait orientation and will throw exception if it is not supported.
  157. // At the same time enabling all orientations by returning UIInterfaceOrientationMaskAll might cause unwanted orientation change
  158. // (e.g. when using UIActivityViewController to "share to" another application, iOS will use supportedInterfaceOrientations to possibly reorient).
  159. // So to avoid exception we are returning combination of constraints for root view controller and orientation requested by iOS.
  160. // _forceInterfaceOrientationMask is updated in willChangeStatusBarOrientation, which is called if some presentation controller insists on orientation change.
  161. return [[window rootViewController] supportedInterfaceOrientations] | _forceInterfaceOrientationMask;
  162. }
  163. - (void)application:(UIApplication*)application willChangeStatusBarOrientation:(UIInterfaceOrientation)newStatusBarOrientation duration:(NSTimeInterval)duration
  164. {
  165. // Setting orientation mask which is requested by iOS: see supportedInterfaceOrientationsForWindow above for details
  166. _forceInterfaceOrientationMask = 1 << newStatusBarOrientation;
  167. }
  168. #endif
  169. #if !PLATFORM_TVOS
  170. - (void)application:(UIApplication*)application didReceiveLocalNotification:(UILocalNotification*)notification
  171. {
  172. AppController_SendNotificationWithArg(kUnityDidReceiveLocalNotification, notification);
  173. UnitySendLocalNotification(notification);
  174. }
  175. #endif
  176. #if UNITY_USES_REMOTE_NOTIFICATIONS
  177. - (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo
  178. {
  179. AppController_SendNotificationWithArg(kUnityDidReceiveRemoteNotification, userInfo);
  180. UnitySendRemoteNotification(userInfo);
  181. }
  182. - (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
  183. {
  184. AppController_SendNotificationWithArg(kUnityDidRegisterForRemoteNotificationsWithDeviceToken, deviceToken);
  185. UnitySendDeviceToken(deviceToken);
  186. }
  187. #if !PLATFORM_TVOS
  188. - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler
  189. {
  190. AppController_SendNotificationWithArg(kUnityDidReceiveRemoteNotification, userInfo);
  191. UnitySendRemoteNotification(userInfo);
  192. if (handler)
  193. {
  194. handler(UIBackgroundFetchResultNoData);
  195. }
  196. }
  197. #endif
  198. - (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error
  199. {
  200. AppController_SendNotificationWithArg(kUnityDidFailToRegisterForRemoteNotificationsWithError, error);
  201. UnitySendRemoteNotificationError(error);
  202. // alas people do not check remote notification error through api (which is clunky, i agree) so log here to have at least some visibility
  203. ::printf("\nFailed to register for remote notifications:\n%s\n\n", [[error localizedDescription] UTF8String]);
  204. }
  205. #endif
  206. // UIApplicationOpenURLOptionsKey was added only in ios10 sdk, while we still support ios9 sdk
  207. - (BOOL)application:(UIApplication*)app openURL:(NSURL*)url options:(NSDictionary<NSString*, id>*)options
  208. {
  209. id sourceApplication = options[UIApplicationOpenURLOptionsSourceApplicationKey], annotation = options[UIApplicationOpenURLOptionsAnnotationKey];
  210. NSMutableDictionary<NSString*, id>* notifData = [NSMutableDictionary dictionaryWithCapacity: 3];
  211. if (url)
  212. {
  213. notifData[@"url"] = url;
  214. UnitySetAbsoluteURL(url.absoluteString.UTF8String);
  215. }
  216. if (sourceApplication) notifData[@"sourceApplication"] = sourceApplication;
  217. if (annotation) notifData[@"annotation"] = annotation;
  218. AppController_SendNotificationWithArg(kUnityOnOpenURL, notifData);
  219. IOSPlatformSDK * sdk = [IOSPlatformSDK sharedInstance];
  220. [sdk startWithUrl:url];
  221. return YES;
  222. }
  223. - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity
  224. #if defined(__IPHONE_12_0) || defined(__TVOS_12_0)
  225. restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring> > * _Nullable restorableObjects))restorationHandler
  226. #else
  227. restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler
  228. #endif
  229. {
  230. NSURL* url = userActivity.webpageURL;
  231. if (url)
  232. UnitySetAbsoluteURL(url.absoluteString.UTF8String);
  233. return YES;
  234. }
  235. - (BOOL)application:(UIApplication*)application willFinishLaunchingWithOptions:(NSDictionary*)launchOptions
  236. {
  237. AppController_SendNotificationWithArg(kUnityWillFinishLaunchingWithOptions, launchOptions);
  238. return YES;
  239. }
  240. - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
  241. {
  242. ::printf("-> applicationDidFinishLaunching()\n");
  243. // send notfications
  244. #if !PLATFORM_TVOS
  245. if (UILocalNotification* notification = [launchOptions objectForKey: UIApplicationLaunchOptionsLocalNotificationKey])
  246. UnitySendLocalNotification(notification);
  247. if ([UIDevice currentDevice].generatesDeviceOrientationNotifications == NO)
  248. [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
  249. #endif
  250. UnityInitApplicationNoGraphics(UnityDataBundleDir());
  251. [self selectRenderingAPI];
  252. [UnityRenderingView InitializeForAPI: self.renderingAPI];
  253. _window = [[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds];
  254. _unityView = [self createUnityView];
  255. [DisplayManager Initialize];
  256. _mainDisplay = [DisplayManager Instance].mainDisplay;
  257. [_mainDisplay createWithWindow: _window andView: _unityView];
  258. [self createUI];
  259. [self preStartUnity];
  260. // if you wont use keyboard you may comment it out at save some memory
  261. [KeyboardDelegate Initialize];
  262. return YES;
  263. }
  264. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context
  265. {
  266. if ([keyPath isEqual: @"outputVolume"])
  267. {
  268. UnityUpdateMuteState([[AVAudioSession sharedInstance] outputVolume] < 0.01f ? 1 : 0);
  269. }
  270. }
  271. - (void)applicationDidEnterBackground:(UIApplication*)application
  272. {
  273. ::printf("-> applicationDidEnterBackground()\n");
  274. }
  275. - (void)applicationWillEnterForeground:(UIApplication*)application
  276. {
  277. ::printf("-> applicationWillEnterForeground()\n");
  278. // applicationWillEnterForeground: might sometimes arrive *before* actually initing unity (e.g. locking on startup)
  279. if (_unityAppReady)
  280. {
  281. // if we were showing video before going to background - the view size may be changed while we are in background
  282. [GetAppController().unityView recreateRenderingSurfaceIfNeeded];
  283. }
  284. }
  285. - (void)applicationDidBecomeActive:(UIApplication*)application
  286. {
  287. ::printf("-> applicationDidBecomeActive()\n");
  288. [self removeSnapshotView];
  289. if (_unityAppReady)
  290. {
  291. if (UnityIsPaused() && _wasPausedExternal == false)
  292. {
  293. UnityWillResume();
  294. UnityPause(0);
  295. }
  296. if (_wasPausedExternal)
  297. {
  298. if (UnityIsFullScreenPlaying())
  299. TryResumeFullScreenVideo();
  300. }
  301. // need to do this with delay because FMOD restarts audio in AVAudioSessionInterruptionNotification handler
  302. [self performSelector: @selector(updateUnityAudioOutput) withObject: nil afterDelay: 0.1];
  303. UnitySetPlayerFocus(1);
  304. }
  305. else if (!_startUnityScheduled)
  306. {
  307. _startUnityScheduled = true;
  308. [self performSelector: @selector(startUnity:) withObject: application afterDelay: 0];
  309. }
  310. _didResignActive = false;
  311. }
  312. - (void)updateUnityAudioOutput
  313. {
  314. UnityUpdateAudioOutputState();
  315. UnityUpdateMuteState([[AVAudioSession sharedInstance] outputVolume] < 0.01f ? 1 : 0);
  316. }
  317. - (void)removeSnapshotView
  318. {
  319. // do this on the main queue async so that if we try to create one
  320. // and remove in the same frame, this always happens after in the same queue
  321. dispatch_async(dispatch_get_main_queue(), ^{
  322. if (_snapshotView)
  323. {
  324. [_snapshotView removeFromSuperview];
  325. _snapshotView = nil;
  326. // Make sure that the keyboard input field regains focus after the application becomes active.
  327. [[KeyboardDelegate Instance] becomeFirstResponder];
  328. }
  329. });
  330. }
  331. - (void)applicationWillResignActive:(UIApplication*)application
  332. {
  333. ::printf("-> applicationWillResignActive()\n");
  334. if (_unityAppReady)
  335. {
  336. UnitySetPlayerFocus(0);
  337. _wasPausedExternal = UnityIsPaused();
  338. if (_wasPausedExternal == false)
  339. {
  340. // Pause Unity only if we don't need special background processing
  341. // otherwise batched player loop can be called to run user scripts.
  342. if (!UnityGetUseCustomAppBackgroundBehavior())
  343. {
  344. // Force player to do one more frame, so scripts get a chance to render custom screen for minimized app in task manager.
  345. // NB: UnityWillPause will schedule OnApplicationPause message, which will be sent normally inside repaint (unity player loop)
  346. // NB: We will actually pause after the loop (when calling UnityPause).
  347. UnityWillPause();
  348. [self repaint];
  349. UnityPause(1);
  350. // this is done on the next frame so that
  351. // in the case where unity is paused while going
  352. // into the background and an input is deactivated
  353. // we don't mess with the view hierarchy while taking
  354. // a view snapshot (case 760747).
  355. dispatch_async(dispatch_get_main_queue(), ^{
  356. // if we are active again, we don't need to do this anymore
  357. if (!_didResignActive)
  358. {
  359. return;
  360. }
  361. _snapshotView = [self createSnapshotView];
  362. if (_snapshotView)
  363. [_rootView addSubview: _snapshotView];
  364. });
  365. }
  366. }
  367. }
  368. _didResignActive = true;
  369. }
  370. - (void)applicationDidReceiveMemoryWarning:(UIApplication*)application
  371. {
  372. ::printf("WARNING -> applicationDidReceiveMemoryWarning()\n");
  373. UnityLowMemory();
  374. }
  375. - (void)applicationWillTerminate:(UIApplication*)application
  376. {
  377. ::printf("-> applicationWillTerminate()\n");
  378. // Only clean up if Unity has finished initializing, else the clean up process will crash,
  379. // this happens if the app is force closed immediately after opening it.
  380. if (_unityAppReady)
  381. {
  382. UnityCleanup();
  383. UnityCleanupTrampoline();
  384. }
  385. }
  386. - (void)application:(UIApplication*)application handleEventsForBackgroundURLSession:(nonnull NSString *)identifier completionHandler:(nonnull void (^)())completionHandler
  387. {
  388. NSDictionary* arg = @{identifier: completionHandler};
  389. AppController_SendNotificationWithArg(kUnityHandleEventsForBackgroundURLSession, arg);
  390. }
  391. @end
  392. void AppController_SendNotification(NSString* name)
  393. {
  394. [[NSNotificationCenter defaultCenter] postNotificationName: name object: GetAppController()];
  395. }
  396. void AppController_SendNotificationWithArg(NSString* name, id arg)
  397. {
  398. [[NSNotificationCenter defaultCenter] postNotificationName: name object: GetAppController() userInfo: arg];
  399. }
  400. void AppController_SendUnityViewControllerNotification(NSString* name)
  401. {
  402. [[NSNotificationCenter defaultCenter] postNotificationName: name object: UnityGetGLViewController()];
  403. }
  404. extern "C" UIWindow* UnityGetMainWindow()
  405. {
  406. return GetAppController().mainDisplay.window;
  407. }
  408. extern "C" UIViewController* UnityGetGLViewController()
  409. {
  410. return GetAppController().rootViewController;
  411. }
  412. extern "C" UIView* UnityGetGLView()
  413. {
  414. return GetAppController().unityView;
  415. }
  416. extern "C" ScreenOrientation UnityCurrentOrientation() { return GetAppController().unityView.contentOrientation; }
  417. bool LogToNSLogHandler(LogType logType, const char* log, va_list list)
  418. {
  419. NSLogv([NSString stringWithUTF8String: log], list);
  420. return true;
  421. }
  422. static void AddNewAPIImplIfNeeded();
  423. // From https://stackoverflow.com/questions/4744826/detecting-if-ios-app-is-run-in-debugger
  424. static bool isDebuggerAttachedToConsole(void)
  425. // Returns true if the current process is being debugged (either
  426. // running under the debugger or has a debugger attached post facto).
  427. {
  428. int junk;
  429. int mib[4];
  430. struct kinfo_proc info;
  431. size_t size;
  432. // Initialize the flags so that, if sysctl fails for some bizarre
  433. // reason, we get a predictable result.
  434. info.kp_proc.p_flag = 0;
  435. // Initialize mib, which tells sysctl the info we want, in this case
  436. // we're looking for information about a specific process ID.
  437. mib[0] = CTL_KERN;
  438. mib[1] = KERN_PROC;
  439. mib[2] = KERN_PROC_PID;
  440. mib[3] = getpid();
  441. // Call sysctl.
  442. size = sizeof(info);
  443. junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
  444. assert(junk == 0);
  445. // We're being debugged if the P_TRACED flag is set.
  446. return ((info.kp_proc.p_flag & P_TRACED) != 0);
  447. }
  448. void UnityInitTrampoline()
  449. {
  450. InitCrashHandling();
  451. NSString* version = [[UIDevice currentDevice] systemVersion];
  452. #define CHECK_VER(s) [version compare: s options: NSNumericSearch] != NSOrderedAscending
  453. _ios81orNewer = CHECK_VER(@"8.1"), _ios82orNewer = CHECK_VER(@"8.2"), _ios83orNewer = CHECK_VER(@"8.3");
  454. _ios90orNewer = CHECK_VER(@"9.0"), _ios91orNewer = CHECK_VER(@"9.1");
  455. _ios100orNewer = CHECK_VER(@"10.0"), _ios101orNewer = CHECK_VER(@"10.1"), _ios102orNewer = CHECK_VER(@"10.2"), _ios103orNewer = CHECK_VER(@"10.3");
  456. _ios110orNewer = CHECK_VER(@"11.0"), _ios111orNewer = CHECK_VER(@"11.1"), _ios112orNewer = CHECK_VER(@"11.2");
  457. _ios130orNewer = CHECK_VER(@"13.0");
  458. #undef CHECK_VER
  459. AddNewAPIImplIfNeeded();
  460. #if !TARGET_IPHONE_SIMULATOR
  461. // Use NSLog logging if a debugger is not attached, otherwise we write to stdout.
  462. if (!isDebuggerAttachedToConsole())
  463. UnitySetLogEntryHandler(LogToNSLogHandler);
  464. #endif
  465. }
  466. extern "C" bool UnityiOS81orNewer() { return _ios81orNewer; }
  467. extern "C" bool UnityiOS82orNewer() { return _ios82orNewer; }
  468. extern "C" bool UnityiOS90orNewer() { return _ios90orNewer; }
  469. extern "C" bool UnityiOS91orNewer() { return _ios91orNewer; }
  470. extern "C" bool UnityiOS100orNewer() { return _ios100orNewer; }
  471. extern "C" bool UnityiOS101orNewer() { return _ios101orNewer; }
  472. extern "C" bool UnityiOS102orNewer() { return _ios102orNewer; }
  473. extern "C" bool UnityiOS103orNewer() { return _ios103orNewer; }
  474. extern "C" bool UnityiOS110orNewer() { return _ios110orNewer; }
  475. extern "C" bool UnityiOS111orNewer() { return _ios111orNewer; }
  476. extern "C" bool UnityiOS112orNewer() { return _ios112orNewer; }
  477. extern "C" bool UnityiOS130orNewer() { return _ios130orNewer; }
  478. // sometimes apple adds new api with obvious fallback on older ios.
  479. // in that case we simply add these functions ourselves to simplify code
  480. static void AddNewAPIImplIfNeeded()
  481. {
  482. if (![[UIScreen class] instancesRespondToSelector: @selector(maximumFramesPerSecond)])
  483. {
  484. IMP UIScreen_MaximumFramesPerSecond_IMP = imp_implementationWithBlock(^NSInteger(id _self) {
  485. return 60;
  486. });
  487. class_replaceMethod([UIScreen class], @selector(maximumFramesPerSecond), UIScreen_MaximumFramesPerSecond_IMP, UIScreen_maximumFramesPerSecond_Enc);
  488. }
  489. if (![[UIView class] instancesRespondToSelector: @selector(safeAreaInsets)])
  490. {
  491. IMP UIView_SafeAreaInsets_IMP = imp_implementationWithBlock(^UIEdgeInsets(id _self) {
  492. return UIEdgeInsetsZero;
  493. });
  494. class_replaceMethod([UIView class], @selector(safeAreaInsets), UIView_SafeAreaInsets_IMP, UIView_safeAreaInsets_Enc);
  495. }
  496. }