blob: 58e819ab5833a09659e8b93f18989ac189184b31 [file] [log] [blame]
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
/*
NOTE: plugman/cordova cli should have already installed this,
but you need the value UIViewControllerBasedStatusBarAppearance
in your Info.plist as well to set the styles in iOS 7
*/
#import "CDVStatusBar.h"
#import <objc/runtime.h>
#import <Cordova/CDVViewController.h>
static const void *kHideStatusBar = &kHideStatusBar;
static const void *kStatusBarStyle = &kStatusBarStyle;
@interface CDVViewController (StatusBar)
@property (nonatomic, retain) id sb_hideStatusBar;
@property (nonatomic, retain) id sb_statusBarStyle;
@end
@implementation CDVViewController (StatusBar)
@dynamic sb_hideStatusBar;
@dynamic sb_statusBarStyle;
- (id)sb_hideStatusBar {
return objc_getAssociatedObject(self, kHideStatusBar);
}
- (void)setSb_hideStatusBar:(id)newHideStatusBar {
objc_setAssociatedObject(self, kHideStatusBar, newHideStatusBar, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)sb_statusBarStyle {
return objc_getAssociatedObject(self, kStatusBarStyle);
}
- (void)setSb_statusBarStyle:(id)newStatusBarStyle {
objc_setAssociatedObject(self, kStatusBarStyle, newStatusBarStyle, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL) prefersStatusBarHidden {
return [self.sb_hideStatusBar boolValue];
}
- (UIStatusBarStyle)preferredStatusBarStyle
{
return (UIStatusBarStyle)[self.sb_statusBarStyle intValue];
}
@end
@interface CDVStatusBar () <UIScrollViewDelegate>
- (void)fireTappedEvent;
- (void)updateIsVisible:(BOOL)visible;
@end
@implementation CDVStatusBar
- (id)settingForKey:(NSString*)key
{
return [self.commandDelegate.settings objectForKey:[key lowercaseString]];
}
- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context
{
if ([keyPath isEqual:@"statusBarHidden"]) {
NSNumber* newValue = [change objectForKey:NSKeyValueChangeNewKey];
[self updateIsVisible:![newValue boolValue]];
}
}
-(void)statusBarDidChangeFrame:(NSNotification*)notification
{
//add a small delay for iOS 7 ( 0.1 seconds )
__weak CDVStatusBar* weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[weakSelf resizeWebView];
});
}
- (void)pluginInitialize
{
BOOL isiOS7 = (IsAtLeastiOSVersion(@"7.0"));
// init
NSNumber* uiviewControllerBasedStatusBarAppearance = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"];
_uiviewControllerBasedStatusBarAppearance = (uiviewControllerBasedStatusBarAppearance == nil || [uiviewControllerBasedStatusBarAppearance boolValue]) && isiOS7;
// observe the statusBarHidden property
[[UIApplication sharedApplication] addObserver:self forKeyPath:@"statusBarHidden" options:NSKeyValueObservingOptionNew context:NULL];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarDidChangeFrame:) name: UIApplicationDidChangeStatusBarFrameNotification object:nil];
_statusBarOverlaysWebView = YES; // default
[self initializeStatusBarBackgroundView];
self.viewController.view.autoresizesSubviews = YES;
NSString* setting;
setting = @"StatusBarBackgroundColor";
if ([self settingForKey:setting]) {
[self _backgroundColorByHexString:[self settingForKey:setting]];
}
setting = @"StatusBarStyle";
if ([self settingForKey:setting]) {
[self setStatusBarStyle:[self settingForKey:setting]];
}
// blank scroll view to intercept status bar taps
self.webView.scrollView.scrollsToTop = NO;
UIScrollView *fakeScrollView = [[UIScrollView alloc] initWithFrame:UIScreen.mainScreen.bounds];
fakeScrollView.delegate = self;
fakeScrollView.scrollsToTop = YES;
[self.viewController.view addSubview:fakeScrollView]; // Add scrollview to the view heirarchy so that it will begin accepting status bar taps
[self.viewController.view sendSubviewToBack:fakeScrollView]; // Send it to the very back of the view heirarchy
fakeScrollView.contentSize = CGSizeMake(UIScreen.mainScreen.bounds.size.width, UIScreen.mainScreen.bounds.size.height * 2.0f); // Make the scroll view longer than the screen itself
fakeScrollView.contentOffset = CGPointMake(0.0f, UIScreen.mainScreen.bounds.size.height); // Scroll down so a tap will take scroll view back to the top
}
- (void)onReset {
_eventsCallbackId = nil;
}
- (void)fireTappedEvent {
if (_eventsCallbackId == nil) {
return;
}
NSDictionary* payload = @{@"type": @"tap"};
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:payload];
[result setKeepCallbackAsBool:YES];
[self.commandDelegate sendPluginResult:result callbackId:_eventsCallbackId];
}
- (void)updateIsVisible:(BOOL)visible {
if (_eventsCallbackId == nil) {
return;
}
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:visible];
[result setKeepCallbackAsBool:YES];
[self.commandDelegate sendPluginResult:result callbackId:_eventsCallbackId];
}
- (void) _ready:(CDVInvokedUrlCommand*)command
{
_eventsCallbackId = command.callbackId;
[self updateIsVisible:![UIApplication sharedApplication].statusBarHidden];
NSString* setting = @"StatusBarOverlaysWebView";
if ([self settingForKey:setting]) {
self.statusBarOverlaysWebView = [(NSNumber*)[self settingForKey:setting] boolValue];
if (self.statusBarOverlaysWebView) {
[self resizeWebView];
}
}
}
- (void) initializeStatusBarBackgroundView
{
CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
if ([[UIApplication sharedApplication]statusBarOrientation] == UIInterfaceOrientationPortraitUpsideDown &&
statusBarFrame.size.height + statusBarFrame.origin.y == [[UIScreen mainScreen] bounds].size.height) {
// When started in upside-down orientation on iOS 7, status bar will be bound to lower edge of the
// screen (statusBarFrame.origin.y will be somewhere around screen height). In this case we need to
// correct frame's coordinates
statusBarFrame.origin.y = 0;
}
statusBarFrame = [self invertFrameIfNeeded:statusBarFrame];
_statusBarBackgroundView = [[UIView alloc] initWithFrame:statusBarFrame];
_statusBarBackgroundView.backgroundColor = _statusBarBackgroundColor;
_statusBarBackgroundView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin);
_statusBarBackgroundView.autoresizesSubviews = YES;
}
- (CGRect) invertFrameIfNeeded:(CGRect)rect {
// landscape is where (width > height). On iOS < 8, we need to invert since frames are
// always in Portrait context
if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]) && (rect.size.width < rect.size.height)) {
CGFloat temp = rect.size.width;
rect.size.width = rect.size.height;
rect.size.height = temp;
rect.origin = CGPointZero;
}
return rect;
}
- (void) setStatusBarOverlaysWebView:(BOOL)statusBarOverlaysWebView
{
// we only care about the latest iOS version or a change in setting
if (!IsAtLeastiOSVersion(@"7.0") || statusBarOverlaysWebView == _statusBarOverlaysWebView) {
return;
}
_statusBarOverlaysWebView = statusBarOverlaysWebView;
[self resizeWebView];
if (statusBarOverlaysWebView) {
[_statusBarBackgroundView removeFromSuperview];
} else {
[self initializeStatusBarBackgroundView];
[self.webView.superview addSubview:_statusBarBackgroundView];
}
}
- (BOOL) statusBarOverlaysWebView
{
return _statusBarOverlaysWebView;
}
- (void) overlaysWebView:(CDVInvokedUrlCommand*)command
{
id value = [command argumentAtIndex:0];
if (!([value isKindOfClass:[NSNumber class]])) {
value = [NSNumber numberWithBool:YES];
}
self.statusBarOverlaysWebView = [value boolValue];
}
- (void) refreshStatusBarAppearance
{
SEL sel = NSSelectorFromString(@"setNeedsStatusBarAppearanceUpdate");
if ([self.viewController respondsToSelector:sel]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.viewController performSelector:sel withObject:nil];
#pragma clang diagnostic pop
}
}
- (void) setStyleForStatusBar:(UIStatusBarStyle)style
{
if (_uiviewControllerBasedStatusBarAppearance) {
CDVViewController* vc = (CDVViewController*)self.viewController;
vc.sb_statusBarStyle = [NSNumber numberWithInt:style];
[self refreshStatusBarAppearance];
} else {
[[UIApplication sharedApplication] setStatusBarStyle:style];
}
}
- (void) setStatusBarStyle:(NSString*)statusBarStyle
{
// default, lightContent, blackTranslucent, blackOpaque
NSString* lcStatusBarStyle = [statusBarStyle lowercaseString];
if ([lcStatusBarStyle isEqualToString:@"default"]) {
[self styleDefault:nil];
} else if ([lcStatusBarStyle isEqualToString:@"lightcontent"]) {
[self styleLightContent:nil];
} else if ([lcStatusBarStyle isEqualToString:@"blacktranslucent"]) {
[self styleBlackTranslucent:nil];
} else if ([lcStatusBarStyle isEqualToString:@"blackopaque"]) {
[self styleBlackOpaque:nil];
}
}
- (void) styleDefault:(CDVInvokedUrlCommand*)command
{
[self setStyleForStatusBar:UIStatusBarStyleDefault];
}
- (void) styleLightContent:(CDVInvokedUrlCommand*)command
{
[self setStyleForStatusBar:UIStatusBarStyleLightContent];
}
- (void) styleBlackTranslucent:(CDVInvokedUrlCommand*)command
{
#if __IPHONE_OS_VERSION_MAX_ALLOWED < 70000
# define TRANSLUCENT_STYLE UIStatusBarStyleBlackTranslucent
#else
# define TRANSLUCENT_STYLE UIStatusBarStyleLightContent
#endif
[self setStyleForStatusBar:TRANSLUCENT_STYLE];
}
- (void) styleBlackOpaque:(CDVInvokedUrlCommand*)command
{
#if __IPHONE_OS_VERSION_MAX_ALLOWED < 70000
# define OPAQUE_STYLE UIStatusBarStyleBlackOpaque
#else
# define OPAQUE_STYLE UIStatusBarStyleLightContent
#endif
[self setStyleForStatusBar:OPAQUE_STYLE];
}
- (void) backgroundColorByName:(CDVInvokedUrlCommand*)command
{
id value = [command argumentAtIndex:0];
if (!([value isKindOfClass:[NSString class]])) {
value = @"black";
}
SEL selector = NSSelectorFromString([value stringByAppendingString:@"Color"]);
if ([UIColor respondsToSelector:selector]) {
_statusBarBackgroundView.backgroundColor = [UIColor performSelector:selector];
}
}
- (void) _backgroundColorByHexString:(NSString*)hexString
{
unsigned int rgbValue = 0;
NSScanner* scanner = [NSScanner scannerWithString:hexString];
[scanner setScanLocation:1];
[scanner scanHexInt:&rgbValue];
_statusBarBackgroundColor = [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16)/255.0 green:((rgbValue & 0xFF00) >> 8)/255.0 blue:(rgbValue & 0xFF)/255.0 alpha:1.0];
_statusBarBackgroundView.backgroundColor = _statusBarBackgroundColor;
}
- (void) backgroundColorByHexString:(CDVInvokedUrlCommand*)command
{
NSString* value = [command argumentAtIndex:0];
if (!([value isKindOfClass:[NSString class]])) {
value = @"#000000";
}
if (![value hasPrefix:@"#"] || [value length] < 7) {
return;
}
[self _backgroundColorByHexString:value];
}
- (void) hideStatusBar
{
if (_uiviewControllerBasedStatusBarAppearance) {
CDVViewController* vc = (CDVViewController*)self.viewController;
vc.sb_hideStatusBar = [NSNumber numberWithBool:YES];
[self refreshStatusBarAppearance];
} else {
UIApplication* app = [UIApplication sharedApplication];
[app setStatusBarHidden:YES];
}
}
- (void) hide:(CDVInvokedUrlCommand*)command
{
UIApplication* app = [UIApplication sharedApplication];
if (!app.isStatusBarHidden)
{
[self hideStatusBar];
if (IsAtLeastiOSVersion(@"7.0")) {
[_statusBarBackgroundView removeFromSuperview];
}
[self resizeWebView];
_statusBarBackgroundView.hidden = YES;
}
}
- (void) showStatusBar
{
if (_uiviewControllerBasedStatusBarAppearance) {
CDVViewController* vc = (CDVViewController*)self.viewController;
vc.sb_hideStatusBar = [NSNumber numberWithBool:NO];
[self refreshStatusBarAppearance];
} else {
UIApplication* app = [UIApplication sharedApplication];
[app setStatusBarHidden:NO];
}
}
- (void) show:(CDVInvokedUrlCommand*)command
{
UIApplication* app = [UIApplication sharedApplication];
if (app.isStatusBarHidden)
{
BOOL isIOS7 = (IsAtLeastiOSVersion(@"7.0"));
[self showStatusBar];
[self resizeWebView];
if (isIOS7) {
if (!self.statusBarOverlaysWebView) {
// there is a possibility that when the statusbar was hidden, it was in a different orientation
// from the current one. Therefore we need to expand the statusBarBackgroundView as well to the
// statusBar's current size
CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
statusBarFrame = [self invertFrameIfNeeded:statusBarFrame];
CGRect sbBgFrame = _statusBarBackgroundView.frame;
sbBgFrame.size = statusBarFrame.size;
_statusBarBackgroundView.frame = sbBgFrame;
[self.webView.superview addSubview:_statusBarBackgroundView];
}
}
_statusBarBackgroundView.hidden = NO;
}
}
-(void)resizeWebView
{
BOOL isIOS7 = (IsAtLeastiOSVersion(@"7.0"));
if (isIOS7) {
CGRect bounds = [[UIScreen mainScreen] bounds];
bounds = [self invertFrameIfNeeded:bounds];
if (UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation])) {
self.viewController.view.frame = bounds;
} else if (self.viewController.presentedViewController != nil) {
// https://issues.apache.org/jira/browse/CB-11018
BOOL isIOS8 = (IsAtLeastiOSVersion(@"8.0"));
BOOL isIOS9 = (IsAtLeastiOSVersion(@"9.0"));
if (isIOS8 && !isIOS9) {
// iOS 8
bounds = CGRectMake(0, 0, bounds.size.width, bounds.size.height);
} else {
// iOS7, iOS9+
if ([self.viewController.presentedViewController.presentationController isKindOfClass:[UIPopoverPresentationController class]]) {
bounds = CGRectMake(0, 0, bounds.size.width, bounds.size.height);
} else {
bounds = CGRectMake(0, 0, bounds.size.height, bounds.size.width);
}
}
}
self.webView.frame = bounds;
if (!self.statusBarOverlaysWebView) {
CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
statusBarFrame = [self invertFrameIfNeeded:statusBarFrame];
CGRect frame = self.webView.frame;
frame.origin.y = statusBarFrame.size.height;
frame.size.height -= statusBarFrame.size.height;
self.webView.frame = frame;
}
} else {
CGRect bounds = [[UIScreen mainScreen] applicationFrame];
self.viewController.view.frame = bounds;
}
}
- (void) dealloc
{
[[UIApplication sharedApplication] removeObserver:self forKeyPath:@"statusBarHidden"];
[[NSNotificationCenter defaultCenter]removeObserver:self name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
}
#pragma mark - UIScrollViewDelegate
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView
{
[self fireTappedEvent];
return NO;
}
@end