arnaucube
arnaucube/commonroutesServerPublic Self-hosted repository
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