From 5d09656afa3b6d10de522835887b51f2b09485ed Mon Sep 17 00:00:00 2001
From: Salman Alshamrani <[EMAIL REDACTED]>
Date: Sun, 3 Nov 2024 23:12:27 -0500
Subject: [PATCH] Refactor iOS text input activation to better work with
hardware keyboards
---
src/video/uikit/SDL_uikitvideo.m | 4 +-
src/video/uikit/SDL_uikitviewcontroller.h | 10 +-
src/video/uikit/SDL_uikitviewcontroller.m | 108 ++++++++++------------
3 files changed, 54 insertions(+), 68 deletions(-)
diff --git a/src/video/uikit/SDL_uikitvideo.m b/src/video/uikit/SDL_uikitvideo.m
index 4866de9785637..d492178b8151f 100644
--- a/src/video/uikit/SDL_uikitvideo.m
+++ b/src/video/uikit/SDL_uikitvideo.m
@@ -97,8 +97,8 @@ static void UIKit_DeleteDevice(SDL_VideoDevice *device)
#ifdef SDL_IPHONE_KEYBOARD
device->HasScreenKeyboardSupport = UIKit_HasScreenKeyboardSupport;
- device->ShowScreenKeyboard = UIKit_ShowScreenKeyboard;
- device->HideScreenKeyboard = UIKit_HideScreenKeyboard;
+ device->StartTextInput = UIKit_StartTextInput;
+ device->StopTextInput = UIKit_StopTextInput;
device->IsScreenKeyboardShown = UIKit_IsScreenKeyboardShown;
device->UpdateTextInputArea = UIKit_UpdateTextInputArea;
#endif
diff --git a/src/video/uikit/SDL_uikitviewcontroller.h b/src/video/uikit/SDL_uikitviewcontroller.h
index b66258c1f0fe1..dd22e780c7a54 100644
--- a/src/video/uikit/SDL_uikitviewcontroller.h
+++ b/src/video/uikit/SDL_uikitviewcontroller.h
@@ -69,8 +69,8 @@
#endif
#ifdef SDL_IPHONE_KEYBOARD
-- (void)showKeyboard;
-- (void)hideKeyboard;
+- (bool)startTextInput;
+- (bool)stopTextInput;
- (void)initKeyboard;
- (void)deinitKeyboard;
@@ -79,7 +79,7 @@
- (void)updateKeyboard;
-@property(nonatomic, assign, getter=isKeyboardVisible) BOOL keyboardVisible;
+@property(nonatomic, assign, getter=isTextFieldFocused) BOOL textFieldFocused;
@property(nonatomic, assign) SDL_Rect textInputRect;
@property(nonatomic, assign) int keyboardHeight;
#endif
@@ -88,8 +88,8 @@
#ifdef SDL_IPHONE_KEYBOARD
bool UIKit_HasScreenKeyboardSupport(SDL_VideoDevice *_this);
-void UIKit_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
-void UIKit_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window);
+bool UIKit_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
+bool UIKit_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window);
bool UIKit_IsScreenKeyboardShown(SDL_VideoDevice *_this, SDL_Window *window);
bool UIKit_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window);
#endif
diff --git a/src/video/uikit/SDL_uikitviewcontroller.m b/src/video/uikit/SDL_uikitviewcontroller.m
index 3c3cae8555463..832ee33ecbdcb 100644
--- a/src/video/uikit/SDL_uikitviewcontroller.m
+++ b/src/video/uikit/SDL_uikitviewcontroller.m
@@ -80,7 +80,6 @@ @implementation SDL_uikitviewcontroller
#ifdef SDL_IPHONE_KEYBOARD
SDLUITextField *textField;
- BOOL showingKeyboard;
BOOL hidingKeyboard;
BOOL rotatingOrientation;
NSString *committedText;
@@ -97,7 +96,6 @@ - (instancetype)initWithSDLWindow:(SDL_Window *)_window
#ifdef SDL_IPHONE_KEYBOARD
[self initKeyboard];
- showingKeyboard = NO;
hidingKeyboard = NO;
rotatingOrientation = NO;
#endif
@@ -264,7 +262,7 @@ - (BOOL)prefersPointerLocked
@synthesize textInputRect;
@synthesize keyboardHeight;
-@synthesize keyboardVisible;
+@synthesize textFieldFocused;
// Set ourselves up as a UITextFieldDelegate
- (void)initKeyboard
@@ -277,7 +275,7 @@ - (void)initKeyboard
committedText = textField.text;
textField.hidden = YES;
- keyboardVisible = NO;
+ textFieldFocused = NO;
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
#ifndef SDL_PLATFORM_TVOS
@@ -285,10 +283,6 @@ - (void)initKeyboard
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
- [center addObserver:self
- selector:@selector(keyboardDidShow:)
- name:UIKeyboardDidShowNotification
- object:nil];
[center addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
@@ -343,8 +337,10 @@ - (void)setView:(UIView *)view
[view addSubview:textField];
- if (keyboardVisible) {
- [self showKeyboard];
+ if (textFieldFocused) {
+ /* startTextInput has been called before the text field was added to the view,
+ * call it again for the text field to actually become first responder. */
+ [self startTextInput];
}
}
@@ -367,9 +363,6 @@ - (void)deinitKeyboard
[center removeObserver:self
name:UIKeyboardWillShowNotification
object:nil];
- [center removeObserver:self
- name:UIKeyboardDidShowNotification
- object:nil];
[center removeObserver:self
name:UIKeyboardWillHideNotification
object:nil];
@@ -382,7 +375,7 @@ - (void)deinitKeyboard
object:nil];
}
-- (void)setKeyboardProperties:(SDL_PropertiesID) props
+- (void)setTextFieldProperties:(SDL_PropertiesID) props
{
textField.secureTextEntry = NO;
@@ -479,43 +472,36 @@ - (void)setKeyboardProperties:(SDL_PropertiesID) props
}
}
-// reveal onscreen virtual keyboard
-- (void)showKeyboard
+/* requests the SDL text field to become focused and accept text input.
+ * also shows the onscreen virtual keyboard if no hardware keyboard is attached. */
+- (bool)startTextInput
{
- if (keyboardVisible) {
- return;
+ textFieldFocused = YES;
+ if (!textField.window) {
+ /* textField has not been added to the view yet,
+ * we will try again when that happens. */
+ return true;
}
- keyboardVisible = YES;
- if (textField.window) {
- showingKeyboard = YES;
- [textField becomeFirstResponder];
- }
+ return [textField becomeFirstResponder];
}
-// hide onscreen virtual keyboard
-- (void)hideKeyboard
+/* requests the SDL text field to lose focus and stop accepting text input.
+ * also hides the onscreen virtual keyboard if no hardware keyboard is attached. */
+- (bool)stopTextInput
{
- if (!keyboardVisible) {
- return;
+ textFieldFocused = NO;
+ if (!textField.window) {
+ /* textField has not been added to the view yet,
+ * we will try again when that happens. */
+ return true;
}
- keyboardVisible = NO;
- if (textField.window) {
- hidingKeyboard = YES;
- [textField resignFirstResponder];
- }
+ return [textField resignFirstResponder];
}
- (void)keyboardWillShow:(NSNotification *)notification
{
- BOOL shouldStartTextInput = NO;
-
- if (!SDL_TextInputActive(window) && !hidingKeyboard && !rotatingOrientation) {
- shouldStartTextInput = YES;
- }
-
- showingKeyboard = YES;
#ifndef SDL_PLATFORM_TVOS
CGRect kbrect = [[notification userInfo][UIKeyboardFrameEndUserInfoKey] CGRectValue];
@@ -526,28 +512,29 @@ - (void)keyboardWillShow:(NSNotification *)notification
[self setKeyboardHeight:(int)kbrect.size.height];
#endif
- if (shouldStartTextInput) {
+ /* A keyboard hide transition has been interrupted with a show (keyboardWillHide has been called but keyboardDidHide didn't).
+ * since text input was stopped by the hide, we have to start it again. */
+ if (hidingKeyboard) {
SDL_StartTextInput(window);
+ hidingKeyboard = NO;
}
}
-- (void)keyboardDidShow:(NSNotification *)notification
-{
- showingKeyboard = NO;
-}
-
- (void)keyboardWillHide:(NSNotification *)notification
{
- BOOL shouldStopTextInput = NO;
-
- if (SDL_TextInputActive(window) && !showingKeyboard && !rotatingOrientation) {
- shouldStopTextInput = YES;
- }
-
hidingKeyboard = YES;
[self setKeyboardHeight:0];
- if (shouldStopTextInput) {
+ /* When the user dismisses the software keyboard by the "hide" button in the bottom right corner,
+ * we want to reflect that on SDL_TextInputActive by calling SDL_StopTextInput...on certain conditions */
+ if (SDL_TextInputActive(window)
+ /* keyboardWillHide gets called when a hardware keyboard is attached,
+ * keep text input state active if hiding while there is a hardware keyboard.
+ * if the hardware keyboard gets detached, the software keyboard will appear anyway. */
+ && !SDL_HasKeyboard()
+ /* When the device changes orientation, a sequence of hide and show transitions are triggered.
+ * keep text input state active in this case. */
+ && !rotatingOrientation) {
SDL_StopTextInput(window);
}
}
@@ -630,7 +617,6 @@ - (void)updateKeyboard
- (void)setKeyboardHeight:(int)height
{
- keyboardVisible = height > 0;
keyboardHeight = height;
[self updateKeyboard];
}
@@ -651,7 +637,7 @@ - (BOOL)textField:(UITextField *)_textField shouldChangeCharactersInRange:(NSRan
- (BOOL)textFieldShouldReturn:(UITextField *)_textField
{
SDL_SendKeyboardKeyAutoRelease(0, SDL_SCANCODE_RETURN);
- if (keyboardVisible &&
+ if (textFieldFocused &&
SDL_GetHintBoolean(SDL_HINT_RETURN_KEY_HIDES_IME, false)) {
SDL_StopTextInput(window);
}
@@ -682,20 +668,20 @@ bool UIKit_HasScreenKeyboardSupport(SDL_VideoDevice *_this)
return true;
}
-void UIKit_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
+bool UIKit_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
{
@autoreleasepool {
SDL_uikitviewcontroller *vc = GetWindowViewController(window);
- [vc setKeyboardProperties:props];
- [vc showKeyboard];
+ [vc setTextFieldProperties:props];
+ return [vc startTextInput];
}
}
-void UIKit_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window)
+bool UIKit_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window)
{
@autoreleasepool {
SDL_uikitviewcontroller *vc = GetWindowViewController(window);
- [vc hideKeyboard];
+ return [vc stopTextInput];
}
}
@@ -704,7 +690,7 @@ bool UIKit_IsScreenKeyboardShown(SDL_VideoDevice *_this, SDL_Window *window)
@autoreleasepool {
SDL_uikitviewcontroller *vc = GetWindowViewController(window);
if (vc != nil) {
- return vc.keyboardVisible;
+ return vc.textFieldFocused;
}
return false;
}
@@ -717,7 +703,7 @@ bool UIKit_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window)
if (vc != nil) {
vc.textInputRect = window->text_input_rect;
- if (vc.keyboardVisible) {
+ if (vc.textFieldFocused) {
[vc updateKeyboard];
}
}