commonroutesServer/www/plugins/cordova-plugin-splashscreen/src/ios/CDVSplashScreen.m
www/plugins/cordova-plugin-splashscreen/src/ios/CDVSplashScreen.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#import "CDVSplashScreen.h"
21#import <Cordova/CDVViewController.h>
22#import <Cordova/CDVScreenOrientationDelegate.h>
23#import "CDVViewController+SplashScreen.h"
24
25#define kSplashScreenDurationDefault 3000.0f
26#define kFadeDurationDefault 500.0f
27
28
29@implementation CDVSplashScreen
30
31- (void)pluginInitialize
32{
33 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pageDidLoad) name:CDVPageDidLoadNotification object:nil];
34
35 [self setVisible:YES];
36}
37
38- (void)show:(CDVInvokedUrlCommand*)command
39{
40 [self setVisible:YES];
41}
42
43- (void)hide:(CDVInvokedUrlCommand*)command
44{
45 [self setVisible:NO andForce:YES];
46}
47
48- (void)pageDidLoad
49{
50 id autoHideSplashScreenValue = [self.commandDelegate.settings objectForKey:[@"AutoHideSplashScreen" lowercaseString]];
51
52 // if value is missing, default to yes
53 if ((autoHideSplashScreenValue == nil) || [autoHideSplashScreenValue boolValue]) {
54 [self setVisible:NO];
55 }
56}
57
58- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context
59{
60 [self updateImage];
61}
62
63- (void)createViews
64{
65 /*
66 * The Activity View is the top spinning throbber in the status/battery bar. We init it with the default Grey Style.
67 *
68 * whiteLarge = UIActivityIndicatorViewStyleWhiteLarge
69 * white = UIActivityIndicatorViewStyleWhite
70 * gray = UIActivityIndicatorViewStyleGray
71 *
72 */
73
74 // Determine whether rotation should be enabled for this device
75 // Per iOS HIG, landscape is only supported on iPad and iPhone 6+
76 CDV_iOSDevice device = [self getCurrentDevice];
77 BOOL autorotateValue = (device.iPad || device.iPhone6Plus) ?
78 [(CDVViewController *)self.viewController shouldAutorotateDefaultValue] :
79 NO;
80
81 [(CDVViewController *)self.viewController setEnabledAutorotation:autorotateValue];
82
83 NSString* topActivityIndicator = [self.commandDelegate.settings objectForKey:[@"TopActivityIndicator" lowercaseString]];
84 UIActivityIndicatorViewStyle topActivityIndicatorStyle = UIActivityIndicatorViewStyleGray;
85
86 if ([topActivityIndicator isEqualToString:@"whiteLarge"])
87 {
88 topActivityIndicatorStyle = UIActivityIndicatorViewStyleWhiteLarge;
89 }
90 else if ([topActivityIndicator isEqualToString:@"white"])
91 {
92 topActivityIndicatorStyle = UIActivityIndicatorViewStyleWhite;
93 }
94 else if ([topActivityIndicator isEqualToString:@"gray"])
95 {
96 topActivityIndicatorStyle = UIActivityIndicatorViewStyleGray;
97 }
98
99 UIView* parentView = self.viewController.view;
100 parentView.userInteractionEnabled = NO; // disable user interaction while splashscreen is shown
101 _activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:topActivityIndicatorStyle];
102 _activityView.center = CGPointMake(parentView.bounds.size.width / 2, parentView.bounds.size.height / 2);
103 _activityView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin
104 | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin;
105 [_activityView startAnimating];
106
107 // Set the frame & image later.
108 _imageView = [[UIImageView alloc] init];
109 [parentView addSubview:_imageView];
110
111 id showSplashScreenSpinnerValue = [self.commandDelegate.settings objectForKey:[@"ShowSplashScreenSpinner" lowercaseString]];
112 // backwards compatibility - if key is missing, default to true
113 if ((showSplashScreenSpinnerValue == nil) || [showSplashScreenSpinnerValue boolValue])
114 {
115 [parentView addSubview:_activityView];
116 }
117
118 // Frame is required when launching in portrait mode.
119 // Bounds for landscape since it captures the rotation.
120 [parentView addObserver:self forKeyPath:@"frame" options:0 context:nil];
121 [parentView addObserver:self forKeyPath:@"bounds" options:0 context:nil];
122
123 [self updateImage];
124 _destroyed = NO;
125}
126
127- (void)hideViews
128{
129 [_imageView setAlpha:0];
130 [_activityView setAlpha:0];
131}
132
133- (void)destroyViews
134{
135 _destroyed = YES;
136 [(CDVViewController *)self.viewController setEnabledAutorotation:[(CDVViewController *)self.viewController shouldAutorotateDefaultValue]];
137
138 [_imageView removeFromSuperview];
139 [_activityView removeFromSuperview];
140 _imageView = nil;
141 _activityView = nil;
142 _curImageName = nil;
143
144 self.viewController.view.userInteractionEnabled = YES; // re-enable user interaction upon completion
145 @try {
146 [self.viewController.view removeObserver:self forKeyPath:@"frame"];
147 [self.viewController.view removeObserver:self forKeyPath:@"bounds"];
148 }
149 @catch (NSException *exception) {
150 // When reloading the page from a remotely connected Safari, there
151 // are no observers, so the removeObserver method throws an exception,
152 // that we can safely ignore.
153 // Alternatively we can check whether there are observers before calling removeObserver
154 }
155}
156
157- (CDV_iOSDevice) getCurrentDevice
158{
159 CDV_iOSDevice device;
160
161 UIScreen* mainScreen = [UIScreen mainScreen];
162 CGFloat mainScreenHeight = mainScreen.bounds.size.height;
163 CGFloat mainScreenWidth = mainScreen.bounds.size.width;
164
165 int limit = MAX(mainScreenHeight,mainScreenWidth);
166
167 device.iPad = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);
168 device.iPhone = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone);
169 device.retina = ([mainScreen scale] == 2.0);
170 device.iPhone4 = (device.iPhone && limit == 480.0);
171 device.iPhone5 = (device.iPhone && limit == 568.0);
172 // note these below is not a true device detect, for example if you are on an
173 // iPhone 6/6+ but the app is scaled it will prob set iPhone5 as true, but
174 // this is appropriate for detecting the runtime screen environment
175 device.iPhone6 = (device.iPhone && limit == 667.0);
176 device.iPhone6Plus = (device.iPhone && limit == 736.0);
177
178 return device;
179}
180
181- (NSString*)getImageName:(UIInterfaceOrientation)currentOrientation delegate:(id<CDVScreenOrientationDelegate>)orientationDelegate device:(CDV_iOSDevice)device
182{
183 // Use UILaunchImageFile if specified in plist. Otherwise, use Default.
184 NSString* imageName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UILaunchImageFile"];
185
186 NSUInteger supportedOrientations = [orientationDelegate supportedInterfaceOrientations];
187
188 // Checks to see if the developer has locked the orientation to use only one of Portrait or Landscape
189 BOOL supportsLandscape = (supportedOrientations & UIInterfaceOrientationMaskLandscape);
190 BOOL supportsPortrait = (supportedOrientations & UIInterfaceOrientationMaskPortrait || supportedOrientations & UIInterfaceOrientationMaskPortraitUpsideDown);
191 // this means there are no mixed orientations in there
192 BOOL isOrientationLocked = !(supportsPortrait && supportsLandscape);
193
194 if (imageName)
195 {
196 imageName = [imageName stringByDeletingPathExtension];
197 }
198 else
199 {
200 imageName = @"Default";
201 }
202
203 // Add Asset Catalog specific prefixes
204 if ([imageName isEqualToString:@"LaunchImage"])
205 {
206 if (device.iPhone4 || device.iPhone5 || device.iPad) {
207 imageName = [imageName stringByAppendingString:@"-700"];
208 } else if(device.iPhone6) {
209 imageName = [imageName stringByAppendingString:@"-800"];
210 } else if(device.iPhone6Plus) {
211 imageName = [imageName stringByAppendingString:@"-800"];
212 if (currentOrientation == UIInterfaceOrientationPortrait || currentOrientation == UIInterfaceOrientationPortraitUpsideDown)
213 {
214 imageName = [imageName stringByAppendingString:@"-Portrait"];
215 }
216 }
217 }
218
219 if (device.iPhone5)
220 { // does not support landscape
221 imageName = [imageName stringByAppendingString:@"-568h"];
222 }
223 else if (device.iPhone6)
224 { // does not support landscape
225 imageName = [imageName stringByAppendingString:@"-667h"];
226 }
227 else if (device.iPhone6Plus)
228 { // supports landscape
229 if (isOrientationLocked)
230 {
231 imageName = [imageName stringByAppendingString:(supportsLandscape ? @"-Landscape" : @"")];
232 }
233 else
234 {
235 switch (currentOrientation)
236 {
237 case UIInterfaceOrientationLandscapeLeft:
238 case UIInterfaceOrientationLandscapeRight:
239 imageName = [imageName stringByAppendingString:@"-Landscape"];
240 break;
241 default:
242 break;
243 }
244 }
245 imageName = [imageName stringByAppendingString:@"-736h"];
246
247 }
248 else if (device.iPad)
249 { // supports landscape
250 if (isOrientationLocked)
251 {
252 imageName = [imageName stringByAppendingString:(supportsLandscape ? @"-Landscape" : @"-Portrait")];
253 }
254 else
255 {
256 switch (currentOrientation)
257 {
258 case UIInterfaceOrientationLandscapeLeft:
259 case UIInterfaceOrientationLandscapeRight:
260 imageName = [imageName stringByAppendingString:@"-Landscape"];
261 break;
262
263 case UIInterfaceOrientationPortrait:
264 case UIInterfaceOrientationPortraitUpsideDown:
265 default:
266 imageName = [imageName stringByAppendingString:@"-Portrait"];
267 break;
268 }
269 }
270 }
271
272 return imageName;
273}
274
275- (UIInterfaceOrientation)getCurrentOrientation
276{
277 UIInterfaceOrientation iOrientation = [UIApplication sharedApplication].statusBarOrientation;
278 UIDeviceOrientation dOrientation = [UIDevice currentDevice].orientation;
279
280 bool landscape;
281
282 if (dOrientation == UIDeviceOrientationUnknown || dOrientation == UIDeviceOrientationFaceUp || dOrientation == UIDeviceOrientationFaceDown) {
283 // If the device is laying down, use the UIInterfaceOrientation based on the status bar.
284 landscape = UIInterfaceOrientationIsLandscape(iOrientation);
285 } else {
286 // If the device is not laying down, use UIDeviceOrientation.
287 landscape = UIDeviceOrientationIsLandscape(dOrientation);
288
289 // There's a bug in iOS!!!! http://openradar.appspot.com/7216046
290 // So values needs to be reversed for landscape!
291 if (dOrientation == UIDeviceOrientationLandscapeLeft)
292 {
293 iOrientation = UIInterfaceOrientationLandscapeRight;
294 }
295 else if (dOrientation == UIDeviceOrientationLandscapeRight)
296 {
297 iOrientation = UIInterfaceOrientationLandscapeLeft;
298 }
299 else if (dOrientation == UIDeviceOrientationPortrait)
300 {
301 iOrientation = UIInterfaceOrientationPortrait;
302 }
303 else if (dOrientation == UIDeviceOrientationPortraitUpsideDown)
304 {
305 iOrientation = UIInterfaceOrientationPortraitUpsideDown;
306 }
307 }
308
309 return iOrientation;
310}
311
312// Sets the view's frame and image.
313- (void)updateImage
314{
315 NSString* imageName = [self getImageName:[self getCurrentOrientation] delegate:(id<CDVScreenOrientationDelegate>)self.viewController device:[self getCurrentDevice]];
316
317 if (![imageName isEqualToString:_curImageName])
318 {
319 UIImage* img = [UIImage imageNamed:imageName];
320 _imageView.image = img;
321 _curImageName = imageName;
322 }
323
324 // Check that splash screen's image exists before updating bounds
325 if (_imageView.image)
326 {
327 [self updateBounds];
328 }
329 else
330 {
331 NSLog(@"WARNING: The splashscreen image named %@ was not found", imageName);
332 }
333}
334
335- (void)updateBounds
336{
337 UIImage* img = _imageView.image;
338 CGRect imgBounds = (img) ? CGRectMake(0, 0, img.size.width, img.size.height) : CGRectZero;
339
340 CGSize screenSize = [self.viewController.view convertRect:[UIScreen mainScreen].bounds fromView:nil].size;
341 UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
342 CGAffineTransform imgTransform = CGAffineTransformIdentity;
343
344 /* If and only if an iPhone application is landscape-only as per
345 * UISupportedInterfaceOrientations, the view controller's orientation is
346 * landscape. In this case the image must be rotated in order to appear
347 * correctly.
348 */
349 CDV_iOSDevice device = [self getCurrentDevice];
350 if (UIInterfaceOrientationIsLandscape(orientation) && !device.iPhone6Plus && !device.iPad)
351 {
352 imgTransform = CGAffineTransformMakeRotation(M_PI / 2);
353 imgBounds.size = CGSizeMake(imgBounds.size.height, imgBounds.size.width);
354 }
355
356 // There's a special case when the image is the size of the screen.
357 if (CGSizeEqualToSize(screenSize, imgBounds.size))
358 {
359 CGRect statusFrame = [self.viewController.view convertRect:[UIApplication sharedApplication].statusBarFrame fromView:nil];
360 if (!(IsAtLeastiOSVersion(@"7.0")))
361 {
362 imgBounds.origin.y -= statusFrame.size.height;
363 }
364 }
365 else if (imgBounds.size.width > 0)
366 {
367 CGRect viewBounds = self.viewController.view.bounds;
368 CGFloat imgAspect = imgBounds.size.width / imgBounds.size.height;
369 CGFloat viewAspect = viewBounds.size.width / viewBounds.size.height;
370 // This matches the behaviour of the native splash screen.
371 CGFloat ratio;
372 if (viewAspect > imgAspect)
373 {
374 ratio = viewBounds.size.width / imgBounds.size.width;
375 }
376 else
377 {
378 ratio = viewBounds.size.height / imgBounds.size.height;
379 }
380 imgBounds.size.height *= ratio;
381 imgBounds.size.width *= ratio;
382 }
383
384 _imageView.transform = imgTransform;
385 _imageView.frame = imgBounds;
386}
387
388- (void)setVisible:(BOOL)visible
389{
390 [self setVisible:visible andForce:NO];
391}
392
393- (void)setVisible:(BOOL)visible andForce:(BOOL)force
394{
395 if (visible != _visible || force)
396 {
397 _visible = visible;
398
399 id fadeSplashScreenValue = [self.commandDelegate.settings objectForKey:[@"FadeSplashScreen" lowercaseString]];
400 id fadeSplashScreenDuration = [self.commandDelegate.settings objectForKey:[@"FadeSplashScreenDuration" lowercaseString]];
401
402 float fadeDuration = fadeSplashScreenDuration == nil ? kFadeDurationDefault : [fadeSplashScreenDuration floatValue];
403
404 id splashDurationString = [self.commandDelegate.settings objectForKey: [@"SplashScreenDelay" lowercaseString]];
405 float splashDuration = splashDurationString == nil ? kSplashScreenDurationDefault : [splashDurationString floatValue];
406
407 id autoHideSplashScreenValue = [self.commandDelegate.settings objectForKey:[@"AutoHideSplashScreen" lowercaseString]];
408 BOOL autoHideSplashScreen = true;
409
410 if (autoHideSplashScreenValue != nil) {
411 autoHideSplashScreen = [autoHideSplashScreenValue boolValue];
412 }
413
414 if (!autoHideSplashScreen) {
415 // CB-10412 SplashScreenDelay does not make sense if the splashscreen is hidden manually
416 splashDuration = 0;
417 }
418
419
420 if (fadeSplashScreenValue == nil)
421 {
422 fadeSplashScreenValue = @"true";
423 }
424
425 if (![fadeSplashScreenValue boolValue])
426 {
427 fadeDuration = 0;
428 }
429 else if (fadeDuration < 30)
430 {
431 // [CB-9750] This value used to be in decimal seconds, so we will assume that if someone specifies 10
432 // they mean 10 seconds, and not the meaningless 10ms
433 fadeDuration *= 1000;
434 }
435
436 if (_visible)
437 {
438 if (_imageView == nil)
439 {
440 [self createViews];
441 }
442 }
443 else if (fadeDuration == 0 && splashDuration == 0)
444 {
445 [self destroyViews];
446 }
447 else
448 {
449 __weak __typeof(self) weakSelf = self;
450 float effectiveSplashDuration;
451
452 // [CB-10562] AutoHideSplashScreen may be "true" but we should still be able to hide the splashscreen manually.
453 if (!autoHideSplashScreen || force) {
454 effectiveSplashDuration = (fadeDuration) / 1000;
455 } else {
456 effectiveSplashDuration = (splashDuration - fadeDuration) / 1000;
457 }
458
459 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (uint64_t) effectiveSplashDuration * NSEC_PER_SEC), dispatch_get_main_queue(), CFBridgingRelease(CFBridgingRetain(^(void) {
460 if (!_destroyed) {
461 [UIView transitionWithView:self.viewController.view
462 duration:(fadeDuration / 1000)
463 options:UIViewAnimationOptionTransitionNone
464 animations:^(void) {
465 [weakSelf hideViews];
466 }
467 completion:^(BOOL finished) {
468 // Always destroy views, otherwise you could have an
469 // invisible splashscreen that is overlayed over your active views
470 // which causes that no touch events are passed
471 if (!_destroyed) {
472 [weakSelf destroyViews];
473 // TODO: It might also be nice to have a js event happen here -jm
474 }
475 }
476 ];
477 }
478 })));
479 }
480 }
481}
482
483@end