arnaucube
arnaucube/openEventsPlatformAppPublic Self-hosted repository
openEventsPlatformApp/plugins/cordova-plugin-splashscreen/src/ios/CDVSplashScreen.m
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- (BOOL) isUsingCDVLaunchScreen {
182    NSString* launchStoryboardName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UILaunchStoryboardName"];
183    if (launchStoryboardName) {
184        return ([launchStoryboardName isEqualToString:@"CDVLaunchScreen"]);
185    } else {
186        return NO;
187    }
188}
189
190- (NSString*)getImageName:(UIInterfaceOrientation)currentOrientation delegate:(id<CDVScreenOrientationDelegate>)orientationDelegate device:(CDV_iOSDevice)device
191{
192    // Use UILaunchImageFile if specified in plist.  Otherwise, use Default.
193    NSString* imageName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UILaunchImageFile"];
194
195    // detect if we are using CB-9762 Launch Storyboard; if so, return the associated image instead
196    if ([self isUsingCDVLaunchScreen]) {
197        imageName = @"LaunchStoryboard";
198        return imageName;
199    }
200
201    NSUInteger supportedOrientations = [orientationDelegate supportedInterfaceOrientations];
202
203    // Checks to see if the developer has locked the orientation to use only one of Portrait or Landscape
204    BOOL supportsLandscape = (supportedOrientations & UIInterfaceOrientationMaskLandscape);
205    BOOL supportsPortrait = (supportedOrientations & UIInterfaceOrientationMaskPortrait || supportedOrientations & UIInterfaceOrientationMaskPortraitUpsideDown);
206    // this means there are no mixed orientations in there
207    BOOL isOrientationLocked = !(supportsPortrait && supportsLandscape);
208
209    if (imageName)
210    {
211        imageName = [imageName stringByDeletingPathExtension];
212    }
213    else
214    {
215        imageName = @"Default";
216    }
217
218    // Add Asset Catalog specific prefixes
219    if ([imageName isEqualToString:@"LaunchImage"])
220    {
221        if (device.iPhone4 || device.iPhone5 || device.iPad) {
222            imageName = [imageName stringByAppendingString:@"-700"];
223        } else if(device.iPhone6) {
224            imageName = [imageName stringByAppendingString:@"-800"];
225        } else if(device.iPhone6Plus) {
226            imageName = [imageName stringByAppendingString:@"-800"];
227            if (currentOrientation == UIInterfaceOrientationPortrait || currentOrientation == UIInterfaceOrientationPortraitUpsideDown)
228            {
229                imageName = [imageName stringByAppendingString:@"-Portrait"];
230            }
231        }
232    }
233
234    if (device.iPhone5)
235    { // does not support landscape
236        imageName = [imageName stringByAppendingString:@"-568h"];
237    }
238    else if (device.iPhone6)
239    { // does not support landscape
240        imageName = [imageName stringByAppendingString:@"-667h"];
241    }
242    else if (device.iPhone6Plus)
243    { // supports landscape
244        if (isOrientationLocked)
245        {
246            imageName = [imageName stringByAppendingString:(supportsLandscape ? @"-Landscape" : @"")];
247        }
248        else
249        {
250            switch (currentOrientation)
251            {
252                case UIInterfaceOrientationLandscapeLeft:
253                case UIInterfaceOrientationLandscapeRight:
254                        imageName = [imageName stringByAppendingString:@"-Landscape"];
255                    break;
256                default:
257                    break;
258            }
259        }
260        imageName = [imageName stringByAppendingString:@"-736h"];
261
262    }
263    else if (device.iPad)
264    {   // supports landscape
265        if (isOrientationLocked)
266        {
267            imageName = [imageName stringByAppendingString:(supportsLandscape ? @"-Landscape" : @"-Portrait")];
268        }
269        else
270        {
271            switch (currentOrientation)
272            {
273                case UIInterfaceOrientationLandscapeLeft:
274                case UIInterfaceOrientationLandscapeRight:
275                    imageName = [imageName stringByAppendingString:@"-Landscape"];
276                    break;
277
278                case UIInterfaceOrientationPortrait:
279                case UIInterfaceOrientationPortraitUpsideDown:
280                default:
281                    imageName = [imageName stringByAppendingString:@"-Portrait"];
282                    break;
283            }
284        }
285    }
286
287    return imageName;
288}
289
290- (UIInterfaceOrientation)getCurrentOrientation
291{
292    UIInterfaceOrientation iOrientation = [UIApplication sharedApplication].statusBarOrientation;
293    UIDeviceOrientation dOrientation = [UIDevice currentDevice].orientation;
294
295    bool landscape;
296
297    if (dOrientation == UIDeviceOrientationUnknown || dOrientation == UIDeviceOrientationFaceUp || dOrientation == UIDeviceOrientationFaceDown) {
298        // If the device is laying down, use the UIInterfaceOrientation based on the status bar.
299        landscape = UIInterfaceOrientationIsLandscape(iOrientation);
300    } else {
301        // If the device is not laying down, use UIDeviceOrientation.
302        landscape = UIDeviceOrientationIsLandscape(dOrientation);
303
304        // There's a bug in iOS!!!! http://openradar.appspot.com/7216046
305        // So values needs to be reversed for landscape!
306        if (dOrientation == UIDeviceOrientationLandscapeLeft)
307        {
308            iOrientation = UIInterfaceOrientationLandscapeRight;
309        }
310        else if (dOrientation == UIDeviceOrientationLandscapeRight)
311        {
312            iOrientation = UIInterfaceOrientationLandscapeLeft;
313        }
314        else if (dOrientation == UIDeviceOrientationPortrait)
315        {
316            iOrientation = UIInterfaceOrientationPortrait;
317        }
318        else if (dOrientation == UIDeviceOrientationPortraitUpsideDown)
319        {
320            iOrientation = UIInterfaceOrientationPortraitUpsideDown;
321        }
322    }
323
324    return iOrientation;
325}
326
327// Sets the view's frame and image.
328- (void)updateImage
329{
330    NSString* imageName = [self getImageName:[self getCurrentOrientation] delegate:(id<CDVScreenOrientationDelegate>)self.viewController device:[self getCurrentDevice]];
331
332    if (![imageName isEqualToString:_curImageName])
333    {
334        UIImage* img = [UIImage imageNamed:imageName];
335        _imageView.image = img;
336        _curImageName = imageName;
337    }
338
339    // Check that splash screen's image exists before updating bounds
340    if (_imageView.image)
341    {
342        [self updateBounds];
343    }
344    else
345    {
346        NSLog(@"WARNING: The splashscreen image named %@ was not found", imageName);
347    }
348}
349
350- (void)updateBounds
351{
352    if ([self isUsingCDVLaunchScreen]) {
353        // CB-9762's launch screen expects the image to fill the screen and be scaled using AspectFill.
354        CGSize viewportSize = [UIApplication sharedApplication].delegate.window.bounds.size;
355        _imageView.frame = CGRectMake(0, 0, viewportSize.width, viewportSize.height);
356        _imageView.contentMode = UIViewContentModeScaleAspectFill;
357        return; 
358    }
359
360    UIImage* img = _imageView.image;
361    CGRect imgBounds = (img) ? CGRectMake(0, 0, img.size.width, img.size.height) : CGRectZero;
362
363    CGSize screenSize = [self.viewController.view convertRect:[UIScreen mainScreen].bounds fromView:nil].size;
364    UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
365    CGAffineTransform imgTransform = CGAffineTransformIdentity;
366
367    /* If and only if an iPhone application is landscape-only as per
368     * UISupportedInterfaceOrientations, the view controller's orientation is
369     * landscape. In this case the image must be rotated in order to appear
370     * correctly.
371     */
372    CDV_iOSDevice device = [self getCurrentDevice];
373    if (UIInterfaceOrientationIsLandscape(orientation) && !device.iPhone6Plus && !device.iPad)
374    {
375        imgTransform = CGAffineTransformMakeRotation(M_PI / 2);
376        imgBounds.size = CGSizeMake(imgBounds.size.height, imgBounds.size.width);
377    }
378
379    // There's a special case when the image is the size of the screen.
380    if (CGSizeEqualToSize(screenSize, imgBounds.size))
381    {
382        CGRect statusFrame = [self.viewController.view convertRect:[UIApplication sharedApplication].statusBarFrame fromView:nil];
383        if (!(IsAtLeastiOSVersion(@"7.0")))
384        {
385            imgBounds.origin.y -= statusFrame.size.height;
386        }
387    }
388    else if (imgBounds.size.width > 0)
389    {
390        CGRect viewBounds = self.viewController.view.bounds;
391        CGFloat imgAspect = imgBounds.size.width / imgBounds.size.height;
392        CGFloat viewAspect = viewBounds.size.width / viewBounds.size.height;
393        // This matches the behaviour of the native splash screen.
394        CGFloat ratio;
395        if (viewAspect > imgAspect)
396        {
397            ratio = viewBounds.size.width / imgBounds.size.width;
398        }
399        else
400        {
401            ratio = viewBounds.size.height / imgBounds.size.height;
402        }
403        imgBounds.size.height *= ratio;
404        imgBounds.size.width *= ratio;
405    }
406
407    _imageView.transform = imgTransform;
408    _imageView.frame = imgBounds;
409}
410
411- (void)setVisible:(BOOL)visible
412{
413    [self setVisible:visible andForce:NO];
414}
415
416- (void)setVisible:(BOOL)visible andForce:(BOOL)force
417{
418    if (visible != _visible || force)
419    {
420        _visible = visible;
421
422        id fadeSplashScreenValue = [self.commandDelegate.settings objectForKey:[@"FadeSplashScreen" lowercaseString]];
423        id fadeSplashScreenDuration = [self.commandDelegate.settings objectForKey:[@"FadeSplashScreenDuration" lowercaseString]];
424
425        float fadeDuration = fadeSplashScreenDuration == nil ? kFadeDurationDefault : [fadeSplashScreenDuration floatValue];
426
427        id splashDurationString = [self.commandDelegate.settings objectForKey: [@"SplashScreenDelay" lowercaseString]];
428        float splashDuration = splashDurationString == nil ? kSplashScreenDurationDefault : [splashDurationString floatValue];
429
430        id autoHideSplashScreenValue = [self.commandDelegate.settings objectForKey:[@"AutoHideSplashScreen" lowercaseString]];
431        BOOL autoHideSplashScreen = true;
432
433        if (autoHideSplashScreenValue != nil) {
434            autoHideSplashScreen = [autoHideSplashScreenValue boolValue];
435        }
436
437        if (!autoHideSplashScreen) {
438            // CB-10412 SplashScreenDelay does not make sense if the splashscreen is hidden manually
439            splashDuration = 0;
440        }
441
442
443        if (fadeSplashScreenValue == nil)
444        {
445            fadeSplashScreenValue = @"true";
446        }
447
448        if (![fadeSplashScreenValue boolValue])
449        {
450            fadeDuration = 0;
451        }
452        else if (fadeDuration < 30)
453        {
454            // [CB-9750] This value used to be in decimal seconds, so we will assume that if someone specifies 10
455            // they mean 10 seconds, and not the meaningless 10ms
456            fadeDuration *= 1000;
457        }
458
459        if (_visible)
460        {
461            if (_imageView == nil)
462            {
463                [self createViews];
464            }
465        }
466        else if (fadeDuration == 0 && splashDuration == 0)
467        {
468            [self destroyViews];
469        }
470        else
471        {
472            __weak __typeof(self) weakSelf = self;
473            float effectiveSplashDuration;
474
475            // [CB-10562] AutoHideSplashScreen may be "true" but we should still be able to hide the splashscreen manually.
476            if (!autoHideSplashScreen || force) {
477                effectiveSplashDuration = (fadeDuration) / 1000;
478            } else {
479                effectiveSplashDuration = (splashDuration - fadeDuration) / 1000;
480            }
481
482            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (uint64_t) effectiveSplashDuration * NSEC_PER_SEC), dispatch_get_main_queue(), CFBridgingRelease(CFBridgingRetain(^(void) {
483                if (!_destroyed) {
484                    [UIView transitionWithView:self.viewController.view
485                                    duration:(fadeDuration / 1000)
486                                    options:UIViewAnimationOptionTransitionNone
487                                    animations:^(void) {
488                                        [weakSelf hideViews];
489                                    }
490                                    completion:^(BOOL finished) {
491                                        // Always destroy views, otherwise you could have an
492                                        // invisible splashscreen that is overlayed over your active views
493                                        // which causes that no touch events are passed
494                                        if (!_destroyed) {
495                                            [weakSelf destroyViews];
496                                            // TODO: It might also be nice to have a js event happen here -jm
497                                        }
498                                    }
499                    ];
500                }
501            })));
502        }
503    }
504}
505
506@end