From fdaa0f984ef2404ee822f31aa23c08750b6ed7d2 Mon Sep 17 00:00:00 2001 From: Sam Davis Date: Sun, 17 May 2026 10:43:34 +1000 Subject: [PATCH 1/2] ios: add global keyboard dismiss overlay --- platform/ios/ios_loader.m | 134 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/platform/ios/ios_loader.m b/platform/ios/ios_loader.m index 300f617cf..6c3f37016 100644 --- a/platform/ios/ios_loader.m +++ b/platform/ios/ios_loader.m @@ -13,6 +13,7 @@ */ #import +#import #include @@ -30,8 +31,141 @@ #define LANGUAGE "en_US.UTF-8" #define LUA_ERROR "failed to run lua chunk: %s\n" +@interface KOKeyboardDismissOverlay : NSObject +@property (nonatomic, strong) UIButton *button; +@property (nonatomic, assign) CGFloat keyboardTopY; +@end + +@implementation KOKeyboardDismissOverlay + ++ (instancetype)shared { + static KOKeyboardDismissOverlay *overlay; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + overlay = [[KOKeyboardDismissOverlay alloc] init]; + overlay.keyboardTopY = CGFLOAT_MAX; + }); + return overlay; +} + +- (void)start { + NSNotificationCenter *nc = NSNotificationCenter.defaultCenter; + [nc addObserver:self selector:@selector(onKeyboardWillChangeFrame:) + name:UIKeyboardWillChangeFrameNotification object:nil]; + [nc addObserver:self selector:@selector(onKeyboardWillChangeFrame:) + name:UIKeyboardDidChangeFrameNotification object:nil]; + [nc addObserver:self selector:@selector(onKeyboardDidHide:) + name:UIKeyboardDidHideNotification object:nil]; +} + +- (UIWindow *)keyWindow { + UIWindow *win = nil; + for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) { + if (![scene isKindOfClass:[UIWindowScene class]]) continue; + if (scene.activationState != UISceneActivationStateForegroundActive + && scene.activationState != UISceneActivationStateForegroundInactive) { + continue; + } + for (UIWindow *w in ((UIWindowScene *)scene).windows) { + if (w.isKeyWindow) { win = w; break; } + } + if (!win && ((UIWindowScene *)scene).windows.count > 0) { + win = ((UIWindowScene *)scene).windows.firstObject; + } + if (win) break; + } + return win; +} + +- (UIButton *)ensureButtonInWindow:(UIWindow *)window { + if (!self.button) { + self.button = [UIButton buttonWithType:UIButtonTypeSystem]; + self.button.backgroundColor = [UIColor colorWithWhite:0 alpha:0.55]; + [self.button setTitle:@"Dismiss" forState:UIControlStateNormal]; + [self.button setTitleColor:UIColor.whiteColor forState:UIControlStateNormal]; + self.button.titleLabel.font = [UIFont boldSystemFontOfSize:14]; + self.button.contentEdgeInsets = UIEdgeInsetsMake(10, 14, 10, 14); + self.button.layer.cornerRadius = 10; + self.button.layer.masksToBounds = YES; + [self.button addTarget:self action:@selector(onDismissTap) + forControlEvents:UIControlEventTouchUpInside]; + } + if (self.button.superview != window) { + [self.button removeFromSuperview]; + [window addSubview:self.button]; + } + [window bringSubviewToFront:self.button]; + return self.button; +} + +- (void)positionButtonInWindow:(UIWindow *)window { + if (!window || !self.button || self.keyboardTopY == CGFLOAT_MAX) return; + [self.button sizeToFit]; + CGFloat pad = 12; + CGFloat btnW = CGRectGetWidth(self.button.bounds) + 12; + CGFloat btnH = MAX(CGRectGetHeight(self.button.bounds) + 8, 38); + CGFloat maxX = CGRectGetWidth(window.bounds) - pad - btnW; + CGFloat minY = window.safeAreaInsets.top + pad; + CGFloat y = self.keyboardTopY - pad - btnH; + if (y < minY) y = minY; + self.button.frame = CGRectMake(MAX(pad, maxX), y, btnW, btnH); +} + +- (void)onKeyboardWillChangeFrame:(NSNotification *)note { + UIWindow *window = [self keyWindow]; + if (!window) return; + + CGRect kbFrameScreen = [note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; + CGRect kbFrame = [window convertRect:kbFrameScreen fromWindow:nil]; + + BOOL keyboardVisible = CGRectGetMinY(kbFrame) < CGRectGetHeight(window.bounds); + if (!keyboardVisible) { + return; + } + + self.keyboardTopY = CGRectGetMinY(kbFrame); + + UIButton *button = [self ensureButtonInWindow:window]; + [self positionButtonInWindow:window]; + + NSTimeInterval duration = [note.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + NSInteger curve = [note.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; + UIViewAnimationOptions options = ((UIViewAnimationOptions)curve << 16) | UIViewAnimationOptionBeginFromCurrentState; + + if (button.hidden) { + button.alpha = 0; + button.hidden = NO; + [UIView animateWithDuration:duration delay:0 options:options animations:^{ + button.alpha = 1; + } completion:nil]; + } else { + [UIView animateWithDuration:duration delay:0 options:options animations:^{ + button.alpha = 1; + [self positionButtonInWindow:window]; + } completion:nil]; + } +} + +- (void)onKeyboardDidHide:(NSNotification *)note { + if (!self.button || self.button.hidden) return; + self.button.alpha = 0; + self.button.hidden = YES; + self.keyboardTopY = CGFLOAT_MAX; +} + +- (void)onDismissTap { + UIWindow *window = [self keyWindow]; + [window endEditing:YES]; +} + +@end + int main(int argc, char *argv[]) { @autoreleasepool { + dispatch_async(dispatch_get_main_queue(), ^{ + [[KOKeyboardDismissOverlay shared] start]; + }); + NSString *resourcePath = [[NSBundle mainBundle] resourcePath]; if (!resourcePath) { fprintf(stderr, "[%s]: NSBundle resourcePath is nil\n", LOGNAME); From baf5c01aca388ed594d4493a8bbeabf832d85b4a Mon Sep 17 00:00:00 2001 From: Sam Davis Date: Sun, 17 May 2026 10:46:26 +1000 Subject: [PATCH 2/2] ios: tweak keyboard dismiss button label and padding --- platform/ios/ios_loader.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/ios/ios_loader.m b/platform/ios/ios_loader.m index 6c3f37016..a987fe201 100644 --- a/platform/ios/ios_loader.m +++ b/platform/ios/ios_loader.m @@ -81,10 +81,10 @@ - (UIButton *)ensureButtonInWindow:(UIWindow *)window { if (!self.button) { self.button = [UIButton buttonWithType:UIButtonTypeSystem]; self.button.backgroundColor = [UIColor colorWithWhite:0 alpha:0.55]; - [self.button setTitle:@"Dismiss" forState:UIControlStateNormal]; + [self.button setTitle:@"Hide keyboard" forState:UIControlStateNormal]; [self.button setTitleColor:UIColor.whiteColor forState:UIControlStateNormal]; self.button.titleLabel.font = [UIFont boldSystemFontOfSize:14]; - self.button.contentEdgeInsets = UIEdgeInsetsMake(10, 14, 10, 14); + self.button.contentEdgeInsets = UIEdgeInsetsMake(7, 10, 7, 10); self.button.layer.cornerRadius = 10; self.button.layer.masksToBounds = YES; [self.button addTarget:self action:@selector(onDismissTap)