Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions platform/ios/ios_loader.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
*/

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

#include <SDL3/SDL_main.h>

Expand All @@ -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:@"Hide keyboard" forState:UIControlStateNormal];
[self.button setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
self.button.titleLabel.font = [UIFont boldSystemFontOfSize: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)
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);
Expand Down