arnaucube
arnaucube/openEventsPlatformAppPublic Self-hosted repository
openEventsPlatformApp/plugins/cordova-plugin-statusbar/src/ios/CDVStatusBar.m
plugins/cordova-plugin-statusbar/src/ios/CDVStatusBar.mRaw
  1/*
  2 Licensed to the Apache Software Foundation (ASF) under one
  3 or more contributor license agreements.  See the NOTICE file
  4 distributed with this work for additional information
  5 regarding copyright ownership.  The ASF licenses this file
  6 to you under the Apache License, Version 2.0 (the
  7 "License"); you may not use this file except in compliance
  8 with the License.  You may obtain a copy of the License at
  9
 10 http://www.apache.org/licenses/LICENSE-2.0
 11
 12 Unless required by applicable law or agreed to in writing,
 13 software distributed under the License is distributed on an
 14 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15 KIND, either express or implied.  See the License for the
 16 specific language governing permissions and limitations
 17 under the License.
 18 */
 19
 20/*
 21 NOTE: plugman/cordova cli should have already installed this,
 22 but you need the value UIViewControllerBasedStatusBarAppearance
 23 in your Info.plist as well to set the styles in iOS 7
 24 */
 25
 26#import "CDVStatusBar.h"
 27#import <objc/runtime.h>
 28#import <Cordova/CDVViewController.h>
 29
 30static const void *kHideStatusBar = &kHideStatusBar;
 31static const void *kStatusBarStyle = &kStatusBarStyle;
 32
 33@interface CDVViewController (StatusBar)
 34
 35@property (nonatomic, retain) id sb_hideStatusBar;
 36@property (nonatomic, retain) id sb_statusBarStyle;
 37
 38@end
 39
 40@implementation CDVViewController (StatusBar)
 41
 42@dynamic sb_hideStatusBar;
 43@dynamic sb_statusBarStyle;
 44
 45- (id)sb_hideStatusBar {
 46    return objc_getAssociatedObject(self, kHideStatusBar);
 47}
 48
 49- (void)setSb_hideStatusBar:(id)newHideStatusBar {
 50    objc_setAssociatedObject(self, kHideStatusBar, newHideStatusBar, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
 51}
 52
 53- (id)sb_statusBarStyle {
 54    return objc_getAssociatedObject(self, kStatusBarStyle);
 55}
 56
 57- (void)setSb_statusBarStyle:(id)newStatusBarStyle {
 58    objc_setAssociatedObject(self, kStatusBarStyle, newStatusBarStyle, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
 59}
 60
 61- (BOOL) prefersStatusBarHidden {
 62    return [self.sb_hideStatusBar boolValue];
 63}
 64
 65- (UIStatusBarStyle)preferredStatusBarStyle
 66{
 67    return (UIStatusBarStyle)[self.sb_statusBarStyle intValue];
 68}
 69
 70@end
 71
 72
 73@interface CDVStatusBar () <UIScrollViewDelegate>
 74- (void)fireTappedEvent;
 75- (void)updateIsVisible:(BOOL)visible;
 76@end
 77
 78@implementation CDVStatusBar
 79
 80- (id)settingForKey:(NSString*)key
 81{
 82    return [self.commandDelegate.settings objectForKey:[key lowercaseString]];
 83}
 84
 85- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context
 86{
 87    if ([keyPath isEqual:@"statusBarHidden"]) {
 88        NSNumber* newValue = [change objectForKey:NSKeyValueChangeNewKey];
 89        [self updateIsVisible:![newValue boolValue]];
 90    }
 91}
 92
 93-(void)cordovaViewWillAppear:(NSNotification*)notification
 94{
 95    [self resizeWebView];
 96}
 97
 98-(void)statusBarDidChangeFrame:(NSNotification*)notification
 99{
100    //add a small delay for iOS 7 ( 0.1 seconds )
101    __weak CDVStatusBar* weakSelf = self;
102    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
103        [weakSelf resizeWebView];
104    });
105}
106
107- (void)pluginInitialize
108{
109    BOOL isiOS7 = (IsAtLeastiOSVersion(@"7.0"));
110
111    // init
112    NSNumber* uiviewControllerBasedStatusBarAppearance = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"];
113    _uiviewControllerBasedStatusBarAppearance = (uiviewControllerBasedStatusBarAppearance == nil || [uiviewControllerBasedStatusBarAppearance boolValue]) && isiOS7;
114
115    // observe the statusBarHidden property
116    [[UIApplication sharedApplication] addObserver:self forKeyPath:@"statusBarHidden" options:NSKeyValueObservingOptionNew context:NULL];
117
118    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarDidChangeFrame:) name: UIApplicationDidChangeStatusBarFrameNotification object:nil];
119
120    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cordovaViewWillAppear:) name: @"CDVViewWillAppearNotification" object:nil];
121
122    _statusBarOverlaysWebView = YES; // default
123
124    [self initializeStatusBarBackgroundView];
125
126    self.viewController.view.autoresizesSubviews = YES;
127
128    NSString* setting;
129
130    setting  = @"StatusBarBackgroundColor";
131    if ([self settingForKey:setting]) {
132        [self _backgroundColorByHexString:[self settingForKey:setting]];
133    }
134
135    setting  = @"StatusBarStyle";
136    if ([self settingForKey:setting]) {
137        [self setStatusBarStyle:[self settingForKey:setting]];
138    }
139
140    // blank scroll view to intercept status bar taps
141    self.webView.scrollView.scrollsToTop = NO;
142    UIScrollView *fakeScrollView = [[UIScrollView alloc] initWithFrame:UIScreen.mainScreen.bounds];
143    fakeScrollView.delegate = self;
144    fakeScrollView.scrollsToTop = YES;
145    [self.viewController.view addSubview:fakeScrollView]; // Add scrollview to the view heirarchy so that it will begin accepting status bar taps
146    [self.viewController.view sendSubviewToBack:fakeScrollView]; // Send it to the very back of the view heirarchy
147    fakeScrollView.contentSize = CGSizeMake(UIScreen.mainScreen.bounds.size.width, UIScreen.mainScreen.bounds.size.height * 2.0f); // Make the scroll view longer than the screen itself
148    fakeScrollView.contentOffset = CGPointMake(0.0f, UIScreen.mainScreen.bounds.size.height); // Scroll down so a tap will take scroll view back to the top
149}
150
151- (void)onReset {
152    _eventsCallbackId = nil;
153}
154
155- (void)fireTappedEvent {
156    if (_eventsCallbackId == nil) {
157        return;
158    }
159    NSDictionary* payload = @{@"type": @"tap"};
160    CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:payload];
161    [result setKeepCallbackAsBool:YES];
162    [self.commandDelegate sendPluginResult:result callbackId:_eventsCallbackId];
163}
164
165- (void)updateIsVisible:(BOOL)visible {
166    if (_eventsCallbackId == nil) {
167        return;
168    }
169    CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:visible];
170    [result setKeepCallbackAsBool:YES];
171    [self.commandDelegate sendPluginResult:result callbackId:_eventsCallbackId];
172}
173
174- (void) _ready:(CDVInvokedUrlCommand*)command
175{
176    _eventsCallbackId = command.callbackId;
177    [self updateIsVisible:![UIApplication sharedApplication].statusBarHidden];
178    NSString* setting = @"StatusBarOverlaysWebView";
179    if ([self settingForKey:setting]) {
180        self.statusBarOverlaysWebView = [(NSNumber*)[self settingForKey:setting] boolValue];
181        if (self.statusBarOverlaysWebView) {
182            [self resizeWebView];
183        }
184    }
185}
186
187- (void) initializeStatusBarBackgroundView
188{
189    CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
190
191    if ([[UIApplication sharedApplication]statusBarOrientation] == UIInterfaceOrientationPortraitUpsideDown &&
192        statusBarFrame.size.height + statusBarFrame.origin.y == [self.viewController.view.window bounds].size.height) {
193
194        // When started in upside-down orientation on iOS 7, status bar will be bound to lower edge of the
195        // screen (statusBarFrame.origin.y will be somewhere around screen height). In this case we need to
196        // correct frame's coordinates
197        statusBarFrame.origin.y = 0;
198    }
199
200    statusBarFrame = [self invertFrameIfNeeded:statusBarFrame];
201
202    _statusBarBackgroundView = [[UIView alloc] initWithFrame:statusBarFrame];
203    _statusBarBackgroundView.backgroundColor = _statusBarBackgroundColor;
204    _statusBarBackgroundView.autoresizingMask = (UIViewAutoresizingFlexibleWidth  | UIViewAutoresizingFlexibleBottomMargin);
205    _statusBarBackgroundView.autoresizesSubviews = YES;
206}
207
208- (CGRect) invertFrameIfNeeded:(CGRect)rect {
209    // landscape is where (width > height). On iOS < 8, we need to invert since frames are
210    // always in Portrait context. Do not run this on ios 8 or above to avoid breaking ipad pro multitask layout
211    if (!IsAtLeastiOSVersion(@"8.0") && UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])) {
212        CGFloat temp = rect.size.width;
213        rect.size.width = rect.size.height;
214        rect.size.height = temp;
215        rect.origin = CGPointZero;
216    }
217    
218    return rect;
219}
220
221- (void) setStatusBarOverlaysWebView:(BOOL)statusBarOverlaysWebView
222{
223    // we only care about the latest iOS version or a change in setting
224    if (!IsAtLeastiOSVersion(@"7.0") || statusBarOverlaysWebView == _statusBarOverlaysWebView) {
225        return;
226    }
227    
228    _statusBarOverlaysWebView = statusBarOverlaysWebView;
229
230    [self resizeWebView];
231
232    if (statusBarOverlaysWebView) {
233
234        [_statusBarBackgroundView removeFromSuperview];
235
236    } else {
237
238        [self initializeStatusBarBackgroundView];
239        [self.webView.superview addSubview:_statusBarBackgroundView];
240
241    }
242
243}
244
245- (BOOL) statusBarOverlaysWebView
246{
247    return _statusBarOverlaysWebView;
248}
249
250- (void) overlaysWebView:(CDVInvokedUrlCommand*)command
251{
252    id value = [command argumentAtIndex:0];
253    if (!([value isKindOfClass:[NSNumber class]])) {
254        value = [NSNumber numberWithBool:YES];
255    }
256
257    self.statusBarOverlaysWebView = [value boolValue];
258}
259
260- (void) refreshStatusBarAppearance
261{
262    SEL sel = NSSelectorFromString(@"setNeedsStatusBarAppearanceUpdate");
263    if ([self.viewController respondsToSelector:sel]) {
264#pragma clang diagnostic push
265#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
266        [self.viewController performSelector:sel withObject:nil];
267#pragma clang diagnostic pop
268    }
269}
270
271- (void) setStyleForStatusBar:(UIStatusBarStyle)style
272{
273    if (_uiviewControllerBasedStatusBarAppearance) {
274        CDVViewController* vc = (CDVViewController*)self.viewController;
275        vc.sb_statusBarStyle = [NSNumber numberWithInt:style];
276        [self refreshStatusBarAppearance];
277
278    } else {
279        [[UIApplication sharedApplication] setStatusBarStyle:style];
280    }
281}
282
283- (void) setStatusBarStyle:(NSString*)statusBarStyle
284{
285    // default, lightContent, blackTranslucent, blackOpaque
286    NSString* lcStatusBarStyle = [statusBarStyle lowercaseString];
287
288    if ([lcStatusBarStyle isEqualToString:@"default"]) {
289        [self styleDefault:nil];
290    } else if ([lcStatusBarStyle isEqualToString:@"lightcontent"]) {
291        [self styleLightContent:nil];
292    } else if ([lcStatusBarStyle isEqualToString:@"blacktranslucent"]) {
293        [self styleBlackTranslucent:nil];
294    } else if ([lcStatusBarStyle isEqualToString:@"blackopaque"]) {
295        [self styleBlackOpaque:nil];
296    }
297}
298
299- (void) styleDefault:(CDVInvokedUrlCommand*)command
300{
301    [self setStyleForStatusBar:UIStatusBarStyleDefault];
302}
303
304- (void) styleLightContent:(CDVInvokedUrlCommand*)command
305{
306    [self setStyleForStatusBar:UIStatusBarStyleLightContent];
307}
308
309- (void) styleBlackTranslucent:(CDVInvokedUrlCommand*)command
310{
311    #if __IPHONE_OS_VERSION_MAX_ALLOWED < 70000
312    # define TRANSLUCENT_STYLE UIStatusBarStyleBlackTranslucent
313    #else
314    # define TRANSLUCENT_STYLE UIStatusBarStyleLightContent
315    #endif
316    [self setStyleForStatusBar:TRANSLUCENT_STYLE];
317}
318
319- (void) styleBlackOpaque:(CDVInvokedUrlCommand*)command
320{
321    #if __IPHONE_OS_VERSION_MAX_ALLOWED < 70000
322    # define OPAQUE_STYLE UIStatusBarStyleBlackOpaque
323    #else
324    # define OPAQUE_STYLE UIStatusBarStyleLightContent
325    #endif
326    [self setStyleForStatusBar:OPAQUE_STYLE];
327}
328
329- (void) backgroundColorByName:(CDVInvokedUrlCommand*)command
330{
331    id value = [command argumentAtIndex:0];
332    if (!([value isKindOfClass:[NSString class]])) {
333        value = @"black";
334    }
335
336    SEL selector = NSSelectorFromString([value stringByAppendingString:@"Color"]);
337    if ([UIColor respondsToSelector:selector]) {
338        _statusBarBackgroundView.backgroundColor = [UIColor performSelector:selector];
339    }
340}
341
342- (void) _backgroundColorByHexString:(NSString*)hexString
343{
344    unsigned int rgbValue = 0;
345    NSScanner* scanner = [NSScanner scannerWithString:hexString];
346    [scanner setScanLocation:1];
347    [scanner scanHexInt:&rgbValue];
348
349    _statusBarBackgroundColor = [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16)/255.0 green:((rgbValue & 0xFF00) >> 8)/255.0 blue:(rgbValue & 0xFF)/255.0 alpha:1.0];
350    _statusBarBackgroundView.backgroundColor = _statusBarBackgroundColor;
351}
352
353- (void) backgroundColorByHexString:(CDVInvokedUrlCommand*)command
354{
355    NSString* value = [command argumentAtIndex:0];
356    if (!([value isKindOfClass:[NSString class]])) {
357        value = @"#000000";
358    }
359
360    if (![value hasPrefix:@"#"] || [value length] < 7) {
361        return;
362    }
363
364    [self _backgroundColorByHexString:value];
365}
366
367- (void) hideStatusBar
368{
369    if (_uiviewControllerBasedStatusBarAppearance) {
370        CDVViewController* vc = (CDVViewController*)self.viewController;
371        vc.sb_hideStatusBar = [NSNumber numberWithBool:YES];
372        [self refreshStatusBarAppearance];
373
374    } else {
375        UIApplication* app = [UIApplication sharedApplication];
376        [app setStatusBarHidden:YES];
377    }
378}
379
380- (void) hide:(CDVInvokedUrlCommand*)command
381{
382    UIApplication* app = [UIApplication sharedApplication];
383
384    if (!app.isStatusBarHidden)
385    {
386        
387        [self hideStatusBar];
388
389        if (IsAtLeastiOSVersion(@"7.0")) {
390            [_statusBarBackgroundView removeFromSuperview];
391        }
392
393        [self resizeWebView];
394
395        _statusBarBackgroundView.hidden = YES;
396    }
397}
398
399- (void) showStatusBar
400{
401    if (_uiviewControllerBasedStatusBarAppearance) {
402        CDVViewController* vc = (CDVViewController*)self.viewController;
403        vc.sb_hideStatusBar = [NSNumber numberWithBool:NO];
404        [self refreshStatusBarAppearance];
405
406    } else {
407        UIApplication* app = [UIApplication sharedApplication];
408        [app setStatusBarHidden:NO];
409    }
410}
411
412- (void) show:(CDVInvokedUrlCommand*)command
413{
414    UIApplication* app = [UIApplication sharedApplication];
415
416    if (app.isStatusBarHidden)
417    {
418        BOOL isIOS7 = (IsAtLeastiOSVersion(@"7.0"));
419
420        [self showStatusBar];
421        [self resizeWebView];
422
423        if (isIOS7) {
424
425            if (!self.statusBarOverlaysWebView) {
426
427                // there is a possibility that when the statusbar was hidden, it was in a different orientation
428                // from the current one. Therefore we need to expand the statusBarBackgroundView as well to the
429                // statusBar's current size
430                CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
431                statusBarFrame = [self invertFrameIfNeeded:statusBarFrame];
432                CGRect sbBgFrame = _statusBarBackgroundView.frame;
433                sbBgFrame.size = statusBarFrame.size;
434                _statusBarBackgroundView.frame = sbBgFrame;
435                [self.webView.superview addSubview:_statusBarBackgroundView];
436
437            }
438
439        }
440
441        _statusBarBackgroundView.hidden = NO;
442    }
443}
444
445-(void)resizeWebView
446{
447    BOOL isIOS7 = (IsAtLeastiOSVersion(@"7.0"));
448
449    if (isIOS7) {
450        CGRect bounds = [self.viewController.view.window bounds];
451        bounds = [self invertFrameIfNeeded:bounds];
452
453        if (UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation])) {
454            self.viewController.view.frame = bounds;
455        } else if (self.viewController.presentedViewController != nil) {
456            // https://issues.apache.org/jira/browse/CB-11018
457            BOOL isIOS8 = (IsAtLeastiOSVersion(@"8.0"));
458            BOOL isIOS9 = (IsAtLeastiOSVersion(@"9.0"));
459            if (isIOS8 && !isIOS9) {
460                // iOS 8
461                bounds = CGRectMake(0, 0, bounds.size.width, bounds.size.height);
462            } else {
463                // iOS7, iOS9+
464                if ([self.viewController.presentedViewController.presentationController isKindOfClass:[UIPopoverPresentationController class]] || UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad) {
465                    bounds = CGRectMake(0, 0, bounds.size.width, bounds.size.height);
466                } else {
467                    bounds = CGRectMake(0, 0, bounds.size.height, bounds.size.width);
468                }
469            }
470        }
471        self.webView.frame = bounds;
472
473        CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
474        statusBarFrame = [self invertFrameIfNeeded:statusBarFrame];
475        CGRect frame = self.webView.frame;
476        CGFloat height = statusBarFrame.size.height;
477
478        if (!self.statusBarOverlaysWebView) {
479            // CB-10158 If a full screen video is playing the status bar height will be 0, set it to 20
480            frame.origin.y = height > 0 ? height: 20;
481        } else {
482            // Even if overlay is used, we want to handle in-call/recording/hotspot larger status bar
483            frame.origin.y = height >= 20 ? height - 20 : 0;
484        }
485        frame.size.height -= frame.origin.y;
486        self.webView.frame = frame;
487    } else {
488        CGRect bounds = [[UIScreen mainScreen] applicationFrame];
489        self.viewController.view.frame = bounds;
490    }
491}
492
493- (void) dealloc
494{
495    [[UIApplication sharedApplication] removeObserver:self forKeyPath:@"statusBarHidden"];
496    [[NSNotificationCenter defaultCenter]removeObserver:self name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
497}
498
499
500#pragma mark - UIScrollViewDelegate
501
502- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView
503{
504    [self fireTappedEvent];
505    return NO;
506}
507
508@end