arnaucube
arnaucube/commonroutesServerPublic Self-hosted repository
commonroutesServer/www/plugins/cordova-plugin-statusbar/src/ios/CDVStatusBar.m
www/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)statusBarDidChangeFrame:(NSNotification*)notification
 94{
 95    //add a small delay for iOS 7 ( 0.1 seconds )
 96    __weak CDVStatusBar* weakSelf = self;
 97    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
 98        [weakSelf resizeWebView];
 99    });
100}
101
102- (void)pluginInitialize
103{
104    BOOL isiOS7 = (IsAtLeastiOSVersion(@"7.0"));
105
106    // init
107    NSNumber* uiviewControllerBasedStatusBarAppearance = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"];
108    _uiviewControllerBasedStatusBarAppearance = (uiviewControllerBasedStatusBarAppearance == nil || [uiviewControllerBasedStatusBarAppearance boolValue]) && isiOS7;
109
110    // observe the statusBarHidden property
111    [[UIApplication sharedApplication] addObserver:self forKeyPath:@"statusBarHidden" options:NSKeyValueObservingOptionNew context:NULL];
112
113    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarDidChangeFrame:) name: UIApplicationDidChangeStatusBarFrameNotification object:nil];
114
115    _statusBarOverlaysWebView = YES; // default
116
117    [self initializeStatusBarBackgroundView];
118
119    self.viewController.view.autoresizesSubviews = YES;
120
121    NSString* setting;
122
123    setting  = @"StatusBarBackgroundColor";
124    if ([self settingForKey:setting]) {
125        [self _backgroundColorByHexString:[self settingForKey:setting]];
126    }
127
128    setting  = @"StatusBarStyle";
129    if ([self settingForKey:setting]) {
130        [self setStatusBarStyle:[self settingForKey:setting]];
131    }
132
133    // blank scroll view to intercept status bar taps
134    self.webView.scrollView.scrollsToTop = NO;
135    UIScrollView *fakeScrollView = [[UIScrollView alloc] initWithFrame:UIScreen.mainScreen.bounds];
136    fakeScrollView.delegate = self;
137    fakeScrollView.scrollsToTop = YES;
138    [self.viewController.view addSubview:fakeScrollView]; // Add scrollview to the view heirarchy so that it will begin accepting status bar taps
139    [self.viewController.view sendSubviewToBack:fakeScrollView]; // Send it to the very back of the view heirarchy
140    fakeScrollView.contentSize = CGSizeMake(UIScreen.mainScreen.bounds.size.width, UIScreen.mainScreen.bounds.size.height * 2.0f); // Make the scroll view longer than the screen itself
141    fakeScrollView.contentOffset = CGPointMake(0.0f, UIScreen.mainScreen.bounds.size.height); // Scroll down so a tap will take scroll view back to the top
142}
143
144- (void)onReset {
145    _eventsCallbackId = nil;
146}
147
148- (void)fireTappedEvent {
149    if (_eventsCallbackId == nil) {
150        return;
151    }
152    NSDictionary* payload = @{@"type": @"tap"};
153    CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:payload];
154    [result setKeepCallbackAsBool:YES];
155    [self.commandDelegate sendPluginResult:result callbackId:_eventsCallbackId];
156}
157
158- (void)updateIsVisible:(BOOL)visible {
159    if (_eventsCallbackId == nil) {
160        return;
161    }
162    CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:visible];
163    [result setKeepCallbackAsBool:YES];
164    [self.commandDelegate sendPluginResult:result callbackId:_eventsCallbackId];
165}
166
167- (void) _ready:(CDVInvokedUrlCommand*)command
168{
169    _eventsCallbackId = command.callbackId;
170    [self updateIsVisible:![UIApplication sharedApplication].statusBarHidden];
171    NSString* setting = @"StatusBarOverlaysWebView";
172    if ([self settingForKey:setting]) {
173        self.statusBarOverlaysWebView = [(NSNumber*)[self settingForKey:setting] boolValue];
174        if (self.statusBarOverlaysWebView) {
175            [self resizeWebView];
176        }
177    }
178}
179
180- (void) initializeStatusBarBackgroundView
181{
182    CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
183
184    if ([[UIApplication sharedApplication]statusBarOrientation] == UIInterfaceOrientationPortraitUpsideDown &&
185        statusBarFrame.size.height + statusBarFrame.origin.y == [[UIScreen mainScreen] bounds].size.height) {
186
187        // When started in upside-down orientation on iOS 7, status bar will be bound to lower edge of the
188        // screen (statusBarFrame.origin.y will be somewhere around screen height). In this case we need to
189        // correct frame's coordinates
190        statusBarFrame.origin.y = 0;
191    }
192
193    statusBarFrame = [self invertFrameIfNeeded:statusBarFrame];
194
195    _statusBarBackgroundView = [[UIView alloc] initWithFrame:statusBarFrame];
196    _statusBarBackgroundView.backgroundColor = _statusBarBackgroundColor;
197    _statusBarBackgroundView.autoresizingMask = (UIViewAutoresizingFlexibleWidth  | UIViewAutoresizingFlexibleBottomMargin);
198    _statusBarBackgroundView.autoresizesSubviews = YES;
199}
200
201- (CGRect) invertFrameIfNeeded:(CGRect)rect {
202    // landscape is where (width > height). On iOS < 8, we need to invert since frames are
203    // always in Portrait context
204    if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]) && (rect.size.width < rect.size.height)) {
205        CGFloat temp = rect.size.width;
206        rect.size.width = rect.size.height;
207        rect.size.height = temp;
208        rect.origin = CGPointZero;
209    }
210    
211    return rect;
212}
213
214- (void) setStatusBarOverlaysWebView:(BOOL)statusBarOverlaysWebView
215{
216    // we only care about the latest iOS version or a change in setting
217    if (!IsAtLeastiOSVersion(@"7.0") || statusBarOverlaysWebView == _statusBarOverlaysWebView) {
218        return;
219    }
220    
221    _statusBarOverlaysWebView = statusBarOverlaysWebView;
222
223    [self resizeWebView];
224
225    if (statusBarOverlaysWebView) {
226
227        [_statusBarBackgroundView removeFromSuperview];
228
229    } else {
230
231        [self initializeStatusBarBackgroundView];
232        [self.webView.superview addSubview:_statusBarBackgroundView];
233
234    }
235
236}
237
238- (BOOL) statusBarOverlaysWebView
239{
240    return _statusBarOverlaysWebView;
241}
242
243- (void) overlaysWebView:(CDVInvokedUrlCommand*)command
244{
245    id value = [command argumentAtIndex:0];
246    if (!([value isKindOfClass:[NSNumber class]])) {
247        value = [NSNumber numberWithBool:YES];
248    }
249
250    self.statusBarOverlaysWebView = [value boolValue];
251}
252
253- (void) refreshStatusBarAppearance
254{
255    SEL sel = NSSelectorFromString(@"setNeedsStatusBarAppearanceUpdate");
256    if ([self.viewController respondsToSelector:sel]) {
257#pragma clang diagnostic push
258#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
259        [self.viewController performSelector:sel withObject:nil];
260#pragma clang diagnostic pop
261    }
262}
263
264- (void) setStyleForStatusBar:(UIStatusBarStyle)style
265{
266    if (_uiviewControllerBasedStatusBarAppearance) {
267        CDVViewController* vc = (CDVViewController*)self.viewController;
268        vc.sb_statusBarStyle = [NSNumber numberWithInt:style];
269        [self refreshStatusBarAppearance];
270
271    } else {
272        [[UIApplication sharedApplication] setStatusBarStyle:style];
273    }
274}
275
276- (void) setStatusBarStyle:(NSString*)statusBarStyle
277{
278    // default, lightContent, blackTranslucent, blackOpaque
279    NSString* lcStatusBarStyle = [statusBarStyle lowercaseString];
280
281    if ([lcStatusBarStyle isEqualToString:@"default"]) {
282        [self styleDefault:nil];
283    } else if ([lcStatusBarStyle isEqualToString:@"lightcontent"]) {
284        [self styleLightContent:nil];
285    } else if ([lcStatusBarStyle isEqualToString:@"blacktranslucent"]) {
286        [self styleBlackTranslucent:nil];
287    } else if ([lcStatusBarStyle isEqualToString:@"blackopaque"]) {
288        [self styleBlackOpaque:nil];
289    }
290}
291
292- (void) styleDefault:(CDVInvokedUrlCommand*)command
293{
294    [self setStyleForStatusBar:UIStatusBarStyleDefault];
295}
296
297- (void) styleLightContent:(CDVInvokedUrlCommand*)command
298{
299    [self setStyleForStatusBar:UIStatusBarStyleLightContent];
300}
301
302- (void) styleBlackTranslucent:(CDVInvokedUrlCommand*)command
303{
304    #if __IPHONE_OS_VERSION_MAX_ALLOWED < 70000
305    # define TRANSLUCENT_STYLE UIStatusBarStyleBlackTranslucent
306    #else
307    # define TRANSLUCENT_STYLE UIStatusBarStyleLightContent
308    #endif
309    [self setStyleForStatusBar:TRANSLUCENT_STYLE];
310}
311
312- (void) styleBlackOpaque:(CDVInvokedUrlCommand*)command
313{
314    #if __IPHONE_OS_VERSION_MAX_ALLOWED < 70000
315    # define OPAQUE_STYLE UIStatusBarStyleBlackOpaque
316    #else
317    # define OPAQUE_STYLE UIStatusBarStyleLightContent
318    #endif
319    [self setStyleForStatusBar:OPAQUE_STYLE];
320}
321
322- (void) backgroundColorByName:(CDVInvokedUrlCommand*)command
323{
324    id value = [command argumentAtIndex:0];
325    if (!([value isKindOfClass:[NSString class]])) {
326        value = @"black";
327    }
328
329    SEL selector = NSSelectorFromString([value stringByAppendingString:@"Color"]);
330    if ([UIColor respondsToSelector:selector]) {
331        _statusBarBackgroundView.backgroundColor = [UIColor performSelector:selector];
332    }
333}
334
335- (void) _backgroundColorByHexString:(NSString*)hexString
336{
337    unsigned int rgbValue = 0;
338    NSScanner* scanner = [NSScanner scannerWithString:hexString];
339    [scanner setScanLocation:1];
340    [scanner scanHexInt:&rgbValue];
341
342    _statusBarBackgroundColor = [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16)/255.0 green:((rgbValue & 0xFF00) >> 8)/255.0 blue:(rgbValue & 0xFF)/255.0 alpha:1.0];
343    _statusBarBackgroundView.backgroundColor = _statusBarBackgroundColor;
344}
345
346- (void) backgroundColorByHexString:(CDVInvokedUrlCommand*)command
347{
348    NSString* value = [command argumentAtIndex:0];
349    if (!([value isKindOfClass:[NSString class]])) {
350        value = @"#000000";
351    }
352
353    if (![value hasPrefix:@"#"] || [value length] < 7) {
354        return;
355    }
356
357    [self _backgroundColorByHexString:value];
358}
359
360- (void) hideStatusBar
361{
362    if (_uiviewControllerBasedStatusBarAppearance) {
363        CDVViewController* vc = (CDVViewController*)self.viewController;
364        vc.sb_hideStatusBar = [NSNumber numberWithBool:YES];
365        [self refreshStatusBarAppearance];
366
367    } else {
368        UIApplication* app = [UIApplication sharedApplication];
369        [app setStatusBarHidden:YES];
370    }
371}
372
373- (void) hide:(CDVInvokedUrlCommand*)command
374{
375    UIApplication* app = [UIApplication sharedApplication];
376
377    if (!app.isStatusBarHidden)
378    {
379        
380        [self hideStatusBar];
381
382        if (IsAtLeastiOSVersion(@"7.0")) {
383            [_statusBarBackgroundView removeFromSuperview];
384        }
385
386        [self resizeWebView];
387
388        _statusBarBackgroundView.hidden = YES;
389    }
390}
391
392- (void) showStatusBar
393{
394    if (_uiviewControllerBasedStatusBarAppearance) {
395        CDVViewController* vc = (CDVViewController*)self.viewController;
396        vc.sb_hideStatusBar = [NSNumber numberWithBool:NO];
397        [self refreshStatusBarAppearance];
398
399    } else {
400        UIApplication* app = [UIApplication sharedApplication];
401        [app setStatusBarHidden:NO];
402    }
403}
404
405- (void) show:(CDVInvokedUrlCommand*)command
406{
407    UIApplication* app = [UIApplication sharedApplication];
408
409    if (app.isStatusBarHidden)
410    {
411        BOOL isIOS7 = (IsAtLeastiOSVersion(@"7.0"));
412
413        [self showStatusBar];
414        [self resizeWebView];
415
416        if (isIOS7) {
417
418            if (!self.statusBarOverlaysWebView) {
419
420                // there is a possibility that when the statusbar was hidden, it was in a different orientation
421                // from the current one. Therefore we need to expand the statusBarBackgroundView as well to the
422                // statusBar's current size
423                CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
424                statusBarFrame = [self invertFrameIfNeeded:statusBarFrame];
425                CGRect sbBgFrame = _statusBarBackgroundView.frame;
426                sbBgFrame.size = statusBarFrame.size;
427                _statusBarBackgroundView.frame = sbBgFrame;
428                [self.webView.superview addSubview:_statusBarBackgroundView];
429
430            }
431
432        }
433
434        _statusBarBackgroundView.hidden = NO;
435    }
436}
437
438-(void)resizeWebView
439{
440    BOOL isIOS7 = (IsAtLeastiOSVersion(@"7.0"));
441
442    if (isIOS7) {
443        CGRect bounds = [[UIScreen mainScreen] bounds];
444        bounds = [self invertFrameIfNeeded:bounds];
445
446        if (UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation])) {
447            self.viewController.view.frame = bounds;
448        } else if (self.viewController.presentedViewController != nil) {
449            // https://issues.apache.org/jira/browse/CB-11018
450            BOOL isIOS8 = (IsAtLeastiOSVersion(@"8.0"));
451            BOOL isIOS9 = (IsAtLeastiOSVersion(@"9.0"));
452            if (isIOS8 && !isIOS9) {
453                // iOS 8
454                bounds = CGRectMake(0, 0, bounds.size.width, bounds.size.height);
455            } else {
456                // iOS7, iOS9+
457                if ([self.viewController.presentedViewController.presentationController isKindOfClass:[UIPopoverPresentationController class]] || UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad) {
458                    bounds = CGRectMake(0, 0, bounds.size.width, bounds.size.height);
459                } else {
460                    bounds = CGRectMake(0, 0, bounds.size.height, bounds.size.width);
461                }
462            }
463        }
464        self.webView.frame = bounds;
465
466        CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
467        statusBarFrame = [self invertFrameIfNeeded:statusBarFrame];
468        CGRect frame = self.webView.frame;
469
470        if (!self.statusBarOverlaysWebView) {
471            frame.origin.y = statusBarFrame.size.height;
472            frame.size.height -= statusBarFrame.size.height;
473        } else {
474            // even if overlay is used, we want to handle in-call/recording/hotspot larger status bar
475            CGFloat height = statusBarFrame.size.height;
476            frame.origin.y = height >= 20 ? height - 20 : 0;
477            frame.size.height -= frame.origin.y;
478        }
479        self.webView.frame = frame;
480    } else {
481        CGRect bounds = [[UIScreen mainScreen] applicationFrame];
482        self.viewController.view.frame = bounds;
483    }
484}
485
486- (void) dealloc
487{
488    [[UIApplication sharedApplication] removeObserver:self forKeyPath:@"statusBarHidden"];
489    [[NSNotificationCenter defaultCenter]removeObserver:self name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
490}
491
492
493#pragma mark - UIScrollViewDelegate
494
495- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView
496{
497    [self fireTappedEvent];
498    return NO;
499}
500
501@end