Thu, 04 Jun 2015 14:50:33 +0200
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