123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464 |
- #include "SplashScreen.h"
- #include "UnityViewControllerBase.h"
- #include "OrientationSupport.h"
- #include "Unity/ObjCRuntime.h"
- #include "UI/UnityView.h"
- #include <cstring>
- extern "C" const char* UnityGetLaunchScreenXib();
- #include <utility>
- static SplashScreen* _splash = nil;
- static SplashScreenController* _controller = nil;
- static bool _isOrientable = false; // true for iPads and iPhone 6+
- static bool _usesLaunchscreen = false;
- static ScreenOrientation _nonOrientableDefaultOrientation = portrait;
- // we will create and show splash before unity is inited, so we can use only plist settings
- static bool _canRotateToPortrait = false;
- static bool _canRotateToPortraitUpsideDown = false;
- static bool _canRotateToLandscapeLeft = false;
- static bool _canRotateToLandscapeRight = false;
- #if !PLATFORM_TVOS
- typedef id (*WillRotateToInterfaceOrientationSendFunc)(struct objc_super*, SEL, UIInterfaceOrientation, NSTimeInterval);
- typedef id (*DidRotateFromInterfaceOrientationSendFunc)(struct objc_super*, SEL, UIInterfaceOrientation);
- #endif
- typedef id (*ViewWillTransitionToSizeSendFunc)(struct objc_super*, SEL, CGSize, id<UIViewControllerTransitionCoordinator>);
- static const char* GetScaleSuffix(float scale, float maxScale)
- {
- if (scale > maxScale)
- scale = maxScale;
- if (scale <= 1.0)
- return "";
- if (scale <= 2.0)
- return "@2x";
- return "@3x";
- }
- static const char* GetOrientationSuffix(ScreenOrientation orient)
- {
- bool orientPortrait = (orient == portrait || orient == portraitUpsideDown);
- bool orientLandscape = (orient == landscapeLeft || orient == landscapeRight);
- bool rotateToPortrait = _canRotateToPortrait || _canRotateToPortraitUpsideDown;
- bool rotateToLandscape = _canRotateToLandscapeLeft || _canRotateToLandscapeRight;
- if (orientPortrait && rotateToPortrait)
- return "-Portrait";
- else if (orientLandscape && rotateToLandscape)
- return "-Landscape";
- else if (rotateToPortrait)
- return "-Portrait";
- else
- return "-Landscape";
- }
- // Returns a launch image name for launch images stored on file system or asset catalog
- static NSArray<NSString*>* GetLaunchImageNames(UIUserInterfaceIdiom idiom, const CGSize& screenSize,
- ScreenOrientation orient)
- {
- NSMutableArray<NSString*>* ret = [[NSMutableArray<NSString *> alloc] init];
- if (idiom == UIUserInterfaceIdiomPad)
- {
- // iPads
- const char* iOSSuffix = _ios70orNewer ? "-700" : "";
- const char* orientSuffix = GetOrientationSuffix(orient);
- const char* scaleSuffix = GetScaleSuffix([UIScreen mainScreen].scale, 2.0);
- [ret addObject: [NSString stringWithFormat: @"LaunchImage%s%s%s~ipad",
- iOSSuffix, orientSuffix, scaleSuffix]];
- }
- else
- {
- // iPhones
- float scale = [UIScreen mainScreen].scale;
- // Note that on pre-iOS 11 using modifiers such as LaunchImage~568h works. Since
- // iOS launch image support is quite hard to get right and has _many_ gotchas, we
- // just use the old code path on these devices.
- if (screenSize.height == 568 || screenSize.width == 568) // iPhone 5
- {
- const char* iOS7Suffix = _ios70orNewer ? "-700" : "";
- [ret addObject: [NSString stringWithFormat: @"LaunchImage%s-568h@2x", iOS7Suffix]];
- [ret addObject: @"LaunchImage~568h"];
- }
- else if (screenSize.height == 667 || screenSize.width == 667) // iPhone 6
- {
- // note that scale may be 3.0 if display zoom is enabled
- if (scale < 2.0) // not expected, but handle just in case. Image name is valid
- [ret addObject: @"LaunchImage-800-667h"];
- [ret addObject: @"LaunchImage-800-667h@2x"];
- [ret addObject: @"LaunchImage~667h"];
- }
- else if (screenSize.height == 736 || screenSize.width == 736) // iPhone 6+
- {
- const char* orientSuffix = GetOrientationSuffix(orient);
- if (scale < 3.0) // not expected, but handle just in case. Image name is valid
- [ret addObject: [NSString stringWithFormat: @"LaunchImage-800%s-736h", orientSuffix]];
- [ret addObject: [NSString stringWithFormat: @"LaunchImage-800%s-736h@3x", orientSuffix]];
- [ret addObject: @"LaunchImage~736h"];
- }
- else if (screenSize.height == 812 || screenSize.width == 812) // iPhone X
- {
- const char* orientSuffix = GetOrientationSuffix(orient);
- if (scale < 3.0) // not expected, but handle just in case. Image name is valid
- [ret addObject: [NSString stringWithFormat: @"LaunchImage-1100%s-2436h", orientSuffix]];
- [ret addObject: [NSString stringWithFormat: @"LaunchImage-1100%s-2436h@3x", orientSuffix]];
- }
- if (scale > 1.0)
- [ret addObject: @"LaunchImage@2x"];
- }
- [ret addObject: @"LaunchImage"];
- return ret;
- }
- @implementation SplashScreen
- {
- UIImageView* m_ImageView;
- UIView* m_XibView;
- }
- - (id)initWithFrame:(CGRect)frame
- {
- self = [super initWithFrame: frame];
- return self;
- }
- /* The following launch images are produced by Xcode6:
- LaunchImage.png
- LaunchImage@2x.png
- LaunchImage-568h@2x.png
- LaunchImage-700@2x.png
- LaunchImage-700-568h@2x.png
- LaunchImage-700-Landscape@2x~ipad.png
- LaunchImage-700-Landscape~ipad.png
- LaunchImage-700-Portrait@2x~ipad.png
- LaunchImage-700-Portrait~ipad.png
- LaunchImage-800-667h@2x.png
- LaunchImage-800-Landscape-736h@3x.png
- LaunchImage-800-Portrait-736h@3x.png
- LaunchImage-1100-Landscape-2436h@3x.png
- LaunchImage-1100-Portrait-2436h@3x.png
- LaunchImage-Landscape@2x~ipad.png
- LaunchImage-Landscape~ipad.png
- LaunchImage-Portrait@2x~ipad.png
- LaunchImage-Portrait~ipad.png
- */
- - (void)updateOrientation:(ScreenOrientation)orient
- {
- CGFloat scale = UnityScreenScaleFactor([UIScreen mainScreen]);
- UnityReportResizeView(self.bounds.size.width * scale, self.bounds.size.height * scale, orient);
- // Storyboards should have a view controller to automatically configure orientation
- NSString* launchScreenStoryboard = [[[[NSBundle mainBundle] infoDictionary] objectForKey: @"UILaunchStoryboardName"] stringByDeletingPathExtension];
- const bool hasStoryboard = [[NSBundle mainBundle] pathForResource: launchScreenStoryboard ofType: @"storyboardc"] != nil;
- if (hasStoryboard)
- return;
- UIUserInterfaceIdiom idiom = [[UIDevice currentDevice] userInterfaceIdiom];
- NSString* xibName = nil;
- if (idiom == UIUserInterfaceIdiomPhone)
- xibName = @"LaunchScreen-iPhone";
- else if (idiom == UIUserInterfaceIdiomPad)
- xibName = @"LaunchScreen-iPad";
- const bool hasLaunchScreen = [[NSBundle mainBundle] pathForResource: xibName ofType: @"nib"] != nil;
- if (_usesLaunchscreen && hasLaunchScreen)
- {
- // Launch screen uses the same aspect-filled image for all iPhone and/or
- // all iPads, as configured in Unity. We need a special case if there's
- // a launch screen and iOS is configured to use it.
- if (self->m_XibView == nil)
- {
- self->m_XibView = [[[NSBundle mainBundle] loadNibNamed: xibName owner: nil options: nil] objectAtIndex: 0];
- [self addSubview: self->m_XibView];
- }
- return;
- }
- UIImage* image = nil;
- CGSize screenSize = [[UIScreen mainScreen] bounds].size;
- // For launch images we implement fallback order with multiple images. First we try images via
- // [UIImage imageNamed] method and if this fails, we try to load from filesystem directly.
- // Note that file system resource names and image names accepted by UIImage are the same.
- // Multiple fallbacks are implemented because different iOS versions behave differently and have
- // many gotchas that are hard to get right. So we use the images that are present on app bundles
- // made with latest version of Xcode as the first priority and then fall back to any image that we
- // have used at some time in the past.
- NSArray<NSString*>* imageNames = GetLaunchImageNames(idiom, screenSize, orient);
- for (NSString* imageName in imageNames)
- {
- image = [UIImage imageNamed: imageName];
- if (image)
- break;
- }
- if (image == nil)
- {
- // Old launch image from file
- for (NSString* imageName in imageNames)
- {
- image = [UIImage imageNamed: imageName];
- if (image)
- break;
- NSString* imagePath = [[NSBundle mainBundle] pathForResource: imageName ofType: @"png"];
- image = [UIImage imageWithContentsOfFile: imagePath];
- if (image)
- break;
- }
- }
- // should not ever happen, but just in case
- if (image == nil)
- return;
- if (self->m_ImageView == nil)
- {
- self->m_ImageView = [[UIImageView alloc] initWithImage: image];
- [self addSubview: self->m_ImageView];
- }
- else
- {
- self->m_ImageView.image = image;
- }
- }
- - (void)layoutSubviews
- {
- if (self->m_XibView)
- self->m_XibView.frame = self.bounds;
- else if (self->m_ImageView)
- self->m_ImageView.frame = self.bounds;
- }
- + (SplashScreen*)Instance
- {
- return _splash;
- }
- - (void)FreeSubviews
- {
- m_ImageView = nil;
- m_XibView = nil;
- }
- @end
- @implementation SplashScreenController
- #if !PLATFORM_TVOS
- static void WillRotateToInterfaceOrientation_DefaultImpl(id self_, SEL _cmd, UIInterfaceOrientation toInterfaceOrientation, NSTimeInterval duration)
- {
- if (_isOrientable)
- [_splash updateOrientation: ConvertToUnityScreenOrientation(toInterfaceOrientation)];
- UNITY_OBJC_FORWARD_TO_SUPER(self_, [UIViewController class], @selector(willRotateToInterfaceOrientation:duration:), WillRotateToInterfaceOrientationSendFunc, toInterfaceOrientation, duration);
- }
- static void DidRotateFromInterfaceOrientation_DefaultImpl(id self_, SEL _cmd, UIInterfaceOrientation fromInterfaceOrientation)
- {
- if (!_isOrientable)
- OrientView((SplashScreenController*)self_, _splash, _nonOrientableDefaultOrientation);
- UNITY_OBJC_FORWARD_TO_SUPER(self_, [UIViewController class], @selector(didRotateFromInterfaceOrientation:), DidRotateFromInterfaceOrientationSendFunc, fromInterfaceOrientation);
- }
- #endif
- static void ViewWillTransitionToSize_DefaultImpl(id self_, SEL _cmd, CGSize size, id<UIViewControllerTransitionCoordinator> coordinator)
- {
- UnityViewControllerBase* self = (UnityViewControllerBase*)self_;
- ScreenOrientation curOrient = UIViewControllerOrientation(self);
- ScreenOrientation newOrient = OrientationAfterTransform(curOrient, [coordinator targetTransform]);
- if (_isOrientable)
- [_splash updateOrientation: newOrient];
- [coordinator animateAlongsideTransition: nil completion:^(id < UIViewControllerTransitionCoordinatorContext > context) {
- if (!_isOrientable)
- OrientView(self, _splash, _nonOrientableDefaultOrientation);
- }];
- UNITY_OBJC_FORWARD_TO_SUPER(self_, [UIViewController class], @selector(viewWillTransitionToSize:withTransitionCoordinator:), ViewWillTransitionToSizeSendFunc, size, coordinator);
- }
- - (id)init
- {
- if ((self = [super init]))
- {
- #if !PLATFORM_TVOS
- AddViewControllerRotationHandling(
- [SplashScreenController class],
- (IMP)&WillRotateToInterfaceOrientation_DefaultImpl, (IMP)&DidRotateFromInterfaceOrientation_DefaultImpl,
- (IMP)&ViewWillTransitionToSize_DefaultImpl
- );
- #endif
- }
- return self;
- }
- - (void)create:(UIWindow*)window
- {
- NSArray* supportedOrientation = [[[NSBundle mainBundle] infoDictionary] objectForKey: @"UISupportedInterfaceOrientations"];
- bool isIphone = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone;
- bool isIpad = !isIphone;
- // splash will be shown way before unity is inited so we need to override autorotation handling with values read from info.plist
- _canRotateToPortrait = [supportedOrientation containsObject: @"UIInterfaceOrientationPortrait"];
- _canRotateToPortraitUpsideDown = [supportedOrientation containsObject: @"UIInterfaceOrientationPortraitUpsideDown"];
- _canRotateToLandscapeLeft = [supportedOrientation containsObject: @"UIInterfaceOrientationLandscapeRight"];
- _canRotateToLandscapeRight = [supportedOrientation containsObject: @"UIInterfaceOrientationLandscapeLeft"];
- // special handling of devices/ios that do not support upside down orientation
- if (!UnityDeviceSupportsUpsideDown())
- {
- _canRotateToPortraitUpsideDown = false;
- const bool anySupported = _canRotateToPortrait || _canRotateToLandscapeLeft || _canRotateToLandscapeRight;
- if (!anySupported)
- {
- _canRotateToPortrait = true;
- printf_console("This device does not support UpsideDown orientation, so we switched to Portrait.\n");
- }
- }
- CGSize size = [[UIScreen mainScreen] bounds].size;
- // iPads and iPhone 6+ and iOS11 have orientable splash screen
- _isOrientable = isIpad || (size.height == 736 || size.width == 736) || _ios110orNewer;
- // Launch screens are used only on iOS8+ iPhones
- const char* xib = UnityGetLaunchScreenXib();
- #if !PLATFORM_TVOS
- _usesLaunchscreen = false;
- if (_ios80orNewer && xib != NULL)
- {
- const char* expectedName = isIphone ? "LaunchScreen-iPhone" : "LaunchScreen-iPad";
- if (std::strcmp(xib, expectedName) == 0)
- _usesLaunchscreen = true;
- }
- #else
- _usesLaunchscreen = false;
- #endif
- if (_usesLaunchscreen && !(_canRotateToPortrait || _canRotateToPortraitUpsideDown))
- _nonOrientableDefaultOrientation = landscapeLeft;
- else
- _nonOrientableDefaultOrientation = portrait;
- _splash = [[SplashScreen alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
- _splash.contentScaleFactor = [UIScreen mainScreen].scale;
- if (_isOrientable)
- {
- _splash.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
- _splash.autoresizesSubviews = YES;
- }
- else if (_canRotateToPortrait || _canRotateToPortraitUpsideDown)
- {
- _canRotateToLandscapeLeft = false;
- _canRotateToLandscapeRight = false;
- }
- // On non-orientable devices with launch screens, landscapeLeft is always used if both
- // landscapeRight and landscapeLeft are enabled
- if (!_isOrientable && _usesLaunchscreen && _canRotateToLandscapeRight)
- {
- if (_canRotateToLandscapeLeft)
- _canRotateToLandscapeRight = false;
- else
- _nonOrientableDefaultOrientation = landscapeRight;
- }
- window.rootViewController = self;
- self.view = _splash;
- [window addSubview: _splash];
- [window bringSubviewToFront: _splash];
- ScreenOrientation orient = UIViewControllerOrientation(self);
- [_splash updateOrientation: orient];
- if (!_isOrientable)
- orient = _nonOrientableDefaultOrientation;
- // Fix iPhone 5,6 launch images (only in portrait) from being stretched
- if (isIphone && _isOrientable && !_usesLaunchscreen && ((size.height == 568 || size.width == 568) || (size.height == 667 || size.width == 667)))
- orient = portrait;
- OrientView([SplashScreenController Instance], _splash, orient);
- }
- - (BOOL)shouldAutorotate
- {
- return YES;
- }
- - (NSUInteger)supportedInterfaceOrientations
- {
- NSUInteger ret = 0;
- if (_canRotateToPortrait)
- ret |= (1 << UIInterfaceOrientationPortrait);
- if (_canRotateToPortraitUpsideDown)
- ret |= (1 << UIInterfaceOrientationPortraitUpsideDown);
- if (_canRotateToLandscapeLeft)
- ret |= (1 << UIInterfaceOrientationLandscapeRight);
- if (_canRotateToLandscapeRight)
- ret |= (1 << UIInterfaceOrientationLandscapeLeft);
- return ret;
- }
- + (SplashScreenController*)Instance
- {
- return _controller;
- }
- @end
- void ShowSplashScreen(UIWindow* window)
- {
- NSString* launchScreenStoryboard = [[[[NSBundle mainBundle] infoDictionary] objectForKey: @"UILaunchStoryboardName"] stringByDeletingPathExtension];
- const bool hasStoryboard = launchScreenStoryboard != nil && [[NSBundle mainBundle] pathForResource: launchScreenStoryboard ofType: @"storyboardc"] != nil;
- if (hasStoryboard)
- {
- UIStoryboard *storyboard = [UIStoryboard storyboardWithName: launchScreenStoryboard bundle: [NSBundle mainBundle]];
- _controller = [storyboard instantiateInitialViewController];
- window.rootViewController = _controller;
- }
- else
- {
- _controller = [[SplashScreenController alloc] init];
- [_controller create: window];
- }
- [window makeKeyAndVisible];
- }
- void HideSplashScreen()
- {
- if (_splash)
- {
- [_splash removeFromSuperview];
- [_splash FreeSubviews];
- }
- _splash = nil;
- _controller = nil;
- }
|