src/video/uikit/SDL_uikitviewcontroller.m
author Sam Lantinga <slouken@libsdl.org>
Tue, 19 Dec 2017 10:57:21 -0800
changeset 11782 98ea6e4c0d68
parent 11497 65698434e30d
child 11811 5d94cb6b24d3
permissions -rw-r--r--
Fixed bug 4004 - iOS: don't hide keyboard on RETURN

Dominik Reichardt

As discussed in 2012 the iOS onscreen keyboard hides when you hit RETURN (see https://discourse.libsdl.org/t/on-screen-keyboard-change/19216).
IMO this is a bad idea to not be able to influence this behavior and just recently this was fixed for Android by adding the hint SDL_HINT_ANDROID_RETURN_HIDES_IME in changeset 11768 0f2fb9ec6ad6.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2017 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 
    31 #import "SDL_uikitviewcontroller.h"
    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 @implementation SDL_uikitviewcontroller {
    54     CADisplayLink *displayLink;
    55     int animationInterval;
    56     void (*animationCallback)(void*);
    57     void *animationCallbackParam;
    58 
    59 #if SDL_IPHONE_KEYBOARD
    60     UITextField *textField;
    61     BOOL rotatingOrientation;
    62 #endif
    63 }
    64 
    65 @synthesize window;
    66 
    67 - (instancetype)initWithSDLWindow:(SDL_Window *)_window
    68 {
    69     if (self = [super initWithNibName:nil bundle:nil]) {
    70         self.window = _window;
    71 
    72 #if SDL_IPHONE_KEYBOARD
    73         [self initKeyboard];
    74         rotatingOrientation = FALSE;
    75 #endif
    76 
    77 #if TARGET_OS_TV
    78         SDL_AddHintCallback(SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS,
    79                             SDL_AppleTVControllerUIHintChanged,
    80                             (__bridge void *) self);
    81 #endif
    82     }
    83     return self;
    84 }
    85 
    86 - (void)dealloc
    87 {
    88 #if SDL_IPHONE_KEYBOARD
    89     [self deinitKeyboard];
    90 #endif
    91 
    92 #if TARGET_OS_TV
    93     SDL_DelHintCallback(SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS,
    94                         SDL_AppleTVControllerUIHintChanged,
    95                         (__bridge void *) self);
    96 #endif
    97 }
    98 
    99 - (void)setAnimationCallback:(int)interval
   100                     callback:(void (*)(void*))callback
   101                callbackParam:(void*)callbackParam
   102 {
   103     [self stopAnimation];
   104 
   105     animationInterval = interval;
   106     animationCallback = callback;
   107     animationCallbackParam = callbackParam;
   108 
   109     if (animationCallback) {
   110         [self startAnimation];
   111     }
   112 }
   113 
   114 - (void)startAnimation
   115 {
   116     displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(doLoop:)];
   117 
   118 #ifdef __IPHONE_10_3
   119     SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
   120 
   121     if ([displayLink respondsToSelector:@selector(preferredFramesPerSecond)]
   122         && data != nil && data.uiwindow != nil
   123         && [data.uiwindow.screen respondsToSelector:@selector(maximumFramesPerSecond)]) {
   124         displayLink.preferredFramesPerSecond = data.uiwindow.screen.maximumFramesPerSecond / animationInterval;
   125     } else
   126 #endif
   127     {
   128 #if __IPHONE_OS_VERSION_MIN_REQUIRED < 100300
   129         [displayLink setFrameInterval:animationInterval];
   130 #endif
   131     }
   132 
   133     [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
   134 }
   135 
   136 - (void)stopAnimation
   137 {
   138     [displayLink invalidate];
   139     displayLink = nil;
   140 }
   141 
   142 - (void)doLoop:(CADisplayLink*)sender
   143 {
   144     /* Don't run the game loop while a messagebox is up */
   145     if (!UIKit_ShowingMessageBox()) {
   146         /* See the comment in the function definition. */
   147         UIKit_GL_RestoreCurrentContext();
   148 
   149         animationCallback(animationCallbackParam);
   150     }
   151 }
   152 
   153 - (void)loadView
   154 {
   155     /* Do nothing. */
   156 }
   157 
   158 - (void)viewDidLayoutSubviews
   159 {
   160     const CGSize size = self.view.bounds.size;
   161     int w = (int) size.width;
   162     int h = (int) size.height;
   163 
   164     SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, w, h);
   165 }
   166 
   167 #if !TARGET_OS_TV
   168 - (NSUInteger)supportedInterfaceOrientations
   169 {
   170     return UIKit_GetSupportedOrientations(window);
   171 }
   172 
   173 #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0
   174 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orient
   175 {
   176     return ([self supportedInterfaceOrientations] & (1 << orient)) != 0;
   177 }
   178 #endif
   179 
   180 - (BOOL)prefersStatusBarHidden
   181 {
   182     return (window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_BORDERLESS)) != 0;
   183 }
   184 #endif
   185 
   186 /*
   187  ---- Keyboard related functionality below this line ----
   188  */
   189 #if SDL_IPHONE_KEYBOARD
   190 
   191 @synthesize textInputRect;
   192 @synthesize keyboardHeight;
   193 @synthesize keyboardVisible;
   194 
   195 /* Set ourselves up as a UITextFieldDelegate */
   196 - (void)initKeyboard
   197 {
   198     textField = [[UITextField alloc] initWithFrame:CGRectZero];
   199     textField.delegate = self;
   200     /* placeholder so there is something to delete! */
   201     textField.text = @" ";
   202 
   203     /* set UITextInputTrait properties, mostly to defaults */
   204     textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
   205     textField.autocorrectionType = UITextAutocorrectionTypeNo;
   206     textField.enablesReturnKeyAutomatically = NO;
   207     textField.keyboardAppearance = UIKeyboardAppearanceDefault;
   208     textField.keyboardType = UIKeyboardTypeDefault;
   209     textField.returnKeyType = UIReturnKeyDefault;
   210     textField.secureTextEntry = NO;
   211 
   212     textField.hidden = YES;
   213     keyboardVisible = NO;
   214 
   215 #if !TARGET_OS_TV
   216     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
   217     [center addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
   218     [center addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
   219 #endif
   220 }
   221 
   222 - (void)setView:(UIView *)view
   223 {
   224     [super setView:view];
   225 
   226     [view addSubview:textField];
   227 
   228     if (keyboardVisible) {
   229         [self showKeyboard];
   230     }
   231 }
   232 
   233 /* willRotateToInterfaceOrientation and didRotateFromInterfaceOrientation are deprecated in iOS 8+ in favor of viewWillTransitionToSize */
   234 #if TARGET_OS_TV || __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000
   235 - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
   236 {
   237     [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
   238     rotatingOrientation = TRUE;
   239     [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {}
   240                                  completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
   241                                      rotatingOrientation = FALSE;
   242                                  }];
   243 }
   244 #else
   245 - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
   246     [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
   247     rotatingOrientation = TRUE;
   248 }
   249 
   250 - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
   251     [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
   252     rotatingOrientation = FALSE;
   253 }
   254 #endif /* TARGET_OS_TV || __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000 */
   255 
   256 - (void)deinitKeyboard
   257 {
   258 #if !TARGET_OS_TV
   259     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
   260     [center removeObserver:self name:UIKeyboardWillShowNotification object:nil];
   261     [center removeObserver:self name:UIKeyboardWillHideNotification object:nil];
   262 #endif
   263 }
   264 
   265 /* reveal onscreen virtual keyboard */
   266 - (void)showKeyboard
   267 {
   268     keyboardVisible = YES;
   269     if (textField.window) {
   270         [textField becomeFirstResponder];
   271     }
   272 }
   273 
   274 /* hide onscreen virtual keyboard */
   275 - (void)hideKeyboard
   276 {
   277     keyboardVisible = NO;
   278     [textField resignFirstResponder];
   279 }
   280 
   281 - (void)keyboardWillShow:(NSNotification *)notification
   282 {
   283 #if !TARGET_OS_TV
   284     CGRect kbrect = [[notification userInfo][UIKeyboardFrameEndUserInfoKey] CGRectValue];
   285 
   286     /* The keyboard rect is in the coordinate space of the screen/window, but we
   287      * want its height in the coordinate space of the view. */
   288     kbrect = [self.view convertRect:kbrect fromView:nil];
   289 
   290     [self setKeyboardHeight:(int)kbrect.size.height];
   291 #endif
   292 }
   293 
   294 - (void)keyboardWillHide:(NSNotification *)notification
   295 {
   296     if (!rotatingOrientation) {
   297         SDL_StopTextInput();
   298     }
   299     [self setKeyboardHeight:0];
   300 }
   301 
   302 - (void)updateKeyboard
   303 {
   304     CGAffineTransform t = self.view.transform;
   305     CGPoint offset = CGPointMake(0.0, 0.0);
   306     CGRect frame = UIKit_ComputeViewFrame(window, self.view.window.screen);
   307 
   308     if (self.keyboardHeight) {
   309         int rectbottom = self.textInputRect.y + self.textInputRect.h;
   310         int keybottom = self.view.bounds.size.height - self.keyboardHeight;
   311         if (keybottom < rectbottom) {
   312             offset.y = keybottom - rectbottom;
   313         }
   314     }
   315 
   316     /* Apply this view's transform (except any translation) to the offset, in
   317      * order to orient it correctly relative to the frame's coordinate space. */
   318     t.tx = 0.0;
   319     t.ty = 0.0;
   320     offset = CGPointApplyAffineTransform(offset, t);
   321 
   322     /* Apply the updated offset to the view's frame. */
   323     frame.origin.x += offset.x;
   324     frame.origin.y += offset.y;
   325 
   326     self.view.frame = frame;
   327 }
   328 
   329 - (void)setKeyboardHeight:(int)height
   330 {
   331     keyboardVisible = height > 0;
   332     keyboardHeight = height;
   333     [self updateKeyboard];
   334 }
   335 
   336 /* UITextFieldDelegate method.  Invoked when user types something. */
   337 - (BOOL)textField:(UITextField *)_textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
   338 {
   339     NSUInteger len = string.length;
   340 
   341     if (len == 0) {
   342         /* it wants to replace text with nothing, ie a delete */
   343         SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_BACKSPACE);
   344         SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_BACKSPACE);
   345     } else {
   346         /* go through all the characters in the string we've been sent and
   347          * convert them to key presses */
   348         int i;
   349         for (i = 0; i < len; i++) {
   350             unichar c = [string characterAtIndex:i];
   351             Uint16 mod = 0;
   352             SDL_Scancode code;
   353 
   354             if (c < 127) {
   355                 /* figure out the SDL_Scancode and SDL_keymod for this unichar */
   356                 code = unicharToUIKeyInfoTable[c].code;
   357                 mod  = unicharToUIKeyInfoTable[c].mod;
   358             } else {
   359                 /* we only deal with ASCII right now */
   360                 code = SDL_SCANCODE_UNKNOWN;
   361                 mod = 0;
   362             }
   363 
   364             if (mod & KMOD_SHIFT) {
   365                 /* If character uses shift, press shift down */
   366                 SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_LSHIFT);
   367             }
   368 
   369             /* send a keydown and keyup even for the character */
   370             SDL_SendKeyboardKey(SDL_PRESSED, code);
   371             SDL_SendKeyboardKey(SDL_RELEASED, code);
   372 
   373             if (mod & KMOD_SHIFT) {
   374                 /* If character uses shift, press shift back up */
   375                 SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LSHIFT);
   376             }
   377         }
   378 
   379         SDL_SendKeyboardText([string UTF8String]);
   380     }
   381 
   382     return NO; /* don't allow the edit! (keep placeholder text there) */
   383 }
   384 
   385 /* Terminates the editing session */
   386 - (BOOL)textFieldShouldReturn:(UITextField*)_textField
   387 {
   388     SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_RETURN);
   389     SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_RETURN);
   390     if (SDL_GetHintBoolean(SDL_HINT_RETURN_KEY_HIDES_IME, SDL_FALSE)) {
   391          SDL_StopTextInput();
   392     }
   393     return YES;
   394 }
   395 
   396 #endif
   397 
   398 @end
   399 
   400 /* iPhone keyboard addition functions */
   401 #if SDL_IPHONE_KEYBOARD
   402 
   403 static SDL_uikitviewcontroller *
   404 GetWindowViewController(SDL_Window * window)
   405 {
   406     if (!window || !window->driverdata) {
   407         SDL_SetError("Invalid window");
   408         return nil;
   409     }
   410 
   411     SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata;
   412 
   413     return data.viewcontroller;
   414 }
   415 
   416 SDL_bool
   417 UIKit_HasScreenKeyboardSupport(_THIS)
   418 {
   419     return SDL_TRUE;
   420 }
   421 
   422 void
   423 UIKit_ShowScreenKeyboard(_THIS, SDL_Window *window)
   424 {
   425     @autoreleasepool {
   426         SDL_uikitviewcontroller *vc = GetWindowViewController(window);
   427         [vc showKeyboard];
   428     }
   429 }
   430 
   431 void
   432 UIKit_HideScreenKeyboard(_THIS, SDL_Window *window)
   433 {
   434     @autoreleasepool {
   435         SDL_uikitviewcontroller *vc = GetWindowViewController(window);
   436         [vc hideKeyboard];
   437     }
   438 }
   439 
   440 SDL_bool
   441 UIKit_IsScreenKeyboardShown(_THIS, SDL_Window *window)
   442 {
   443     @autoreleasepool {
   444         SDL_uikitviewcontroller *vc = GetWindowViewController(window);
   445         if (vc != nil) {
   446             return vc.isKeyboardVisible;
   447         }
   448         return SDL_FALSE;
   449     }
   450 }
   451 
   452 void
   453 UIKit_SetTextInputRect(_THIS, SDL_Rect *rect)
   454 {
   455     if (!rect) {
   456         SDL_InvalidParamError("rect");
   457         return;
   458     }
   459 
   460     @autoreleasepool {
   461         SDL_uikitviewcontroller *vc = GetWindowViewController(SDL_GetFocusWindow());
   462         if (vc != nil) {
   463             vc.textInputRect = *rect;
   464 
   465             if (vc.keyboardVisible) {
   466                 [vc updateKeyboard];
   467             }
   468         }
   469     }
   470 }
   471 
   472 
   473 #endif /* SDL_IPHONE_KEYBOARD */
   474 
   475 #endif /* SDL_VIDEO_DRIVER_UIKIT */
   476 
   477 /* vi: set ts=4 sw=4 expandtab: */