[add] first
This commit is contained in:
5
Classes/UI/ActivityIndicator.h
Normal file
5
Classes/UI/ActivityIndicator.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
void ShowActivityIndicator(UIView* parent, int style);
|
||||
void ShowActivityIndicator(UIView* parent);
|
||||
void HideActivityIndicator();
|
||||
66
Classes/UI/ActivityIndicator.mm
Normal file
66
Classes/UI/ActivityIndicator.mm
Normal file
@@ -0,0 +1,66 @@
|
||||
#include "ActivityIndicator.h"
|
||||
#include "OrientationSupport.h"
|
||||
|
||||
@interface ActivityIndicator : UIActivityIndicatorView
|
||||
{
|
||||
UIView* _parent;
|
||||
}
|
||||
@end
|
||||
static ActivityIndicator* _activityIndicator = nil;
|
||||
|
||||
|
||||
@implementation ActivityIndicator
|
||||
- (void)show:(UIView*)parent
|
||||
{
|
||||
_parent = parent;
|
||||
[parent addSubview: self];
|
||||
[self startAnimating];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
self.center = CGPointMake([_parent bounds].size.width / 2, [_parent bounds].size.height / 2);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
void ShowActivityIndicator(UIView* parent, int style)
|
||||
{
|
||||
if (_activityIndicator != nil)
|
||||
return;
|
||||
|
||||
if (style >= 0)
|
||||
{
|
||||
_activityIndicator = [[ActivityIndicator alloc] initWithActivityIndicatorStyle: (UIActivityIndicatorViewStyle)style];
|
||||
_activityIndicator.contentScaleFactor = [UIScreen mainScreen].scale;
|
||||
}
|
||||
|
||||
if (_activityIndicator != nil)
|
||||
[_activityIndicator show: parent];
|
||||
}
|
||||
|
||||
void ShowActivityIndicator(UIView* parent)
|
||||
{
|
||||
ShowActivityIndicator(parent, UnityGetShowActivityIndicatorOnLoading());
|
||||
}
|
||||
|
||||
void HideActivityIndicator()
|
||||
{
|
||||
if (_activityIndicator)
|
||||
{
|
||||
[_activityIndicator stopAnimating];
|
||||
[_activityIndicator removeFromSuperview];
|
||||
_activityIndicator = nil;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void UnityStartActivityIndicator()
|
||||
{
|
||||
// AppleTV does not support activity indicators
|
||||
ShowActivityIndicator(UnityGetGLView());
|
||||
}
|
||||
|
||||
extern "C" void UnityStopActivityIndicator()
|
||||
{
|
||||
HideActivityIndicator();
|
||||
}
|
||||
69
Classes/UI/Keyboard.h
Normal file
69
Classes/UI/Keyboard.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
const char* text;
|
||||
const char* placeholder;
|
||||
|
||||
UIKeyboardType keyboardType;
|
||||
UITextAutocorrectionType autocorrectionType;
|
||||
UITextSpellCheckingType spellcheckingType;
|
||||
UIKeyboardAppearance appearance;
|
||||
|
||||
BOOL multiline;
|
||||
BOOL secure;
|
||||
int characterLimit;
|
||||
BOOL oneTimeCode;
|
||||
}
|
||||
KeyboardShowParam;
|
||||
|
||||
|
||||
@interface KeyboardDelegate : NSObject<UITextFieldDelegate, UITextViewDelegate>
|
||||
{
|
||||
}
|
||||
- (void)setPendingSelectionRequest;
|
||||
- (BOOL)textFieldShouldReturn:(UITextField*)textField;
|
||||
- (void)textInputDone:(id)sender;
|
||||
- (void)textInputCancel:(id)sender;
|
||||
- (void)textInputLostFocus;
|
||||
- (void)textViewDidChange:(UITextView *)textView;
|
||||
- (void)keyboardWillShow:(NSNotification*)notification;
|
||||
- (void)keyboardDidShow:(NSNotification*)notification;
|
||||
- (void)keyboardWillHide:(NSNotification*)notification;
|
||||
- (void)keyboardDidHide:(NSNotification*)notification;
|
||||
- (void)becomeFirstResponder;
|
||||
|
||||
// on older devices initial keyboard creation might be slow, so it is good to init in on initial loading.
|
||||
// on the other hand, if you dont use keyboard (or use it rarely), you can avoid having all related stuff in memory:
|
||||
// keyboard will be created on demand anyway (in Instance method)
|
||||
+ (void)Initialize;
|
||||
+ (KeyboardDelegate*)Instance;
|
||||
+ (void)Destroy;
|
||||
|
||||
- (id)init;
|
||||
- (void)setKeyboardParams:(KeyboardShowParam)param;
|
||||
- (void)show;
|
||||
- (void)hide;
|
||||
- (void)positionInput:(CGRect)keyboardRect x:(float)x y:(float)y;
|
||||
- (void)shouldHideInput:(BOOL)hide;
|
||||
|
||||
+ (void)StartReorientation;
|
||||
+ (void)FinishReorientation;
|
||||
|
||||
- (CGRect)queryArea;
|
||||
- (NSString*)getText;
|
||||
- (void)setText:(NSString*)newText;
|
||||
- (BOOL)hasExternalKeyboard;
|
||||
|
||||
@property (readonly, nonatomic, getter = queryArea) CGRect area;
|
||||
@property (readonly, nonatomic) BOOL active;
|
||||
@property (readonly, nonatomic) KeyboardStatus status;
|
||||
@property (retain, nonatomic, getter = getText, setter = setText:) NSString* text;
|
||||
@property (assign, nonatomic) int characterLimit;
|
||||
@property (readonly, nonatomic) BOOL canGetSelection;
|
||||
@property (nonatomic, getter = querySelection, setter = assignSelection:) NSRange selection;
|
||||
@property (nonatomic) BOOL hasUsedDictation;
|
||||
|
||||
@end
|
||||
1056
Classes/UI/Keyboard.mm
Normal file
1056
Classes/UI/Keyboard.mm
Normal file
File diff suppressed because it is too large
Load Diff
20
Classes/UI/OrientationSupport.h
Normal file
20
Classes/UI/OrientationSupport.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <CoreGraphics/CGAffineTransform.h>
|
||||
|
||||
#if !PLATFORM_TVOS
|
||||
ScreenOrientation ConvertToUnityScreenOrientation(UIInterfaceOrientation hwOrient);
|
||||
UIInterfaceOrientation ConvertToIosScreenOrientation(ScreenOrientation orient);
|
||||
#endif
|
||||
|
||||
#if !PLATFORM_TVOS
|
||||
UIInterfaceOrientation UIViewControllerInterfaceOrientation(UIViewController* controller);
|
||||
#endif
|
||||
ScreenOrientation UIViewControllerOrientation(UIViewController* controller);
|
||||
|
||||
CGAffineTransform TransformForOrientation(ScreenOrientation curOrient);
|
||||
CGAffineTransform TransformBetweenOrientations(ScreenOrientation fromOrient, ScreenOrientation toOrient);
|
||||
|
||||
ScreenOrientation OrientationAfterTransform(ScreenOrientation curOrient, CGAffineTransform transform);
|
||||
|
||||
void OrientView(UIViewController* host, UIView* view, ScreenOrientation to);
|
||||
159
Classes/UI/OrientationSupport.mm
Normal file
159
Classes/UI/OrientationSupport.mm
Normal file
@@ -0,0 +1,159 @@
|
||||
#include "OrientationSupport.h"
|
||||
#include <math.h>
|
||||
|
||||
CGAffineTransform TransformForOrientation(ScreenOrientation orient)
|
||||
{
|
||||
switch (orient)
|
||||
{
|
||||
case portrait: return CGAffineTransformIdentity;
|
||||
case portraitUpsideDown: return CGAffineTransformMakeRotation(M_PI);
|
||||
case landscapeLeft: return CGAffineTransformMakeRotation(M_PI_2);
|
||||
case landscapeRight: return CGAffineTransformMakeRotation(-M_PI_2);
|
||||
|
||||
default: return CGAffineTransformIdentity;
|
||||
}
|
||||
return CGAffineTransformIdentity;
|
||||
}
|
||||
|
||||
CGAffineTransform TransformBetweenOrientations(ScreenOrientation fromOrient, ScreenOrientation toOrient)
|
||||
{
|
||||
CGAffineTransform fromTransform = TransformForOrientation(fromOrient);
|
||||
CGAffineTransform toTransform = TransformForOrientation(toOrient);
|
||||
|
||||
return CGAffineTransformConcat(CGAffineTransformInvert(fromTransform), toTransform);
|
||||
}
|
||||
|
||||
#if !PLATFORM_TVOS
|
||||
UIInterfaceOrientation ConvertToIosScreenOrientation(ScreenOrientation orient)
|
||||
{
|
||||
switch (orient)
|
||||
{
|
||||
case portrait: return UIInterfaceOrientationPortrait;
|
||||
case portraitUpsideDown: return UIInterfaceOrientationPortraitUpsideDown;
|
||||
// landscape left/right have switched values in device/screen orientation
|
||||
// though unity docs are adjusted with device orientation values, so swap here
|
||||
case landscapeLeft: return UIInterfaceOrientationLandscapeRight;
|
||||
case landscapeRight: return UIInterfaceOrientationLandscapeLeft;
|
||||
|
||||
case orientationUnknown: return (UIInterfaceOrientation)UIInterfaceOrientationUnknown;
|
||||
|
||||
default: return UIInterfaceOrientationPortrait;
|
||||
}
|
||||
|
||||
return UIInterfaceOrientationPortrait;
|
||||
}
|
||||
|
||||
ScreenOrientation ConvertToUnityScreenOrientation(UIInterfaceOrientation orient)
|
||||
{
|
||||
switch (orient)
|
||||
{
|
||||
case UIInterfaceOrientationPortrait: return portrait;
|
||||
case UIInterfaceOrientationPortraitUpsideDown: return portraitUpsideDown;
|
||||
// landscape left/right have switched values in device/screen orientation
|
||||
// though unity docs are adjusted with device orientation values, so swap here
|
||||
case UIInterfaceOrientationLandscapeLeft: return landscapeRight;
|
||||
case UIInterfaceOrientationLandscapeRight: return landscapeLeft;
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wswitch"
|
||||
case UIInterfaceOrientationUnknown: return orientationUnknown;
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
default: return portrait;
|
||||
}
|
||||
}
|
||||
|
||||
// Replacement for UIViewController.interfaceOrientation which is obsolete since iOS 8.0
|
||||
UIInterfaceOrientation UIViewControllerInterfaceOrientation(UIViewController* c)
|
||||
{
|
||||
CGPoint fixedPoint = [c.view.window.screen.coordinateSpace convertPoint: CGPointMake(0.0, 0.0) toCoordinateSpace: c.view.window.screen.fixedCoordinateSpace];
|
||||
|
||||
if (fabs(fixedPoint.x) < FLT_EPSILON)
|
||||
{
|
||||
if (fabs(fixedPoint.y) < FLT_EPSILON)
|
||||
return UIInterfaceOrientationPortrait;
|
||||
else
|
||||
return UIInterfaceOrientationLandscapeLeft;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (fabs(fixedPoint.y) < FLT_EPSILON)
|
||||
return UIInterfaceOrientationLandscapeRight;
|
||||
else
|
||||
return UIInterfaceOrientationPortraitUpsideDown;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
ScreenOrientation UIViewControllerOrientation(UIViewController* controller)
|
||||
{
|
||||
#if PLATFORM_TVOS
|
||||
return UNITY_TVOS_ORIENTATION;
|
||||
#else
|
||||
return ConvertToUnityScreenOrientation(UIViewControllerInterfaceOrientation(controller));
|
||||
#endif
|
||||
}
|
||||
|
||||
ScreenOrientation OrientationAfterTransform(ScreenOrientation curOrient, CGAffineTransform transform)
|
||||
{
|
||||
int rotDeg = (int)::roundf(::atan2f(transform.b, transform.a) * (180 / M_PI));
|
||||
assert(rotDeg == 0 || rotDeg == 90 || rotDeg == -90 || rotDeg == 180 || rotDeg == -180);
|
||||
|
||||
if (rotDeg == 0)
|
||||
{
|
||||
return curOrient;
|
||||
}
|
||||
else if ((rotDeg == 180) || (rotDeg == -180))
|
||||
{
|
||||
if (curOrient == portrait)
|
||||
return portraitUpsideDown;
|
||||
else if (curOrient == portraitUpsideDown)
|
||||
return portrait;
|
||||
else if (curOrient == landscapeRight)
|
||||
return landscapeLeft;
|
||||
else if (curOrient == landscapeLeft)
|
||||
return landscapeRight;
|
||||
}
|
||||
else if (rotDeg == 90)
|
||||
{
|
||||
if (curOrient == portrait)
|
||||
return landscapeLeft;
|
||||
else if (curOrient == portraitUpsideDown)
|
||||
return landscapeRight;
|
||||
else if (curOrient == landscapeRight)
|
||||
return portrait;
|
||||
else if (curOrient == landscapeLeft)
|
||||
return portraitUpsideDown;
|
||||
}
|
||||
else if (rotDeg == -90)
|
||||
{
|
||||
if (curOrient == portrait)
|
||||
return landscapeRight;
|
||||
else if (curOrient == portraitUpsideDown)
|
||||
return landscapeLeft;
|
||||
else if (curOrient == landscapeRight)
|
||||
return portraitUpsideDown;
|
||||
else if (curOrient == landscapeLeft)
|
||||
return portrait;
|
||||
}
|
||||
|
||||
::printf("rotation unhandled: %d\n", rotDeg);
|
||||
return curOrient;
|
||||
}
|
||||
|
||||
void OrientView(UIViewController* host, UIView* view, ScreenOrientation to)
|
||||
{
|
||||
ScreenOrientation fromController = UIViewControllerOrientation(host);
|
||||
|
||||
CGAffineTransform transform = TransformBetweenOrientations(fromController, to);
|
||||
|
||||
// this is for unity-inited orientation. In that case we need to manually adjust bounds if changing portrait/landscape
|
||||
// the easiest way would be to manually rotate current bounds (to acknowledge the fact that we do NOT rotate controller itself)
|
||||
// NB: as we use current view bounds we need to use view transform to properly adjust them
|
||||
CGRect rect = view.bounds;
|
||||
CGSize ext = CGSizeApplyAffineTransform(rect.size, CGAffineTransformConcat(CGAffineTransformInvert(view.transform), transform));
|
||||
|
||||
view.transform = transform;
|
||||
view.bounds = CGRectMake(0, 0, ::fabs(ext.width), ::fabs(ext.height));
|
||||
}
|
||||
20
Classes/UI/SplashScreen.h
Normal file
20
Classes/UI/SplashScreen.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "UnityViewControllerBase.h"
|
||||
|
||||
|
||||
@interface SplashScreen : UIImageView
|
||||
{
|
||||
}
|
||||
+ (SplashScreen*)Instance;
|
||||
@end
|
||||
|
||||
@interface SplashScreenController : UnityViewControllerBase
|
||||
{
|
||||
}
|
||||
+ (SplashScreenController*)Instance;
|
||||
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator;
|
||||
@end
|
||||
|
||||
void ShowSplashScreen(UIWindow* window);
|
||||
void HideSplashScreen();
|
||||
243
Classes/UI/SplashScreen.mm
Normal file
243
Classes/UI/SplashScreen.mm
Normal file
@@ -0,0 +1,243 @@
|
||||
#include "SplashScreen.h"
|
||||
#include "UnityViewControllerBase.h"
|
||||
#include "OrientationSupport.h"
|
||||
#include "Unity/ObjCRuntime.h"
|
||||
#include "UI/UnityView.h"
|
||||
#include <cstring>
|
||||
#include "Unity/UnitySharedDecls.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
static SplashScreen* _splash = nil;
|
||||
static SplashScreenController* _controller = nil;
|
||||
|
||||
@implementation SplashScreen
|
||||
{
|
||||
UIImageView* m_ImageView;
|
||||
UIView* m_XibView;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame: frame];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)createUI
|
||||
{
|
||||
NSString* launchScreen = [[NSBundle mainBundle].infoDictionary[@"UILaunchStoryboardName"] stringByDeletingPathExtension];
|
||||
const bool hasXIB = [[NSBundle mainBundle] pathForResource: launchScreen ofType: @"nib"] != nil;
|
||||
|
||||
if (hasXIB)
|
||||
{
|
||||
self->m_XibView = [[[NSBundle mainBundle] loadNibNamed: launchScreen owner: nil options: nil] objectAtIndex: 0];
|
||||
[self addSubview: self->m_XibView];
|
||||
}
|
||||
else
|
||||
{
|
||||
#if !PLATFORM_TVOS
|
||||
NSAssert(NO, @"no storyboard/xib was provided.");
|
||||
#endif
|
||||
|
||||
// we still support launch images on tvos, but unlike iOS there are only two options and no orientations
|
||||
UIImage* launchImage = nil;
|
||||
if ([UIScreen mainScreen].scale > 1.0)
|
||||
launchImage = [UIImage imageNamed: @"LaunchImage@2x"];
|
||||
if (!launchImage)
|
||||
launchImage = [UIImage imageNamed: @"LaunchImage@"];
|
||||
|
||||
self->m_ImageView = [[UIImageView alloc] initWithImage: launchImage];
|
||||
[self addSubview: self->m_ImageView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateOrientation:(ScreenOrientation)orient withSupportedOrientations:(const OrientationMask&)supportedOrientations
|
||||
{
|
||||
CGFloat scale = UnityScreenScaleFactor([UIScreen mainScreen]);
|
||||
UnityReportResizeView((unsigned)(self.bounds.size.width * scale), (unsigned)(self.bounds.size.height * scale), orient);
|
||||
ReportSafeAreaChangeForView(self);
|
||||
|
||||
// for iOS only xib/storyboard are supported, for tvOS (launch images are supported) no orientation takes place at all
|
||||
}
|
||||
|
||||
- (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]);
|
||||
[_splash updateOrientation: newOrient withSupportedOrientations: self->_supportedOrientations];
|
||||
|
||||
[super viewWillTransitionToSize: size withTransitionCoordinator: coordinator];
|
||||
}
|
||||
|
||||
- (void)initImpl
|
||||
{
|
||||
NSArray* supportedOrientation = [[[NSBundle mainBundle] infoDictionary] objectForKey: @"UISupportedInterfaceOrientations"];
|
||||
// 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"];
|
||||
|
||||
// special handling of devices/ios that do not support upside down orientation
|
||||
if (!UnityDeviceSupportsUpsideDown())
|
||||
{
|
||||
self->_supportedOrientations.portraitUpsideDown = false;
|
||||
|
||||
OrientationMask om = self->_supportedOrientations;
|
||||
const bool anySupported = om.portrait || om.landscapeLeft || om.landscapeRight;
|
||||
if (!anySupported)
|
||||
{
|
||||
self->_supportedOrientations.portrait = true;
|
||||
printf_console("This device does not support UpsideDown orientation, so we switched to Portrait.\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder
|
||||
{
|
||||
if ((self = [super initWithCoder: coder]))
|
||||
[self initImpl];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)create:(UIWindow*)window
|
||||
{
|
||||
[self initImpl];
|
||||
|
||||
_splash = [[SplashScreen alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
|
||||
_splash.contentScaleFactor = UnityScreenScaleFactor([UIScreen mainScreen]);
|
||||
_splash.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
_splash.autoresizesSubviews = YES;
|
||||
[_splash createUI];
|
||||
|
||||
window.rootViewController = self;
|
||||
self.view = _splash;
|
||||
|
||||
[window addSubview: _splash];
|
||||
[window bringSubviewToFront: _splash];
|
||||
|
||||
ScreenOrientation orient = UIViewControllerOrientation(self);
|
||||
[_splash updateOrientation: orient withSupportedOrientations: self->_supportedOrientations];
|
||||
OrientView([SplashScreenController Instance], _splash, orient);
|
||||
}
|
||||
|
||||
#if PLATFORM_IOS
|
||||
|
||||
- (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;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+ (SplashScreenController*)Instance
|
||||
{
|
||||
return _controller;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
void ShowSplashScreen(UIWindow* window)
|
||||
{
|
||||
NSString* launchScreen = [[NSBundle mainBundle].infoDictionary[@"UILaunchStoryboardName"] stringByDeletingPathExtension];
|
||||
#if PLATFORM_IOS
|
||||
// since launch images are no longer supported on ios we MUST have UILaunchStoryboardName filled
|
||||
assert(launchScreen != nil && "UILaunchStoryboardName key is missing from info.plist");
|
||||
#endif
|
||||
|
||||
const bool hasStoryboard = launchScreen != nil && [[NSBundle mainBundle] pathForResource: launchScreen ofType: @"storyboardc"] != nil;
|
||||
if (hasStoryboard)
|
||||
{
|
||||
UIStoryboard *storyboard = [UIStoryboard storyboardWithName: launchScreen bundle: [NSBundle mainBundle]];
|
||||
|
||||
// on ios13 we can finally tweak initial storyboard view controller: use unity base view controller
|
||||
// this way we can handle orientations/status-bar/whatever-we-want-to-tweak uniformly
|
||||
// as we still support xcode pre-11 we must do this weird dance of checking for both sdk and runtime version
|
||||
// otherwise it fails to compile (due to unknown selector)
|
||||
#if (PLATFORM_IOS && defined(__IPHONE_13_0)) || (PLATFORM_TVOS && defined(__TVOS_13_0))
|
||||
if (@available(iOS 13.0, tvOS 13.0, *))
|
||||
{
|
||||
_controller = [storyboard instantiateInitialViewControllerWithCreator:^(NSCoder *coder) {
|
||||
return [[SplashScreenController alloc] initWithCoder: coder];
|
||||
}];
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
_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;
|
||||
}
|
||||
18
Classes/UI/StoreReview.m
Normal file
18
Classes/UI/StoreReview.m
Normal file
@@ -0,0 +1,18 @@
|
||||
#if PLATFORM_IOS
|
||||
|
||||
// This definition is here only for compiler to know about selector requestReview
|
||||
@interface UnityStoreReviewController
|
||||
+ requestReview;
|
||||
@end
|
||||
|
||||
bool UnityRequestStoreReview()
|
||||
{
|
||||
Class classSKStoreReviewController = NSClassFromString(@"SKStoreReviewController");
|
||||
if (!classSKStoreReviewController || ![classSKStoreReviewController respondsToSelector: @selector(requestReview)])
|
||||
return false;
|
||||
|
||||
[classSKStoreReviewController performSelector: @selector(requestReview)];
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
105
Classes/UI/UnityAppController+ViewHandling.h
Normal file
105
Classes/UI/UnityAppController+ViewHandling.h
Normal file
@@ -0,0 +1,105 @@
|
||||
#pragma once
|
||||
|
||||
#include "UnityAppController.h"
|
||||
#include <AvailabilityMacros.h>
|
||||
|
||||
|
||||
@interface UnityAppController (ViewHandling)
|
||||
|
||||
// tweaking view hierarchy and handling of orientation
|
||||
|
||||
// there are 3 main uses cases regarding UI handling:
|
||||
//
|
||||
// 1. normal game case: you shouldnt care about all this at all
|
||||
//
|
||||
// 2. you need some not-so-trivial overlayed views and/or minor UI tweaking
|
||||
// most likely all you need is to subscribe to "orientation changed" notification
|
||||
// or in case you have per-orientation UI logic override willTransitionToViewController
|
||||
//
|
||||
// 3. you create UI-rich app where unity view is just one of many
|
||||
// in that case you might want to create your own controllers and implement transitions on top
|
||||
// also instead of orientUnity: (and Screen.orientation in script) you should use orientInterface
|
||||
|
||||
|
||||
// override this if you need customized unityview (subclassing)
|
||||
// if you simply want different root view, tweak view hierarchy in createAutorotatingUnityViewController
|
||||
- (UnityView*)createUnityView;
|
||||
|
||||
// for view controllers we discern between platforms that do support orientation (e.g. iOS) and the ones that dont (e.g. tvOS)
|
||||
// both have concept of "default" view controller: for iOS it will be auto-rotating one (with possible constraints) and "simple" controller otherwise
|
||||
// in case of supporting orientation we will discern case of fixed-orientation view controller (that seems to be the only way to handle it robustly)
|
||||
// _unityView will be inited at the point of calling any of "create view controller" methods
|
||||
// please note that these are actual "create" methods: there is no need to tweak hierarchy right away
|
||||
|
||||
- (UIViewController*)createUnityViewControllerDefault;
|
||||
#if UNITY_SUPPORT_ROTATION
|
||||
- (UIViewController*)createUnityViewControllerForOrientation:(UIInterfaceOrientation)orient;
|
||||
#endif
|
||||
|
||||
#if UNITY_SUPPORT_ROTATION
|
||||
// if you override these you need to call super
|
||||
// if your root controller is not subclassed from UnityViewControllerBase, call these when rotation is happening
|
||||
- (void)interfaceWillChangeOrientationTo:(UIInterfaceOrientation)toInterfaceOrientation;
|
||||
- (void)interfaceDidChangeOrientationFrom:(UIInterfaceOrientation)fromInterfaceOrientation;
|
||||
#endif
|
||||
|
||||
// handling of changing ViewControllers:
|
||||
// willStartWithViewController: will be called on startup, when creating view hierarchy
|
||||
// willTransitionToViewController:fromViewController: didTransitionToViewController:fromViewController:
|
||||
// are called before/after we are doing some magic to switch to new root controller due to forced orientation change
|
||||
|
||||
// by default:
|
||||
// willStartWithViewController: will make _unityView as root view
|
||||
// willTransitionToViewController:fromViewController: will do nothing
|
||||
// didTransitionToViewController:fromViewController: will send orientation events to unity view
|
||||
// you can use them to tweak view hierarchy if needed
|
||||
|
||||
- (void)willStartWithViewController:(UIViewController*)controller;
|
||||
- (void)willTransitionToViewController:(UIViewController*)toController fromViewController:(UIViewController*)fromController;
|
||||
- (void)didTransitionToViewController:(UIViewController*)toController fromViewController:(UIViewController*)fromController;
|
||||
|
||||
|
||||
// override this if you want to have custom snapshot view.
|
||||
// by default it will capture the frame drawn inside applicationWillResignActive specifically to let app respond to OnApplicationPause
|
||||
// will be called on every applicationWillResignActive; returned view will be released in applicationDidBecomeActive
|
||||
// NB: case of returning nil will be handled gracefully
|
||||
- (UIView*)createSnapshotView;
|
||||
|
||||
// you should not override these methods
|
||||
|
||||
// creates initial UI hierarchy (e.g. splash screen) and calls willStartWithViewController
|
||||
- (void)createUI;
|
||||
// shows game itself (hides splash, and bring _rootView to front)
|
||||
- (void)showGameUI;
|
||||
|
||||
// returns the topmost presentedViewController if there is one, or just rootViewController
|
||||
- (UIViewController*)topMostController;
|
||||
|
||||
// will create the correct view controller for requested orientation/autorotation
|
||||
- (UIViewController*)createRootViewController;
|
||||
|
||||
// old deprecated methods: no longer used
|
||||
// the caveat is: there are some issues in clang related to method deprecation
|
||||
// which results in warnings not being generated for overriding deprecated methods (in some circumstances).
|
||||
// so instead of deprecating these methods we just remove them and will check at runtime if user have them and whine about it
|
||||
|
||||
//- (UnityView*)createUnityViewImpl DEPRECATED_MSG_ATTRIBUTE("Will not be called. Override createUnityView");
|
||||
//- (void)createViewHierarchyImpl DEPRECATED_MSG_ATTRIBUTE("Will not be called. Override willStartWithViewController");
|
||||
//- (void)createViewHierarchy DEPRECATED_MSG_ATTRIBUTE("Is not implemented. Use createUI");
|
||||
|
||||
@end
|
||||
|
||||
#if UNITY_SUPPORT_ROTATION
|
||||
@interface UnityAppController (OrientationSupport)
|
||||
// will create the correct view controller for given orientation
|
||||
- (UIViewController*)createRootViewControllerForOrientation:(UIInterfaceOrientation)orientation;
|
||||
|
||||
// forcibly orient interface
|
||||
- (void)orientInterface:(UIInterfaceOrientation)orient;
|
||||
|
||||
// check unity requested orientation and applies it
|
||||
- (void)checkOrientationRequest;
|
||||
|
||||
- (void)orientUnity:(UIInterfaceOrientation)orient __deprecated_msg("use orientInterface instead.");
|
||||
@end
|
||||
#endif
|
||||
407
Classes/UI/UnityAppController+ViewHandling.mm
Normal file
407
Classes/UI/UnityAppController+ViewHandling.mm
Normal file
@@ -0,0 +1,407 @@
|
||||
#include "UnityAppController+ViewHandling.h"
|
||||
#include "UnityAppController+Rendering.h"
|
||||
|
||||
#include "UI/OrientationSupport.h"
|
||||
#include "UI/UnityView.h"
|
||||
#include "UI/UnityViewControllerBase.h"
|
||||
#include "Unity/DisplayManager.h"
|
||||
|
||||
|
||||
// TEMP: ?
|
||||
#include "UI/ActivityIndicator.h"
|
||||
#include "UI/SplashScreen.h"
|
||||
#include "UI/Keyboard.h"
|
||||
#include <utility>
|
||||
|
||||
extern bool _skipPresent;
|
||||
extern bool _unityAppReady;
|
||||
|
||||
@implementation UnityAppController (ViewHandling)
|
||||
|
||||
#if UNITY_SUPPORT_ROTATION
|
||||
// special case for when we DO know the app orientation, but dont get it through normal mechanism (UIViewController orientation handling)
|
||||
// how can this happen:
|
||||
// 1. On startup: ios is not sending "change orientation" notifications on startup (but rather we "start" in correct one already)
|
||||
// 2. When using presentation controller it can override orientation constraints, so on dismissing we need to tweak app orientation;
|
||||
// pretty much like startup situation UIViewController would have correct orientation, and app will be out-of-sync
|
||||
- (void)updateAppOrientation:(UIInterfaceOrientation)orientation
|
||||
{
|
||||
// update our (AppContoller) view of orientation
|
||||
_curOrientation = orientation;
|
||||
|
||||
// do unity view "orientation magic"
|
||||
[_unityView willRotateToOrientation: orientation fromOrientation: (UIInterfaceOrientation)UIInterfaceOrientationUnknown];
|
||||
[_unityView didRotate];
|
||||
|
||||
// after we have updated unity view, this will poke unity itself about the changes in orient/extents
|
||||
[_unityView boundsUpdated];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
- (UnityView*)createUnityView
|
||||
{
|
||||
return [[UnityView alloc] initFromMainScreen];
|
||||
}
|
||||
|
||||
- (UIViewController*)createUnityViewControllerDefault
|
||||
{
|
||||
UnityViewControllerBase* ret = [AllocUnityDefaultViewController() init];
|
||||
ret.notificationDelegate = [[UnityViewControllerNotificationsDefaultSender alloc] init];
|
||||
|
||||
#if PLATFORM_TVOS
|
||||
ret.controllerUserInteractionEnabled = YES;
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if UNITY_SUPPORT_ROTATION
|
||||
- (UIViewController*)createUnityViewControllerForOrientation:(UIInterfaceOrientation)orient
|
||||
{
|
||||
UnityViewControllerBase* ret = [AllocUnitySingleOrientationViewController(orient) init];
|
||||
ret.notificationDelegate = [[UnityViewControllerNotificationsDefaultSender alloc] init];
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
- (UIViewController*)createRootViewController
|
||||
{
|
||||
UIViewController* ret = nil;
|
||||
if (!UNITY_SUPPORT_ROTATION || UnityShouldAutorotate())
|
||||
ret = [self createUnityViewControllerDefault];
|
||||
|
||||
#if UNITY_SUPPORT_ROTATION
|
||||
if (ret == nil)
|
||||
ret = [self createRootViewControllerForOrientation: ConvertToIosScreenOrientation((ScreenOrientation)UnityRequestedScreenOrientation())];
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (UIViewController*)topMostController
|
||||
{
|
||||
UIViewController *topController = self.window.rootViewController;
|
||||
while (topController.presentedViewController)
|
||||
topController = topController.presentedViewController;
|
||||
return topController;
|
||||
}
|
||||
|
||||
- (void)willStartWithViewController:(UIViewController*)controller
|
||||
{
|
||||
_unityView.contentScaleFactor = UnityScreenScaleFactor([UIScreen mainScreen]);
|
||||
_unityView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
|
||||
_rootController.view = _rootView = _unityView;
|
||||
}
|
||||
|
||||
- (void)willTransitionToViewController:(UIViewController*)toController fromViewController:(UIViewController*)fromController
|
||||
{
|
||||
}
|
||||
|
||||
- (void)didTransitionToViewController:(UIViewController*)toController fromViewController:(UIViewController*)fromController
|
||||
{
|
||||
#if UNITY_SUPPORT_ROTATION
|
||||
// when transitioning between view controllers ios will not send reorient events (because they are bound to controllers, not view)
|
||||
// so we imitate them here so unity view can update its size/orientation
|
||||
[_unityView willRotateToOrientation: UIViewControllerInterfaceOrientation(toController) fromOrientation: ConvertToIosScreenOrientation(_unityView.contentOrientation)];
|
||||
[_unityView didRotate];
|
||||
|
||||
// NB: this is both important and insane at the same time (that we have several places to keep current orentation and we need to sync them)
|
||||
_curOrientation = UIViewControllerInterfaceOrientation(toController);
|
||||
#endif
|
||||
}
|
||||
|
||||
- (UIView*)createSnapshotView
|
||||
{
|
||||
// Note that on iPads with iOS 9 or later (up to iOS 10.2 at least) there's a bug in the iOS compositor: any use of -[UIView snapshotViewAfterScreenUpdates]
|
||||
// causes black screen being shown temporarily when 4 finger gesture to swipe to another app in the task switcher is being performed slowly
|
||||
return [_rootView snapshotViewAfterScreenUpdates: YES];
|
||||
}
|
||||
|
||||
- (void)createUI
|
||||
{
|
||||
NSAssert(_unityView != nil, @"_unityView should be inited at this point");
|
||||
NSAssert(_window != nil, @"_window should be inited at this point");
|
||||
|
||||
_rootController = [self createRootViewController];
|
||||
|
||||
[self willStartWithViewController: _rootController];
|
||||
|
||||
NSAssert(_rootView != nil, @"_rootView should be inited at this point");
|
||||
NSAssert(_rootController != nil, @"_rootController should be inited at this point");
|
||||
|
||||
// We need to add the root view to the view hierarchy before initializing graphics,
|
||||
// as plugins might need to access view properties (e.g. safeAreaInsets). Otherwise,
|
||||
// they will get default values if the view is not yet added to the window.
|
||||
[_window addSubview: _rootView];
|
||||
|
||||
[UIView setAnimationsEnabled: NO];
|
||||
ShowSplashScreen(_window);
|
||||
// make window visible only after we have set up initial controller we want to show
|
||||
[_window makeKeyAndVisible];
|
||||
|
||||
#if UNITY_SUPPORT_ROTATION
|
||||
// to be able to query orientation from view controller we should actually show it.
|
||||
// at this point we can only show splash screen, so update app orientation after we started showing it
|
||||
// NB: _window.rootViewController = splash view controller (not _rootController)
|
||||
[self updateAppOrientation: ConvertToIosScreenOrientation(UIViewControllerOrientation(_window.rootViewController))];
|
||||
#endif
|
||||
|
||||
NSNumber* style = [[[NSBundle mainBundle] infoDictionary] objectForKey: @"Unity_LoadingActivityIndicatorStyle"];
|
||||
ShowActivityIndicator([SplashScreen Instance], style ? [style intValue] : -1);
|
||||
|
||||
NSNumber* vcControlled = [[[NSBundle mainBundle] infoDictionary] objectForKey: @"UIViewControllerBasedStatusBarAppearance"];
|
||||
if (vcControlled && ![vcControlled boolValue])
|
||||
printf_console("\nSetting UIViewControllerBasedStatusBarAppearance to NO is no longer supported.\n"
|
||||
"Apple actively discourages that, and all application-wide methods of changing status bar appearance are deprecated\n\n"
|
||||
);
|
||||
}
|
||||
|
||||
- (void)showGameUI
|
||||
{
|
||||
HideActivityIndicator();
|
||||
HideSplashScreen();
|
||||
|
||||
// make sure that we start up with correctly created/inited rendering surface
|
||||
// NB: recreateRenderingSurface won't go into rendering because _unityAppReady is false
|
||||
#if UNITY_SUPPORT_ROTATION
|
||||
[self checkOrientationRequest];
|
||||
#endif
|
||||
[_unityView recreateRenderingSurface];
|
||||
|
||||
// UI hierarchy
|
||||
_window.rootViewController = _rootController;
|
||||
[_window bringSubviewToFront: _rootView];
|
||||
|
||||
#if UNITY_SUPPORT_ROTATION
|
||||
// to be able to query orientation from view controller we should actually show it.
|
||||
// at this point we finally started to show game view controller. Just in case update orientation again
|
||||
[self updateAppOrientation: ConvertToIosScreenOrientation(UIViewControllerOrientation(_rootController))];
|
||||
#endif
|
||||
|
||||
// why we set level ready only now:
|
||||
// surface recreate will try to repaint if this var is set (poking unity to do it)
|
||||
// but this frame now is actually the first one we want to process/draw
|
||||
// so all the recreateSurface before now (triggered by reorientation) should simply change extents
|
||||
|
||||
_unityAppReady = true;
|
||||
|
||||
// why we skip present:
|
||||
// this will be the first frame to draw, so Start methods will be called
|
||||
// and we want to properly handle resolution request in Start (which might trigger surface recreate)
|
||||
// NB: we want to draw right after showing window, to avoid black frame creeping in
|
||||
|
||||
_skipPresent = true;
|
||||
|
||||
if (!UnityIsPaused())
|
||||
UnityRepaint();
|
||||
|
||||
_skipPresent = false;
|
||||
[self repaint];
|
||||
|
||||
[UIView setAnimationsEnabled: YES];
|
||||
}
|
||||
|
||||
- (void)transitionToViewController:(UIViewController*)vc
|
||||
{
|
||||
[self willTransitionToViewController: vc fromViewController: _rootController];
|
||||
|
||||
// first: remove from view hierarchy.
|
||||
// if we simply hide the window before assigning the new view controller, it will cause black frame flickering
|
||||
// on the other hand, hiding the window is important by itself to better signal the intent to iOS
|
||||
// e.g. unless we hide the window view, safeArea might stop working (due to bug in iOS if we're to speculate)
|
||||
// due to that we do this hide/unhide sequence: we want to to make it hidden, but still unhide it before changing window view controller.
|
||||
_window.hidden = YES;
|
||||
_window.hidden = NO;
|
||||
|
||||
_rootController.view = nil;
|
||||
_window.rootViewController = nil;
|
||||
|
||||
// second: assign new root controller (and view hierarchy with that), restore bounds
|
||||
// this is very important to first set _rootController, and only then window root controller
|
||||
// as the latter will poke [UIApplicationDelegate application:supportedInterfaceOrientationsForWindow:]
|
||||
// and unity implementation expects _rootController to be already set
|
||||
_window.rootViewController = _rootController = vc;
|
||||
_rootController.view = _rootView;
|
||||
|
||||
// CODE ARCHEOLOGY: in here we were tweaking window bounds to agree with screen bounds (and did some iOS8 specific workaround)
|
||||
// This is no longer needed it seems, and is actually harmful for the "split view" supporting apps
|
||||
// If you have fullscreen window, it will be automatically resized to take the whole screen
|
||||
// and otherwise we must not touch it, as it will be controlled by multitasking
|
||||
|
||||
// third: restore window as key and layout subviews to finalize size changes
|
||||
[_window makeKeyAndVisible];
|
||||
[_window layoutSubviews];
|
||||
|
||||
[self didTransitionToViewController: vc fromViewController: _rootController];
|
||||
}
|
||||
|
||||
#if UNITY_SUPPORT_ROTATION
|
||||
- (void)interfaceWillChangeOrientationTo:(UIInterfaceOrientation)toInterfaceOrientation
|
||||
{
|
||||
UIInterfaceOrientation fromInterfaceOrientation = _curOrientation;
|
||||
|
||||
_curOrientation = toInterfaceOrientation;
|
||||
[_unityView willRotateToOrientation: toInterfaceOrientation fromOrientation: fromInterfaceOrientation];
|
||||
}
|
||||
|
||||
- (void)interfaceDidChangeOrientationFrom:(UIInterfaceOrientation)fromInterfaceOrientation
|
||||
{
|
||||
[_unityView didRotate];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
- (void)notifyHideHomeButtonChange
|
||||
{
|
||||
#if PLATFORM_IOS
|
||||
// setNeedsUpdateOfHomeIndicatorAutoHidden is not implemented on iOS 11.0.
|
||||
// The bug has been fixed in iOS 11.0.1. See http://www.openradar.me/35127134
|
||||
if ([_rootController respondsToSelector: @selector(setNeedsUpdateOfHomeIndicatorAutoHidden)])
|
||||
[_rootController setNeedsUpdateOfHomeIndicatorAutoHidden];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)notifyDeferSystemGesturesChange
|
||||
{
|
||||
#if PLATFORM_IOS
|
||||
[_rootController setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#if UNITY_SUPPORT_ROTATION
|
||||
|
||||
@implementation UnityAppController (OrientationSupport)
|
||||
- (UIViewController*)createRootViewControllerForOrientation:(UIInterfaceOrientation)orientation
|
||||
{
|
||||
return [self createUnityViewControllerForOrientation: orientation];
|
||||
}
|
||||
|
||||
- (void)checkOrientationRequest
|
||||
{
|
||||
// if no orientation/allowed-orientation change - do nothing
|
||||
if (!UnityHasOrientationRequest() && !UnityShouldChangeAllowedOrientations())
|
||||
return;
|
||||
|
||||
// if there is a presentation controller, it takes over orientation control
|
||||
// in this case we should completely ignore all orientation changes
|
||||
// mind you, we just *delay* them, and they will be satisfied once presentation controller is dismissed
|
||||
// extra care like this is needed, because below we might recreate ViewController completely breaking
|
||||
// presentation controller dismissal
|
||||
if (_rootController.presentedViewController)
|
||||
return;
|
||||
|
||||
// normally we want to call attemptRotationToDeviceOrientation to tell iOS that we changed orientation constraints
|
||||
// but if the current orientation is disabled we need special processing, as iOS will simply ignore us
|
||||
// the only good/robust way is to simply recreate "autorotating" view controller and transition to it if needed
|
||||
|
||||
// please note that we want to trigger "orientation request" code path if we recreate autorotating view controller
|
||||
bool changeOrient = UnityHasOrientationRequest();
|
||||
|
||||
// if we should recreate autorotating view controller - see below
|
||||
bool shouldTransferToNewAutorotVC = false;
|
||||
|
||||
// first we check if we need to update orientations enabled for autorotation
|
||||
// this needs to be done *only* if we are to continue autorotating
|
||||
// otherwise we will transition from this view controller
|
||||
// and iOS will reread enabled orientations on next ViewController activation
|
||||
const bool autorot = UnityShouldAutorotate(), autorotChanged = UnityAutorotationStatusChanged();
|
||||
if (UnityShouldChangeAllowedOrientations() && autorot)
|
||||
{
|
||||
NSUInteger rootOrient = 1 << UIViewControllerInterfaceOrientation(self.rootViewController);
|
||||
if (!autorotChanged && (rootOrient & EnabledAutorotationInterfaceOrientations()))
|
||||
{
|
||||
// instead of querying unity for supported orientations, we keep them in the default (autorotating) controller
|
||||
// this is THE place where we should update those (otherwise, filled on creation)
|
||||
if ([self.rootViewController isKindOfClass: [UnityDefaultViewController class]])
|
||||
[(UnityDefaultViewController*)self.rootViewController updateSupportedOrientations];
|
||||
|
||||
// if we are currently autorotating AND changed allowed orientations while keeping current interface orientation allowed:
|
||||
// we can simply trigger attemptRotationToDeviceOrientation and we are done
|
||||
// please note that this can happen when current *device* orientation is disabled (and we want to enable it)
|
||||
[UIViewController attemptRotationToDeviceOrientation];
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise we recreate default autorotating view controller
|
||||
// to spell it out, we recreate if:
|
||||
// - we continue doing autorotation, but the current orientation is disabled
|
||||
// - we were not autorotating but start now
|
||||
shouldTransferToNewAutorotVC = true;
|
||||
changeOrient = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changeOrient)
|
||||
{
|
||||
// on some devices like iPhone XS layoutSubview is not called when transitioning from different orientations with the same resolution
|
||||
// therefore forcing layoutSubview on all orientation changes
|
||||
[_unityView setNeedsLayout];
|
||||
|
||||
if (autorot)
|
||||
{
|
||||
// just started autorotating or decided to recreate autorot controller above
|
||||
if (autorotChanged || shouldTransferToNewAutorotVC)
|
||||
[self transitionToViewController: [self createUnityViewControllerDefault]];
|
||||
[UIViewController attemptRotationToDeviceOrientation];
|
||||
}
|
||||
else
|
||||
{
|
||||
UIInterfaceOrientation requestedOrient = ConvertToIosScreenOrientation((ScreenOrientation)UnityRequestedScreenOrientation());
|
||||
// on one hand orientInterface: should be perfectly fine "reorienting" to current orientation
|
||||
// in reality, ios might be confused by transitionToViewController: shenanigans coupled with "nothing have changed actually"
|
||||
// as an example: prior to ios12 that might result in status bar going "bad" (becoming transparent)
|
||||
// NOTE: if we have switched from autorotation to fixed orientation, we must do the switch to pick new VC
|
||||
if (_curOrientation != requestedOrient || autorotChanged)
|
||||
[self orientInterface: requestedOrient];
|
||||
}
|
||||
}
|
||||
|
||||
UnityOrientationRequestWasCommitted();
|
||||
}
|
||||
|
||||
- (void)orientInterface:(UIInterfaceOrientation)orient
|
||||
{
|
||||
if (_unityAppReady)
|
||||
UnityFinishRendering();
|
||||
|
||||
[KeyboardDelegate StartReorientation];
|
||||
|
||||
[CATransaction begin];
|
||||
{
|
||||
UIInterfaceOrientation oldOrient = _curOrientation;
|
||||
UIInterfaceOrientation newOrient = orient;
|
||||
|
||||
[self interfaceWillChangeOrientationTo: newOrient];
|
||||
[self transitionToViewController: [self createRootViewControllerForOrientation: newOrient]];
|
||||
[self interfaceDidChangeOrientationFrom: oldOrient];
|
||||
|
||||
[UIApplication sharedApplication].statusBarOrientation = orient;
|
||||
}
|
||||
[CATransaction commit];
|
||||
|
||||
[KeyboardDelegate FinishReorientation];
|
||||
}
|
||||
|
||||
- (void)orientUnity:(UIInterfaceOrientation)orient
|
||||
{
|
||||
[self orientInterface: orient];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
||||
extern "C" void UnityNotifyHideHomeButtonChange()
|
||||
{
|
||||
[GetAppController() notifyHideHomeButtonChange];
|
||||
}
|
||||
|
||||
extern "C" void UnityNotifyDeferSystemGesturesChange()
|
||||
{
|
||||
[GetAppController() notifyDeferSystemGesturesChange];
|
||||
}
|
||||
264
Classes/UI/UnityView+Keyboard.mm
Normal file
264
Classes/UI/UnityView+Keyboard.mm
Normal file
@@ -0,0 +1,264 @@
|
||||
#import "UnityView.h"
|
||||
#include "UI/Keyboard.h"
|
||||
#include <sys/time.h>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
static NSArray* keyboardCommands = nil;
|
||||
|
||||
extern "C" int UnityGetAppleTVRemoteAllowExitToMenu();
|
||||
extern "C" void UnitySetAppleTVRemoteAllowExitToMenu(int val);
|
||||
|
||||
@implementation UnityView (Keyboard)
|
||||
|
||||
// Keyboard shortcuts don't provide events for key up
|
||||
// Keyboard shortcut callbacks are called with 0.4 (first time) and 0.1 (following times) seconds interval while pressing the key
|
||||
// Below we implement key expiration mechanism where key up event is generated if shortcut callback
|
||||
// is not called for specific key for more than <kKeyTimeoutInSeconds>
|
||||
|
||||
typedef std::map<int, double> KeyMap;
|
||||
static const double kKeyTimeoutInSeconds = 0.5;
|
||||
|
||||
static KeyMap& GetKeyMap()
|
||||
{
|
||||
static KeyMap s_Map;
|
||||
return s_Map;
|
||||
}
|
||||
|
||||
static double GetTimeInSeconds()
|
||||
{
|
||||
timeval now;
|
||||
gettimeofday(&now, NULL);
|
||||
|
||||
return now.tv_sec + now.tv_usec / 1000000.0;
|
||||
}
|
||||
|
||||
- (void)createKeyboard
|
||||
{
|
||||
// only English keyboard layout is supported
|
||||
NSString* baseLayout = @"1234567890-=qwertyuiop[]asdfghjkl;'\\`zxcvbnm,./!@#$%^&*()_+{}:\"|<>?~ \t\r\b\\";
|
||||
NSString* numpadLayout = @"1234567890-=*+/.\r";
|
||||
NSString* upperCaseLetters = @"QWERTYUIOPASDFGHJKLZXCVBNM";
|
||||
|
||||
size_t sizeOfKeyboardCommands = baseLayout.length + numpadLayout.length + upperCaseLetters.length + 11;
|
||||
NSMutableArray* commands = [NSMutableArray arrayWithCapacity: sizeOfKeyboardCommands];
|
||||
|
||||
void (^addKey)(NSString *keyName, UIKeyModifierFlags modifierFlags) = ^(NSString *keyName, UIKeyModifierFlags modifierFlags)
|
||||
{
|
||||
UIKeyCommand* command = [UIKeyCommand keyCommandWithInput: keyName modifierFlags: modifierFlags action: @selector(handleCommand:)];
|
||||
#if UNITY_HAS_IOSSDK_15_0
|
||||
if (@available(iOS 15.0, *))
|
||||
command.wantsPriorityOverSystemBehavior = YES;
|
||||
#endif
|
||||
[commands addObject: command];
|
||||
};
|
||||
|
||||
for (NSInteger i = 0; i < baseLayout.length; ++i)
|
||||
{
|
||||
NSString* input = [baseLayout substringWithRange: NSMakeRange(i, 1)];
|
||||
addKey(input, kNilOptions);
|
||||
}
|
||||
for (NSInteger i = 0; i < numpadLayout.length; ++i)
|
||||
{
|
||||
NSString* input = [numpadLayout substringWithRange: NSMakeRange(i, 1)];
|
||||
addKey(input, UIKeyModifierNumericPad);
|
||||
}
|
||||
for (NSInteger i = 0; i < upperCaseLetters.length; ++i)
|
||||
{
|
||||
NSString* input = [upperCaseLetters substringWithRange: NSMakeRange(i, 1)];
|
||||
addKey(input, UIKeyModifierShift);
|
||||
}
|
||||
|
||||
// pageUp, pageDown
|
||||
addKey(@"UIKeyInputPageUp", kNilOptions);
|
||||
addKey(@"UIKeyInputPageDown", kNilOptions);
|
||||
|
||||
// up, down, left, right, esc
|
||||
addKey(UIKeyInputUpArrow, kNilOptions);
|
||||
addKey(UIKeyInputDownArrow, kNilOptions);
|
||||
addKey(UIKeyInputLeftArrow, kNilOptions);
|
||||
addKey(UIKeyInputRightArrow, kNilOptions);
|
||||
addKey(UIKeyInputEscape, kNilOptions);
|
||||
|
||||
// caps Lock, shift, control, option, command
|
||||
addKey(@"", UIKeyModifierAlphaShift);
|
||||
addKey(@"", UIKeyModifierShift);
|
||||
addKey(@"", UIKeyModifierControl);
|
||||
addKey(@"", UIKeyModifierAlternate);
|
||||
addKey(@"", UIKeyModifierCommand);
|
||||
|
||||
keyboardCommands = commands.copy;
|
||||
}
|
||||
|
||||
- (NSArray*)keyCommands
|
||||
{
|
||||
//keyCommands take control of buttons over UITextView, that's why need to return nil if text input field is active or we have an external keyboard attached AND a first responder
|
||||
if ([[KeyboardDelegate Instance] status] == Visible || ([[KeyboardDelegate Instance] hasExternalKeyboard] && [self hasFirstResponderInHeirachy: UnityGetGLView()]))
|
||||
return nil;
|
||||
|
||||
if (keyboardCommands == nil)
|
||||
{
|
||||
[self createKeyboard];
|
||||
}
|
||||
return keyboardCommands;
|
||||
}
|
||||
|
||||
- (bool)hasFirstResponderInHeirachy:(UIView*)view
|
||||
{
|
||||
if (view.isFirstResponder)
|
||||
return true;
|
||||
|
||||
for (UIView* subview in view.subviews)
|
||||
{
|
||||
if ([self hasFirstResponderInHeirachy: subview])
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
- (bool)isValidCodeForButton:(int)code
|
||||
{
|
||||
return (code > 0 && code < 128);
|
||||
}
|
||||
|
||||
- (void)handleCommand:(UIKeyCommand *)command
|
||||
{
|
||||
NSString* input = command.input;
|
||||
UIKeyModifierFlags modifierFlags = command.modifierFlags;
|
||||
|
||||
char inputChar = ([input length] > 0) ? [input characterAtIndex: 0] : 0;
|
||||
int code = (int)inputChar; // ASCII code
|
||||
UnitySendKeyboardCommand(command);
|
||||
|
||||
if (![self isValidCodeForButton: code])
|
||||
{
|
||||
code = 0;
|
||||
}
|
||||
|
||||
if ((modifierFlags & UIKeyModifierAlphaShift) != 0)
|
||||
code = UnityStringToKey("caps lock");
|
||||
if ((modifierFlags & UIKeyModifierShift) != 0)
|
||||
code = UnityStringToKey("left shift");
|
||||
if ((modifierFlags & UIKeyModifierControl) != 0)
|
||||
code = UnityStringToKey("left ctrl");
|
||||
if ((modifierFlags & UIKeyModifierAlternate) != 0)
|
||||
code = UnityStringToKey("left alt");
|
||||
if ((modifierFlags & UIKeyModifierCommand) != 0)
|
||||
code = UnityStringToKey("left cmd");
|
||||
|
||||
if ((modifierFlags & UIKeyModifierNumericPad) != 0)
|
||||
{
|
||||
switch (inputChar)
|
||||
{
|
||||
case '0':
|
||||
code = UnityStringToKey("[0]");
|
||||
break;
|
||||
case '1':
|
||||
code = UnityStringToKey("[1]");
|
||||
break;
|
||||
case '2':
|
||||
code = UnityStringToKey("[2]");
|
||||
break;
|
||||
case '3':
|
||||
code = UnityStringToKey("[3]");
|
||||
break;
|
||||
case '4':
|
||||
code = UnityStringToKey("[4]");
|
||||
break;
|
||||
case '5':
|
||||
code = UnityStringToKey("[5]");
|
||||
break;
|
||||
case '6':
|
||||
code = UnityStringToKey("[6]");
|
||||
break;
|
||||
case '7':
|
||||
code = UnityStringToKey("[7]");
|
||||
break;
|
||||
case '8':
|
||||
code = UnityStringToKey("[8]");
|
||||
break;
|
||||
case '9':
|
||||
code = UnityStringToKey("[9]");
|
||||
break;
|
||||
case '-':
|
||||
code = UnityStringToKey("[-]");
|
||||
break;
|
||||
case '=':
|
||||
code = UnityStringToKey("equals");
|
||||
break;
|
||||
case '*':
|
||||
code = UnityStringToKey("[*]");
|
||||
break;
|
||||
case '+':
|
||||
code = UnityStringToKey("[+]");
|
||||
break;
|
||||
case '/':
|
||||
code = UnityStringToKey("[/]");
|
||||
break;
|
||||
case '.':
|
||||
code = UnityStringToKey("[.]");
|
||||
break;
|
||||
case '\r':
|
||||
code = UnityStringToKey("enter");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (input == UIKeyInputUpArrow)
|
||||
code = UnityStringToKey("up");
|
||||
else if (input == UIKeyInputDownArrow)
|
||||
code = UnityStringToKey("down");
|
||||
else if (input == UIKeyInputRightArrow)
|
||||
code = UnityStringToKey("right");
|
||||
else if (input == UIKeyInputLeftArrow)
|
||||
code = UnityStringToKey("left");
|
||||
else if (input == UIKeyInputEscape)
|
||||
code = UnityStringToKey("escape");
|
||||
else if ([input isEqualToString: @"UIKeyInputPageUp"])
|
||||
code = UnityStringToKey("page up");
|
||||
else if ([input isEqualToString: @"UIKeyInputPageDown"])
|
||||
code = UnityStringToKey("page down");
|
||||
|
||||
KeyMap::iterator item = GetKeyMap().find(code);
|
||||
if (item == GetKeyMap().end())
|
||||
{
|
||||
// New key is down, register it and its time
|
||||
UnitySetKeyboardKeyState(code, true);
|
||||
GetKeyMap()[code] = GetTimeInSeconds();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Still holding the key, update its time
|
||||
item->second = GetTimeInSeconds();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)processKeyboard
|
||||
{
|
||||
KeyMap& map = GetKeyMap();
|
||||
if (map.size() == 0)
|
||||
return;
|
||||
std::vector<int> keysToUnpress;
|
||||
double nowTime = GetTimeInSeconds();
|
||||
for (KeyMap::iterator item = map.begin();
|
||||
item != map.end();
|
||||
item++)
|
||||
{
|
||||
// Key has expired, register it for key up event
|
||||
if (nowTime - item->second > kKeyTimeoutInSeconds)
|
||||
keysToUnpress.push_back(item->first);
|
||||
}
|
||||
|
||||
for (std::vector<int>::iterator item = keysToUnpress.begin();
|
||||
item != keysToUnpress.end();
|
||||
item++)
|
||||
{
|
||||
map.erase(*item);
|
||||
UnitySetKeyboardKeyState(*item, false);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
15
Classes/UI/UnityView+iOS.h
Normal file
15
Classes/UI/UnityView+iOS.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
@interface UnityView (iOS)
|
||||
|
||||
// will simply update content orientation (it might be tweaked in layoutSubviews, due to disagreement between unity and view controller)
|
||||
- (void)willRotateToOrientation:(UIInterfaceOrientation)toOrientation fromOrientation:(UIInterfaceOrientation)fromOrientation;
|
||||
// will recreate gles backing if needed and repaint once to make sure we dont have black frame creeping in
|
||||
- (void)didRotate;
|
||||
|
||||
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event;
|
||||
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event;
|
||||
- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event;
|
||||
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event;
|
||||
|
||||
@end
|
||||
49
Classes/UI/UnityView+iOS.mm
Normal file
49
Classes/UI/UnityView+iOS.mm
Normal file
@@ -0,0 +1,49 @@
|
||||
#if PLATFORM_IOS
|
||||
|
||||
#import "UnityView.h"
|
||||
#import "UnityAppController+Rendering.h"
|
||||
#include "OrientationSupport.h"
|
||||
|
||||
extern bool _unityAppReady;
|
||||
|
||||
@interface UnityView ()
|
||||
@property (nonatomic, readwrite) ScreenOrientation contentOrientation;
|
||||
@end
|
||||
|
||||
@implementation UnityView (iOS)
|
||||
- (void)willRotateToOrientation:(UIInterfaceOrientation)toOrientation fromOrientation:(UIInterfaceOrientation)fromOrientation;
|
||||
{
|
||||
// to support the case of interface and unity content orientation being different
|
||||
// we will cheat a bit:
|
||||
// we will calculate transform between interface orientations and apply it to unity view orientation
|
||||
// you can still tweak unity view as you see fit in AppController, but this is what you want in 99% of cases
|
||||
|
||||
ScreenOrientation to = ConvertToUnityScreenOrientation(toOrientation);
|
||||
ScreenOrientation from = ConvertToUnityScreenOrientation(fromOrientation);
|
||||
|
||||
if (fromOrientation == UIInterfaceOrientationUnknown)
|
||||
_curOrientation = to;
|
||||
else
|
||||
_curOrientation = OrientationAfterTransform(_curOrientation, TransformBetweenOrientations(from, to));
|
||||
|
||||
_viewIsRotating = YES;
|
||||
}
|
||||
|
||||
- (void)didRotate
|
||||
{
|
||||
if (_shouldRecreateView)
|
||||
{
|
||||
[self recreateRenderingSurface];
|
||||
}
|
||||
|
||||
_viewIsRotating = NO;
|
||||
}
|
||||
|
||||
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { UnitySendTouchesBegin(touches, event); }
|
||||
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { UnitySendTouchesEnded(touches, event); }
|
||||
- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { UnitySendTouchesCancelled(touches, event); }
|
||||
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { UnitySendTouchesMoved(touches, event); }
|
||||
|
||||
@end
|
||||
|
||||
#endif // PLATFORM_IOS
|
||||
15
Classes/UI/UnityView+tvOS.h
Normal file
15
Classes/UI/UnityView+tvOS.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
@interface UnityView (tvOS)
|
||||
|
||||
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event;
|
||||
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event;
|
||||
- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event;
|
||||
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event;
|
||||
|
||||
#if UNITY_TVOS_SIMULATOR_FAKE_REMOTE
|
||||
- (void)pressesBegan:(NSSet<UIPress*>*)presses withEvent:(UIEvent*)event;
|
||||
- (void)pressesEnded:(NSSet<UIPress*>*)presses withEvent:(UIEvent*)event;
|
||||
#endif
|
||||
|
||||
@end
|
||||
66
Classes/UI/UnityView+tvOS.mm
Normal file
66
Classes/UI/UnityView+tvOS.mm
Normal file
@@ -0,0 +1,66 @@
|
||||
#if PLATFORM_TVOS
|
||||
|
||||
#import "UnityView.h"
|
||||
#include "iphone_Sensors.h"
|
||||
|
||||
@implementation UnityView (tvOS)
|
||||
|
||||
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
|
||||
{
|
||||
#if UNITY_TVOS_SIMULATOR_FAKE_REMOTE
|
||||
ReportSimulatedRemoteTouchesBegan(self, touches);
|
||||
#endif
|
||||
|
||||
if (UnityGetAppleTVRemoteTouchesEnabled())
|
||||
UnitySendTouchesBegin(touches, event);
|
||||
}
|
||||
|
||||
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
|
||||
{
|
||||
#if UNITY_TVOS_SIMULATOR_FAKE_REMOTE
|
||||
ReportSimulatedRemoteTouchesEnded(self, touches);
|
||||
#endif
|
||||
|
||||
if (UnityGetAppleTVRemoteTouchesEnabled())
|
||||
UnitySendTouchesEnded(touches, event);
|
||||
}
|
||||
|
||||
- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event
|
||||
{
|
||||
#if UNITY_TVOS_SIMULATOR_FAKE_REMOTE
|
||||
ReportSimulatedRemoteTouchesEnded(self, touches);
|
||||
#endif
|
||||
|
||||
if (UnityGetAppleTVRemoteTouchesEnabled())
|
||||
UnitySendTouchesCancelled(touches, event);
|
||||
}
|
||||
|
||||
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
|
||||
{
|
||||
#if UNITY_TVOS_SIMULATOR_FAKE_REMOTE
|
||||
ReportSimulatedRemoteTouchesMoved(self, touches);
|
||||
#endif
|
||||
|
||||
if (UnityGetAppleTVRemoteTouchesEnabled())
|
||||
UnitySendTouchesMoved(touches, event);
|
||||
}
|
||||
|
||||
#if UNITY_TVOS_SIMULATOR_FAKE_REMOTE
|
||||
- (void)pressesBegan:(NSSet<UIPress*>*)presses withEvent:(UIEvent*)event
|
||||
{
|
||||
for (UIPress *press in presses)
|
||||
ReportSimulatedRemoteButtonPress(press.type);
|
||||
}
|
||||
|
||||
- (void)pressesEnded:(NSSet<UIPress*>*)presses withEvent:(UIEvent*)event
|
||||
{
|
||||
for (UIPress *press in presses)
|
||||
ReportSimulatedRemoteButtonRelease(press.type);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#endif // PLATFORM_TVOS
|
||||
59
Classes/UI/UnityView.h
Normal file
59
Classes/UI/UnityView.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
@interface UnityRenderingView : UIView
|
||||
{
|
||||
}
|
||||
+ (void)InitializeForAPI:(UnityRenderingAPI)api;
|
||||
@end
|
||||
|
||||
@interface UnityView : UnityRenderingView
|
||||
{
|
||||
@private ScreenOrientation _curOrientation;
|
||||
@private BOOL _shouldRecreateView;
|
||||
@private BOOL _viewIsRotating;
|
||||
}
|
||||
|
||||
// we take scale factor into account because gl backbuffer size depends on it
|
||||
- (id)initWithFrame:(CGRect)frame scaleFactor:(CGFloat)scale;
|
||||
- (id)initWithFrame:(CGRect)frame;
|
||||
- (id)initFromMainScreen;
|
||||
|
||||
// in here we will go through subviews and call onUnityUpdateViewLayout selector (if present)
|
||||
// that allows to handle simple overlay child view layout without doing view controller magic
|
||||
- (void)layoutSubviews;
|
||||
|
||||
- (void)recreateRenderingSurfaceIfNeeded;
|
||||
- (void)recreateRenderingSurface;
|
||||
|
||||
// will match script-side Screen.orientation
|
||||
@property (nonatomic, readonly) ScreenOrientation contentOrientation;
|
||||
|
||||
@end
|
||||
|
||||
@interface UnityView (Deprecated)
|
||||
- (void)recreateGLESSurfaceIfNeeded __deprecated_msg("use recreateRenderingSurfaceIfNeeded instead.");
|
||||
- (void)recreateGLESSurface __deprecated_msg("use recreateRenderingSurface instead.");
|
||||
@end
|
||||
|
||||
@interface UnityView (Keyboard)
|
||||
- (void)processKeyboard;
|
||||
@end
|
||||
|
||||
@interface UnityView (UnityAppController)
|
||||
// if we know that unity view bounds have changed but need to update unity-side size/orientation immediately
|
||||
// otherwise the update will be delayed to next layoutSubviews
|
||||
- (void)boundsUpdated;
|
||||
@end
|
||||
|
||||
#if PLATFORM_IOS
|
||||
#include "UnityView+iOS.h"
|
||||
#elif PLATFORM_TVOS
|
||||
#include "UnityView+tvOS.h"
|
||||
#endif
|
||||
|
||||
void ReportSafeAreaChangeForView(UIView* view);
|
||||
|
||||
// Computes safe area for a view in Unity coordinate system (origin of the view
|
||||
// is bottom-left, as compared to standard top-left)
|
||||
CGRect ComputeSafeArea(UIView* view);
|
||||
CGSize GetCutoutToScreenRatio();
|
||||
329
Classes/UI/UnityView.mm
Normal file
329
Classes/UI/UnityView.mm
Normal file
@@ -0,0 +1,329 @@
|
||||
#include "UnityView.h"
|
||||
#include "UnityAppController.h"
|
||||
#include "UnityAppController+Rendering.h"
|
||||
#include "OrientationSupport.h"
|
||||
#include "Unity/DisplayManager.h"
|
||||
#include "Unity/UnityMetalSupport.h"
|
||||
#include "Unity/ObjCRuntime.h"
|
||||
|
||||
extern bool _renderingInited;
|
||||
extern bool _unityAppReady;
|
||||
extern bool _skipPresent;
|
||||
|
||||
@implementation UnityView
|
||||
{
|
||||
CGSize _surfaceSize;
|
||||
}
|
||||
|
||||
@synthesize contentOrientation = _curOrientation;
|
||||
|
||||
- (void)onUpdateSurfaceSize:(CGSize)size
|
||||
{
|
||||
_surfaceSize = size;
|
||||
|
||||
CGSize systemRenderSize = CGSizeMake(size.width * self.contentScaleFactor, size.height * self.contentScaleFactor);
|
||||
_curOrientation = (ScreenOrientation)UnityReportResizeView((unsigned)systemRenderSize.width, (unsigned)systemRenderSize.height, _curOrientation);
|
||||
ReportSafeAreaChangeForView(self);
|
||||
}
|
||||
|
||||
- (void)boundsUpdated
|
||||
{
|
||||
[self onUpdateSurfaceSize: self.bounds.size];
|
||||
}
|
||||
|
||||
- (void)initImpl:(CGRect)frame scaleFactor:(CGFloat)scale
|
||||
{
|
||||
#if !PLATFORM_TVOS
|
||||
self.multipleTouchEnabled = YES;
|
||||
self.exclusiveTouch = YES;
|
||||
#endif
|
||||
self.contentScaleFactor = scale;
|
||||
self.isAccessibilityElement = TRUE;
|
||||
self.accessibilityTraits = UIAccessibilityTraitAllowsDirectInteraction;
|
||||
|
||||
#if UNITY_TVOS
|
||||
_curOrientation = UNITY_TVOS_ORIENTATION;
|
||||
#endif
|
||||
|
||||
[self onUpdateSurfaceSize: frame.size];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame scaleFactor:(CGFloat)scale;
|
||||
{
|
||||
if ((self = [super initWithFrame: frame]))
|
||||
[self initImpl: frame scaleFactor: scale];
|
||||
return self;
|
||||
}
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if ((self = [super initWithFrame: frame]))
|
||||
[self initImpl: frame scaleFactor: 1.0f];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initFromMainScreen
|
||||
{
|
||||
CGRect frame = [UIScreen mainScreen].bounds;
|
||||
CGFloat scale = UnityScreenScaleFactor([UIScreen mainScreen]);
|
||||
if ((self = [super initWithFrame: frame]))
|
||||
[self initImpl: frame scaleFactor: scale];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
if (_surfaceSize.width != self.bounds.size.width || _surfaceSize.height != self.bounds.size.height)
|
||||
_shouldRecreateView = YES;
|
||||
[self onUpdateSurfaceSize: self.bounds.size];
|
||||
|
||||
for (UIView* subView in self.subviews)
|
||||
{
|
||||
if ([subView respondsToSelector: @selector(onUnityUpdateViewLayout)])
|
||||
[subView performSelector: @selector(onUnityUpdateViewLayout)];
|
||||
}
|
||||
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)safeAreaInsetsDidChange
|
||||
{
|
||||
ReportSafeAreaChangeForView(self);
|
||||
}
|
||||
|
||||
- (void)recreateRenderingSurfaceIfNeeded
|
||||
{
|
||||
float requestedContentScaleFactor = UnityScreenScaleFactor([UIScreen mainScreen]);
|
||||
if (abs(requestedContentScaleFactor - self.contentScaleFactor) > FLT_EPSILON)
|
||||
{
|
||||
self.contentScaleFactor = requestedContentScaleFactor;
|
||||
[self onUpdateSurfaceSize: self.bounds.size];
|
||||
}
|
||||
|
||||
unsigned requestedW, requestedH; UnityGetRenderingResolution(&requestedW, &requestedH);
|
||||
int requestedMSAA = UnityGetDesiredMSAASampleCount(1);
|
||||
int requestedSRGB = UnityGetSRGBRequested();
|
||||
int requestedWideColor = UnityGetWideColorRequested();
|
||||
int requestedHDR = UnityGetHDRModeRequested();
|
||||
int requestedMemorylessDepth = UnityMetalMemorylessDepth();
|
||||
|
||||
UnityDisplaySurfaceBase* surf = GetMainDisplaySurface();
|
||||
|
||||
if (_shouldRecreateView == YES
|
||||
|| surf->targetW != requestedW || surf->targetH != requestedH
|
||||
|| surf->disableDepthAndStencil != UnityDisableDepthAndStencilBuffers()
|
||||
|| surf->msaaSamples != requestedMSAA
|
||||
|| surf->srgb != requestedSRGB
|
||||
|| surf->wideColor != requestedWideColor
|
||||
|| surf->hdr != requestedHDR
|
||||
|| surf->memorylessDepth != requestedMemorylessDepth
|
||||
)
|
||||
{
|
||||
[self recreateRenderingSurface];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)recreateRenderingSurface
|
||||
{
|
||||
if (_renderingInited)
|
||||
{
|
||||
unsigned requestedW, requestedH;
|
||||
UnityGetRenderingResolution(&requestedW, &requestedH);
|
||||
|
||||
RenderingSurfaceParams params =
|
||||
{
|
||||
.msaaSampleCount = UnityGetDesiredMSAASampleCount(1),
|
||||
.renderW = (int)requestedW,
|
||||
.renderH = (int)requestedH,
|
||||
.srgb = UnityGetSRGBRequested(),
|
||||
.wideColor = UnityGetWideColorRequested(),
|
||||
.hdr = UnityGetHDRModeRequested(),
|
||||
.metalFramebufferOnly = UnityMetalFramebufferOnly(),
|
||||
.metalMemorylessDepth = UnityMetalMemorylessDepth(),
|
||||
.disableDepthAndStencil = UnityDisableDepthAndStencilBuffers(),
|
||||
.useCVTextureCache = 0,
|
||||
};
|
||||
|
||||
APP_CONTROLLER_RENDER_PLUGIN_METHOD_ARG(onBeforeMainDisplaySurfaceRecreate, ¶ms);
|
||||
[GetMainDisplay() recreateSurface: params];
|
||||
|
||||
// actually poke unity about updated back buffer and notify that extents were changed
|
||||
UnityReportBackbufferChange(GetMainDisplaySurface()->unityColorBuffer, GetMainDisplaySurface()->unityDepthBuffer);
|
||||
APP_CONTROLLER_RENDER_PLUGIN_METHOD(onAfterMainDisplaySurfaceRecreate);
|
||||
|
||||
if (_unityAppReady)
|
||||
{
|
||||
// seems like ios sometimes got confused about abrupt swap chain destroy
|
||||
// draw 2 times to fill "both" buffers (we assume double buffering)
|
||||
// present only once to make sure correct image goes to CA
|
||||
// if we are calling this from inside repaint, second draw and present will be done automatically
|
||||
// please note that we still need to pretend we did come from displaylink to make sure vsync magic works
|
||||
// NOTE: unity does handle "draw frame with exact same timestamp" just fine
|
||||
_skipPresent = true;
|
||||
if (!UnityIsPaused())
|
||||
{
|
||||
UnityDisplayLinkCallback(GetAppController().unityDisplayLink.timestamp);
|
||||
UnityRepaint();
|
||||
// we are not inside repaint so we need to draw second time ourselves
|
||||
if (_viewIsRotating)
|
||||
{
|
||||
UnityDisplayLinkCallback(GetAppController().unityDisplayLink.timestamp);
|
||||
UnityRepaint();
|
||||
}
|
||||
}
|
||||
_skipPresent = false;
|
||||
}
|
||||
}
|
||||
|
||||
_shouldRecreateView = NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UnityView (Deprecated)
|
||||
- (void)recreateGLESSurfaceIfNeeded { [self recreateRenderingSurfaceIfNeeded]; }
|
||||
- (void)recreateGLESSurface { [self recreateRenderingSurface]; }
|
||||
@end
|
||||
|
||||
static Class UnityRenderingView_LayerClassMTL(id self_, SEL _cmd)
|
||||
{
|
||||
return NSClassFromString(@"CAMetalLayer");
|
||||
}
|
||||
|
||||
static Class UnityRenderingView_LayerClassNULL(id self_, SEL _cmd)
|
||||
{
|
||||
return NSClassFromString(@"CALayer");
|
||||
}
|
||||
|
||||
@implementation UnityRenderingView
|
||||
+ (Class)layerClass
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (void)InitializeForAPI:(UnityRenderingAPI)api
|
||||
{
|
||||
IMP layerClassImpl = api == apiMetal ? (IMP)UnityRenderingView_LayerClassMTL : (IMP)UnityRenderingView_LayerClassNULL;
|
||||
class_replaceMethod(object_getClass([UnityRenderingView class]), @selector(layerClass), layerClassImpl, UIView_LayerClass_Enc);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
void ReportSafeAreaChangeForView(UIView* view)
|
||||
{
|
||||
CGRect safeArea = ComputeSafeArea(view);
|
||||
UnityReportSafeAreaChange(safeArea.origin.x, safeArea.origin.y,
|
||||
safeArea.size.width, safeArea.size.height);
|
||||
|
||||
if (UnityDeviceHasCutout())
|
||||
{
|
||||
CGSize cutoutSizeRatio = GetCutoutToScreenRatio();
|
||||
|
||||
if (!CGSizeEqualToSize(CGSizeZero, cutoutSizeRatio))
|
||||
{
|
||||
const float w = ([UIScreen mainScreen].nativeBounds.size.width * cutoutSizeRatio.width);
|
||||
const float h = ([UIScreen mainScreen].nativeBounds.size.height * cutoutSizeRatio.height);
|
||||
|
||||
// Apple's cutouts are currently centred on the horizontal, and stuck to the top of the vertical, hence this positioning.
|
||||
const float x = (([UIScreen mainScreen].nativeBounds.size.width - w) / 2);
|
||||
const float y = ([UIScreen mainScreen].nativeBounds.size.height - h);
|
||||
UnityReportDisplayCutouts(&x, &y, &w, &h, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
UnityReportDisplayCutouts(nullptr, nullptr, nullptr, nullptr, 0);
|
||||
}
|
||||
|
||||
CGRect ComputeSafeArea(UIView* view)
|
||||
{
|
||||
CGSize screenSize = view.bounds.size;
|
||||
CGRect screenRect = CGRectMake(0, 0, screenSize.width, screenSize.height);
|
||||
|
||||
UIEdgeInsets insets = [view safeAreaInsets];
|
||||
float insetLeft = insets.left, insetBottom = insets.bottom;
|
||||
float insetWidth = insetLeft + insets.right, insetHeight = insetBottom + insets.top;
|
||||
|
||||
#if PLATFORM_IOS
|
||||
// pre-iOS 15 there is a bug with safeAreaInsets when coupled with the way unity handles forced orientation
|
||||
// when we create/show new ViewController with fixed orientation, safeAreaInsets include status bar always
|
||||
// alas, we did not find a good way to work around that (this can be seen even in View Debugging: Safe Area would have status bar accounted for)
|
||||
// we know for sure that status bar height is 20 (at least on ios16 or older), so we can check if the safe area
|
||||
// includes inset of this size while status bar should be hidden, resetting vertical insets in this case
|
||||
if (@available(iOS 15, *))
|
||||
{
|
||||
// everything works as expected
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isStatusBarHidden = false;
|
||||
#if UNITY_HAS_IOSSDK_13_0
|
||||
if (@available(iOS 13, *))
|
||||
{
|
||||
isStatusBarHidden = view.window.windowScene.statusBarManager.statusBarHidden;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
isStatusBarHidden = [UIApplication sharedApplication].statusBarHidden;
|
||||
}
|
||||
|
||||
if (isStatusBarHidden && fabsf(insetHeight - 20) < 1e-6f)
|
||||
insetHeight = insetBottom = 0.0f;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Unity uses bottom left as the origin
|
||||
screenRect = CGRectOffset(screenRect, insetLeft, insetBottom);
|
||||
screenRect.size.width -= insetWidth;
|
||||
screenRect.size.height -= insetHeight;
|
||||
|
||||
const float scale = view.contentScaleFactor;
|
||||
|
||||
// Truncate safe area size because in some cases (for example when Display zoom is turned on)
|
||||
// it might become larger than Screen.width/height which are returned as ints.
|
||||
screenRect.origin.x = (unsigned)(screenRect.origin.x * scale);
|
||||
screenRect.origin.y = (unsigned)(screenRect.origin.y * scale);
|
||||
screenRect.size.width = (unsigned)(screenRect.size.width * scale);
|
||||
screenRect.size.height = (unsigned)(screenRect.size.height * scale);
|
||||
|
||||
return screenRect;
|
||||
}
|
||||
|
||||
// Apple does not provide the cutout width and height in points/pixels. They *do* however list the
|
||||
// size of the cutout and screen in mm for accessory makers. We can use this information to calculate the percentage of the screen is cutout.
|
||||
// This information can be found here - https://developer.apple.com/accessories/Accessory-Design-Guidelines.pdf
|
||||
CGSize GetCutoutToScreenRatio()
|
||||
{
|
||||
switch (UnityDeviceGeneration())
|
||||
{
|
||||
case deviceiPhone13ProMax:
|
||||
return CGSizeMake(0.373, 0.036);
|
||||
case deviceiPhone13Pro:
|
||||
case deviceiPhone13:
|
||||
return CGSizeMake(0.4148, 0.0399);
|
||||
case deviceiPhone13Mini:
|
||||
return CGSizeMake(0.4644, 0.0462);
|
||||
case deviceiPhone12ProMax:
|
||||
return CGSizeMake(0.4897, 0.0346);
|
||||
case deviceiPhone12Pro:
|
||||
case deviceiPhone12:
|
||||
return CGSizeMake(0.5393, 0.0379);
|
||||
case deviceiPhone12Mini:
|
||||
return CGSizeMake(0.604, 0.0424);
|
||||
case deviceiPhone11ProMax:
|
||||
return CGSizeMake(0.5057, 0.0335);
|
||||
case deviceiPhone11Pro:
|
||||
return CGSizeMake(0.5583, 0.037);
|
||||
case deviceiPhone11:
|
||||
case deviceiPhoneXR:
|
||||
return CGSizeMake(0.5568, 0.0398);
|
||||
case deviceiPhoneXSMax:
|
||||
return CGSizeMake(0.4884, 0.0333);
|
||||
case deviceiPhoneX:
|
||||
case deviceiPhoneXS:
|
||||
return CGSizeMake(0.5391, 0.0368);
|
||||
default:
|
||||
NSCAssert(!UnityDeviceHasCutout(), @"Device has a cutout, but no ratio has been added for it.");
|
||||
return CGSizeZero;
|
||||
}
|
||||
}
|
||||
45
Classes/UI/UnityViewControllerBase+iOS.h
Normal file
45
Classes/UI/UnityViewControllerBase+iOS.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
@interface UnityViewControllerBase (iOS)
|
||||
- (BOOL)shouldAutorotate;
|
||||
|
||||
- (BOOL)prefersStatusBarHidden;
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle;
|
||||
@end
|
||||
|
||||
// for better handling of user-imposed screen orientation we will have specific ViewController implementations
|
||||
|
||||
// view controllers constrained to one orientation
|
||||
|
||||
@interface UnityPortraitOnlyViewController : UnityViewControllerBase
|
||||
{
|
||||
}
|
||||
@end
|
||||
@interface UnityPortraitUpsideDownOnlyViewController : UnityViewControllerBase
|
||||
{
|
||||
}
|
||||
@end
|
||||
@interface UnityLandscapeLeftOnlyViewController : UnityViewControllerBase
|
||||
{
|
||||
}
|
||||
@end
|
||||
@interface UnityLandscapeRightOnlyViewController : UnityViewControllerBase
|
||||
{
|
||||
}
|
||||
@end
|
||||
|
||||
// this is default view controller implementation (autorotation enabled)
|
||||
|
||||
@interface UnityDefaultViewController : UnityViewControllerBase
|
||||
{
|
||||
}
|
||||
|
||||
// we have well defined points where we should update supported orientations:
|
||||
// on init and inside [UnityAppController checkOrientationRequest]
|
||||
// note that the latter will recreate default view controller if supported orientations conflict with the current orientation
|
||||
// this is done as opposed to [UnityDefaultViewController supportedInterfaceOrientations] poking unity for that
|
||||
// as this might happen in "random" places, out-of-sync with our handling of "orientation constraints were changed at unity side"
|
||||
- (void)updateSupportedOrientations;
|
||||
@end
|
||||
|
||||
NSUInteger EnabledAutorotationInterfaceOrientations();
|
||||
239
Classes/UI/UnityViewControllerBase+iOS.mm
Normal file
239
Classes/UI/UnityViewControllerBase+iOS.mm
Normal file
@@ -0,0 +1,239 @@
|
||||
#if PLATFORM_IOS
|
||||
|
||||
#import "UnityViewControllerBase.h"
|
||||
#import "UnityAppController.h"
|
||||
|
||||
#include "OrientationSupport.h"
|
||||
#include "Keyboard.h"
|
||||
#include "UnityView.h"
|
||||
#include "PluginBase/UnityViewControllerListener.h"
|
||||
#include "UnityAppController.h"
|
||||
#include "UnityAppController+ViewHandling.h"
|
||||
#include "Unity/ObjCRuntime.h"
|
||||
|
||||
// when returning from presenting UIViewController we might need to update app orientation to "correct" one, as we wont get rotation notification
|
||||
@interface UnityAppController ()
|
||||
- (void)updateAppOrientation:(UIInterfaceOrientation)orientation;
|
||||
@end
|
||||
|
||||
|
||||
@implementation UnityViewControllerBase (iOS)
|
||||
|
||||
- (BOOL)shouldAutorotate
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)prefersStatusBarHidden
|
||||
{
|
||||
static bool _PrefersStatusBarHidden = true;
|
||||
|
||||
static bool _PrefersStatusBarHiddenInited = false;
|
||||
if (!_PrefersStatusBarHiddenInited)
|
||||
{
|
||||
NSNumber* hidden = [[[NSBundle mainBundle] infoDictionary] objectForKey: @"UIStatusBarHidden"];
|
||||
_PrefersStatusBarHidden = hidden ? [hidden boolValue] : YES;
|
||||
|
||||
_PrefersStatusBarHiddenInited = true;
|
||||
}
|
||||
return _PrefersStatusBarHidden;
|
||||
}
|
||||
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle
|
||||
{
|
||||
static UIStatusBarStyle _PreferredStatusBarStyle = UIStatusBarStyleDefault;
|
||||
|
||||
static bool _PreferredStatusBarStyleInited = false;
|
||||
if (!_PreferredStatusBarStyleInited)
|
||||
{
|
||||
NSString* style = [[[NSBundle mainBundle] infoDictionary] objectForKey: @"UIStatusBarStyle"];
|
||||
if (style && [style isEqualToString: @"UIStatusBarStyleLightContent"])
|
||||
_PreferredStatusBarStyle = UIStatusBarStyleLightContent;
|
||||
|
||||
_PreferredStatusBarStyleInited = true;
|
||||
}
|
||||
|
||||
return _PreferredStatusBarStyle;
|
||||
}
|
||||
|
||||
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures
|
||||
{
|
||||
UIRectEdge res = UIRectEdgeNone;
|
||||
if (UnityGetDeferSystemGesturesTopEdge())
|
||||
res |= UIRectEdgeTop;
|
||||
if (UnityGetDeferSystemGesturesBottomEdge())
|
||||
res |= UIRectEdgeBottom;
|
||||
if (UnityGetDeferSystemGesturesLeftEdge())
|
||||
res |= UIRectEdgeLeft;
|
||||
if (UnityGetDeferSystemGesturesRightEdge())
|
||||
res |= UIRectEdgeRight;
|
||||
return res;
|
||||
}
|
||||
|
||||
- (BOOL)prefersHomeIndicatorAutoHidden
|
||||
{
|
||||
return UnityGetHideHomeButton();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UnityDefaultViewController
|
||||
|
||||
// these will be updated in one place where we "sync" UI side orientation handling to unity side
|
||||
NSUInteger _supportedOrientations;
|
||||
// this will be updated either in viewDidAppear: (when we "start" in a given orient) or in viewWillTransitionToSize: (when we change orient)
|
||||
ScreenOrientation _currentOrientation;
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
NSAssert(UnityShouldAutorotate(), @"UnityDefaultViewController should be used only if unity is set to autorotate");
|
||||
_supportedOrientations = EnabledAutorotationInterfaceOrientations();
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)updateSupportedOrientations
|
||||
{
|
||||
_supportedOrientations = EnabledAutorotationInterfaceOrientations();
|
||||
}
|
||||
|
||||
- (NSUInteger)supportedInterfaceOrientations
|
||||
{
|
||||
return _supportedOrientations;
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated
|
||||
{
|
||||
_currentOrientation = UIViewControllerOrientation(self);
|
||||
[GetAppController() updateAppOrientation: ConvertToIosScreenOrientation(_currentOrientation)];
|
||||
[super viewDidAppear: animated];
|
||||
}
|
||||
|
||||
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
|
||||
{
|
||||
// CODE ARCHEOLOGY: we were using UIViewControllerOrientation, but on showing view with "Requires full screen"
|
||||
// CODE ARCHEOLOGY: we will get the size/orientation *already* set, and the rotation logic would break
|
||||
const ScreenOrientation curOrient = _currentOrientation;
|
||||
const ScreenOrientation newOrient = OrientationAfterTransform(curOrient, [coordinator targetTransform]);
|
||||
_currentOrientation = newOrient;
|
||||
|
||||
// in case of presentation controller it will take control over orientations
|
||||
// so to avoid crazy corner cases, make default view controller to ignore "wrong" orientations
|
||||
// as they will come only in case of presentation view controller and will be reverted anyway
|
||||
// NB: we still want to pass message to super, we just want to skip unity-specific magic
|
||||
NSUInteger targetMask = 1 << ConvertToIosScreenOrientation(newOrient);
|
||||
if (([self supportedInterfaceOrientations] & targetMask) != 0)
|
||||
{
|
||||
[UIView setAnimationsEnabled: UnityUseAnimatedAutorotation() ? YES : NO];
|
||||
[KeyboardDelegate StartReorientation];
|
||||
|
||||
[GetAppController() interfaceWillChangeOrientationTo: ConvertToIosScreenOrientation(newOrient)];
|
||||
|
||||
[coordinator animateAlongsideTransition: nil completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
|
||||
[self.view setNeedsLayout];
|
||||
[GetAppController() interfaceDidChangeOrientationFrom: ConvertToIosScreenOrientation(curOrient)];
|
||||
|
||||
[KeyboardDelegate FinishReorientation];
|
||||
[UIView setAnimationsEnabled: YES];
|
||||
}];
|
||||
}
|
||||
[super viewWillTransitionToSize: size withTransitionCoordinator: coordinator];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UnityPortraitOnlyViewController
|
||||
- (NSUInteger)supportedInterfaceOrientations
|
||||
{
|
||||
return 1 << UIInterfaceOrientationPortrait;
|
||||
}
|
||||
|
||||
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
|
||||
{
|
||||
return UIInterfaceOrientationPortrait;
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[GetAppController() updateAppOrientation: UIInterfaceOrientationPortrait];
|
||||
[super viewWillAppear: animated];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UnityPortraitUpsideDownOnlyViewController
|
||||
- (NSUInteger)supportedInterfaceOrientations
|
||||
{
|
||||
return 1 << UIInterfaceOrientationPortraitUpsideDown;
|
||||
}
|
||||
|
||||
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
|
||||
{
|
||||
return UIInterfaceOrientationPortraitUpsideDown;
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[GetAppController() updateAppOrientation: UIInterfaceOrientationPortraitUpsideDown];
|
||||
[super viewWillAppear: animated];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UnityLandscapeLeftOnlyViewController
|
||||
- (NSUInteger)supportedInterfaceOrientations
|
||||
{
|
||||
return 1 << UIInterfaceOrientationLandscapeLeft;
|
||||
}
|
||||
|
||||
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
|
||||
{
|
||||
return UIInterfaceOrientationLandscapeLeft;
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[GetAppController() updateAppOrientation: UIInterfaceOrientationLandscapeLeft];
|
||||
[super viewWillAppear: animated];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UnityLandscapeRightOnlyViewController
|
||||
- (NSUInteger)supportedInterfaceOrientations
|
||||
{
|
||||
return 1 << UIInterfaceOrientationLandscapeRight;
|
||||
}
|
||||
|
||||
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
|
||||
{
|
||||
return UIInterfaceOrientationLandscapeRight;
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[GetAppController() updateAppOrientation: UIInterfaceOrientationLandscapeRight];
|
||||
[super viewWillAppear: animated];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NSUInteger EnabledAutorotationInterfaceOrientations()
|
||||
{
|
||||
NSUInteger ret = 0;
|
||||
|
||||
if (UnityIsOrientationEnabled(portrait))
|
||||
ret |= (1 << UIInterfaceOrientationPortrait);
|
||||
if (UnityDeviceSupportsUpsideDown() && UnityIsOrientationEnabled(portraitUpsideDown))
|
||||
ret |= (1 << UIInterfaceOrientationPortraitUpsideDown);
|
||||
if (UnityIsOrientationEnabled(landscapeLeft))
|
||||
ret |= (1 << UIInterfaceOrientationLandscapeRight);
|
||||
if (UnityIsOrientationEnabled(landscapeRight))
|
||||
ret |= (1 << UIInterfaceOrientationLandscapeLeft);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif // PLATFORM_IOS
|
||||
7
Classes/UI/UnityViewControllerBase+tvOS.h
Normal file
7
Classes/UI/UnityViewControllerBase+tvOS.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
//for tvOS we need just one view controller subclass as there is no screen orientation here
|
||||
@interface UnityDefaultViewController : UnityViewControllerBase
|
||||
{
|
||||
}
|
||||
@end
|
||||
9
Classes/UI/UnityViewControllerBase+tvOS.mm
Normal file
9
Classes/UI/UnityViewControllerBase+tvOS.mm
Normal file
@@ -0,0 +1,9 @@
|
||||
#if PLATFORM_TVOS
|
||||
|
||||
#import "UnityViewControllerBase.h"
|
||||
#import "UnityAppController.h"
|
||||
|
||||
@implementation UnityDefaultViewController
|
||||
@end
|
||||
|
||||
#endif // PLATFORM_TVOS
|
||||
40
Classes/UI/UnityViewControllerBase.h
Normal file
40
Classes/UI/UnityViewControllerBase.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "PluginBase/UnityViewControllerListener.h"
|
||||
|
||||
#if PLATFORM_IOS
|
||||
#define UNITY_VIEW_CONTROLLER_BASE_CLASS UIViewController
|
||||
#elif PLATFORM_TVOS
|
||||
#import <GameController/GCController.h>
|
||||
#define UNITY_VIEW_CONTROLLER_BASE_CLASS GCEventViewController
|
||||
#endif
|
||||
|
||||
@interface UnityViewControllerBase : UNITY_VIEW_CONTROLLER_BASE_CLASS
|
||||
{
|
||||
id<UnityViewControllerNotifications> _notificationDelegate;
|
||||
}
|
||||
- (void)viewWillLayoutSubviews;
|
||||
- (void)viewDidLayoutSubviews;
|
||||
- (void)viewDidDisappear:(BOOL)animated;
|
||||
- (void)viewWillDisappear:(BOOL)animated;
|
||||
- (void)viewDidAppear:(BOOL)animated;
|
||||
- (void)viewWillAppear:(BOOL)animated;
|
||||
|
||||
@property (nonatomic, retain) id<UnityViewControllerNotifications> notificationDelegate;
|
||||
|
||||
@end
|
||||
|
||||
#if PLATFORM_IOS
|
||||
#include "UnityViewControllerBase+iOS.h"
|
||||
#elif PLATFORM_TVOS
|
||||
#include "UnityViewControllerBase+tvOS.h"
|
||||
#endif
|
||||
|
||||
// this should be used to create view controller that plays nicely with unity and account for player settings
|
||||
UnityViewControllerBase* AllocUnityViewController(void);
|
||||
|
||||
UnityViewControllerBase* AllocUnityDefaultViewController(void);
|
||||
#if UNITY_SUPPORT_ROTATION
|
||||
UnityViewControllerBase* AllocUnitySingleOrientationViewController(UIInterfaceOrientation orient);
|
||||
#endif
|
||||
90
Classes/UI/UnityViewControllerBase.mm
Normal file
90
Classes/UI/UnityViewControllerBase.mm
Normal file
@@ -0,0 +1,90 @@
|
||||
#import "UnityViewControllerBase.h"
|
||||
#import "UnityAppController.h"
|
||||
#import "UnityAppController+ViewHandling.h"
|
||||
|
||||
#include "OrientationSupport.h"
|
||||
|
||||
|
||||
@implementation UnityViewControllerBase
|
||||
|
||||
@synthesize notificationDelegate = _notificationDelegate;
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
self.modalPresentationStyle = UIModalPresentationFullScreen;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewWillLayoutSubviews
|
||||
{
|
||||
[super viewWillLayoutSubviews];
|
||||
[_notificationDelegate onViewWillLayoutSubviews];
|
||||
}
|
||||
|
||||
- (void)viewDidLayoutSubviews
|
||||
{
|
||||
[super viewDidLayoutSubviews];
|
||||
[_notificationDelegate onViewDidLayoutSubviews];
|
||||
}
|
||||
|
||||
- (void)viewDidDisappear:(BOOL)animated
|
||||
{
|
||||
[super viewDidDisappear: animated];
|
||||
[_notificationDelegate onViewDidDisappear: animated];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated
|
||||
{
|
||||
[super viewWillDisappear: animated];
|
||||
[_notificationDelegate onViewWillDisappear: animated];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated
|
||||
{
|
||||
[super viewDidAppear: animated];
|
||||
[_notificationDelegate onViewDidAppear: animated];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear: animated];
|
||||
[_notificationDelegate onViewWillAppear: animated];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
UnityViewControllerBase* AllocUnityDefaultViewController()
|
||||
{
|
||||
return [UnityDefaultViewController alloc];
|
||||
}
|
||||
|
||||
#if UNITY_SUPPORT_ROTATION
|
||||
UnityViewControllerBase* AllocUnitySingleOrientationViewController(UIInterfaceOrientation orient)
|
||||
{
|
||||
switch (orient)
|
||||
{
|
||||
case UIInterfaceOrientationPortrait: return [UnityPortraitOnlyViewController alloc];
|
||||
case UIInterfaceOrientationPortraitUpsideDown: return [UnityPortraitUpsideDownOnlyViewController alloc];
|
||||
case UIInterfaceOrientationLandscapeLeft: return [UnityLandscapeLeftOnlyViewController alloc];
|
||||
case UIInterfaceOrientationLandscapeRight: return [UnityLandscapeRightOnlyViewController alloc];
|
||||
|
||||
default: assert(false && "bad UIInterfaceOrientation provided");
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
UnityViewControllerBase* AllocUnityViewController()
|
||||
{
|
||||
#if UNITY_SUPPORT_ROTATION
|
||||
if (UnityShouldAutorotate())
|
||||
return AllocUnityDefaultViewController();
|
||||
|
||||
UIInterfaceOrientation orient = ConvertToIosScreenOrientation((ScreenOrientation)UnityRequestedScreenOrientation());
|
||||
return AllocUnitySingleOrientationViewController(orient);
|
||||
#else
|
||||
return AllocUnityDefaultViewController();
|
||||
#endif
|
||||
}
|
||||
Reference in New Issue
Block a user