From 26cf5535b4fe69063a9e470602af01725e9df6e4 Mon Sep 17 00:00:00 2001 From: Igor Chernakov Date: Mon, 5 Oct 2026 18:17:42 +0200 Subject: [PATCH] iOS: slide the app view above the on-screen keyboard While the software keyboard covers the bottom of the window, the root view translates up by the covered height using the keyboard's own animation curve, and back down when it hides. A view transform keeps the drawable size, and locationInView: keeps pointer coordinates in the view's own space. Co-Authored-By: Claude Opus 5.5 --- ios/README.md | 5 ++++- ios/ios_window.m | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/ios/README.md b/ios/README.md index 33fa33d..2dcadcb 100644 --- a/ios/README.md +++ b/ios/README.md @@ -35,7 +35,10 @@ finish GL work before suspension. Touch and coalesced Pencil samples become the standard AX pointer events; additional fingers are ignored during a stroke. Two-finger pan becomes scroll input. Hardware keys retain modifier flags for framework accelerators. -`axSetTextInput` shows or hides the keyboard for an active text control. +`axSetTextInput` shows or hides the keyboard for an active text control. While the +on-screen keyboard covers the bottom of the window, the backend slides the +whole app view up by the covered height, with the keyboard's own animation, +and back down when it hides. Pointer coordinates are unaffected. The event queue accepts worker-thread posts and coalesces paint/resize events by target. Timers run on the main run loop and are cancelled when their owner diff --git a/ios/ios_window.m b/ios/ios_window.m index e6a0aa9..34e9394 100644 --- a/ios/ios_window.m +++ b/ios/ios_window.m @@ -209,6 +209,26 @@ - (void)viewDidLoad { scroll.allowedTouchTypes = @[@(UITouchTypeIndirectPointer)]; scroll.allowedScrollTypesMask = UIScrollTypeMaskAll; [ios_view addGestureRecognizer:scroll]; + for (NSNotificationName name in @[UIKeyboardWillChangeFrameNotification, UIKeyboardWillHideNotification]) + [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(keyboard:) name:name object:nil]; +} +// The app slides up by the part of the window the on-screen keyboard covers. +// A view transform keeps the drawable size, and locationInView: still reports +// touches in the view's own coordinates. +- (void)keyboard:(NSNotification *)note { + CGFloat cover = 0; + if (note.name != UIKeyboardWillHideNotification) { + CGRect end = [note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; + id screen = self.view.window.screen.coordinateSpace; + CGRect kb = screen ? [self.view convertRect:end fromCoordinateSpace:screen] : CGRectZero; + CGRect hidden = CGRectIntersection(kb, self.view.bounds); + if (!CGRectIsNull(hidden) && CGRectGetMaxY(kb) >= CGRectGetMaxY(self.view.bounds)) cover = hidden.size.height; + } + IOS_TRACE("keyboard cover=%.1f", cover); + double duration = [note.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + UIViewAnimationOptions curve = (UIViewAnimationOptions)[note.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue] << 16; + [UIView animateWithDuration:duration delay:0 options:curve | UIViewAnimationOptionBeginFromCurrentState + animations:^{ ios_view.transform = CGAffineTransformMakeTranslation(0, -cover); } completion:nil]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated];