Skip to content
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
151 changes: 135 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,99 @@ @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 && @available(iOS 13.0, *)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@available(iOS 13.0, *) should always be true for Capacitor 8+, so it's not needed right?

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 {
if (self.bridge.config.backgroundColor) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capacitor config has config.backgroundColor and config.ios.backgroundColor, which is said that should override the global one. In that case we should check if config.ios.backgroundColor is defined and use that one, otherwise use config.backgroundColor, or update from the DOM?

Source: https://github.com/ionic-team/capacitor/blob/main/cli/src/declarations.ts

[self forceBackdropColor:self.bridge.config.backgroundColor];
} else {
[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 +191,16 @@ - (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;
}
Comment on lines +195 to +200
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to how vastly different UIs of Web Apps / Capacitor Apps can be, I'm wondering if such a change could have unforeseen consequences in app's UI. Is this how it is on Android?


[self updateBackdropColor];
}

#pragma mark Keyboard events

Expand All @@ -124,11 +226,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 +248,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 +269,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 +335,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