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