Touchgui/plugins/org.apache.cordova.splashscreen/src/ios/CDVSplashScreen.m

Thu, 04 Jun 2015 14:50:33 +0200

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 04 Jun 2015 14:50:33 +0200
changeset 0
e8ccd40d0ef6
permissions
-rw-r--r--

Genesis of lecture sources for Droidcon Berlin 2015 in Postbahnhof.

     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
    10  http://www.apache.org/licenses/LICENSE-2.0
    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  */
    20 #import "CDVSplashScreen.h"
    21 #import <Cordova/CDVViewController.h>
    22 #import <Cordova/CDVScreenOrientationDelegate.h>
    24 #define kSplashScreenDurationDefault 0.25f
    27 @implementation CDVSplashScreen
    29 - (void)pluginInitialize
    30 {
    31     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pageDidLoad) name:CDVPageDidLoadNotification object:self.webView];
    33     [self setVisible:YES];
    34 }
    36 - (void)show:(CDVInvokedUrlCommand*)command
    37 {
    38     [self setVisible:YES];
    39 }
    41 - (void)hide:(CDVInvokedUrlCommand*)command
    42 {
    43     [self setVisible:NO];
    44 }
    46 - (void)pageDidLoad
    47 {
    48     id autoHideSplashScreenValue = [self.commandDelegate.settings objectForKey:[@"AutoHideSplashScreen" lowercaseString]];
    50     // if value is missing, default to yes
    51     if ((autoHideSplashScreenValue == nil) || [autoHideSplashScreenValue boolValue]) {
    52         [self setVisible:NO];
    53     }
    54 }
    56 - (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context
    57 {
    58     [self updateImage];
    59 }
    61 - (void)createViews
    62 {
    63     /*
    64      * The Activity View is the top spinning throbber in the status/battery bar. We init it with the default Grey Style.
    65      *
    66      *     whiteLarge = UIActivityIndicatorViewStyleWhiteLarge
    67      *     white      = UIActivityIndicatorViewStyleWhite
    68      *     gray       = UIActivityIndicatorViewStyleGray
    69      *
    70      */
    71     NSString* topActivityIndicator = [self.commandDelegate.settings objectForKey:[@"TopActivityIndicator" lowercaseString]];
    72     UIActivityIndicatorViewStyle topActivityIndicatorStyle = UIActivityIndicatorViewStyleGray;
    74     if ([topActivityIndicator isEqualToString:@"whiteLarge"]) {
    75         topActivityIndicatorStyle = UIActivityIndicatorViewStyleWhiteLarge;
    76     } else if ([topActivityIndicator isEqualToString:@"white"]) {
    77         topActivityIndicatorStyle = UIActivityIndicatorViewStyleWhite;
    78     } else if ([topActivityIndicator isEqualToString:@"gray"]) {
    79         topActivityIndicatorStyle = UIActivityIndicatorViewStyleGray;
    80     }
    82     UIView* parentView = self.viewController.view;
    83     parentView.userInteractionEnabled = NO;  // disable user interaction while splashscreen is shown
    84     _activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:topActivityIndicatorStyle];
    85     _activityView.center = CGPointMake(parentView.bounds.size.width / 2, parentView.bounds.size.height / 2);
    86     _activityView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin
    87         | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin;
    88     [_activityView startAnimating];
    90     // Set the frame & image later.
    91     _imageView = [[UIImageView alloc] init];
    92     [parentView addSubview:_imageView];
    94     id showSplashScreenSpinnerValue = [self.commandDelegate.settings objectForKey:[@"ShowSplashScreenSpinner" lowercaseString]];
    95     // backwards compatibility - if key is missing, default to true
    96     if ((showSplashScreenSpinnerValue == nil) || [showSplashScreenSpinnerValue boolValue]) {
    97         [parentView addSubview:_activityView];
    98     }
   100     // Frame is required when launching in portrait mode.
   101     // Bounds for landscape since it captures the rotation.
   102     [parentView addObserver:self forKeyPath:@"frame" options:0 context:nil];
   103     [parentView addObserver:self forKeyPath:@"bounds" options:0 context:nil];
   105     [self updateImage];
   106 }
   108 - (void)destroyViews
   109 {
   110     [_imageView removeFromSuperview];
   111     [_activityView removeFromSuperview];
   112     _imageView = nil;
   113     _activityView = nil;
   114     _curImageName = nil;
   116     self.viewController.view.userInteractionEnabled = YES;  // re-enable user interaction upon completion
   117     [self.viewController.view removeObserver:self forKeyPath:@"frame"];
   118     [self.viewController.view removeObserver:self forKeyPath:@"bounds"];
   119 }
   121 - (CDV_iOSDevice) getCurrentDevice
   122 {
   123     CDV_iOSDevice device;
   125     UIScreen* mainScreen = [UIScreen mainScreen];
   126     CGFloat mainScreenHeight = mainScreen.bounds.size.height;
   127     CGFloat mainScreenWidth = mainScreen.bounds.size.width;
   129     int limit = MAX(mainScreenHeight,mainScreenWidth);
   131     device.iPad = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);
   132     device.iPhone = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone);
   133     device.retina = ([mainScreen scale] == 2.0);
   134     device.iPhone5 = (device.iPhone && limit == 568.0);
   135     // note these below is not a true device detect, for example if you are on an
   136     // iPhone 6/6+ but the app is scaled it will prob set iPhone5 as true, but
   137     // this is appropriate for detecting the runtime screen environment
   138     device.iPhone6 = (device.iPhone && limit == 667.0);
   139     device.iPhone6Plus = (device.iPhone && limit == 736.0);
   141     return device;
   142 }
   144 - (NSString*)getImageName:(UIInterfaceOrientation)currentOrientation delegate:(id<CDVScreenOrientationDelegate>)orientationDelegate device:(CDV_iOSDevice)device
   145 {
   146     // Use UILaunchImageFile if specified in plist.  Otherwise, use Default.
   147     NSString* imageName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UILaunchImageFile"];
   149     NSUInteger supportedOrientations = [orientationDelegate supportedInterfaceOrientations];
   151     // Checks to see if the developer has locked the orientation to use only one of Portrait or Landscape
   152     BOOL supportsLandscape = (supportedOrientations & UIInterfaceOrientationMaskLandscape);
   153     BOOL supportsPortrait = (supportedOrientations & UIInterfaceOrientationMaskPortrait || supportedOrientations & UIInterfaceOrientationMaskPortraitUpsideDown);
   154     // this means there are no mixed orientations in there
   155     BOOL isOrientationLocked = !(supportsPortrait && supportsLandscape);
   157     if (imageName) {
   158         imageName = [imageName stringByDeletingPathExtension];
   159     } else {
   160         imageName = @"Default";
   161     }
   163     if (device.iPhone5) { // does not support landscape
   164         imageName = [imageName stringByAppendingString:@"-568h"];
   165     } else if (device.iPhone6) { // does not support landscape
   166         imageName = [imageName stringByAppendingString:@"-667h"];
   167     } else if (device.iPhone6Plus) { // supports landscape
   168         if (isOrientationLocked) {
   169             imageName = [imageName stringByAppendingString:(supportsLandscape ? @"-Landscape" : @"")];
   170         } else {
   171             switch (currentOrientation) {
   172                 case UIInterfaceOrientationLandscapeLeft:
   173                 case UIInterfaceOrientationLandscapeRight:
   174                         imageName = [imageName stringByAppendingString:@"-Landscape"];
   175                     break;
   176                 default:
   177                     break;
   178             }
   179         }
   180         imageName = [imageName stringByAppendingString:@"-736h"];
   182     } else if (device.iPad) { // supports landscape
   183         if (isOrientationLocked) {
   184             imageName = [imageName stringByAppendingString:(supportsLandscape ? @"-Landscape" : @"-Portrait")];
   185         } else {
   186             switch (currentOrientation) {
   187                 case UIInterfaceOrientationLandscapeLeft:
   188                 case UIInterfaceOrientationLandscapeRight:
   189                     imageName = [imageName stringByAppendingString:@"-Landscape"];
   190                     break;
   192                 case UIInterfaceOrientationPortrait:
   193                 case UIInterfaceOrientationPortraitUpsideDown:
   194                 default:
   195                     imageName = [imageName stringByAppendingString:@"-Portrait"];
   196                     break;
   197             }
   198         }
   199     }
   201     return imageName;
   202 }
   204 // Sets the view's frame and image.
   205 - (void)updateImage
   206 {
   207     NSString* imageName = [self getImageName:self.viewController.interfaceOrientation delegate:(id<CDVScreenOrientationDelegate>)self.viewController device:[self getCurrentDevice]];
   209     if (![imageName isEqualToString:_curImageName]) {
   210         UIImage* img = [UIImage imageNamed:imageName];
   211         _imageView.image = img;
   212         _curImageName = imageName;
   213     }
   215     // Check that splash screen's image exists before updating bounds
   216     if (_imageView.image) {
   217         [self updateBounds];
   218     } else {
   219         NSLog(@"WARNING: The splashscreen image named %@ was not found", imageName);
   220     }
   221 }
   223 - (void)updateBounds
   224 {
   225     UIImage* img = _imageView.image;
   226     CGRect imgBounds = (img) ? CGRectMake(0, 0, img.size.width, img.size.height) : CGRectZero;
   228     CGSize screenSize = [self.viewController.view convertRect:[UIScreen mainScreen].bounds fromView:nil].size;
   229     UIInterfaceOrientation orientation = self.viewController.interfaceOrientation;
   230     CGAffineTransform imgTransform = CGAffineTransformIdentity;
   232     /* If and only if an iPhone application is landscape-only as per
   233      * UISupportedInterfaceOrientations, the view controller's orientation is
   234      * landscape. In this case the image must be rotated in order to appear
   235      * correctly.
   236      */
   237     if (UIInterfaceOrientationIsLandscape(orientation) && !CDV_IsIPad()) {
   238         imgTransform = CGAffineTransformMakeRotation(M_PI / 2);
   239         imgBounds.size = CGSizeMake(imgBounds.size.height, imgBounds.size.width);
   240     }
   242     // There's a special case when the image is the size of the screen.
   243     if (CGSizeEqualToSize(screenSize, imgBounds.size)) {
   244         CGRect statusFrame = [self.viewController.view convertRect:[UIApplication sharedApplication].statusBarFrame fromView:nil];
   245         if (!(IsAtLeastiOSVersion(@"7.0"))) {
   246             imgBounds.origin.y -= statusFrame.size.height;
   247         }
   248     } else if (imgBounds.size.width > 0) {
   249         CGRect viewBounds = self.viewController.view.bounds;
   250         CGFloat imgAspect = imgBounds.size.width / imgBounds.size.height;
   251         CGFloat viewAspect = viewBounds.size.width / viewBounds.size.height;
   252         // This matches the behaviour of the native splash screen.
   253         CGFloat ratio;
   254         if (viewAspect > imgAspect) {
   255             ratio = viewBounds.size.width / imgBounds.size.width;
   256         } else {
   257             ratio = viewBounds.size.height / imgBounds.size.height;
   258         }
   259         imgBounds.size.height *= ratio;
   260         imgBounds.size.width *= ratio;
   261     }
   263     _imageView.transform = imgTransform;
   264     _imageView.frame = imgBounds;
   265 }
   267 - (void)setVisible:(BOOL)visible
   268 {
   269     if (visible == _visible) {
   270         return;
   271     }
   272     _visible = visible;
   274     id fadeSplashScreenValue = [self.commandDelegate.settings objectForKey:[@"FadeSplashScreen" lowercaseString]];
   275     id fadeSplashScreenDuration = [self.commandDelegate.settings objectForKey:[@"FadeSplashScreenDuration" lowercaseString]];
   277     float fadeDuration = fadeSplashScreenDuration == nil ? kSplashScreenDurationDefault : [fadeSplashScreenDuration floatValue];
   279     if ((fadeSplashScreenValue == nil) || ![fadeSplashScreenValue boolValue]) {
   280         fadeDuration = 0;
   281     }
   283     // Never animate the showing of the splash screen.
   284     if (visible) {
   285         if (_imageView == nil) {
   286             [self createViews];
   287         }
   288     } else if (fadeDuration == 0) {
   289         [self destroyViews];
   290     } else {
   291         [UIView transitionWithView:self.viewController.view
   292                           duration:fadeDuration
   293                            options:UIViewAnimationOptionTransitionNone
   294                         animations:^(void) {
   295                             [_imageView setAlpha:0];
   296                             [_activityView setAlpha:0];
   297                         }
   298                         completion:^(BOOL finished) {
   299                             if (finished) {
   300                                 [self destroyViews];
   301                             }
   302                         }
   303         ];
   304     }
   305 }
   307 @end

mercurial