LearnWithTouch/App/LearnWithTouch_ios/Classes/UI/SplashScreen.mm
2019-07-01 14:33:21 +02:00

421 lines
15 KiB
Plaintext

#define UnityGetLaunchScreenXib MyUnityGetLaunchScreenXib
static const char *MyUnityGetLaunchScreenXib() { return NULL; }
#include "SplashScreen.h"
#include "UnityViewControllerBase.h"
#include "OrientationSupport.h"
#include "Unity/ObjCRuntime.h"
#include "UI/UnityView.h"
#include <cstring>
#include "Classes/Unity/UnitySharedDecls.h"
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;
#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(const OrientationMask& supportedOrientations, ScreenOrientation orient)
{
bool orientPortrait = (orient == portrait || orient == portraitUpsideDown);
bool orientLandscape = (orient == landscapeLeft || orient == landscapeRight);
bool supportsPortrait = supportedOrientations.portrait || supportedOrientations.portraitUpsideDown;
bool supportsLandscape = supportedOrientations.landscapeLeft || supportedOrientations.landscapeRight;
if (orientPortrait && supportsPortrait)
return "-Portrait";
else if (orientLandscape && supportsLandscape)
return "-Landscape";
else if (supportsPortrait)
return "-Portrait";
else
return "-Landscape";
}
// Returns a launch image name for launch images stored on file system or asset catalog
extern "C" NSArray<NSString*>* GetLaunchImageNames(UIUserInterfaceIdiom idiom, const OrientationMask&supportedOrientations,
const CGSize&screenSize, ScreenOrientation orient, float scale)
{
NSMutableArray<NSString*>* ret = [[NSMutableArray<NSString *> alloc] init];
if (idiom == UIUserInterfaceIdiomPad)
{
// iPads
const char* iOSSuffix = "-700";
const char* orientSuffix = GetOrientationSuffix(supportedOrientations, orient);
const char* scaleSuffix = GetScaleSuffix(scale, 2.0);
[ret addObject: [NSString stringWithFormat: @"LaunchImage%s%s%s~ipad",
iOSSuffix, orientSuffix, scaleSuffix]];
}
else
{
// iPhones
// 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
{
[ret addObject: @"LaunchImage-700-568h@2x"];
[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(supportedOrientations, 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(supportedOrientations, 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 withSupportedOrientations:(const OrientationMask&)supportedOrientations
{
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
bool hasStoryboard = [[NSBundle mainBundle] pathForResource: @"LaunchScreen" ofType: @"storyboardc"] != nullptr;
if (hasStoryboard)
return;
UIUserInterfaceIdiom idiom = [[UIDevice currentDevice] userInterfaceIdiom];
NSString* xibName = nil;
if (idiom == UIUserInterfaceIdiomPhone)
xibName = @"LaunchScreen-iPhone";
else if (idiom == UIUserInterfaceIdiomPad)
xibName = @"LaunchScreen-iPad";
bool hasLaunchScreen = [[NSBundle mainBundle] pathForResource: xibName ofType: @"nib"] != nullptr;
if (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;
CGFloat screenScale = [UIScreen mainScreen].scale;
// 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, supportedOrientations, screenSize, orient, screenScale);
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
{
OrientationMask _supportedOrientations;
}
- (id)init
{
self = [super init];
if (self)
{
self->_supportedOrientations = { false, false, false, false };
}
return self;
}
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
ScreenOrientation curOrient = UIViewControllerOrientation(self);
ScreenOrientation newOrient = OrientationAfterTransform(curOrient, [coordinator targetTransform]);
if (_isOrientable)
[_splash updateOrientation: newOrient withSupportedOrientations: self->_supportedOrientations];
[coordinator animateAlongsideTransition: nil completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
if (!_isOrientable)
OrientView(self, _splash, _nonOrientableDefaultOrientation);
}];
[super viewWillTransitionToSize: size withTransitionCoordinator: coordinator];
}
- (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
self->_supportedOrientations.portrait = [supportedOrientation containsObject: @"UIInterfaceOrientationPortrait"];
self->_supportedOrientations.portraitUpsideDown = [supportedOrientation containsObject: @"UIInterfaceOrientationPortraitUpsideDown"];
self->_supportedOrientations.landscapeLeft = [supportedOrientation containsObject: @"UIInterfaceOrientationLandscapeRight"];
self->_supportedOrientations.landscapeRight = [supportedOrientation containsObject: @"UIInterfaceOrientationLandscapeLeft"];
CGSize size = [[UIScreen mainScreen] bounds].size;
// iPads and iPhone Plus models and iOS11 have orientable splash screen
_isOrientable = isIpad || (size.height == 736 || size.width == 736) || UnityiOS110orNewer();
// Launch screens are used only on iOS8+ iPhones
const char* xib = UnityGetLaunchScreenXib();
#if !PLATFORM_TVOS
_usesLaunchscreen = false;
if (xib != NULL)
{
const char* expectedName = isIphone ? "LaunchScreen-iPhone" : "LaunchScreen-iPad";
if (std::strcmp(xib, expectedName) == 0)
_usesLaunchscreen = true;
}
#else
_usesLaunchscreen = false;
#endif
if (!(self->_supportedOrientations.portrait || self->_supportedOrientations.portraitUpsideDown))
_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 (self->_supportedOrientations.portrait || self->_supportedOrientations.portraitUpsideDown)
{
self->_supportedOrientations.landscapeLeft = false;
self->_supportedOrientations.landscapeRight = false;
}
// On non-orientable devices with launch screens, landscapeLeft is always used if both
// landscapeRight and landscapeLeft are enabled
if (!_isOrientable && _supportedOrientations.landscapeRight)
{
if (self->_supportedOrientations.landscapeLeft)
self->_supportedOrientations.landscapeRight = false;
else
_nonOrientableDefaultOrientation = landscapeRight;
}
window.rootViewController = self;
self.view = _splash;
[window addSubview: _splash];
[window bringSubviewToFront: _splash];
ScreenOrientation orient = UIViewControllerOrientation(self);
[_splash updateOrientation: orient withSupportedOrientations: self->_supportedOrientations];
if (!_isOrientable)
orient = _nonOrientableDefaultOrientation;
// fix iPhone 5,6 launch images (only in portrait) from being stretched
if (isIphone && _isOrientable && !((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 (self->_supportedOrientations.portrait)
ret |= (1 << UIInterfaceOrientationPortrait);
if (self->_supportedOrientations.portraitUpsideDown)
ret |= (1 << UIInterfaceOrientationPortraitUpsideDown);
if (self->_supportedOrientations.landscapeLeft)
ret |= (1 << UIInterfaceOrientationLandscapeRight);
if (self->_supportedOrientations.landscapeRight)
ret |= (1 << UIInterfaceOrientationLandscapeLeft);
return ret;
}
+ (SplashScreenController*)Instance
{
return _controller;
}
@end
void ShowSplashScreen(UIWindow* window)
{
bool hasStoryboard = [[NSBundle mainBundle] pathForResource: @"LaunchScreen" ofType: @"storyboardc"] != nullptr;
if (hasStoryboard)
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName: @"LaunchScreen" 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;
}