SDL  2.0
SDL_uikitviewcontroller.m
Go to the documentation of this file.
1 /*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2018 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  if (@available(iOS 11.0, *)) {
61  [viewcontroller setNeedsUpdateOfHomeIndicatorAutoHidden];
62  [viewcontroller setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
63  }
64  }
65 }
66 #endif
67 
68 @implementation SDL_uikitviewcontroller {
69  CADisplayLink *displayLink;
70  int animationInterval;
71  void (*animationCallback)(void*);
72  void *animationCallbackParam;
73 
74 #if SDL_IPHONE_KEYBOARD
75  UITextField *textField;
76  BOOL rotatingOrientation;
77  NSString *changeText;
78  NSString *obligateForBackspace;
79 #endif
80 }
81 
82 @synthesize window;
83 
84 - (instancetype)initWithSDLWindow:(SDL_Window *)_window
85 {
86  if (self = [super initWithNibName:nil bundle:nil]) {
87  self.window = _window;
88 
89 #if SDL_IPHONE_KEYBOARD
90  [self initKeyboard];
91  rotatingOrientation = FALSE;
92 #endif
93 
94 #if TARGET_OS_TV
96  SDL_AppleTVControllerUIHintChanged,
97  (__bridge void *) self);
98 #endif
99 
100 #if !TARGET_OS_TV
102  SDL_HideHomeIndicatorHintChanged,
103  (__bridge void *) self);
104 #endif
105  }
106  return self;
107 }
108 
109 - (void)dealloc
110 {
111 #if SDL_IPHONE_KEYBOARD
112  [self deinitKeyboard];
113 #endif
114 
115 #if TARGET_OS_TV
117  SDL_AppleTVControllerUIHintChanged,
118  (__bridge void *) self);
119 #endif
120 
121 #if !TARGET_OS_TV
123  SDL_HideHomeIndicatorHintChanged,
124  (__bridge void *) self);
125 #endif
126 }
127 
128 - (void)setAnimationCallback:(int)interval
129  callback:(void (*)(void*))callback
130  callbackParam:(void*)callbackParam
131 {
132  [self stopAnimation];
133 
134  animationInterval = interval;
135  animationCallback = callback;
136  animationCallbackParam = callbackParam;
137 
138  if (animationCallback) {
139  [self startAnimation];
140  }
141 }
142 
144 {
145  displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(doLoop:)];
146 
147 #ifdef __IPHONE_10_3
148  SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
149 
150  if ([displayLink respondsToSelector:@selector(preferredFramesPerSecond)]
151  && data != nil && data.uiwindow != nil
152  && [data.uiwindow.screen respondsToSelector:@selector(maximumFramesPerSecond)]) {
153  displayLink.preferredFramesPerSecond = data.uiwindow.screen.maximumFramesPerSecond / animationInterval;
154  } else
155 #endif
156  {
157 #if __IPHONE_OS_VERSION_MIN_REQUIRED < 100300
158  [displayLink setFrameInterval:animationInterval];
159 #endif
160  }
161 
162  [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
163 }
164 
166 {
167  [displayLink invalidate];
168  displayLink = nil;
169 }
170 
171 - (void)doLoop:(CADisplayLink*)sender
172 {
173  /* Don't run the game loop while a messagebox is up */
174  if (!UIKit_ShowingMessageBox()) {
175  /* See the comment in the function definition. */
177 
178  animationCallback(animationCallbackParam);
179  }
180 }
181 
182 - (void)loadView
183 {
184  /* Do nothing. */
185 }
186 
188 {
189  const CGSize size = self.view.bounds.size;
190  int w = (int) size.width;
191  int h = (int) size.height;
192 
194 }
195 
196 #if !TARGET_OS_TV
197 - (NSUInteger)supportedInterfaceOrientations
198 {
200 }
201 
202 #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0
203 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orient
204 {
205  return ([self supportedInterfaceOrientations] & (1 << orient)) != 0;
206 }
207 #endif
208 
210 {
211  BOOL hidden = (window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_BORDERLESS)) != 0;
212  return hidden;
213 }
214 
216 {
217  BOOL hidden = NO;
218  if (self.homeIndicatorHidden == 1) {
219  hidden = YES;
220  }
221  return hidden;
222 }
223 
225 {
226  if (self.homeIndicatorHidden >= 0) {
227  if (self.homeIndicatorHidden == 2) {
228  return UIRectEdgeAll;
229  } else {
230  return UIRectEdgeNone;
231  }
232  }
233 
234  /* By default, fullscreen and borderless windows get all screen gestures */
235  if ((window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_BORDERLESS)) != 0) {
236  return UIRectEdgeAll;
237  } else {
238  return UIRectEdgeNone;
239  }
240 }
241 #endif
242 
243 /*
244  ---- Keyboard related functionality below this line ----
245  */
246 #if SDL_IPHONE_KEYBOARD
247 
248 @synthesize textInputRect;
249 @synthesize keyboardHeight;
250 @synthesize keyboardVisible;
251 
252 /* Set ourselves up as a UITextFieldDelegate */
253 - (void)initKeyboard
254 {
255  changeText = nil;
256  obligateForBackspace = @" "; /* 64 space */
257  textField = [[UITextField alloc] initWithFrame:CGRectZero];
258  textField.delegate = self;
259  /* placeholder so there is something to delete! */
260  textField.text = obligateForBackspace;
261 
262  /* set UITextInputTrait properties, mostly to defaults */
263  textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
264  textField.autocorrectionType = UITextAutocorrectionTypeNo;
265  textField.enablesReturnKeyAutomatically = NO;
266  textField.keyboardAppearance = UIKeyboardAppearanceDefault;
267  textField.keyboardType = UIKeyboardTypeDefault;
268  textField.returnKeyType = UIReturnKeyDefault;
269  textField.secureTextEntry = NO;
270 
271  textField.hidden = YES;
272  keyboardVisible = NO;
273 
274  NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
275 #if !TARGET_OS_TV
276  [center addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
277  [center addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
278 #endif
279  [center addObserver:self selector:@selector(textFieldTextDidChange:) name:UITextFieldTextDidChangeNotification object:nil];
280 }
281 
282 - (void)setView:(UIView *)view
283 {
284  [super setView:view];
285 
286  [view addSubview:textField];
287 
288  if (keyboardVisible) {
289  [self showKeyboard];
290  }
291 }
292 
293 /* willRotateToInterfaceOrientation and didRotateFromInterfaceOrientation are deprecated in iOS 8+ in favor of viewWillTransitionToSize */
294 #if TARGET_OS_TV || __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000
295 - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
296 {
297  [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
298  rotatingOrientation = TRUE;
299  [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {}
300  completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
301  rotatingOrientation = FALSE;
302  }];
303 }
304 #else
305 - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
306  [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
307  rotatingOrientation = TRUE;
308 }
309 
310 - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
311  [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
312  rotatingOrientation = FALSE;
313 }
314 #endif /* TARGET_OS_TV || __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000 */
315 
316 - (void)deinitKeyboard
317 {
318  NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
319 #if !TARGET_OS_TV
320  [center removeObserver:self name:UIKeyboardWillShowNotification object:nil];
321  [center removeObserver:self name:UIKeyboardWillHideNotification object:nil];
322 #endif
323  [center removeObserver:self name:UITextFieldTextDidChangeNotification object:nil];
324 }
325 
326 /* reveal onscreen virtual keyboard */
327 - (void)showKeyboard
328 {
329  keyboardVisible = YES;
330  if (textField.window) {
331  [textField becomeFirstResponder];
332  }
333 }
334 
335 /* hide onscreen virtual keyboard */
336 - (void)hideKeyboard
337 {
338  keyboardVisible = NO;
339  [textField resignFirstResponder];
340 }
341 
342 - (void)keyboardWillShow:(NSNotification *)notification
343 {
344 #if !TARGET_OS_TV
345  CGRect kbrect = [[notification userInfo][UIKeyboardFrameEndUserInfoKey] CGRectValue];
346 
347  /* The keyboard rect is in the coordinate space of the screen/window, but we
348  * want its height in the coordinate space of the view. */
349  kbrect = [self.view convertRect:kbrect fromView:nil];
350 
351  [self setKeyboardHeight:(int)kbrect.size.height];
352 #endif
353 }
354 
355 - (void)keyboardWillHide:(NSNotification *)notification
356 {
357  if (!rotatingOrientation) {
359  }
360  [self setKeyboardHeight:0];
361 }
362 
363 - (void)textFieldTextDidChange:(NSNotification *)notification
364 {
365  if (changeText!=nil && textField.markedTextRange == nil)
366  {
367  NSUInteger len = changeText.length;
368  if (len > 0) {
369  /* Go through all the characters in the string we've been sent and
370  * convert them to key presses */
371  int i;
372  for (i = 0; i < len; i++) {
373  unichar c = [changeText characterAtIndex:i];
374  SDL_Scancode code;
375  Uint16 mod;
376 
377  if (c < 127) {
378  /* Figure out the SDL_Scancode and SDL_keymod for this unichar */
379  code = unicharToUIKeyInfoTable[c].code;
380  mod = unicharToUIKeyInfoTable[c].mod;
381  } else {
382  /* We only deal with ASCII right now */
383  code = SDL_SCANCODE_UNKNOWN;
384  mod = 0;
385  }
386 
387  if (mod & KMOD_SHIFT) {
388  /* If character uses shift, press shift down */
390  }
391 
392  /* send a keydown and keyup even for the character */
395 
396  if (mod & KMOD_SHIFT) {
397  /* If character uses shift, press shift back up */
399  }
400  }
401  SDL_SendKeyboardText([changeText UTF8String]);
402  }
403  changeText = nil;
404  }
405 }
406 
407 - (void)updateKeyboard
408 {
409  CGAffineTransform t = self.view.transform;
410  CGPoint offset = CGPointMake(0.0, 0.0);
411  CGRect frame = UIKit_ComputeViewFrame(window, self.view.window.screen);
412 
413  if (self.keyboardHeight) {
414  int rectbottom = self.textInputRect.y + self.textInputRect.h;
415  int keybottom = self.view.bounds.size.height - self.keyboardHeight;
416  if (keybottom < rectbottom) {
417  offset.y = keybottom - rectbottom;
418  }
419  }
420 
421  /* Apply this view's transform (except any translation) to the offset, in
422  * order to orient it correctly relative to the frame's coordinate space. */
423  t.tx = 0.0;
424  t.ty = 0.0;
425  offset = CGPointApplyAffineTransform(offset, t);
426 
427  /* Apply the updated offset to the view's frame. */
428  frame.origin.x += offset.x;
429  frame.origin.y += offset.y;
430 
431  self.view.frame = frame;
432 }
433 
434 - (void)setKeyboardHeight:(int)height
435 {
436  keyboardVisible = height > 0;
437  keyboardHeight = height;
438  [self updateKeyboard];
439 }
440 
441 /* UITextFieldDelegate method. Invoked when user types something. */
442 - (BOOL)textField:(UITextField *)_textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
443 {
444  NSUInteger len = string.length;
445  if (len == 0) {
446  changeText = nil;
447  if (textField.markedTextRange == nil) {
448  /* it wants to replace text with nothing, ie a delete */
451  }
452  if (textField.text.length < 16) {
453  textField.text = obligateForBackspace;
454  }
455  } else {
456  changeText = string;
457  }
458  return YES;
459 }
460 
461 /* Terminates the editing session */
462 - (BOOL)textFieldShouldReturn:(UITextField*)_textField
463 {
468  }
469  return YES;
470 }
471 
472 #endif
473 
474 @end
475 
476 /* iPhone keyboard addition functions */
477 #if SDL_IPHONE_KEYBOARD
478 
480 GetWindowViewController(SDL_Window * window)
481 {
482  if (!window || !window->driverdata) {
483  SDL_SetError("Invalid window");
484  return nil;
485  }
486 
487  SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata;
488 
489  return data.viewcontroller;
490 }
491 
492 SDL_bool
493 UIKit_HasScreenKeyboardSupport(_THIS)
494 {
495  return SDL_TRUE;
496 }
497 
498 void
499 UIKit_ShowScreenKeyboard(_THIS, SDL_Window *window)
500 {
501  @autoreleasepool {
502  SDL_uikitviewcontroller *vc = GetWindowViewController(window);
503  [vc showKeyboard];
504  }
505 }
506 
507 void
508 UIKit_HideScreenKeyboard(_THIS, SDL_Window *window)
509 {
510  @autoreleasepool {
511  SDL_uikitviewcontroller *vc = GetWindowViewController(window);
512  [vc hideKeyboard];
513  }
514 }
515 
516 SDL_bool
517 UIKit_IsScreenKeyboardShown(_THIS, SDL_Window *window)
518 {
519  @autoreleasepool {
520  SDL_uikitviewcontroller *vc = GetWindowViewController(window);
521  if (vc != nil) {
522  return vc.keyboardVisible;
523  }
524  return SDL_FALSE;
525  }
526 }
527 
528 void
529 UIKit_SetTextInputRect(_THIS, SDL_Rect *rect)
530 {
531  if (!rect) {
532  SDL_InvalidParamError("rect");
533  return;
534  }
535 
536  @autoreleasepool {
537  SDL_uikitviewcontroller *vc = GetWindowViewController(SDL_GetFocusWindow());
538  if (vc != nil) {
539  vc.textInputRect = *rect;
540 
541  if (vc.keyboardVisible) {
542  [vc updateKeyboard];
543  }
544  }
545  }
546 }
547 
548 
549 #endif /* SDL_IPHONE_KEYBOARD */
550 
551 #endif /* SDL_VIDEO_DRIVER_UIKIT */
552 
553 /* vi: set ts=4 sw=4 expandtab: */
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:878
GLint GLint GLint GLint GLint x
Definition: SDL_opengl.h:1574
SDL_Rect rect
Definition: testrelative.c:27
static int available()
Definition: video.c:356
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:2671
static UIKitKeyInfo unicharToUIKeyInfoTable[]
Definition: keyinfotable.h:41
#define TRUE
Definition: edid-parse.c:33
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:389
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 FALSE
Definition: edid-parse.c:34
#define SDL_RELEASED
Definition: SDL_events.h:49
#define SDLCALL
Definition: SDL_internal.h:45
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:368
GLdouble GLdouble t
Definition: SDL_opengl.h:2071
A rectangle, with the origin at the upper left.
Definition: SDL_rect.h:64
NSUInteger supportedInterfaceOrientations()