-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor iOS text input activation to better work with hardware keyboards #11406
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -80,8 +80,6 @@ @implementation SDL_uikitviewcontroller | |
|
||
#ifdef SDL_IPHONE_KEYBOARD | ||
SDLUITextField *textField; | ||
BOOL hardwareKeyboard; | ||
BOOL showingKeyboard; | ||
BOOL hidingKeyboard; | ||
BOOL rotatingOrientation; | ||
NSString *committedText; | ||
|
@@ -98,8 +96,6 @@ - (instancetype)initWithSDLWindow:(SDL_Window *)_window | |
|
||
#ifdef SDL_IPHONE_KEYBOARD | ||
[self initKeyboard]; | ||
hardwareKeyboard = NO; | ||
showingKeyboard = NO; | ||
hidingKeyboard = NO; | ||
rotatingOrientation = NO; | ||
#endif | ||
|
@@ -266,7 +262,7 @@ - (BOOL)prefersPointerLocked | |
|
||
@synthesize textInputRect; | ||
@synthesize keyboardHeight; | ||
@synthesize keyboardVisible; | ||
@synthesize textFieldFocused; | ||
|
||
// Set ourselves up as a UITextFieldDelegate | ||
- (void)initKeyboard | ||
|
@@ -279,18 +275,14 @@ - (void)initKeyboard | |
committedText = textField.text; | ||
|
||
textField.hidden = YES; | ||
keyboardVisible = NO; | ||
textFieldFocused = NO; | ||
|
||
NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; | ||
#ifndef SDL_PLATFORM_TVOS | ||
[center addObserver:self | ||
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 | ||
|
@@ -345,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]; | ||
} | ||
} | ||
|
||
|
@@ -369,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]; | ||
|
@@ -384,7 +375,7 @@ - (void)deinitKeyboard | |
object:nil]; | ||
} | ||
|
||
- (void)setKeyboardProperties:(SDL_PropertiesID) props | ||
- (void)setTextFieldProperties:(SDL_PropertiesID) props | ||
{ | ||
textField.secureTextEntry = NO; | ||
|
||
|
@@ -479,45 +470,53 @@ - (void)setKeyboardProperties:(SDL_PropertiesID) props | |
} else { | ||
textField.enablesReturnKeyAutomatically = NO; | ||
} | ||
} | ||
|
||
// reveal onscreen virtual keyboard | ||
- (void)showKeyboard | ||
{ | ||
if (keyboardVisible) { | ||
if (!textField.window) { | ||
/* textField has not been added to the view yet, | ||
we don't have to do anything. */ | ||
return; | ||
} | ||
|
||
keyboardVisible = YES; | ||
if (textField.window) { | ||
showingKeyboard = YES; | ||
// the text field needs to be re-added to the view in order to update correctly. | ||
UIView *superview = textField.superview; | ||
[textField removeFromSuperview]; | ||
[superview addSubview:textField]; | ||
|
||
if (SDL_TextInputActive(window)) { | ||
[textField becomeFirstResponder]; | ||
} | ||
} | ||
|
||
// hide onscreen virtual keyboard | ||
- (void)hideKeyboard | ||
/* 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 = NO; | ||
if (textField.window) { | ||
hidingKeyboard = YES; | ||
[textField resignFirstResponder]; | ||
} | ||
return [textField becomeFirstResponder]; | ||
} | ||
|
||
- (void)keyboardWillShow:(NSNotification *)notification | ||
/* 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 | ||
{ | ||
BOOL shouldStartTextInput = NO; | ||
|
||
if (!SDL_TextInputActive(window) && !hidingKeyboard && !rotatingOrientation) { | ||
shouldStartTextInput = YES; | ||
textFieldFocused = NO; | ||
if (!textField.window) { | ||
/* textField has not been added to the view yet, | ||
* we will try again when that happens. */ | ||
return true; | ||
} | ||
|
||
showingKeyboard = YES; | ||
return [textField resignFirstResponder]; | ||
} | ||
|
||
- (void)keyboardWillShow:(NSNotification *)notification | ||
{ | ||
#ifndef SDL_PLATFORM_TVOS | ||
CGRect kbrect = [[notification userInfo][UIKeyboardFrameEndUserInfoKey] CGRectValue]; | ||
|
||
|
@@ -528,28 +527,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); | ||
} | ||
Comment on lines
+543
to
554
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Upon further testing, having this also breaks password manager integrations (when setting RPReplay_Final1730711349.MP4Notice how pressing the "Passwords" button in the keyboard hides the keyboard, which triggers the SDL application to stop text input. This is problematic because when the user selects their password from the keychain, the input is ignored since text input is not active. I think it would be a better option to keep text input state completely manual rather than tied to the state of the keyboard, and do away with all these conditionals that are set in place here. @slouken thoughts? do other platforms (aka Android) stop text input when the user hides the keyboard? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, the expectation is that text input state is tied to the visibility of the on-screen keyboard, if there is no hardware keyboard attached. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this is SDL 3.0, we can revisit that assumption, but that's the way it is currently. Bringing up the on-screen keyboard on Android, for example, will enable text input so it can be used to input text into the application. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the situation above, there is no possible way to enter the selected password without breaking that assumption, unless SDL can somehow bypass the "text input active" check when sending text events and do that on Assuming that each application that starts text input has a path for stopping text input, it would be much easier to keep text input state manual and detached from the visibility of the on-screen keyboard, to allow integration with password managers etc. |
||
} | ||
|
@@ -632,7 +632,6 @@ - (void)updateKeyboard | |
|
||
- (void)setKeyboardHeight:(int)height | ||
{ | ||
keyboardVisible = height > 0; | ||
keyboardHeight = height; | ||
[self updateKeyboard]; | ||
} | ||
|
@@ -653,7 +652,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); | ||
} | ||
|
@@ -684,20 +683,27 @@ 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); | ||
return [vc startTextInput]; | ||
} | ||
} | ||
|
||
bool UIKit_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window) | ||
{ | ||
@autoreleasepool { | ||
SDL_uikitviewcontroller *vc = GetWindowViewController(window); | ||
[vc setKeyboardProperties:props]; | ||
[vc showKeyboard]; | ||
return [vc stopTextInput]; | ||
} | ||
} | ||
|
||
void UIKit_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window) | ||
void UIKit_SetTextInputProperties(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props) | ||
{ | ||
@autoreleasepool { | ||
SDL_uikitviewcontroller *vc = GetWindowViewController(window); | ||
[vc hideKeyboard]; | ||
[vc setTextFieldProperties:props]; | ||
} | ||
} | ||
|
||
|
@@ -706,7 +712,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; | ||
} | ||
|
@@ -719,7 +725,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]; | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can technically be split to a separate PR but I figured I could include it in this PR since I'm touching the entire file anyway.