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.

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

mercurial