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
133 changes: 117 additions & 16 deletions ios/Sources/KeyboardPlugin/Keyboard.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Licensed to the Apache Software Foundation (ASF) under one
#import "Keyboard.h"
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
#import <Capacitor/Capacitor.h>
#import <Capacitor/Capacitor-Swift.h>
#import <Capacitor/CAPBridgedPlugin.h>
Expand Down Expand Up @@ -48,12 +49,92 @@ @interface KeyboardPlugin () <UIScrollViewDelegate>
// protocol conformance for this class is implemented by a macro and clang isn't detecting that
@implementation KeyboardPlugin

/// Heights below this on iPad are treated as QuickType bar, not a real keyboard
static const CGFloat QUICKTYPE_IGNORE_THRESHOLD = 100.0;

NSTimer *hideTimer;
NSString* UIClassString;
NSString* WKClassString;
NSString* UITraitsClassString;
double stageManagerOffset;

#pragma mark - Helpers

- (UIWindow *)currentKeyWindow {
UIWindow *window = nil;
if ([[[UIApplication sharedApplication] delegate] respondsToSelector:@selector(window)]) {
window = [[[UIApplication sharedApplication] delegate] window];
}
if (!window && @available(iOS 13.0, *)) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"self isKindOfClass: %@", UIWindowScene.class];
UIScene *scene = [UIApplication.sharedApplication.connectedScenes.allObjects filteredArrayUsingPredicate:predicate].firstObject;
window = [[(UIWindowScene*)scene windows] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"isKeyWindow == YES"]].firstObject;
}
return window;
}

- (void)forceBackdropColor:(UIColor *)color {
UIWindow *w = [self currentKeyWindow];
if (w) {
dispatch_async(dispatch_get_main_queue(), ^{
w.backgroundColor = color;
});
}
}

- (BOOL)isIPad {
return ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad);
}

- (BOOL)shouldIgnoreResizeForHeight:(double)height {
if (![self isIPad]) return NO;
if (height <= 0.0) return NO;
return (height < QUICKTYPE_IGNORE_THRESHOLD);
}

#pragma mark - Lifecycle

- (UIColor *)colorFromCssColorString:(NSString *)cssColor {
NSString *trimmed = [cssColor stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];

if ([trimmed hasPrefix:@"rgb"]) {
NSString *clean = [[trimmed stringByReplacingOccurrencesOfString:@"rgb(" withString:@""] stringByReplacingOccurrencesOfString:@")" withString:@""];
NSArray *parts = [clean componentsSeparatedByString:@","];
if (parts.count >= 3) {
CGFloat r = [parts[0] floatValue] / 255.0;
CGFloat g = [parts[1] floatValue] / 255.0;
CGFloat b = [parts[2] floatValue] / 255.0;
return [UIColor colorWithRed:r green:g blue:b alpha:1.0];
}
}

if ([trimmed hasPrefix:@"#"]) {
unsigned rgbValue = 0;
NSScanner *scanner = [NSScanner scannerWithString:trimmed];
[scanner setScanLocation:1];
if ([scanner scanHexInt:&rgbValue]) {
CGFloat r = ((rgbValue & 0xFF0000) >> 16) / 255.0;
CGFloat g = ((rgbValue & 0x00FF00) >> 8) / 255.0;
CGFloat b = (rgbValue & 0x0000FF) / 255.0;
return [UIColor colorWithRed:r green:g blue:b alpha:1.0];
}
}

return [UIColor whiteColor]; // fallback
}

- (void)updateBackdropColorFromDOM {
if (!self.webView) return;
[self.webView evaluateJavaScript:@"window.getComputedStyle(document.body).backgroundColor" completionHandler:^(id result, NSError *error) {
if (result && [result isKindOfClass:[NSString class]]) {
UIColor *color = [self colorFromCssColorString:(NSString *)result];
if (color) {
[self forceBackdropColor:color];
}
}
}];
}

- (void)load
{
self.disableScroll = !self.bridge.config.scrollingEnabled;
Expand Down Expand Up @@ -97,8 +178,17 @@ - (void)load
[nc removeObserver:self.webView name:UIKeyboardWillShowNotification object:nil];
[nc removeObserver:self.webView name:UIKeyboardWillChangeFrameNotification object:nil];
[nc removeObserver:self.webView name:UIKeyboardDidChangeFrameNotification object:nil];
}

// Make WKWebView transparent
if (self.webView) {
self.webView.opaque = NO;
self.webView.backgroundColor = UIColor.clearColor;
self.webView.scrollView.backgroundColor = UIColor.clearColor;
}

// Force DOM color on load
[self updateBackdropColorFromDOM];
}

#pragma mark Keyboard events

Expand All @@ -124,11 +214,15 @@ - (void)onKeyboardWillShow:(NSNotification *)notification
if (hideTimer != nil) {
[hideTimer invalidate];
}

// Force DOM color whenever keyboard shows
[self updateBackdropColorFromDOM];

CGRect rect = [[notification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];

double height = rect.size.height;

if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
if ([self isIPad]) {
if (stageManagerOffset > 0) {
height = stageManagerOffset;
} else {
Expand All @@ -142,8 +236,14 @@ - (void)onKeyboardWillShow:(NSNotification *)notification
}
}

BOOL ignored = [self shouldIgnoreResizeForHeight:height];
if (ignored) {
NSLog(@"KeyboardPlugin: Ignoring QuickType Bar (%.1f) -> treat as 0.", height);
height = 0.0;
}

double duration = [[notification.userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]+0.2;
[self setKeyboardHeight:height delay:duration];
[self setKeyboardHeight:(int)height delay:duration];
[self resetScrollView];

NSString * data = [NSString stringWithFormat:@"{ 'keyboardHeight': %d }", (int)height];
Expand All @@ -157,6 +257,18 @@ - (void)onKeyboardDidShow:(NSNotification *)notification
CGRect rect = [[notification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
double height = rect.size.height;

if ([self isIPad]) {
if (stageManagerOffset > 0) {
height = stageManagerOffset;
}
}

BOOL ignored = [self shouldIgnoreResizeForHeight:height];
if (ignored) {
NSLog(@"KeyboardPlugin: (didShow) Ignoring QuickType Bar (%.1f) -> report 0.", height);
height = 0.0;
}

[self resetScrollView];

NSString * data = [NSString stringWithFormat:@"{ 'keyboardHeight': %d }", (int)height];
Expand Down Expand Up @@ -205,19 +317,8 @@ - (void)resizeElement:(NSString *)element withPaddingBottom:(int)paddingBottom w
- (void)_updateFrame
{
CGRect f, wf = CGRectZero;
UIWindow * window = nil;

if ([[[UIApplication sharedApplication] delegate] respondsToSelector:@selector(window)]) {
window = [[[UIApplication sharedApplication] delegate] window];
}

if (!window) {
if (@available(iOS 13.0, *)) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"self isKindOfClass: %@", UIWindowScene.class];
UIScene *scene = [UIApplication.sharedApplication.connectedScenes.allObjects filteredArrayUsingPredicate:predicate].firstObject;
window = [[(UIWindowScene*)scene windows] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"isKeyWindow == YES"]].firstObject;
}
}
UIWindow *window = [self currentKeyWindow];

if (window) {
f = [window bounds];
}
Expand Down