|
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 |
|
24 #define kSplashScreenDurationDefault 0.25f |
|
25 |
|
26 |
|
27 @implementation CDVSplashScreen |
|
28 |
|
29 - (void)pluginInitialize |
|
30 { |
|
31 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pageDidLoad) name:CDVPageDidLoadNotification object:self.webView]; |
|
32 |
|
33 [self setVisible:YES]; |
|
34 } |
|
35 |
|
36 - (void)show:(CDVInvokedUrlCommand*)command |
|
37 { |
|
38 [self setVisible:YES]; |
|
39 } |
|
40 |
|
41 - (void)hide:(CDVInvokedUrlCommand*)command |
|
42 { |
|
43 [self setVisible:NO]; |
|
44 } |
|
45 |
|
46 - (void)pageDidLoad |
|
47 { |
|
48 id autoHideSplashScreenValue = [self.commandDelegate.settings objectForKey:[@"AutoHideSplashScreen" lowercaseString]]; |
|
49 |
|
50 // if value is missing, default to yes |
|
51 if ((autoHideSplashScreenValue == nil) || [autoHideSplashScreenValue boolValue]) { |
|
52 [self setVisible:NO]; |
|
53 } |
|
54 } |
|
55 |
|
56 - (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context |
|
57 { |
|
58 [self updateImage]; |
|
59 } |
|
60 |
|
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; |
|
73 |
|
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 } |
|
81 |
|
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]; |
|
89 |
|
90 // Set the frame & image later. |
|
91 _imageView = [[UIImageView alloc] init]; |
|
92 [parentView addSubview:_imageView]; |
|
93 |
|
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 } |
|
99 |
|
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]; |
|
104 |
|
105 [self updateImage]; |
|
106 } |
|
107 |
|
108 - (void)destroyViews |
|
109 { |
|
110 [_imageView removeFromSuperview]; |
|
111 [_activityView removeFromSuperview]; |
|
112 _imageView = nil; |
|
113 _activityView = nil; |
|
114 _curImageName = nil; |
|
115 |
|
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 } |
|
120 |
|
121 - (CDV_iOSDevice) getCurrentDevice |
|
122 { |
|
123 CDV_iOSDevice device; |
|
124 |
|
125 UIScreen* mainScreen = [UIScreen mainScreen]; |
|
126 CGFloat mainScreenHeight = mainScreen.bounds.size.height; |
|
127 CGFloat mainScreenWidth = mainScreen.bounds.size.width; |
|
128 |
|
129 int limit = MAX(mainScreenHeight,mainScreenWidth); |
|
130 |
|
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); |
|
140 |
|
141 return device; |
|
142 } |
|
143 |
|
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"]; |
|
148 |
|
149 NSUInteger supportedOrientations = [orientationDelegate supportedInterfaceOrientations]; |
|
150 |
|
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); |
|
156 |
|
157 if (imageName) { |
|
158 imageName = [imageName stringByDeletingPathExtension]; |
|
159 } else { |
|
160 imageName = @"Default"; |
|
161 } |
|
162 |
|
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"]; |
|
181 |
|
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; |
|
191 |
|
192 case UIInterfaceOrientationPortrait: |
|
193 case UIInterfaceOrientationPortraitUpsideDown: |
|
194 default: |
|
195 imageName = [imageName stringByAppendingString:@"-Portrait"]; |
|
196 break; |
|
197 } |
|
198 } |
|
199 } |
|
200 |
|
201 return imageName; |
|
202 } |
|
203 |
|
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]]; |
|
208 |
|
209 if (![imageName isEqualToString:_curImageName]) { |
|
210 UIImage* img = [UIImage imageNamed:imageName]; |
|
211 _imageView.image = img; |
|
212 _curImageName = imageName; |
|
213 } |
|
214 |
|
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 } |
|
222 |
|
223 - (void)updateBounds |
|
224 { |
|
225 UIImage* img = _imageView.image; |
|
226 CGRect imgBounds = (img) ? CGRectMake(0, 0, img.size.width, img.size.height) : CGRectZero; |
|
227 |
|
228 CGSize screenSize = [self.viewController.view convertRect:[UIScreen mainScreen].bounds fromView:nil].size; |
|
229 UIInterfaceOrientation orientation = self.viewController.interfaceOrientation; |
|
230 CGAffineTransform imgTransform = CGAffineTransformIdentity; |
|
231 |
|
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 } |
|
241 |
|
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 } |
|
262 |
|
263 _imageView.transform = imgTransform; |
|
264 _imageView.frame = imgBounds; |
|
265 } |
|
266 |
|
267 - (void)setVisible:(BOOL)visible |
|
268 { |
|
269 if (visible == _visible) { |
|
270 return; |
|
271 } |
|
272 _visible = visible; |
|
273 |
|
274 id fadeSplashScreenValue = [self.commandDelegate.settings objectForKey:[@"FadeSplashScreen" lowercaseString]]; |
|
275 id fadeSplashScreenDuration = [self.commandDelegate.settings objectForKey:[@"FadeSplashScreenDuration" lowercaseString]]; |
|
276 |
|
277 float fadeDuration = fadeSplashScreenDuration == nil ? kSplashScreenDurationDefault : [fadeSplashScreenDuration floatValue]; |
|
278 |
|
279 if ((fadeSplashScreenValue == nil) || ![fadeSplashScreenValue boolValue]) { |
|
280 fadeDuration = 0; |
|
281 } |
|
282 |
|
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 } |
|
306 |
|
307 @end |