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