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