Initial commit.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
void ShowActivityIndicator(UIView* parent, int style);
|
||||
void ShowActivityIndicator(UIView* parent);
|
||||
void HideActivityIndicator();
|
||||
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct
|
||||
{
|
||||
const char* text;
|
||||
const char* placeholder;
|
||||
|
||||
UIKeyboardType keyboardType;
|
||||
UITextAutocorrectionType autocorrectionType;
|
||||
UIKeyboardAppearance appearance;
|
||||
|
||||
BOOL multiline;
|
||||
BOOL secure;
|
||||
int characterLimit;
|
||||
}
|
||||
KeyboardShowParam;
|
||||
|
||||
|
||||
@interface KeyboardDelegate : NSObject<UITextFieldDelegate, UITextViewDelegate>
|
||||
{
|
||||
}
|
||||
- (BOOL)textFieldShouldReturn:(UITextField*)textField;
|
||||
- (void)textInputDone:(id)sender;
|
||||
- (void)textInputCancel:(id)sender;
|
||||
- (void)textInputLostFocus;
|
||||
- (void)keyboardWillShow:(NSNotification*)notification;
|
||||
- (void)keyboardDidShow:(NSNotification*)notification;
|
||||
- (void)keyboardWillHide:(NSNotification*)notification;
|
||||
|
||||
// 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;
|
||||
|
||||
- (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;
|
||||
|
||||
@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;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,861 @@
|
||||
#include "Keyboard.h"
|
||||
#include "DisplayManager.h"
|
||||
#include "UnityAppController.h"
|
||||
#include "UnityForwardDecls.h"
|
||||
#include <string>
|
||||
|
||||
#ifndef FILTER_EMOJIS_IOS_KEYBOARD
|
||||
#define FILTER_EMOJIS_IOS_KEYBOARD 0
|
||||
#endif
|
||||
|
||||
|
||||
static KeyboardDelegate* _keyboard = nil;
|
||||
|
||||
static bool _shouldHideInput = false;
|
||||
static bool _shouldHideInputChanged = false;
|
||||
static const unsigned kToolBarHeight = 40;
|
||||
static const unsigned kSystemButtonsSpace = 2 * 60 + 3 * 18; // empirical value, there is no way to know the exact widths of the system bar buttons
|
||||
|
||||
@implementation KeyboardDelegate
|
||||
{
|
||||
// UI handling
|
||||
// in case of single line we use UITextField inside UIToolbar
|
||||
// in case of multi-line input we use UITextView with UIToolbar as accessory view
|
||||
// toolbar buttons are kept around to prevent releasing them
|
||||
// tvOS does not support multiline input thus only UITextField option is implemented
|
||||
#if PLATFORM_IOS
|
||||
UITextView* textView;
|
||||
|
||||
UIToolbar* viewToolbar;
|
||||
NSArray* viewToolbarItems;
|
||||
|
||||
NSLayoutConstraint* widthConstraint;
|
||||
#endif
|
||||
|
||||
UITextField* textField;
|
||||
|
||||
// keep toolbar items for both single- and multi- line edit in NSArray to make sure they are kept around
|
||||
#if PLATFORM_IOS
|
||||
UIToolbar* fieldToolbar;
|
||||
NSArray* fieldToolbarItems;
|
||||
#endif
|
||||
|
||||
// inputView is view used for actual input (it will be responder): UITextField [single-line] or UITextView [multi-line]
|
||||
// editView is the "root" view for keyboard: UIToolbar [single-line] or UITextView [multi-line]
|
||||
UIView* inputView;
|
||||
UIView* editView;
|
||||
KeyboardShowParam cachedKeyboardParam;
|
||||
|
||||
CGRect _area;
|
||||
NSString* initialText;
|
||||
|
||||
UIKeyboardType keyboardType;
|
||||
|
||||
BOOL _multiline;
|
||||
BOOL _inputHidden;
|
||||
BOOL _active;
|
||||
KeyboardStatus _status;
|
||||
int _characterLimit;
|
||||
|
||||
// not pretty but seems like easiest way to keep "we are rotating" status
|
||||
BOOL _rotating;
|
||||
}
|
||||
|
||||
@synthesize area;
|
||||
@synthesize active = _active;
|
||||
@synthesize status = _status;
|
||||
@synthesize text;
|
||||
@synthesize selection;
|
||||
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(UITextField*)textFieldObj
|
||||
{
|
||||
[self textInputDone: nil];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)textInputDone:(id)sender
|
||||
{
|
||||
if (_status == Visible)
|
||||
_status = Done;
|
||||
[self hide];
|
||||
}
|
||||
|
||||
- (void)textInputCancel:(id)sender
|
||||
{
|
||||
_status = Canceled;
|
||||
[self hide];
|
||||
}
|
||||
|
||||
- (void)textInputLostFocus
|
||||
{
|
||||
if (_status == Visible)
|
||||
_status = LostFocus;
|
||||
[self hide];
|
||||
}
|
||||
|
||||
- (BOOL)textViewShouldBeginEditing:(UITextView*)view
|
||||
{
|
||||
#if !PLATFORM_TVOS
|
||||
view.inputAccessoryView = viewToolbar;
|
||||
#endif
|
||||
return YES;
|
||||
}
|
||||
|
||||
#if PLATFORM_IOS
|
||||
|
||||
- (void)keyboardWillShow:(NSNotification *)notification
|
||||
{
|
||||
if (notification.userInfo == nil || inputView == nil)
|
||||
return;
|
||||
|
||||
CGRect srcRect = [[notification.userInfo objectForKey: UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
||||
CGRect rect = [UnityGetGLView() convertRect: srcRect fromView: nil];
|
||||
rect.origin.y = [UnityGetGLView() frame].size.height - rect.size.height; // iPhone X sometimes reports wrong y value for keyboard
|
||||
|
||||
[self positionInput: rect x: rect.origin.x y: rect.origin.y];
|
||||
}
|
||||
|
||||
- (void)keyboardDidShow:(NSNotification*)notification
|
||||
{
|
||||
_active = YES;
|
||||
}
|
||||
|
||||
- (void)keyboardWillHide:(NSNotification*)notification
|
||||
{
|
||||
[self systemHideKeyboard];
|
||||
}
|
||||
|
||||
- (void)keyboardDidChangeFrame:(NSNotification*)notification
|
||||
{
|
||||
_active = true;
|
||||
|
||||
CGRect srcRect = [[notification.userInfo objectForKey: UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
||||
CGRect rect = [UnityGetGLView() convertRect: srcRect fromView: nil];
|
||||
|
||||
if (rect.origin.y >= [UnityGetGLView() bounds].size.height)
|
||||
[self systemHideKeyboard];
|
||||
else
|
||||
{
|
||||
rect.origin.y = [UnityGetGLView() frame].size.height - rect.size.height; // iPhone X sometimes reports wrong y value for keyboard
|
||||
[self positionInput: rect x: rect.origin.x y: rect.origin.y];
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+ (void)Initialize
|
||||
{
|
||||
NSAssert(_keyboard == nil, @"[KeyboardDelegate Initialize] called after creating keyboard");
|
||||
if (!_keyboard)
|
||||
_keyboard = [[KeyboardDelegate alloc] init];
|
||||
}
|
||||
|
||||
+ (KeyboardDelegate*)Instance
|
||||
{
|
||||
if (!_keyboard)
|
||||
_keyboard = [[KeyboardDelegate alloc] init];
|
||||
|
||||
return _keyboard;
|
||||
}
|
||||
|
||||
#if PLATFORM_IOS
|
||||
struct CreateToolbarResult
|
||||
{
|
||||
UIToolbar* toolbar;
|
||||
NSArray* items;
|
||||
};
|
||||
- (CreateToolbarResult)createToolbarWithView:(UIView*)view
|
||||
{
|
||||
UIToolbar* toolbar = [[UIToolbar alloc] initWithFrame: CGRectMake(0, 840, 320, kToolBarHeight)];
|
||||
UnitySetViewTouchProcessing(toolbar, touchesIgnored);
|
||||
toolbar.hidden = NO;
|
||||
|
||||
UIBarButtonItem* inputItem = view ? [[UIBarButtonItem alloc] initWithCustomView: view] : nil;
|
||||
UIBarButtonItem* doneItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem: UIBarButtonSystemItemDone target: self action: @selector(textInputDone:)];
|
||||
UIBarButtonItem* cancelItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem: UIBarButtonSystemItemCancel target: self action: @selector(textInputCancel:)];
|
||||
|
||||
NSArray* items = view ? @[inputItem, doneItem, cancelItem] : @[doneItem, cancelItem];
|
||||
toolbar.items = items;
|
||||
|
||||
inputItem = nil;
|
||||
doneItem = nil;
|
||||
cancelItem = nil;
|
||||
|
||||
CreateToolbarResult ret = {toolbar, items};
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
- (id)init
|
||||
{
|
||||
NSAssert(_keyboard == nil, @"You can have only one instance of KeyboardDelegate");
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
#if PLATFORM_IOS
|
||||
textView = [[UITextView alloc] initWithFrame: CGRectMake(0, 840, 480, 30)];
|
||||
textView.delegate = self;
|
||||
textView.font = [UIFont systemFontOfSize: 18.0];
|
||||
textView.hidden = YES;
|
||||
#endif
|
||||
|
||||
textField = [[UITextField alloc] initWithFrame: CGRectMake(0, 0, 120, 30)];
|
||||
textField.delegate = self;
|
||||
textField.borderStyle = UITextBorderStyleRoundedRect;
|
||||
textField.font = [UIFont systemFontOfSize: 20.0];
|
||||
textField.clearButtonMode = UITextFieldViewModeWhileEditing;
|
||||
|
||||
#if PLATFORM_IOS
|
||||
widthConstraint = [NSLayoutConstraint constraintWithItem: textField attribute: NSLayoutAttributeWidth relatedBy: NSLayoutRelationEqual toItem: nil attribute: NSLayoutAttributeNotAnAttribute multiplier: 1.0 constant: textField.frame.size.width];
|
||||
[textField addConstraint: widthConstraint];
|
||||
#endif
|
||||
|
||||
#define CREATE_TOOLBAR(t, i, v) \
|
||||
do { \
|
||||
CreateToolbarResult res = [self createToolbarWithView:v]; \
|
||||
t = res.toolbar; \
|
||||
i = res.items; \
|
||||
} while(0)
|
||||
|
||||
#if PLATFORM_IOS
|
||||
CREATE_TOOLBAR(viewToolbar, viewToolbarItems, nil);
|
||||
CREATE_TOOLBAR(fieldToolbar, fieldToolbarItems, textField);
|
||||
#endif
|
||||
|
||||
#undef CREATE_TOOLBAR
|
||||
|
||||
#if PLATFORM_IOS
|
||||
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(keyboardWillShow:) name: UIKeyboardWillShowNotification object: nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(keyboardDidShow:) name: UIKeyboardDidShowNotification object: nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(keyboardWillHide:) name: UIKeyboardWillHideNotification object: nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(keyboardDidChangeFrame:) name: UIKeyboardDidChangeFrameNotification object: nil];
|
||||
#endif
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(textInputDone:) name: UITextFieldTextDidEndEditingNotification object: nil];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setTextInputTraits:(id<UITextInputTraits>)traits
|
||||
withParam:(KeyboardShowParam)param
|
||||
withCap:(UITextAutocapitalizationType)capitalization
|
||||
{
|
||||
traits.keyboardType = param.keyboardType;
|
||||
traits.autocorrectionType = param.autocorrectionType;
|
||||
traits.secureTextEntry = param.secure;
|
||||
traits.keyboardAppearance = param.appearance;
|
||||
traits.autocapitalizationType = capitalization;
|
||||
}
|
||||
|
||||
- (void)setKeyboardParams:(KeyboardShowParam)param
|
||||
{
|
||||
if (!editView.hidden)
|
||||
{
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget: self];
|
||||
if (cachedKeyboardParam.multiline != param.multiline ||
|
||||
cachedKeyboardParam.secure != param.secure ||
|
||||
cachedKeyboardParam.keyboardType != param.keyboardType ||
|
||||
cachedKeyboardParam.autocorrectionType != param.autocorrectionType ||
|
||||
cachedKeyboardParam.appearance != param.appearance)
|
||||
{
|
||||
[self hideUIDelayed];
|
||||
}
|
||||
}
|
||||
cachedKeyboardParam = param;
|
||||
|
||||
if (_active)
|
||||
[self hide];
|
||||
|
||||
initialText = param.text ? [[NSString alloc] initWithUTF8String: param.text] : @"";
|
||||
|
||||
_characterLimit = param.characterLimit;
|
||||
|
||||
UITextAutocapitalizationType capitalization = UITextAutocapitalizationTypeSentences;
|
||||
if (param.keyboardType == UIKeyboardTypeURL || param.keyboardType == UIKeyboardTypeEmailAddress || param.keyboardType == UIKeyboardTypeWebSearch)
|
||||
capitalization = UITextAutocapitalizationTypeNone;
|
||||
|
||||
#if PLATFORM_IOS
|
||||
_multiline = param.multiline;
|
||||
if (_multiline)
|
||||
{
|
||||
textView.text = initialText;
|
||||
[self setTextInputTraits: textView withParam: param withCap: capitalization];
|
||||
|
||||
UITextPosition* end = [textView endOfDocument];
|
||||
UITextRange* endTextRange = [textView textRangeFromPosition: end toPosition: end];
|
||||
[textView setSelectedTextRange: endTextRange];
|
||||
}
|
||||
else
|
||||
{
|
||||
textField.text = initialText;
|
||||
[self setTextInputTraits: textField withParam: param withCap: capitalization];
|
||||
textField.placeholder = [NSString stringWithUTF8String: param.placeholder];
|
||||
|
||||
UITextPosition* end = [textField endOfDocument];
|
||||
UITextRange* endTextRange = [textField textRangeFromPosition: end toPosition: end];
|
||||
[textField setSelectedTextRange: endTextRange];
|
||||
}
|
||||
inputView = _multiline ? textView : textField;
|
||||
editView = _multiline ? textView : fieldToolbar;
|
||||
|
||||
#else // PLATFORM_TVOS
|
||||
textField.text = initialText;
|
||||
[self setTextInputTraits: textField withParam: param withCap: capitalization];
|
||||
textField.placeholder = [NSString stringWithUTF8String: param.placeholder];
|
||||
inputView = textField;
|
||||
editView = textField;
|
||||
|
||||
UITextPosition* end = [textField endOfDocument];
|
||||
UITextRange* endTextRange = [textField textRangeFromPosition: end toPosition: end];
|
||||
[textField setSelectedTextRange: endTextRange];
|
||||
#endif
|
||||
|
||||
[self shouldHideInput: _shouldHideInput];
|
||||
|
||||
_status = Visible;
|
||||
_active = YES;
|
||||
}
|
||||
|
||||
// we need to show/hide keyboard to react to orientation too, so extract we extract UI fiddling
|
||||
|
||||
- (void)showUI
|
||||
{
|
||||
// if we unhide everything now the input will be shown smaller then needed quickly (and resized later)
|
||||
// so unhide only when keyboard is actually shown (we will update it when reacting to ios notifications)
|
||||
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget: self];
|
||||
if (!inputView.isFirstResponder)
|
||||
{
|
||||
editView.hidden = YES;
|
||||
|
||||
[UnityGetGLView() addSubview: editView];
|
||||
[inputView becomeFirstResponder];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)hideUI
|
||||
{
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget: self];
|
||||
[self performSelector: @selector(hideUIDelayed) withObject: nil afterDelay: 0.05]; // to avoid unnecessary hiding
|
||||
}
|
||||
|
||||
- (void)hideUIDelayed
|
||||
{
|
||||
[inputView resignFirstResponder];
|
||||
|
||||
[editView removeFromSuperview];
|
||||
editView.hidden = YES;
|
||||
}
|
||||
|
||||
- (void)systemHideKeyboard
|
||||
{
|
||||
// when we are rotating os will bombard us with keyboardWillHide: and keyboardDidChangeFrame:
|
||||
// ignore all of them (we do it here only to simplify code: we call systemHideKeyboard only from these notification handlers)
|
||||
if (_rotating)
|
||||
return;
|
||||
|
||||
_active = editView.isFirstResponder;
|
||||
editView.hidden = YES;
|
||||
|
||||
_area = CGRectMake(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
- (void)show
|
||||
{
|
||||
[self showUI];
|
||||
}
|
||||
|
||||
- (void)hide
|
||||
{
|
||||
[self hideUI];
|
||||
}
|
||||
|
||||
- (void)updateInputHidden
|
||||
{
|
||||
if (_shouldHideInputChanged)
|
||||
{
|
||||
[self shouldHideInput: _shouldHideInput];
|
||||
_shouldHideInputChanged = false;
|
||||
}
|
||||
|
||||
textField.returnKeyType = _inputHidden ? UIReturnKeyDone : UIReturnKeyDefault;
|
||||
|
||||
editView.hidden = _inputHidden ? YES : NO;
|
||||
inputView.hidden = _inputHidden ? YES : NO;
|
||||
}
|
||||
|
||||
#if PLATFORM_IOS
|
||||
- (void)positionInput:(CGRect)kbRect x:(float)x y:(float)y
|
||||
{
|
||||
float safeAreaInsetLeft = 0;
|
||||
float safeAreaInsetRight = 0;
|
||||
|
||||
#if UNITY_HAS_IOSSDK_11_0
|
||||
if (@available(iOS 11.0, *))
|
||||
{
|
||||
safeAreaInsetLeft = [UnityGetGLView() safeAreaInsets].left;
|
||||
safeAreaInsetRight = [UnityGetGLView() safeAreaInsets].right;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_multiline)
|
||||
{
|
||||
// use smaller area for iphones and bigger one for ipads
|
||||
int height = UnityDeviceDPI() > 300 ? 75 : 100;
|
||||
|
||||
editView.frame = CGRectMake(safeAreaInsetLeft, y - height, kbRect.size.width - safeAreaInsetLeft - safeAreaInsetRight, height);
|
||||
}
|
||||
else
|
||||
{
|
||||
editView.frame = CGRectMake(0, y - kToolBarHeight, kbRect.size.width, kToolBarHeight);
|
||||
|
||||
// old constraint must be removed, changing value while constraint is active causes conflict when changing inputView.frame
|
||||
[inputView removeConstraint: widthConstraint];
|
||||
|
||||
inputView.frame = CGRectMake(inputView.frame.origin.x,
|
||||
inputView.frame.origin.y,
|
||||
kbRect.size.width - safeAreaInsetLeft - safeAreaInsetRight - kSystemButtonsSpace,
|
||||
inputView.frame.size.height);
|
||||
|
||||
// required to avoid auto-resizing on iOS 11 in case if input text is too long
|
||||
widthConstraint.constant = inputView.frame.size.width;
|
||||
[inputView addConstraint: widthConstraint];
|
||||
}
|
||||
|
||||
_area = CGRectMake(x, y, kbRect.size.width, kbRect.size.height);
|
||||
[self updateInputHidden];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
- (CGRect)queryArea
|
||||
{
|
||||
return editView.hidden ? _area : CGRectUnion(_area, editView.frame);
|
||||
}
|
||||
|
||||
- (NSRange)querySelection
|
||||
{
|
||||
UIView<UITextInput>* textInput;
|
||||
|
||||
#if PLATFORM_TVOS
|
||||
textInput = textField;
|
||||
#else
|
||||
textInput = _multiline ? textView : textField;
|
||||
#endif
|
||||
|
||||
UITextPosition* beginning = textInput.beginningOfDocument;
|
||||
|
||||
UITextRange* selectedRange = textInput.selectedTextRange;
|
||||
UITextPosition* selectionStart = selectedRange.start;
|
||||
UITextPosition* selectionEnd = selectedRange.end;
|
||||
|
||||
const NSInteger location = [textInput offsetFromPosition: beginning toPosition: selectionStart];
|
||||
const NSInteger length = [textInput offsetFromPosition: selectionStart toPosition: selectionEnd];
|
||||
|
||||
return NSMakeRange(location, length);
|
||||
}
|
||||
|
||||
- (void)assignSelection:(NSRange)range
|
||||
{
|
||||
UIView<UITextInput>* textInput;
|
||||
|
||||
#if PLATFORM_TVOS
|
||||
textInput = textField;
|
||||
#else
|
||||
textInput = _multiline ? textView : textField;
|
||||
#endif
|
||||
|
||||
UITextPosition* begin = [textInput beginningOfDocument];
|
||||
UITextPosition* caret = [textInput positionFromPosition: begin offset: range.location];
|
||||
UITextPosition* select = [textInput positionFromPosition: caret offset: range.length];
|
||||
UITextRange* textRange = [textInput textRangeFromPosition: caret toPosition: select];
|
||||
|
||||
[textInput setSelectedTextRange: textRange];
|
||||
}
|
||||
|
||||
+ (void)StartReorientation
|
||||
{
|
||||
if (_keyboard && _keyboard.active)
|
||||
_keyboard->_rotating = YES;
|
||||
}
|
||||
|
||||
+ (void)FinishReorientation
|
||||
{
|
||||
if (_keyboard)
|
||||
_keyboard->_rotating = NO;
|
||||
}
|
||||
|
||||
- (NSString*)getText
|
||||
{
|
||||
if (_status == Canceled)
|
||||
return initialText;
|
||||
else
|
||||
{
|
||||
#if PLATFORM_TVOS
|
||||
return [textField text];
|
||||
#else
|
||||
return _multiline ? [textView text] : [textField text];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setText:(NSString*)newText
|
||||
{
|
||||
#if PLATFORM_IOS
|
||||
if (_multiline)
|
||||
textView.text = newText;
|
||||
else
|
||||
textField.text = newText;
|
||||
#else
|
||||
textField.text = newText;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)shouldHideInput:(BOOL)hide
|
||||
{
|
||||
if (hide)
|
||||
{
|
||||
switch (keyboardType)
|
||||
{
|
||||
case UIKeyboardTypeDefault: hide = YES; break;
|
||||
case UIKeyboardTypeASCIICapable: hide = YES; break;
|
||||
case UIKeyboardTypeNumbersAndPunctuation: hide = YES; break;
|
||||
case UIKeyboardTypeURL: hide = YES; break;
|
||||
case UIKeyboardTypeNumberPad: hide = NO; break;
|
||||
case UIKeyboardTypePhonePad: hide = NO; break;
|
||||
case UIKeyboardTypeNamePhonePad: hide = NO; break;
|
||||
case UIKeyboardTypeEmailAddress: hide = YES; break;
|
||||
case UIKeyboardTypeTwitter: hide = YES; break;
|
||||
case UIKeyboardTypeWebSearch: hide = YES; break;
|
||||
default: hide = NO; break;
|
||||
}
|
||||
}
|
||||
|
||||
_inputHidden = hide;
|
||||
}
|
||||
|
||||
#if FILTER_EMOJIS_IOS_KEYBOARD
|
||||
|
||||
static bool StringContainsEmoji(NSString *string);
|
||||
- (BOOL)textField:(UITextField*)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString*)string_
|
||||
{
|
||||
if (range.length + range.location > textField.text.length)
|
||||
return NO;
|
||||
|
||||
return [self currentText: textField.text shouldChangeInRange: range replacementText: string_] && !StringContainsEmoji(string_);
|
||||
}
|
||||
|
||||
- (BOOL)textView:(UITextView*)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString*)text_
|
||||
{
|
||||
if (range.length + range.location > textView.text.length)
|
||||
return NO;
|
||||
|
||||
return [self currentText: textView.text shouldChangeInRange: range replacementText: text_] && !StringContainsEmoji(text_);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString*)string_
|
||||
{
|
||||
if (range.length + range.location > textField.text.length)
|
||||
return NO;
|
||||
|
||||
return [self currentText: textField.text shouldChangeInRange: range replacementText: string_];
|
||||
}
|
||||
|
||||
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString*)text_
|
||||
{
|
||||
if (range.length + range.location > textView.text.length)
|
||||
return NO;
|
||||
|
||||
return [self currentText: textView.text shouldChangeInRange: range replacementText: text_];
|
||||
}
|
||||
|
||||
#endif // FILTER_EMOJIS_IOS_KEYBOARD
|
||||
|
||||
- (BOOL)currentText:(NSString*)currentText shouldChangeInRange:(NSRange)range replacementText:(NSString*)text_
|
||||
{
|
||||
NSUInteger newLength = currentText.length + (text_.length - range.length);
|
||||
if (newLength > _characterLimit && _characterLimit != 0 && newLength >= currentText.length)
|
||||
{
|
||||
NSString* newReplacementText = @"";
|
||||
if ((currentText.length - range.length) < _characterLimit)
|
||||
newReplacementText = [text_ substringWithRange: NSMakeRange(0, _characterLimit - (currentText.length - range.length))];
|
||||
|
||||
NSString* newText = [currentText stringByReplacingCharactersInRange: range withString: newReplacementText];
|
||||
|
||||
#if PLATFORM_IOS
|
||||
if (_multiline)
|
||||
[textView setText: newText];
|
||||
else
|
||||
[textField setText: newText];
|
||||
#else
|
||||
[textField setText: newText];
|
||||
#endif
|
||||
|
||||
return NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//==============================================================================
|
||||
//
|
||||
// Unity Interface:
|
||||
|
||||
extern "C" void UnityKeyboard_Create(unsigned keyboardType, int autocorrection, int multiline, int secure, int alert, const char* text, const char* placeholder, int characterLimit)
|
||||
{
|
||||
#if PLATFORM_TVOS
|
||||
// Not supported. The API for showing keyboard for editing multi-line text
|
||||
// is not available on tvOS
|
||||
multiline = false;
|
||||
#endif
|
||||
|
||||
static const UIKeyboardType keyboardTypes[] =
|
||||
{
|
||||
UIKeyboardTypeDefault,
|
||||
UIKeyboardTypeASCIICapable,
|
||||
UIKeyboardTypeNumbersAndPunctuation,
|
||||
UIKeyboardTypeURL,
|
||||
UIKeyboardTypeNumberPad,
|
||||
UIKeyboardTypePhonePad,
|
||||
UIKeyboardTypeNamePhonePad,
|
||||
UIKeyboardTypeEmailAddress,
|
||||
UIKeyboardTypeDefault, // Default is used in case Wii U specific NintendoNetworkAccount type is selected (indexed at 8 in UnityEngine.TouchScreenKeyboardType)
|
||||
UIKeyboardTypeTwitter,
|
||||
UIKeyboardTypeWebSearch
|
||||
};
|
||||
|
||||
static const UITextAutocorrectionType autocorrectionTypes[] =
|
||||
{
|
||||
UITextAutocorrectionTypeNo,
|
||||
UITextAutocorrectionTypeDefault,
|
||||
};
|
||||
|
||||
static const UIKeyboardAppearance keyboardAppearances[] =
|
||||
{
|
||||
UIKeyboardAppearanceDefault,
|
||||
UIKeyboardAppearanceAlert,
|
||||
};
|
||||
|
||||
KeyboardShowParam param =
|
||||
{
|
||||
text, placeholder,
|
||||
keyboardTypes[keyboardType],
|
||||
autocorrectionTypes[autocorrection],
|
||||
keyboardAppearances[alert],
|
||||
(BOOL)multiline, (BOOL)secure,
|
||||
characterLimit
|
||||
};
|
||||
|
||||
[[KeyboardDelegate Instance] setKeyboardParams: param];
|
||||
}
|
||||
|
||||
extern "C" void UnityKeyboard_Show()
|
||||
{
|
||||
// do not send hide if didnt create keyboard
|
||||
// TODO: probably assert?
|
||||
if (!_keyboard)
|
||||
return;
|
||||
|
||||
[[KeyboardDelegate Instance] show];
|
||||
}
|
||||
|
||||
extern "C" void UnityKeyboard_Hide()
|
||||
{
|
||||
// do not send hide if didnt create keyboard
|
||||
// TODO: probably assert?
|
||||
if (!_keyboard)
|
||||
return;
|
||||
|
||||
[[KeyboardDelegate Instance] textInputLostFocus];
|
||||
}
|
||||
|
||||
extern "C" void UnityKeyboard_SetText(const char* text)
|
||||
{
|
||||
[KeyboardDelegate Instance].text = [NSString stringWithUTF8String: text];
|
||||
}
|
||||
|
||||
extern "C" NSString* UnityKeyboard_GetText()
|
||||
{
|
||||
return [KeyboardDelegate Instance].text;
|
||||
}
|
||||
|
||||
extern "C" int UnityKeyboard_IsActive()
|
||||
{
|
||||
return (_keyboard && _keyboard.active) ? 1 : 0;
|
||||
}
|
||||
|
||||
extern "C" int UnityKeyboard_Status()
|
||||
{
|
||||
return _keyboard ? _keyboard.status : Canceled;
|
||||
}
|
||||
|
||||
extern "C" void UnityKeyboard_SetInputHidden(int hidden)
|
||||
{
|
||||
_shouldHideInput = hidden;
|
||||
_shouldHideInputChanged = true;
|
||||
|
||||
// update hidden status only if keyboard is on screen to avoid showing input view out of nowhere
|
||||
if (_keyboard && _keyboard.active)
|
||||
[_keyboard updateInputHidden];
|
||||
}
|
||||
|
||||
extern "C" int UnityKeyboard_IsInputHidden()
|
||||
{
|
||||
return _shouldHideInput ? 1 : 0;
|
||||
}
|
||||
|
||||
extern "C" void UnityKeyboard_GetRect(float* x, float* y, float* w, float* h)
|
||||
{
|
||||
CGRect area = _keyboard ? _keyboard.area : CGRectMake(0, 0, 0, 0);
|
||||
|
||||
// convert to unity coord system
|
||||
|
||||
float multX = (float)GetMainDisplaySurface()->targetW / UnityGetGLView().bounds.size.width;
|
||||
float multY = (float)GetMainDisplaySurface()->targetH / UnityGetGLView().bounds.size.height;
|
||||
|
||||
*x = 0;
|
||||
*y = area.origin.y * multY;
|
||||
*w = area.size.width * multX;
|
||||
*h = area.size.height * multY;
|
||||
}
|
||||
|
||||
extern "C" void UnityKeyboard_SetCharacterLimit(unsigned characterLimit)
|
||||
{
|
||||
[KeyboardDelegate Instance].characterLimit = characterLimit;
|
||||
}
|
||||
|
||||
extern "C" int UnityKeyboard_CanGetSelection()
|
||||
{
|
||||
return (_keyboard) ? 1 : 0;
|
||||
}
|
||||
|
||||
extern "C" void UnityKeyboard_GetSelection(int* location, int* length)
|
||||
{
|
||||
if (_keyboard)
|
||||
{
|
||||
NSRange selection = _keyboard.selection;
|
||||
|
||||
*location = (int)selection.location;
|
||||
*length = (int)selection.length;
|
||||
}
|
||||
else
|
||||
{
|
||||
*location = 0;
|
||||
*length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int UnityKeyboard_CanSetSelection()
|
||||
{
|
||||
return (_keyboard) ? 1 : 0;
|
||||
}
|
||||
|
||||
extern "C" void UnityKeyboard_SetSelection(int location, int length)
|
||||
{
|
||||
if (_keyboard)
|
||||
{
|
||||
NSRange range = NSMakeRange(location, length);
|
||||
_keyboard.selection = range;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
//
|
||||
// Emoji Filtering: unicode magic
|
||||
|
||||
#if FILTER_EMOJIS_IOS_KEYBOARD
|
||||
static bool StringContainsEmoji(NSString *string)
|
||||
{
|
||||
__block BOOL returnValue = NO;
|
||||
|
||||
[string enumerateSubstringsInRange: NSMakeRange(0, string.length)
|
||||
options: NSStringEnumerationByComposedCharacterSequences
|
||||
usingBlock:^(NSString* substring, NSRange substringRange, NSRange enclosingRange, BOOL* stop)
|
||||
{
|
||||
const unichar hs = [substring characterAtIndex: 0];
|
||||
const unichar ls = substring.length > 1 ? [substring characterAtIndex: 1] : 0;
|
||||
|
||||
#define IS_IN(val, min, max) (((val) >= (min)) && ((val) <= (max)))
|
||||
|
||||
if (IS_IN(hs, 0xD800, 0xDBFF))
|
||||
{
|
||||
if (substring.length > 1)
|
||||
{
|
||||
const int uc = ((hs - 0xD800) * 0x400) + (ls - 0xDC00) + 0x10000;
|
||||
|
||||
// Musical: [U+1D000, U+1D24F]
|
||||
// Enclosed Alphanumeric Supplement: [U+1F100, U+1F1FF]
|
||||
// Enclosed Ideographic Supplement: [U+1F200, U+1F2FF]
|
||||
// Miscellaneous Symbols and Pictographs: [U+1F300, U+1F5FF]
|
||||
// Supplemental Symbols and Pictographs: [U+1F900, U+1F9FF]
|
||||
// Emoticons: [U+1F600, U+1F64F]
|
||||
// Transport and Map Symbols: [U+1F680, U+1F6FF]
|
||||
if (IS_IN(uc, 0x1D000, 0x1F9FF))
|
||||
returnValue = YES;
|
||||
}
|
||||
}
|
||||
else if (substring.length > 1 && ls == 0x20E3)
|
||||
{
|
||||
// emojis for numbers: number + modifier ls = U+20E3
|
||||
returnValue = YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( // Latin-1 Supplement
|
||||
hs == 0x00A9 || hs == 0x00AE
|
||||
// General Punctuation
|
||||
|| hs == 0x203C || hs == 0x2049
|
||||
// Letterlike Symbols
|
||||
|| hs == 0x2122 || hs == 0x2139
|
||||
// Arrows
|
||||
|| IS_IN(hs, 0x2194, 0x2199) || IS_IN(hs, 0x21A9, 0x21AA)
|
||||
// Miscellaneous Technical
|
||||
|| IS_IN(hs, 0x231A, 0x231B) || IS_IN(hs, 0x23E9, 0x23F3) || IS_IN(hs, 0x23F8, 0x23FA) || hs == 0x2328 || hs == 0x23CF
|
||||
// Geometric Shapes
|
||||
|| IS_IN(hs, 0x25AA, 0x25AB) || IS_IN(hs, 0x25FB, 0x25FE) || hs == 0x25B6 || hs == 0x25C0
|
||||
// Miscellaneous Symbols
|
||||
|| IS_IN(hs, 0x2600, 0x2604) || IS_IN(hs, 0x2614, 0x2615) || IS_IN(hs, 0x2622, 0x2623) || IS_IN(hs, 0x262E, 0x262F)
|
||||
|| IS_IN(hs, 0x2638, 0x263A) || IS_IN(hs, 0x2648, 0x2653) || IS_IN(hs, 0x2665, 0x2666) || IS_IN(hs, 0x2692, 0x2694)
|
||||
|| IS_IN(hs, 0x2696, 0x2697) || IS_IN(hs, 0x269B, 0x269C) || IS_IN(hs, 0x26A0, 0x26A1) || IS_IN(hs, 0x26AA, 0x26AB)
|
||||
|| IS_IN(hs, 0x26B0, 0x26B1) || IS_IN(hs, 0x26BD, 0x26BE) || IS_IN(hs, 0x26C4, 0x26C5) || IS_IN(hs, 0x26CE, 0x26CF)
|
||||
|| IS_IN(hs, 0x26D3, 0x26D4) || IS_IN(hs, 0x26D3, 0x26D4) || IS_IN(hs, 0x26E9, 0x26EA) || IS_IN(hs, 0x26F0, 0x26F5)
|
||||
|| IS_IN(hs, 0x26F7, 0x26FA)
|
||||
|| hs == 0x260E || hs == 0x2611 || hs == 0x2618 || hs == 0x261D || hs == 0x2620 || hs == 0x2626 || hs == 0x262A
|
||||
|| hs == 0x2660 || hs == 0x2663 || hs == 0x2668 || hs == 0x267B || hs == 0x267F || hs == 0x2699 || hs == 0x26C8
|
||||
|| hs == 0x26D1 || hs == 0x26FD
|
||||
// Dingbats
|
||||
|| IS_IN(hs, 0x2708, 0x270D) || IS_IN(hs, 0x2733, 0x2734) || IS_IN(hs, 0x2753, 0x2755)
|
||||
|| IS_IN(hs, 0x2763, 0x2764) || IS_IN(hs, 0x2795, 0x2797)
|
||||
|| hs == 0x2702 || hs == 0x2705 || hs == 0x270F || hs == 0x2712 || hs == 0x2714 || hs == 0x2716 || hs == 0x271D
|
||||
|| hs == 0x2721 || hs == 0x2728 || hs == 0x2744 || hs == 0x2747 || hs == 0x274C || hs == 0x274E || hs == 0x2757
|
||||
|| hs == 0x27A1 || hs == 0x27B0 || hs == 0x27BF
|
||||
// CJK Symbols and Punctuation
|
||||
|| hs == 0x3030 || hs == 0x303D
|
||||
// Enclosed CJK Letters and Months
|
||||
|| hs == 0x3297 || hs == 0x3299
|
||||
// Supplemental Arrows-B
|
||||
|| IS_IN(hs, 0x2934, 0x2935)
|
||||
// Miscellaneous Symbols and Arrows
|
||||
|| IS_IN(hs, 0x2B05, 0x2B07) || IS_IN(hs, 0x2B1B, 0x2B1C) || hs == 0x2B50 || hs == 0x2B55
|
||||
)
|
||||
{
|
||||
returnValue = YES;
|
||||
}
|
||||
}
|
||||
|
||||
#undef IS_IN
|
||||
}];
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
#endif // FILTER_EMOJIS_IOS_KEYBOARD
|
||||
@@ -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);
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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();
|
||||
@@ -0,0 +1,420 @@
|
||||
#define UnityGetLaunchScreenXib MyUnityGetLaunchScreenXib
|
||||
static const char *MyUnityGetLaunchScreenXib() { return NULL; }
|
||||
#include "SplashScreen.h"
|
||||
#include "UnityViewControllerBase.h"
|
||||
#include "OrientationSupport.h"
|
||||
#include "Unity/ObjCRuntime.h"
|
||||
#include "UI/UnityView.h"
|
||||
#include <cstring>
|
||||
#include "Classes/Unity/UnitySharedDecls.h"
|
||||
|
||||
extern "C" const char* UnityGetLaunchScreenXib();
|
||||
|
||||
#include <utility>
|
||||
|
||||
static SplashScreen* _splash = nil;
|
||||
static SplashScreenController* _controller = nil;
|
||||
static bool _isOrientable = false; // true for iPads and iPhone 6+
|
||||
static bool _usesLaunchscreen = false;
|
||||
static ScreenOrientation _nonOrientableDefaultOrientation = portrait;
|
||||
|
||||
#if !PLATFORM_TVOS
|
||||
typedef id (*WillRotateToInterfaceOrientationSendFunc)(struct objc_super*, SEL, UIInterfaceOrientation, NSTimeInterval);
|
||||
typedef id (*DidRotateFromInterfaceOrientationSendFunc)(struct objc_super*, SEL, UIInterfaceOrientation);
|
||||
#endif
|
||||
typedef id (*ViewWillTransitionToSizeSendFunc)(struct objc_super*, SEL, CGSize, id<UIViewControllerTransitionCoordinator>);
|
||||
|
||||
static const char* GetScaleSuffix(float scale, float maxScale)
|
||||
{
|
||||
if (scale > maxScale)
|
||||
scale = maxScale;
|
||||
if (scale <= 1.0)
|
||||
return "";
|
||||
if (scale <= 2.0)
|
||||
return "@2x";
|
||||
return "@3x";
|
||||
}
|
||||
|
||||
static const char* GetOrientationSuffix(const OrientationMask& supportedOrientations, ScreenOrientation orient)
|
||||
{
|
||||
bool orientPortrait = (orient == portrait || orient == portraitUpsideDown);
|
||||
bool orientLandscape = (orient == landscapeLeft || orient == landscapeRight);
|
||||
|
||||
bool supportsPortrait = supportedOrientations.portrait || supportedOrientations.portraitUpsideDown;
|
||||
bool supportsLandscape = supportedOrientations.landscapeLeft || supportedOrientations.landscapeRight;
|
||||
|
||||
if (orientPortrait && supportsPortrait)
|
||||
return "-Portrait";
|
||||
else if (orientLandscape && supportsLandscape)
|
||||
return "-Landscape";
|
||||
else if (supportsPortrait)
|
||||
return "-Portrait";
|
||||
else
|
||||
return "-Landscape";
|
||||
}
|
||||
|
||||
// Returns a launch image name for launch images stored on file system or asset catalog
|
||||
extern "C" NSArray<NSString*>* GetLaunchImageNames(UIUserInterfaceIdiom idiom, const OrientationMask&supportedOrientations,
|
||||
const CGSize&screenSize, ScreenOrientation orient, float scale)
|
||||
{
|
||||
NSMutableArray<NSString*>* ret = [[NSMutableArray<NSString *> alloc] init];
|
||||
|
||||
if (idiom == UIUserInterfaceIdiomPad)
|
||||
{
|
||||
// iPads
|
||||
const char* iOSSuffix = "-700";
|
||||
const char* orientSuffix = GetOrientationSuffix(supportedOrientations, orient);
|
||||
const char* scaleSuffix = GetScaleSuffix(scale, 2.0);
|
||||
[ret addObject: [NSString stringWithFormat: @"LaunchImage%s%s%s~ipad",
|
||||
iOSSuffix, orientSuffix, scaleSuffix]];
|
||||
}
|
||||
else
|
||||
{
|
||||
// iPhones
|
||||
|
||||
// Note that on pre-iOS 11 using modifiers such as LaunchImage~568h works. Since
|
||||
// iOS launch image support is quite hard to get right and has _many_ gotchas, we
|
||||
// just use the old code path on these devices.
|
||||
|
||||
if (screenSize.height == 568 || screenSize.width == 568) // iPhone 5
|
||||
{
|
||||
[ret addObject: @"LaunchImage-700-568h@2x"];
|
||||
[ret addObject: @"LaunchImage~568h"];
|
||||
}
|
||||
else if (screenSize.height == 667 || screenSize.width == 667) // iPhone 6
|
||||
{
|
||||
// note that scale may be 3.0 if display zoom is enabled
|
||||
if (scale < 2.0) // not expected, but handle just in case. Image name is valid
|
||||
[ret addObject: @"LaunchImage-800-667h"];
|
||||
[ret addObject: @"LaunchImage-800-667h@2x"];
|
||||
[ret addObject: @"LaunchImage~667h"];
|
||||
}
|
||||
else if (screenSize.height == 736 || screenSize.width == 736) // iPhone 6+
|
||||
{
|
||||
const char* orientSuffix = GetOrientationSuffix(supportedOrientations, orient);
|
||||
if (scale < 3.0) // not expected, but handle just in case. Image name is valid
|
||||
[ret addObject: [NSString stringWithFormat: @"LaunchImage-800%s-736h", orientSuffix]];
|
||||
[ret addObject: [NSString stringWithFormat: @"LaunchImage-800%s-736h@3x", orientSuffix]];
|
||||
[ret addObject: @"LaunchImage~736h"];
|
||||
}
|
||||
else if (screenSize.height == 812 || screenSize.width == 812) // iPhone X
|
||||
{
|
||||
const char* orientSuffix = GetOrientationSuffix(supportedOrientations, orient);
|
||||
if (scale < 3.0) // not expected, but handle just in case. Image name is valid
|
||||
[ret addObject: [NSString stringWithFormat: @"LaunchImage-1100%s-2436h", orientSuffix]];
|
||||
[ret addObject: [NSString stringWithFormat: @"LaunchImage-1100%s-2436h@3x", orientSuffix]];
|
||||
}
|
||||
|
||||
if (scale > 1.0)
|
||||
[ret addObject: @"LaunchImage@2x"];
|
||||
}
|
||||
[ret addObject: @"LaunchImage"];
|
||||
return ret;
|
||||
}
|
||||
|
||||
@implementation SplashScreen
|
||||
{
|
||||
UIImageView* m_ImageView;
|
||||
UIView* m_XibView;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame: frame];
|
||||
return self;
|
||||
}
|
||||
|
||||
/* The following launch images are produced by Xcode6:
|
||||
|
||||
LaunchImage.png
|
||||
LaunchImage@2x.png
|
||||
LaunchImage-568h@2x.png
|
||||
LaunchImage-700@2x.png
|
||||
LaunchImage-700-568h@2x.png
|
||||
LaunchImage-700-Landscape@2x~ipad.png
|
||||
LaunchImage-700-Landscape~ipad.png
|
||||
LaunchImage-700-Portrait@2x~ipad.png
|
||||
LaunchImage-700-Portrait~ipad.png
|
||||
LaunchImage-800-667h@2x.png
|
||||
LaunchImage-800-Landscape-736h@3x.png
|
||||
LaunchImage-800-Portrait-736h@3x.png
|
||||
LaunchImage-1100-Landscape-2436h@3x.png
|
||||
LaunchImage-1100-Portrait-2436h@3x.png
|
||||
LaunchImage-Landscape@2x~ipad.png
|
||||
LaunchImage-Landscape~ipad.png
|
||||
LaunchImage-Portrait@2x~ipad.png
|
||||
LaunchImage-Portrait~ipad.png
|
||||
*/
|
||||
- (void)updateOrientation:(ScreenOrientation)orient withSupportedOrientations:(const OrientationMask&)supportedOrientations
|
||||
{
|
||||
CGFloat scale = UnityScreenScaleFactor([UIScreen mainScreen]);
|
||||
UnityReportResizeView(self.bounds.size.width * scale, self.bounds.size.height * scale, orient);
|
||||
|
||||
// Storyboards should have a view controller to automatically configure orientation
|
||||
bool hasStoryboard = [[NSBundle mainBundle] pathForResource: @"LaunchScreen" ofType: @"storyboardc"] != nullptr;
|
||||
if (hasStoryboard)
|
||||
return;
|
||||
|
||||
UIUserInterfaceIdiom idiom = [[UIDevice currentDevice] userInterfaceIdiom];
|
||||
|
||||
NSString* xibName = nil;
|
||||
if (idiom == UIUserInterfaceIdiomPhone)
|
||||
xibName = @"LaunchScreen-iPhone";
|
||||
else if (idiom == UIUserInterfaceIdiomPad)
|
||||
xibName = @"LaunchScreen-iPad";
|
||||
|
||||
bool hasLaunchScreen = [[NSBundle mainBundle] pathForResource: xibName ofType: @"nib"] != nullptr;
|
||||
|
||||
if (hasLaunchScreen)
|
||||
{
|
||||
// Launch screen uses the same aspect-filled image for all iPhone and/or
|
||||
// all iPads, as configured in Unity. We need a special case if there's
|
||||
// a launch screen and iOS is configured to use it.
|
||||
if (self->m_XibView == nil)
|
||||
{
|
||||
self->m_XibView = [[[NSBundle mainBundle] loadNibNamed: xibName owner: nil options: nil] objectAtIndex: 0];
|
||||
[self addSubview: self->m_XibView];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
UIImage* image = nil;
|
||||
CGSize screenSize = [[UIScreen mainScreen] bounds].size;
|
||||
CGFloat screenScale = [UIScreen mainScreen].scale;
|
||||
|
||||
// For launch images we implement fallback order with multiple images. First we try images via
|
||||
// [UIImage imageNamed] method and if this fails, we try to load from filesystem directly.
|
||||
// Note that file system resource names and image names accepted by UIImage are the same.
|
||||
// Multiple fallbacks are implemented because different iOS versions behave differently and have
|
||||
// many gotchas that are hard to get right. So we use the images that are present on app bundles
|
||||
// made with latest version of Xcode as the first priority and then fall back to any image that we
|
||||
// have used at some time in the past.
|
||||
NSArray<NSString*>* imageNames = GetLaunchImageNames(idiom, supportedOrientations, screenSize, orient, screenScale);
|
||||
|
||||
for (NSString* imageName in imageNames)
|
||||
{
|
||||
image = [UIImage imageNamed: imageName];
|
||||
if (image)
|
||||
break;
|
||||
}
|
||||
|
||||
if (image == nil)
|
||||
{
|
||||
// Old launch image from file
|
||||
for (NSString* imageName in imageNames)
|
||||
{
|
||||
image = [UIImage imageNamed: imageName];
|
||||
if (image)
|
||||
break;
|
||||
|
||||
NSString* imagePath = [[NSBundle mainBundle] pathForResource: imageName ofType: @"png"];
|
||||
image = [UIImage imageWithContentsOfFile: imagePath];
|
||||
if (image)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// should not ever happen, but just in case
|
||||
if (image == nil)
|
||||
return;
|
||||
|
||||
if (self->m_ImageView == nil)
|
||||
{
|
||||
self->m_ImageView = [[UIImageView alloc] initWithImage: image];
|
||||
[self addSubview: self->m_ImageView];
|
||||
}
|
||||
else
|
||||
{
|
||||
self->m_ImageView.image = image;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
if (self->m_XibView)
|
||||
self->m_XibView.frame = self.bounds;
|
||||
else if (self->m_ImageView)
|
||||
self->m_ImageView.frame = self.bounds;
|
||||
}
|
||||
|
||||
+ (SplashScreen*)Instance
|
||||
{
|
||||
return _splash;
|
||||
}
|
||||
|
||||
- (void)FreeSubviews
|
||||
{
|
||||
m_ImageView = nil;
|
||||
m_XibView = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation SplashScreenController
|
||||
{
|
||||
OrientationMask _supportedOrientations;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
self->_supportedOrientations = { false, false, false, false };
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
|
||||
{
|
||||
ScreenOrientation curOrient = UIViewControllerOrientation(self);
|
||||
ScreenOrientation newOrient = OrientationAfterTransform(curOrient, [coordinator targetTransform]);
|
||||
|
||||
if (_isOrientable)
|
||||
[_splash updateOrientation: newOrient withSupportedOrientations: self->_supportedOrientations];
|
||||
|
||||
[coordinator animateAlongsideTransition: nil completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
|
||||
if (!_isOrientable)
|
||||
OrientView(self, _splash, _nonOrientableDefaultOrientation);
|
||||
}];
|
||||
[super viewWillTransitionToSize: size withTransitionCoordinator: coordinator];
|
||||
}
|
||||
|
||||
- (void)create:(UIWindow*)window
|
||||
{
|
||||
NSArray* supportedOrientation = [[[NSBundle mainBundle] infoDictionary] objectForKey: @"UISupportedInterfaceOrientations"];
|
||||
bool isIphone = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone;
|
||||
bool isIpad = !isIphone;
|
||||
|
||||
// splash will be shown way before unity is inited so we need to override autorotation handling with values read from info.plist
|
||||
self->_supportedOrientations.portrait = [supportedOrientation containsObject: @"UIInterfaceOrientationPortrait"];
|
||||
self->_supportedOrientations.portraitUpsideDown = [supportedOrientation containsObject: @"UIInterfaceOrientationPortraitUpsideDown"];
|
||||
self->_supportedOrientations.landscapeLeft = [supportedOrientation containsObject: @"UIInterfaceOrientationLandscapeRight"];
|
||||
self->_supportedOrientations.landscapeRight = [supportedOrientation containsObject: @"UIInterfaceOrientationLandscapeLeft"];
|
||||
|
||||
CGSize size = [[UIScreen mainScreen] bounds].size;
|
||||
|
||||
// iPads and iPhone Plus models and iOS11 have orientable splash screen
|
||||
_isOrientable = isIpad || (size.height == 736 || size.width == 736) || UnityiOS110orNewer();
|
||||
|
||||
// Launch screens are used only on iOS8+ iPhones
|
||||
const char* xib = UnityGetLaunchScreenXib();
|
||||
#if !PLATFORM_TVOS
|
||||
_usesLaunchscreen = false;
|
||||
if (xib != NULL)
|
||||
{
|
||||
const char* expectedName = isIphone ? "LaunchScreen-iPhone" : "LaunchScreen-iPad";
|
||||
if (std::strcmp(xib, expectedName) == 0)
|
||||
_usesLaunchscreen = true;
|
||||
}
|
||||
#else
|
||||
_usesLaunchscreen = false;
|
||||
#endif
|
||||
|
||||
if (!(self->_supportedOrientations.portrait || self->_supportedOrientations.portraitUpsideDown))
|
||||
_nonOrientableDefaultOrientation = landscapeLeft;
|
||||
else
|
||||
_nonOrientableDefaultOrientation = portrait;
|
||||
|
||||
_splash = [[SplashScreen alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
|
||||
_splash.contentScaleFactor = [UIScreen mainScreen].scale;
|
||||
|
||||
if (_isOrientable)
|
||||
{
|
||||
_splash.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
_splash.autoresizesSubviews = YES;
|
||||
}
|
||||
else if (self->_supportedOrientations.portrait || self->_supportedOrientations.portraitUpsideDown)
|
||||
{
|
||||
self->_supportedOrientations.landscapeLeft = false;
|
||||
self->_supportedOrientations.landscapeRight = false;
|
||||
}
|
||||
// On non-orientable devices with launch screens, landscapeLeft is always used if both
|
||||
// landscapeRight and landscapeLeft are enabled
|
||||
if (!_isOrientable && _supportedOrientations.landscapeRight)
|
||||
{
|
||||
if (self->_supportedOrientations.landscapeLeft)
|
||||
self->_supportedOrientations.landscapeRight = false;
|
||||
else
|
||||
_nonOrientableDefaultOrientation = landscapeRight;
|
||||
}
|
||||
|
||||
window.rootViewController = self;
|
||||
|
||||
self.view = _splash;
|
||||
|
||||
[window addSubview: _splash];
|
||||
[window bringSubviewToFront: _splash];
|
||||
|
||||
ScreenOrientation orient = UIViewControllerOrientation(self);
|
||||
[_splash updateOrientation: orient withSupportedOrientations: self->_supportedOrientations];
|
||||
|
||||
if (!_isOrientable)
|
||||
orient = _nonOrientableDefaultOrientation;
|
||||
|
||||
// fix iPhone 5,6 launch images (only in portrait) from being stretched
|
||||
if (isIphone && _isOrientable && !((size.height == 568 || size.width == 568) || (size.height == 667 || size.width == 667)))
|
||||
orient = portrait;
|
||||
|
||||
OrientView([SplashScreenController Instance], _splash, orient);
|
||||
}
|
||||
|
||||
- (BOOL)shouldAutorotate
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSUInteger)supportedInterfaceOrientations
|
||||
{
|
||||
NSUInteger ret = 0;
|
||||
|
||||
if (self->_supportedOrientations.portrait)
|
||||
ret |= (1 << UIInterfaceOrientationPortrait);
|
||||
if (self->_supportedOrientations.portraitUpsideDown)
|
||||
ret |= (1 << UIInterfaceOrientationPortraitUpsideDown);
|
||||
if (self->_supportedOrientations.landscapeLeft)
|
||||
ret |= (1 << UIInterfaceOrientationLandscapeRight);
|
||||
if (self->_supportedOrientations.landscapeRight)
|
||||
ret |= (1 << UIInterfaceOrientationLandscapeLeft);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
+ (SplashScreenController*)Instance
|
||||
{
|
||||
return _controller;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
void ShowSplashScreen(UIWindow* window)
|
||||
{
|
||||
bool hasStoryboard = [[NSBundle mainBundle] pathForResource: @"LaunchScreen" ofType: @"storyboardc"] != nullptr;
|
||||
|
||||
if (hasStoryboard)
|
||||
{
|
||||
UIStoryboard *storyboard = [UIStoryboard storyboardWithName: @"LaunchScreen" bundle: [NSBundle mainBundle]];
|
||||
|
||||
_controller = [storyboard instantiateInitialViewController];
|
||||
window.rootViewController = _controller;
|
||||
}
|
||||
else
|
||||
{
|
||||
_controller = [[SplashScreenController alloc] init];
|
||||
[_controller create: window];
|
||||
}
|
||||
|
||||
[window makeKeyAndVisible];
|
||||
}
|
||||
|
||||
void HideSplashScreen()
|
||||
{
|
||||
if (_splash)
|
||||
{
|
||||
[_splash removeFromSuperview];
|
||||
[_splash FreeSubviews];
|
||||
}
|
||||
|
||||
_splash = nil;
|
||||
_controller = nil;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 or return from cache correct view controller for requested orientation
|
||||
- (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 or return from cache 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
|
||||
@@ -0,0 +1,418 @@
|
||||
#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
|
||||
{
|
||||
_curOrientation = orientation;
|
||||
[_unityView willRotateToOrientation: orientation fromOrientation: (UIInterfaceOrientation)UIInterfaceOrientationUnknown];
|
||||
[_unityView didRotate];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
- (UnityView*)createUnityView
|
||||
{
|
||||
return [[UnityView alloc] initFromMainScreen];
|
||||
}
|
||||
|
||||
- (UIViewController*)createUnityViewControllerDefault
|
||||
{
|
||||
UnityDefaultViewController* ret = [[UnityDefaultViewController alloc] init];
|
||||
#if PLATFORM_TVOS
|
||||
// This enables game controller use in on-screen keyboard
|
||||
ret.controllerUserInteractionEnabled = YES;
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if UNITY_SUPPORT_ROTATION
|
||||
- (UIViewController*)createUnityViewControllerForOrientation:(UIInterfaceOrientation)orient
|
||||
{
|
||||
switch (orient)
|
||||
{
|
||||
case UIInterfaceOrientationPortrait: return [[UnityPortraitOnlyViewController alloc] init];
|
||||
case UIInterfaceOrientationPortraitUpsideDown: return [[UnityPortraitUpsideDownOnlyViewController alloc] init];
|
||||
case UIInterfaceOrientationLandscapeLeft: return [[UnityLandscapeLeftOnlyViewController alloc] init];
|
||||
case UIInterfaceOrientationLandscapeRight: return [[UnityLandscapeRightOnlyViewController alloc] init];
|
||||
|
||||
default: NSAssert(false, @"bad UIInterfaceOrientation provided");
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
- (UIViewController*)createRootViewController
|
||||
{
|
||||
UIViewController* ret = nil;
|
||||
if (!UNITY_SUPPORT_ROTATION || UnityShouldAutorotate())
|
||||
{
|
||||
if (_viewControllerForOrientation[0] == nil)
|
||||
_viewControllerForOrientation[0] = [self createUnityViewControllerDefault];
|
||||
ret = _viewControllerForOrientation[0];
|
||||
}
|
||||
|
||||
#if UNITY_SUPPORT_ROTATION
|
||||
if (ret == nil)
|
||||
{
|
||||
UIInterfaceOrientation orientation = ConvertToIosScreenOrientation((ScreenOrientation)UnityRequestedScreenOrientation());
|
||||
ret = [self createRootViewControllerForOrientation: orientation];
|
||||
}
|
||||
#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
|
||||
{
|
||||
// Snapshot API appeared on iOS 7, however before iOS 8 tweaking hierarchy like that on going to
|
||||
// background results in all kind of weird things when going back to foreground so we do snapshotting
|
||||
// only on iOS 8 and newer.
|
||||
|
||||
// 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
|
||||
#if UNITY_SNAPSHOT_VIEW_ON_APPLICATION_PAUSE
|
||||
return [_rootView snapshotViewAfterScreenUpdates: YES];
|
||||
#else
|
||||
return nil;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (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");
|
||||
|
||||
[_window makeKeyAndVisible];
|
||||
[UIView setAnimationsEnabled: NO];
|
||||
|
||||
// TODO: extract it?
|
||||
|
||||
ShowSplashScreen(_window);
|
||||
|
||||
#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 addSubview: _rootView];
|
||||
_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
|
||||
_rootController = _window.rootViewController = vc;
|
||||
_rootController.view = _rootView;
|
||||
|
||||
_window.bounds = [UIScreen mainScreen].bounds;
|
||||
// required for iOS 8, otherwise view bounds will be incorrect
|
||||
_rootView.bounds = _window.bounds;
|
||||
_rootView.center = _window.center;
|
||||
|
||||
// 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
|
||||
|
||||
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
|
||||
|
||||
- (void)executeForEveryViewController:(void(^)(UIViewController*))callback
|
||||
{
|
||||
for (unsigned i = 0; i < ARRAY_SIZE(_viewControllerForOrientation); ++i)
|
||||
{
|
||||
UIViewController* vc = _viewControllerForOrientation[i];
|
||||
if (vc)
|
||||
callback(vc);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)notifyHideHomeButtonChange
|
||||
{
|
||||
// Note that we need to update all view controllers because UIKit won't necessarily
|
||||
// update the properties of view controllers when orientation is changed.
|
||||
#if PLATFORM_IOS && UNITY_HAS_IOSSDK_11_0
|
||||
if (@available(iOS 11.0, *))
|
||||
{
|
||||
[self executeForEveryViewController: ^(UIViewController* vc)
|
||||
{
|
||||
// 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 ([vc respondsToSelector: @selector(setNeedsUpdateOfHomeIndicatorAutoHidden)])
|
||||
[vc setNeedsUpdateOfHomeIndicatorAutoHidden];
|
||||
}];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)notifyDeferSystemGesturesChange
|
||||
{
|
||||
#if PLATFORM_IOS && UNITY_HAS_IOSSDK_11_0
|
||||
if (@available(iOS 11.0, *))
|
||||
{
|
||||
[self executeForEveryViewController: ^(UIViewController* vc)
|
||||
{
|
||||
[vc setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
|
||||
}];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#if UNITY_SUPPORT_ROTATION
|
||||
|
||||
@implementation UnityAppController (OrientationSupport)
|
||||
- (UIViewController*)createRootViewControllerForOrientation:(UIInterfaceOrientation)orientation
|
||||
{
|
||||
NSAssert(orientation != 0, @"Bad UIInterfaceOrientation provided");
|
||||
if (_viewControllerForOrientation[orientation] == nil)
|
||||
_viewControllerForOrientation[orientation] = [self createUnityViewControllerForOrientation: orientation];
|
||||
return _viewControllerForOrientation[orientation];
|
||||
}
|
||||
|
||||
- (void)checkOrientationRequest
|
||||
{
|
||||
if (!UnityHasOrientationRequest() && !UnityShouldChangeAllowedOrientations())
|
||||
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 (UnityShouldChangeAllowedOrientations())
|
||||
{
|
||||
// so we can say that the only corner case is when we are autorotating and will autorotate
|
||||
// AND changed allowed orientations while keeping current allowed
|
||||
// in that case we simply trigger attemptRotationToDeviceOrientation and we are done
|
||||
// please note that it this can happen when current *device* orientation is disabled (and we want to enable it)
|
||||
|
||||
NSUInteger rootOrient = 1 << UIViewControllerInterfaceOrientation(self.rootViewController);
|
||||
if (UnityShouldAutorotate() && _rootController == _viewControllerForOrientation[0] && (rootOrient & EnabledAutorotationInterfaceOrientations()))
|
||||
{
|
||||
[UIViewController attemptRotationToDeviceOrientation];
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise we recreate default autorotating view controller
|
||||
// please note that below we will check if root controller still equals _viewControllerForOrientation[0]
|
||||
// in that case (we update _viewControllerForOrientation[0]) the check will fail and trigger transition (as expected)
|
||||
// you may look at this check as "are we autorotating with same constraints"
|
||||
_viewControllerForOrientation[0] = [self createUnityViewControllerDefault];
|
||||
changeOrient = true;
|
||||
}
|
||||
|
||||
if (changeOrient)
|
||||
{
|
||||
if (UnityShouldAutorotate())
|
||||
{
|
||||
if (_viewControllerForOrientation[0] == nil)
|
||||
_viewControllerForOrientation[0] = [self createUnityViewControllerDefault];
|
||||
if (_rootController != _viewControllerForOrientation[0])
|
||||
[self transitionToViewController: _viewControllerForOrientation[0]];
|
||||
[UIViewController attemptRotationToDeviceOrientation];
|
||||
}
|
||||
else
|
||||
{
|
||||
ScreenOrientation requestedOrient = (ScreenOrientation)UnityRequestedScreenOrientation();
|
||||
[self orientInterface: ConvertToIosScreenOrientation(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];
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
#import "UnityView.h"
|
||||
#include "UI/Keyboard.h"
|
||||
|
||||
static NSArray* keyboardCommands = nil;
|
||||
static int pressedButton = 0;
|
||||
|
||||
@interface UnityView (Keyboard)
|
||||
@end
|
||||
|
||||
@implementation UnityView (Keyboard)
|
||||
|
||||
- (void)createKeyboard
|
||||
{
|
||||
// only English keyboard layout is supported
|
||||
NSString* baseLayout = @"1234567890-=qwertyuiop[]asdfghjkl;'\\`zxcvbnm,./!@#$%^&*()_+{}:\"|<>?~ \t\r\b\\";
|
||||
NSString* numpadLayout = @"1234567890-=*+/.\r";
|
||||
|
||||
size_t sizeOfKeyboardCommands = baseLayout.length + numpadLayout.length + 11;
|
||||
NSMutableArray* commands = [NSMutableArray arrayWithCapacity: sizeOfKeyboardCommands];
|
||||
|
||||
for (NSInteger i = 0; i < baseLayout.length; ++i)
|
||||
{
|
||||
NSString* input = [baseLayout substringWithRange: NSMakeRange(i, 1)];
|
||||
[commands addObject: [UIKeyCommand keyCommandWithInput: input modifierFlags: kNilOptions action: @selector(handleCommand:)]];
|
||||
}
|
||||
for (NSInteger i = 0; i < numpadLayout.length; ++i)
|
||||
{
|
||||
NSString* input = [numpadLayout substringWithRange: NSMakeRange(i, 1)];
|
||||
[commands addObject: [UIKeyCommand keyCommandWithInput: input modifierFlags: UIKeyModifierNumericPad action: @selector(handleCommand:)]];
|
||||
}
|
||||
|
||||
// up, down, left, right, esc
|
||||
[commands addObject: [UIKeyCommand keyCommandWithInput: UIKeyInputUpArrow modifierFlags: kNilOptions action: @selector(handleCommand:)]];
|
||||
[commands addObject: [UIKeyCommand keyCommandWithInput: UIKeyInputDownArrow modifierFlags: kNilOptions action: @selector(handleCommand:)]];
|
||||
[commands addObject: [UIKeyCommand keyCommandWithInput: UIKeyInputLeftArrow modifierFlags: kNilOptions action: @selector(handleCommand:)]];
|
||||
[commands addObject: [UIKeyCommand keyCommandWithInput: UIKeyInputRightArrow modifierFlags: kNilOptions action: @selector(handleCommand:)]];
|
||||
[commands addObject: [UIKeyCommand keyCommandWithInput: UIKeyInputEscape modifierFlags: kNilOptions action: @selector(handleCommand:)]];
|
||||
|
||||
// caps Lock, shift, control, option, command
|
||||
[commands addObject: [UIKeyCommand keyCommandWithInput: @"" modifierFlags: UIKeyModifierAlphaShift action: @selector(handleCommand:)]];
|
||||
[commands addObject: [UIKeyCommand keyCommandWithInput: @"" modifierFlags: UIKeyModifierShift action: @selector(handleCommand:)]];
|
||||
[commands addObject: [UIKeyCommand keyCommandWithInput: @"" modifierFlags: UIKeyModifierControl action: @selector(handleCommand:)]];
|
||||
[commands addObject: [UIKeyCommand keyCommandWithInput: @"" modifierFlags: UIKeyModifierAlternate action: @selector(handleCommand:)]];
|
||||
[commands addObject: [UIKeyCommand keyCommandWithInput: @"" modifierFlags: UIKeyModifierCommand action: @selector(handleCommand:)]];
|
||||
|
||||
keyboardCommands = commands.copy;
|
||||
}
|
||||
|
||||
// UIKeyCommand can't handle key up events,
|
||||
// So we're simulating key up event in case if any other button is pressed or UIView calls keyCommands method.
|
||||
// Because of this there is no way to handle simultanious key presses.
|
||||
- (void)releaseButton
|
||||
{
|
||||
if (pressedButton != 0)
|
||||
{
|
||||
UnitySetKeyState(pressedButton, false);
|
||||
pressedButton = 0;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray*)keyCommands
|
||||
{
|
||||
[self releaseButton];
|
||||
|
||||
//keyCommands take controll of buttons over UITextView, that's why need to return nil if text input field is active
|
||||
if ([[KeyboardDelegate Instance] status] == Visible)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (keyboardCommands == nil)
|
||||
{
|
||||
[self createKeyboard];
|
||||
}
|
||||
return keyboardCommands;
|
||||
}
|
||||
|
||||
- (bool)isValidCodeForButton:(int)code
|
||||
{
|
||||
return (code > 0 && code < 128);
|
||||
}
|
||||
|
||||
- (void)handleCommand:(UIKeyCommand *)command
|
||||
{
|
||||
[self releaseButton];
|
||||
|
||||
NSString* input = command.input;
|
||||
UIKeyModifierFlags modifierFlags = command.modifierFlags;
|
||||
|
||||
char inputChar = ([input length] > 0) ? [input characterAtIndex: 0] : 0;
|
||||
int code = (int)inputChar; // ASCII code
|
||||
|
||||
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");
|
||||
|
||||
UnitySetKeyState(code, true);
|
||||
pressedButton = code;
|
||||
}
|
||||
|
||||
#if PLATFORM_TVOS
|
||||
- (int)pressTypeToCode:(UIPress *)press
|
||||
{
|
||||
if ([press type] == UIPressTypeUpArrow)
|
||||
return UnityStringToKey("up");
|
||||
else if ([press type] == UIPressTypeDownArrow)
|
||||
return UnityStringToKey("down");
|
||||
else if ([press type] == UIPressTypeRightArrow)
|
||||
return UnityStringToKey("right");
|
||||
else if ([press type] == UIPressTypeLeftArrow)
|
||||
return UnityStringToKey("left");
|
||||
else if ([press type] == UIPressTypeSelect)
|
||||
return UnityStringToKey("joystick button 14");
|
||||
else if ([press type] == UIPressTypePlayPause)
|
||||
return UnityStringToKey("joystick button 15");
|
||||
else if ([press type] == UIPressTypeMenu)
|
||||
return UnityStringToKey("joystick button 0");
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
|
||||
{
|
||||
for (UIPress *press in presses)
|
||||
{
|
||||
UnitySetKeyState([self pressTypeToCode: press], true);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
|
||||
{
|
||||
for (UIPress *press in presses)
|
||||
{
|
||||
UnitySetKeyState([self pressTypeToCode: press], false);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,48 @@
|
||||
#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
|
||||
|
||||
#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);
|
||||
@@ -0,0 +1,233 @@
|
||||
#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;
|
||||
extern bool _supportsMSAA;
|
||||
|
||||
@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(systemRenderSize.width, systemRenderSize.height, _curOrientation);
|
||||
ReportSafeAreaChangeForView(self);
|
||||
|
||||
#if UNITY_CAN_USE_METAL
|
||||
if (UnitySelectedRenderingAPI() == apiMetal)
|
||||
((CAMetalLayer*)self.layer).drawableSize = systemRenderSize;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (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
|
||||
{
|
||||
unsigned requestedW, requestedH; UnityGetRenderingResolution(&requestedW, &requestedH);
|
||||
int requestedMSAA = UnityGetDesiredMSAASampleCount(MSAA_DEFAULT_SAMPLE_COUNT);
|
||||
int requestedSRGB = UnityGetSRGBRequested();
|
||||
int requestedWideColor = UnityGetWideColorRequested();
|
||||
int requestedMemorylessDepth = UnityMetalMemorylessDepth();
|
||||
|
||||
UnityDisplaySurfaceBase* surf = GetMainDisplaySurface();
|
||||
|
||||
if (_shouldRecreateView == YES
|
||||
|| surf->targetW != requestedW || surf->targetH != requestedH
|
||||
|| surf->disableDepthAndStencil != UnityDisableDepthAndStencilBuffers()
|
||||
|| (_supportsMSAA && surf->msaaSamples != requestedMSAA)
|
||||
|| surf->srgb != requestedSRGB
|
||||
|| surf->wideColor != requestedWideColor
|
||||
|| surf->memorylessDepth != requestedMemorylessDepth
|
||||
)
|
||||
{
|
||||
[self recreateRenderingSurface];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)recreateRenderingSurface
|
||||
{
|
||||
if (_renderingInited)
|
||||
{
|
||||
unsigned requestedW, requestedH;
|
||||
UnityGetRenderingResolution(&requestedW, &requestedH);
|
||||
|
||||
RenderingSurfaceParams params =
|
||||
{
|
||||
.msaaSampleCount = UnityGetDesiredMSAASampleCount(MSAA_DEFAULT_SAMPLE_COUNT),
|
||||
.renderW = (int)requestedW,
|
||||
.renderH = (int)requestedH,
|
||||
.srgb = UnityGetSRGBRequested(),
|
||||
.wideColor = UnityGetWideColorRequested(),
|
||||
.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
|
||||
// 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
|
||||
_skipPresent = true;
|
||||
if (!UnityIsPaused())
|
||||
{
|
||||
UnityRepaint();
|
||||
// we are not inside repaint so we need to draw second time ourselves
|
||||
if (_viewIsRotating)
|
||||
UnityRepaint();
|
||||
}
|
||||
_skipPresent = false;
|
||||
}
|
||||
}
|
||||
|
||||
_shouldRecreateView = NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UnityView (Deprecated)
|
||||
- (void)recreateGLESSurfaceIfNeeded { [self recreateRenderingSurfaceIfNeeded]; }
|
||||
- (void)recreateGLESSurface { [self recreateRenderingSurface]; }
|
||||
@end
|
||||
|
||||
static Class UnityRenderingView_LayerClassGLES(id self_, SEL _cmd)
|
||||
{
|
||||
return [CAEAGLLayer class];
|
||||
}
|
||||
|
||||
static Class UnityRenderingView_LayerClassMTL(id self_, SEL _cmd)
|
||||
{
|
||||
return [[NSBundle bundleWithPath: @"/System/Library/Frameworks/QuartzCore.framework"] classNamed: @"CAMetalLayer"];
|
||||
}
|
||||
|
||||
@implementation UnityRenderingView
|
||||
+ (Class)layerClass
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (void)InitializeForAPI:(UnityRenderingAPI)api
|
||||
{
|
||||
IMP layerClassImpl = 0;
|
||||
if (api == apiOpenGLES2 || api == apiOpenGLES3)
|
||||
layerClassImpl = (IMP)UnityRenderingView_LayerClassGLES;
|
||||
else if (api == apiMetal)
|
||||
layerClassImpl = (IMP)UnityRenderingView_LayerClassMTL;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
CGRect ComputeSafeArea(UIView* view)
|
||||
{
|
||||
CGSize screenSize = view.bounds.size;
|
||||
CGRect screenRect = CGRectMake(0, 0, screenSize.width, screenSize.height);
|
||||
|
||||
UIEdgeInsets insets = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||
#if UNITY_HAS_IOSSDK_11_0 || UNITY_HAS_TVOSSDK_11_0
|
||||
if (@available(iOS 11.0, tvOS 11.0, *))
|
||||
insets = [view safeAreaInsets];
|
||||
#endif
|
||||
|
||||
screenRect.origin.x += insets.left;
|
||||
screenRect.origin.y += insets.bottom; // Unity uses bottom left as the origin
|
||||
screenRect.size.width -= insets.left + insets.right;
|
||||
screenRect.size.height -= insets.top + insets.bottom;
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
@interface UnityViewControllerBase (iOS)
|
||||
- (BOOL)shouldAutorotate;
|
||||
|
||||
- (BOOL)prefersStatusBarHidden;
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle;
|
||||
|
||||
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator;
|
||||
@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
|
||||
{
|
||||
}
|
||||
@end
|
||||
|
||||
NSUInteger EnabledAutorotationInterfaceOrientations();
|
||||
@@ -0,0 +1,190 @@
|
||||
#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();
|
||||
}
|
||||
|
||||
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
|
||||
{
|
||||
ScreenOrientation curOrient = UIViewControllerOrientation(self);
|
||||
ScreenOrientation newOrient = OrientationAfterTransform(curOrient, [coordinator targetTransform]);
|
||||
|
||||
// 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 UnityDefaultViewController
|
||||
- (NSUInteger)supportedInterfaceOrientations
|
||||
{
|
||||
NSAssert(UnityShouldAutorotate(), @"UnityDefaultViewController should be used only if unity is set to autorotate");
|
||||
|
||||
return EnabledAutorotationInterfaceOrientations();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UnityPortraitOnlyViewController
|
||||
- (NSUInteger)supportedInterfaceOrientations
|
||||
{
|
||||
return 1 << UIInterfaceOrientationPortrait;
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[GetAppController() updateAppOrientation: UIInterfaceOrientationPortrait];
|
||||
[super viewWillAppear: animated];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UnityPortraitUpsideDownOnlyViewController
|
||||
- (NSUInteger)supportedInterfaceOrientations
|
||||
{
|
||||
return 1 << UIInterfaceOrientationPortraitUpsideDown;
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[GetAppController() updateAppOrientation: UIInterfaceOrientationPortraitUpsideDown];
|
||||
[super viewWillAppear: animated];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UnityLandscapeLeftOnlyViewController
|
||||
- (NSUInteger)supportedInterfaceOrientations
|
||||
{
|
||||
return 1 << UIInterfaceOrientationLandscapeLeft;
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[GetAppController() updateAppOrientation: UIInterfaceOrientationLandscapeLeft];
|
||||
[super viewWillAppear: animated];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UnityLandscapeRightOnlyViewController
|
||||
- (NSUInteger)supportedInterfaceOrientations
|
||||
{
|
||||
return 1 << UIInterfaceOrientationLandscapeRight;
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[GetAppController() updateAppOrientation: UIInterfaceOrientationLandscapeRight];
|
||||
[super viewWillAppear: animated];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NSUInteger EnabledAutorotationInterfaceOrientations()
|
||||
{
|
||||
NSUInteger ret = 0;
|
||||
|
||||
if (UnityIsOrientationEnabled(portrait))
|
||||
ret |= (1 << UIInterfaceOrientationPortrait);
|
||||
if (UnityIsOrientationEnabled(portraitUpsideDown))
|
||||
ret |= (1 << UIInterfaceOrientationPortraitUpsideDown);
|
||||
if (UnityIsOrientationEnabled(landscapeLeft))
|
||||
ret |= (1 << UIInterfaceOrientationLandscapeRight);
|
||||
if (UnityIsOrientationEnabled(landscapeRight))
|
||||
ret |= (1 << UIInterfaceOrientationLandscapeLeft);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif // PLATFORM_IOS
|
||||
@@ -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
|
||||
@@ -0,0 +1,9 @@
|
||||
#if PLATFORM_TVOS
|
||||
|
||||
#import "UnityViewControllerBase.h"
|
||||
#import "UnityAppController.h"
|
||||
|
||||
@implementation UnityDefaultViewController
|
||||
@end
|
||||
|
||||
#endif // PLATFORM_TVOS
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#import <UIKit/UIKit.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
|
||||
{
|
||||
}
|
||||
- (void)viewWillLayoutSubviews;
|
||||
- (void)viewDidLayoutSubviews;
|
||||
- (void)viewDidDisappear:(BOOL)animated;
|
||||
- (void)viewWillDisappear:(BOOL)animated;
|
||||
- (void)viewDidAppear:(BOOL)animated;
|
||||
- (void)viewWillAppear:(BOOL)animated;
|
||||
@end
|
||||
|
||||
#if PLATFORM_IOS
|
||||
#include "UnityViewControllerBase+iOS.h"
|
||||
#elif PLATFORM_TVOS
|
||||
#include "UnityViewControllerBase+tvOS.h"
|
||||
#endif
|
||||
@@ -0,0 +1,44 @@
|
||||
#import "UnityViewControllerBase.h"
|
||||
#import "UnityAppController.h"
|
||||
#import "UnityAppController+ViewHandling.h"
|
||||
#import "PluginBase/UnityViewControllerListener.h"
|
||||
|
||||
@implementation UnityViewControllerBase
|
||||
|
||||
- (void)viewWillLayoutSubviews
|
||||
{
|
||||
[super viewWillLayoutSubviews];
|
||||
AppController_SendUnityViewControllerNotification(kUnityViewWillLayoutSubviews);
|
||||
}
|
||||
|
||||
- (void)viewDidLayoutSubviews
|
||||
{
|
||||
[super viewDidLayoutSubviews];
|
||||
AppController_SendUnityViewControllerNotification(kUnityViewDidLayoutSubviews);
|
||||
}
|
||||
|
||||
- (void)viewDidDisappear:(BOOL)animated
|
||||
{
|
||||
[super viewDidDisappear: animated];
|
||||
AppController_SendUnityViewControllerNotification(kUnityViewDidDisappear);
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated
|
||||
{
|
||||
[super viewWillDisappear: animated];
|
||||
AppController_SendUnityViewControllerNotification(kUnityViewWillDisappear);
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated
|
||||
{
|
||||
[super viewDidAppear: animated];
|
||||
AppController_SendUnityViewControllerNotification(kUnityViewDidAppear);
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear: animated];
|
||||
AppController_SendUnityViewControllerNotification(kUnityViewWillAppear);
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user