SDL  2.0
SDL_uikitviewcontroller.m
Go to the documentation of this file.
1 /*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
4 
5  This software is provided 'as-is', without any express or implied
6  warranty. In no event will the authors be held liable for any damages
7  arising from the use of this software.
8 
9  Permission is granted to anyone to use this software for any purpose,
10  including commercial applications, and to alter it and redistribute it
11  freely, subject to the following restrictions:
12 
13  1. The origin of this software must not be misrepresented; you must not
14  claim that you wrote the original software. If you use this software
15  in a product, an acknowledgment in the product documentation would be
16  appreciated but is not required.
17  2. Altered source versions must be plainly marked as such, and must not be
18  misrepresented as being the original software.
19  3. This notice may not be removed or altered from any source distribution.
20 */
21 #include "../../SDL_internal.h"
22 
23 #if SDL_VIDEO_DRIVER_UIKIT
24 
25 #include "SDL_video.h"
26 #include "SDL_assert.h"
27 #include "SDL_hints.h"
28 #include "../SDL_sysvideo.h"
29 #include "../../events/SDL_events_c.h"
30 
32 #import "SDL_uikitmessagebox.h"
33 #include "SDL_uikitvideo.h"
34 #include "SDL_uikitmodes.h"
35 #include "SDL_uikitwindow.h"
36 #include "SDL_uikitopengles.h"
37 
38 #if SDL_IPHONE_KEYBOARD
39 #include "keyinfotable.h"
40 #endif
41 
42 #if TARGET_OS_TV
43 static void SDLCALL
44 SDL_AppleTVControllerUIHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
45 {
46  @autoreleasepool {
47  SDL_uikitviewcontroller *viewcontroller = (__bridge SDL_uikitviewcontroller *) userdata;
48  viewcontroller.controllerUserInteractionEnabled = hint && (*hint != '0');
49  }
50 }
51 #endif
52 
53 #if !TARGET_OS_TV
54 static void SDLCALL
55 SDL_HideHomeIndicatorHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
56 {
57  @autoreleasepool {
58  SDL_uikitviewcontroller *viewcontroller = (__bridge SDL_uikitviewcontroller *) userdata;
59  viewcontroller.homeIndicatorHidden = (hint && *hint) ? SDL_atoi(hint) : -1;
60 #pragma clang diagnostic push
61 #pragma clang diagnostic ignored "-Wunguarded-availability-new"
62  if ([viewcontroller respondsToSelector:@selector(setNeedsUpdateOfHomeIndicatorAutoHidden)]) {
63  [viewcontroller setNeedsUpdateOfHomeIndicatorAutoHidden];
64  [viewcontroller setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
65  }
66 #pragma clang diagnostic pop
67  }
68 }
69 #endif
70 
71 @implementation SDL_uikitviewcontroller {
72  CADisplayLink *displayLink;
73  int animationInterval;
74  void (*animationCallback)(void*);
75  void *animationCallbackParam;
76 
77 #if SDL_IPHONE_KEYBOARD
78  UITextField *textField;
79  BOOL hardwareKeyboard;
80  BOOL showingKeyboard;
81  BOOL rotatingOrientation;
82  NSString *changeText;
83  NSString *obligateForBackspace;
84 #endif
85 }
86 
87 @synthesize window;
88 
89 - (instancetype)initWithSDLWindow:(SDL_Window *)_window
90 {
91  if (self = [super initWithNibName:nil bundle:nil]) {
92  self.window = _window;
93 
94 #if SDL_IPHONE_KEYBOARD
95  [self initKeyboard];
96  hardwareKeyboard = NO;
97  showingKeyboard = NO;
98  rotatingOrientation = NO;
99 #endif
100 
101 #if TARGET_OS_TV
103  SDL_AppleTVControllerUIHintChanged,
104  (__bridge void *) self);
105 #endif
106 
107 #if !TARGET_OS_TV
109  SDL_HideHomeIndicatorHintChanged,
110  (__bridge void *) self);
111 #endif
112  }
113  return self;
114 }
115 
116 - (void)dealloc
117 {
118 #if SDL_IPHONE_KEYBOARD
119  [self deinitKeyboard];
120 #endif
121 
122 #if TARGET_OS_TV
124  SDL_AppleTVControllerUIHintChanged,
125  (__bridge void *) self);
126 #endif
127 
128 #if !TARGET_OS_TV
130  SDL_HideHomeIndicatorHintChanged,
131  (__bridge void *) self);
132 #endif
133 }
134 
135 - (void)setAnimationCallback:(int)interval
136  callback:(void (*)(void*))callback
137  callbackParam:(void*)callbackParam
138 {
139  [self stopAnimation];
140 
141  animationInterval = interval;
142  animationCallback = callback;
143  animationCallbackParam = callbackParam;
144 
145  if (animationCallback) {
146  [self startAnimation];
147  }
148 }
149 
151 {
152  displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(doLoop:)];
153 
154 #ifdef __IPHONE_10_3
155  SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
156 
157  if ([displayLink respondsToSelector:@selector(preferredFramesPerSecond)]
158  && data != nil && data.uiwindow != nil
159  && [data.uiwindow.screen respondsToSelector:@selector(maximumFramesPerSecond)]) {
160  displayLink.preferredFramesPerSecond = data.uiwindow.screen.maximumFramesPerSecond / animationInterval;
161  } else
162 #endif
163  {
164 #if __IPHONE_OS_VERSION_MIN_REQUIRED < 100300
165  [displayLink setFrameInterval:animationInterval];
166 #endif
167  }
168 
169  [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
170 }
171 
173 {
174  [displayLink invalidate];
175  displayLink = nil;
176 }
177 
178 - (void)doLoop:(CADisplayLink*)sender
179 {
180  /* Don't run the game loop while a messagebox is up */
181  if (!UIKit_ShowingMessageBox()) {
182  /* See the comment in the function definition. */
184 
185  animationCallback(animationCallbackParam);
186  }
187 }
188 
189 - (void)loadView
190 {
191  /* Do nothing. */
192 }
193 
195 {
196  const CGSize size = self.view.bounds.size;
197  int w = (int) size.width;
198  int h = (int) size.height;
199 
201 }
202 
203 #if !TARGET_OS_TV
204 - (NSUInteger)supportedInterfaceOrientations
205 {
207 }
208 
209 #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0
210 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orient
211 {
212  return ([self supportedInterfaceOrientations] & (1 << orient)) != 0;
213 }
214 #endif
215 
217 {
218  BOOL hidden = (window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_BORDERLESS)) != 0;
219  return hidden;
220 }
221 
223 {
224  BOOL hidden = NO;
225  if (self.homeIndicatorHidden == 1) {
226  hidden = YES;
227  }
228  return hidden;
229 }
230 
232 {
233  if (self.homeIndicatorHidden >= 0) {
234  if (self.homeIndicatorHidden == 2) {
235  return UIRectEdgeAll;
236  } else {
237  return UIRectEdgeNone;
238  }
239  }
240 
241  /* By default, fullscreen and borderless windows get all screen gestures */
242  if ((window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_BORDERLESS)) != 0) {
243  return UIRectEdgeAll;
244  } else {
245  return UIRectEdgeNone;
246  }
247 }
248 #endif
249 
250 /*
251  ---- Keyboard related functionality below this line ----
252  */
253 #if SDL_IPHONE_KEYBOARD
254 
255 @synthesize textInputRect;
256 @synthesize keyboardHeight;
257 @synthesize keyboardVisible;
258 
259 /* Set ourselves up as a UITextFieldDelegate */
260 - (void)initKeyboard
261 {
262  changeText = nil;
263  obligateForBackspace = @" "; /* 64 space */
264  textField = [[UITextField alloc] initWithFrame:CGRectZero];
265  textField.delegate = self;
266  /* placeholder so there is something to delete! */
267  textField.text = obligateForBackspace;
268 
269  /* set UITextInputTrait properties, mostly to defaults */
270  textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
271  textField.autocorrectionType = UITextAutocorrectionTypeNo;
272  textField.enablesReturnKeyAutomatically = NO;
273  textField.keyboardAppearance = UIKeyboardAppearanceDefault;
274  textField.keyboardType = UIKeyboardTypeDefault;
275  textField.returnKeyType = UIReturnKeyDefault;
276  textField.secureTextEntry = NO;
277 
278  textField.hidden = YES;
279  keyboardVisible = NO;
280 
281  NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
282 #if !TARGET_OS_TV
283  [center addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
284  [center addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
285 #endif
286  [center addObserver:self selector:@selector(textFieldTextDidChange:) name:UITextFieldTextDidChangeNotification object:nil];
287 }
288 
289 - (NSArray *) keyCommands {
290  NSMutableArray *commands = [[NSMutableArray alloc] init];
291  [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
292  [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
293  [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
294  [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
295  [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputEscape modifierFlags:kNilOptions action:@selector(handleCommand:)]];
296  return [NSArray arrayWithArray:commands];
297 }
298 
299 - (void) handleCommand: (UIKeyCommand *) keyCommand {
301 
302  if (keyCommand.input == UIKeyInputUpArrow) {
303  scancode = SDL_SCANCODE_UP;
304  } else if (keyCommand.input == UIKeyInputDownArrow) {
305  scancode = SDL_SCANCODE_DOWN;
306  } else if (keyCommand.input == UIKeyInputLeftArrow) {
307  scancode = SDL_SCANCODE_LEFT;
308  } else if (keyCommand.input == UIKeyInputRightArrow) {
309  scancode = SDL_SCANCODE_RIGHT;
310  } else if (keyCommand.input == UIKeyInputEscape) {
311  scancode = SDL_SCANCODE_ESCAPE;
312  }
313 
314  if (scancode != SDL_SCANCODE_UNKNOWN) {
315  SDL_SendKeyboardKey(SDL_PRESSED, scancode);
317  }
318 }
319 
320 - (void) downArrow: (UIKeyCommand *) keyCommand {
321  NSLog(@"down arrow!");
322 }
323 
324 - (void)setView:(UIView *)view
325 {
326  [super setView:view];
327 
328  [view addSubview:textField];
329 
330  if (keyboardVisible) {
331  [self showKeyboard];
332  }
333 }
334 
335 /* willRotateToInterfaceOrientation and didRotateFromInterfaceOrientation are deprecated in iOS 8+ in favor of viewWillTransitionToSize */
336 #if TARGET_OS_TV || __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000
337 - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
338 {
339  [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
340  rotatingOrientation = YES;
341  [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {}
342  completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
343  rotatingOrientation = NO;
344  }];
345 }
346 #else
347 - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
348  [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
349  rotatingOrientation = YES;
350 }
351 
352 - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
353  [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
354  rotatingOrientation = NO;
355 }
356 #endif /* TARGET_OS_TV || __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000 */
357 
358 - (void)deinitKeyboard
359 {
360  NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
361 #if !TARGET_OS_TV
362  [center removeObserver:self name:UIKeyboardWillShowNotification object:nil];
363  [center removeObserver:self name:UIKeyboardWillHideNotification object:nil];
364 #endif
365  [center removeObserver:self name:UITextFieldTextDidChangeNotification object:nil];
366 }
367 
368 /* reveal onscreen virtual keyboard */
369 - (void)showKeyboard
370 {
371  keyboardVisible = YES;
372  if (textField.window) {
373  showingKeyboard = YES;
374  [textField becomeFirstResponder];
375  showingKeyboard = NO;
376  }
377 }
378 
379 /* hide onscreen virtual keyboard */
380 - (void)hideKeyboard
381 {
382  keyboardVisible = NO;
383  [textField resignFirstResponder];
384 }
385 
386 - (void)keyboardWillShow:(NSNotification *)notification
387 {
388 #if !TARGET_OS_TV
389  CGRect kbrect = [[notification userInfo][UIKeyboardFrameEndUserInfoKey] CGRectValue];
390 
391  /* The keyboard rect is in the coordinate space of the screen/window, but we
392  * want its height in the coordinate space of the view. */
393  kbrect = [self.view convertRect:kbrect fromView:nil];
394 
395  [self setKeyboardHeight:(int)kbrect.size.height];
396 #endif
397 }
398 
399 - (void)keyboardWillHide:(NSNotification *)notification
400 {
401  if (!showingKeyboard && !rotatingOrientation) {
403  }
404  [self setKeyboardHeight:0];
405 }
406 
407 - (void)textFieldTextDidChange:(NSNotification *)notification
408 {
409  if (changeText!=nil && textField.markedTextRange == nil)
410  {
411  NSUInteger len = changeText.length;
412  if (len > 0) {
413  /* Go through all the characters in the string we've been sent and
414  * convert them to key presses */
415  int i;
416  for (i = 0; i < len; i++) {
417  unichar c = [changeText characterAtIndex:i];
418  SDL_Scancode code;
419  Uint16 mod;
420 
421  if (c < 127) {
422  /* Figure out the SDL_Scancode and SDL_keymod for this unichar */
423  code = unicharToUIKeyInfoTable[c].code;
424  mod = unicharToUIKeyInfoTable[c].mod;
425  } else {
426  /* We only deal with ASCII right now */
427  code = SDL_SCANCODE_UNKNOWN;
428  mod = 0;
429  }
430 
431  if (mod & KMOD_SHIFT) {
432  /* If character uses shift, press shift down */
434  }
435 
436  /* send a keydown and keyup even for the character */
439 
440  if (mod & KMOD_SHIFT) {
441  /* If character uses shift, press shift back up */
443  }
444  }
445  SDL_SendKeyboardText([changeText UTF8String]);
446  }
447  changeText = nil;
448  }
449 }
450 
451 - (void)updateKeyboard
452 {
453  CGAffineTransform t = self.view.transform;
454  CGPoint offset = CGPointMake(0.0, 0.0);
455  CGRect frame = UIKit_ComputeViewFrame(window, self.view.window.screen);
456 
457  if (self.keyboardHeight) {
458  int rectbottom = self.textInputRect.y + self.textInputRect.h;
459  int keybottom = self.view.bounds.size.height - self.keyboardHeight;
460  if (keybottom < rectbottom) {
461  offset.y = keybottom - rectbottom;
462  }
463  }
464 
465  /* Apply this view's transform (except any translation) to the offset, in
466  * order to orient it correctly relative to the frame's coordinate space. */
467  t.tx = 0.0;
468  t.ty = 0.0;
469  offset = CGPointApplyAffineTransform(offset, t);
470 
471  /* Apply the updated offset to the view's frame. */
472  frame.origin.x += offset.x;
473  frame.origin.y += offset.y;
474 
475  self.view.frame = frame;
476 }
477 
478 - (void)setKeyboardHeight:(int)height
479 {
480  keyboardVisible = height > 0;
481  keyboardHeight = height;
482  [self updateKeyboard];
483 }
484 
485 /* UITextFieldDelegate method. Invoked when user types something. */
486 - (BOOL)textField:(UITextField *)_textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
487 {
488  NSUInteger len = string.length;
489  if (len == 0) {
490  changeText = nil;
491  if (textField.markedTextRange == nil) {
492  /* it wants to replace text with nothing, ie a delete */
495  }
496  if (textField.text.length < 16) {
497  textField.text = obligateForBackspace;
498  }
499  } else {
500  changeText = string;
501  }
502  return YES;
503 }
504 
505 /* Terminates the editing session */
506 - (BOOL)textFieldShouldReturn:(UITextField*)_textField
507 {
510  if (keyboardVisible &&
513  }
514  return YES;
515 }
516 
517 #endif
518 
519 @end
520 
521 /* iPhone keyboard addition functions */
522 #if SDL_IPHONE_KEYBOARD
523 
525 GetWindowViewController(SDL_Window * window)
526 {
527  if (!window || !window->driverdata) {
528  SDL_SetError("Invalid window");
529  return nil;
530  }
531 
532  SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata;
533 
534  return data.viewcontroller;
535 }
536 
537 SDL_bool
538 UIKit_HasScreenKeyboardSupport(_THIS)
539 {
540  return SDL_TRUE;
541 }
542 
543 void
544 UIKit_ShowScreenKeyboard(_THIS, SDL_Window *window)
545 {
546  @autoreleasepool {
547  SDL_uikitviewcontroller *vc = GetWindowViewController(window);
548  [vc showKeyboard];
549  }
550 }
551 
552 void
553 UIKit_HideScreenKeyboard(_THIS, SDL_Window *window)
554 {
555  @autoreleasepool {
556  SDL_uikitviewcontroller *vc = GetWindowViewController(window);
557  [vc hideKeyboard];
558  }
559 }
560 
561 SDL_bool
562 UIKit_IsScreenKeyboardShown(_THIS, SDL_Window *window)
563 {
564  @autoreleasepool {
565  SDL_uikitviewcontroller *vc = GetWindowViewController(window);
566  if (vc != nil) {
567  return vc.keyboardVisible;
568  }
569  return SDL_FALSE;
570  }
571 }
572 
573 void
574 UIKit_SetTextInputRect(_THIS, SDL_Rect *rect)
575 {
576  if (!rect) {
577  SDL_InvalidParamError("rect");
578  return;
579  }
580 
581  @autoreleasepool {
582  SDL_uikitviewcontroller *vc = GetWindowViewController(SDL_GetFocusWindow());
583  if (vc != nil) {
584  vc.textInputRect = *rect;
585 
586  if (vc.keyboardVisible) {
587  [vc updateKeyboard];
588  }
589  }
590  }
591 }
592 
593 
594 #endif /* SDL_IPHONE_KEYBOARD */
595 
596 #endif /* SDL_VIDEO_DRIVER_UIKIT */
597 
598 /* vi: set ts=4 sw=4 expandtab: */
GLsizei const GLubyte * commands
UIRectEdge preferredScreenEdgesDeferringSystemGestures()
GLsizei const GLchar *const * string
#define SDL_HINT_RETURN_KEY_HIDES_IME
A variable to control whether the return key on the soft keyboard should hide the soft keyboard on An...
Definition: SDL_hints.h:897
GLint GLint GLint GLint GLint x
Definition: SDL_opengl.h:1574
SDL_Rect rect
Definition: testrelative.c:27
GLfloat GLfloat GLfloat GLfloat h
SDL_uikitviewcontroller * viewcontroller
SDL_Scancode code
Definition: keyinfotable.h:36
uint16_t Uint16
Definition: SDL_stdinc.h:191
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const GLvoid * data
Definition: SDL_opengl.h:1974
GLintptr offset
int SDL_SendWindowEvent(SDL_Window *window, Uint8 windowevent, int data1, int data2)
#define SDL_InvalidParamError(param)
Definition: SDL_error.h:54
NSUInteger UIKit_GetSupportedOrientations(SDL_Window *window)
GLenum GLsizei len
GLuint const GLchar * name
void UIKit_GL_RestoreCurrentContext(void)
int SDL_SendKeyboardKey(Uint8 state, SDL_Scancode scancode)
Definition: SDL_keyboard.c:679
#define SDL_GetHintBoolean
#define SDL_StopTextInput
#define KMOD_SHIFT
Definition: SDL_keycode.h:343
#define _THIS
int SDL_SendKeyboardText(const char *text)
Definition: SDL_keyboard.c:789
int frame
Definition: teststreaming.c:60
SDL_Window * SDL_GetFocusWindow(void)
Definition: SDL_video.c:2696
static UIKitKeyInfo unicharToUIKeyInfoTable[]
Definition: keyinfotable.h:41
const GLubyte * c
GLubyte GLubyte GLubyte GLubyte w
static Uint32 callback(Uint32 interval, void *param)
Definition: testtimer.c:34
GLint GLint GLint GLint GLint GLint y
Definition: SDL_opengl.h:1574
UIWindow * uiwindow
#define SDL_atoi
GLsizeiptr size
return Display return Display Bool Bool int int int return Display XEvent Bool(*) XPointer return Display return Display Drawable _Xconst char unsigned int unsigned int return Display Pixmap Pixmap XColor XColor unsigned int unsigned int return Display _Xconst char char int char return Display Visual unsigned int int int char unsigned int unsigned int in i)
Definition: SDL_x11sym.h:50
SDL_bool
Definition: SDL_stdinc.h:161
#define SDL_SetError
GLint GLint GLsizei GLsizei height
Definition: SDL_opengl.h:1572
#define SDL_HINT_IOS_HIDE_HOME_INDICATOR
A variable controlling whether the home indicator bar on iPhone X should be hidden.
Definition: SDL_hints.h:399
EGLSurface EGLNativeWindowType * window
Definition: eglext.h:1025
SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char const char SDL_SCANF_FORMAT_STRING const char return SDL_ThreadFunction const char void return Uint32 return Uint32 void
The type used to identify a window.
Definition: SDL_sysvideo.h:73
#define SDL_AddHintCallback
#define SDL_DelHintCallback
void * driverdata
Definition: SDL_sysvideo.h:111
#define SDL_PRESSED
Definition: SDL_events.h:50
#define SDL_RELEASED
Definition: SDL_events.h:49
#define SDLCALL
Definition: SDL_internal.h:49
SDL_Scancode
The SDL keyboard scancode representation.
Definition: SDL_scancode.h:43
#define SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS
A variable controlling whether controllers used with the Apple TV generate UI events.
Definition: SDL_hints.h:378
GLdouble GLdouble t
Definition: SDL_opengl.h:2071
A rectangle, with the origin at the upper left (integer).
Definition: SDL_rect.h:77
NSUInteger supportedInterfaceOrientations()