Skip to content
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,10 @@ public void load() {
@PluginMethod
public void show(final PluginCall call) {
execute(() ->
new Handler(Looper.getMainLooper()).postDelayed(
() -> {
implementation.show();
call.resolve();
},
350
)
new Handler(Looper.getMainLooper()).postDelayed(() -> {
implementation.show();
call.resolve();
}, 350)
);
}

Expand Down
8 changes: 7 additions & 1 deletion example-app/capacitor.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'io.ionic.starter',
appName: 'CAP-KEYB-SAMPLE',
webDir: 'dist'
webDir: 'dist',
backgroundColor: "#fffb38",
plugins: {
Keyboard: {
autoBackdropColor: 'auto'
}
}
};

export default config;
150 changes: 134 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 @@ -54,6 +55,105 @@ @implementation KeyboardPlugin
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) {
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;
CGFloat screenHeight = UIScreen.mainScreen.bounds.size.height;
return (height / screenHeight) < 0.20;
}
Comment thread
theproducer marked this conversation as resolved.

#pragma mark - Lifecycle

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

if ([trimmed isEqualToString:@"transparent"]) {
return [UIColor clearColor];
}

if ([trimmed hasPrefix:@"rgb"]) {
NSRange parenRange = [trimmed rangeOfString:@"("];
NSString *clean = parenRange.location != NSNotFound ? [trimmed substringFromIndex:parenRange.location + 1] : trimmed;
clean = [clean 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;
CGFloat a = parts.count >= 4 ? [parts[3] floatValue] : 1.0;
return [UIColor colorWithRed:r green:g blue:b alpha:a];
}
}

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)updateBackdropColor {
NSString *mode = [[self getConfig] getString:@"autoBackdropColor": @"off"];

if ([mode isEqualToString:@"auto"]) {
if (self.bridge.config.backgroundColor) {
[self forceBackdropColor:self.bridge.config.backgroundColor];
} else {
[self updateBackdropColorFromDOM];
}
} else if ([mode isEqualToString:@"dom"]) {
[self updateBackdropColorFromDOM];
}
}

- (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 +197,9 @@ - (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];
}

[self updateBackdropColor];
}

#pragma mark Keyboard events

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

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

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 +247,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 +268,24 @@ - (void)onKeyboardDidShow:(NSNotification *)notification
CGRect rect = [[notification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
double height = rect.size.height;

if ([self isIPad]) {
if (stageManagerOffset > 0) {
height = stageManagerOffset;
} else {
CGRect webViewAbsolute = [self.webView convertRect:self.webView.frame toCoordinateSpace:self.webView.window.screen.coordinateSpace];
height = (webViewAbsolute.size.height + webViewAbsolute.origin.y) - (UIScreen.mainScreen.bounds.size.height - rect.size.height);
if (height < 0) {
height = 0;
}
}
}

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 +334,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
19 changes: 19 additions & 0 deletions src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,25 @@ declare module '@capacitor/cli' {
* @example true
*/
resizeOnFullScreen?: boolean;

/**
* Controls how the keyboard backdrop color (the area visible behind the
* keyboard) is set every time the keyboard is about to show.
*
* - `'off'` — Do not tint the backdrop.
* - `'auto'` — Use the `backgroundColor` set in the Capacitor config;
* otherwise derive the color from the web app's DOM body background.
* - `'dom'` — Always derive the color from the web app's DOM body
* background, ignoring the `backgroundColor` config. If the DOM
* has no resolvable background, the backdrop is left untouched.
*
* Only available on iOS.
*
* @since 8.1.0
* @default "off"
* @example "auto"
*/
autoBackdropColor?: 'off' | 'auto' | 'dom';
};
}
}
Expand Down