[add] first

This commit is contained in:
2023-10-08 10:24:48 +08:00
commit b1ae0510a9
1048 changed files with 3254361 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
#pragma once
void ShowActivityIndicator(UIView* parent, int style);
void ShowActivityIndicator(UIView* parent);
void HideActivityIndicator();

View 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
View 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

File diff suppressed because it is too large Load Diff

View 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);

View 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
View 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
View 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
View 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

View 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

View 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];
}

View 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

View 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

View 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

View 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

View 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
View 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
View 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, &params);
[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;
}
}

View 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();

View 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

View 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

View File

@@ -0,0 +1,9 @@
#if PLATFORM_TVOS
#import "UnityViewControllerBase.h"
#import "UnityAppController.h"
@implementation UnityDefaultViewController
@end
#endif // PLATFORM_TVOS

View 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

View 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
}