UNPKG

44.6 kBPlain TextView Raw
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 "CDVInAppBrowser.h"
21#import <Cordova/CDVPluginResult.h>
22#import <Cordova/CDVUserAgentUtil.h>
23
24#define kInAppBrowserTargetSelf @"_self"
25#define kInAppBrowserTargetSystem @"_system"
26#define kInAppBrowserTargetBlank @"_blank"
27
28#define kInAppBrowserToolbarBarPositionBottom @"bottom"
29#define kInAppBrowserToolbarBarPositionTop @"top"
30
31#define TOOLBAR_HEIGHT 44.0
32#define STATUSBAR_HEIGHT 20.0
33#define LOCATIONBAR_HEIGHT 21.0
34#define FOOTER_HEIGHT ((TOOLBAR_HEIGHT) + (LOCATIONBAR_HEIGHT))
35
36#pragma mark CDVInAppBrowser
37
38@interface CDVInAppBrowser () {
39 NSInteger _previousStatusBarStyle;
40}
41@end
42
43@implementation CDVInAppBrowser
44
45- (void)pluginInitialize
46{
47 _previousStatusBarStyle = -1;
48 _callbackIdPattern = nil;
49}
50
51- (id)settingForKey:(NSString*)key
52{
53 return [self.commandDelegate.settings objectForKey:[key lowercaseString]];
54}
55
56- (void)onReset
57{
58 [self close:nil];
59}
60
61- (void)close:(CDVInvokedUrlCommand*)command
62{
63 if (self.inAppBrowserViewController == nil) {
64 NSLog(@"IAB.close() called but it was already closed.");
65 return;
66 }
67 // Things are cleaned up in browserExit.
68 [self.inAppBrowserViewController close];
69}
70
71- (BOOL) isSystemUrl:(NSURL*)url
72{
73 if ([[url host] isEqualToString:@"itunes.apple.com"]) {
74 return YES;
75 }
76
77 return NO;
78}
79
80- (void)open:(CDVInvokedUrlCommand*)command
81{
82 CDVPluginResult* pluginResult;
83
84 NSString* url = [command argumentAtIndex:0];
85 NSString* target = [command argumentAtIndex:1 withDefault:kInAppBrowserTargetSelf];
86 NSString* options = [command argumentAtIndex:2 withDefault:@"" andClass:[NSString class]];
87
88 self.callbackId = command.callbackId;
89
90 if (url != nil) {
91#ifdef __CORDOVA_4_0_0
92 NSURL* baseUrl = [self.webViewEngine URL];
93#else
94 NSURL* baseUrl = [self.webView.request URL];
95#endif
96 NSURL* absoluteUrl = [[NSURL URLWithString:url relativeToURL:baseUrl] absoluteURL];
97
98 if ([self isSystemUrl:absoluteUrl]) {
99 target = kInAppBrowserTargetSystem;
100 }
101
102 if ([target isEqualToString:kInAppBrowserTargetSelf]) {
103 [self openInCordovaWebView:absoluteUrl withOptions:options];
104 } else if ([target isEqualToString:kInAppBrowserTargetSystem]) {
105 [self openInSystem:absoluteUrl];
106 } else { // _blank or anything else
107 [self openInInAppBrowser:absoluteUrl withOptions:options];
108 }
109
110 pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
111 } else {
112 pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"incorrect number of arguments"];
113 }
114
115 [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
116 [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
117}
118
119- (void)openInInAppBrowser:(NSURL*)url withOptions:(NSString*)options
120{
121 CDVInAppBrowserOptions* browserOptions = [CDVInAppBrowserOptions parseOptions:options];
122
123 if (browserOptions.clearcache) {
124 NSHTTPCookie *cookie;
125 NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
126 for (cookie in [storage cookies])
127 {
128 if (![cookie.domain isEqual: @".^filecookies^"]) {
129 [storage deleteCookie:cookie];
130 }
131 }
132 }
133
134 if (browserOptions.clearsessioncache) {
135 NSHTTPCookie *cookie;
136 NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
137 for (cookie in [storage cookies])
138 {
139 if (![cookie.domain isEqual: @".^filecookies^"] && cookie.isSessionOnly) {
140 [storage deleteCookie:cookie];
141 }
142 }
143 }
144
145 if (self.inAppBrowserViewController == nil) {
146 NSString* userAgent = [CDVUserAgentUtil originalUserAgent];
147 NSString* overrideUserAgent = [self settingForKey:@"OverrideUserAgent"];
148 NSString* appendUserAgent = [self settingForKey:@"AppendUserAgent"];
149 if(overrideUserAgent){
150 userAgent = overrideUserAgent;
151 }
152 if(appendUserAgent){
153 userAgent = [userAgent stringByAppendingString: appendUserAgent];
154 }
155 self.inAppBrowserViewController = [[CDVInAppBrowserViewController alloc] initWithUserAgent:userAgent prevUserAgent:[self.commandDelegate userAgent] browserOptions: browserOptions];
156 self.inAppBrowserViewController.navigationDelegate = self;
157
158 if ([self.viewController conformsToProtocol:@protocol(CDVScreenOrientationDelegate)]) {
159 self.inAppBrowserViewController.orientationDelegate = (UIViewController <CDVScreenOrientationDelegate>*)self.viewController;
160 }
161 }
162
163 [self.inAppBrowserViewController showLocationBar:browserOptions.location];
164 [self.inAppBrowserViewController showToolBar:browserOptions.toolbar :browserOptions.toolbarposition];
165 if (browserOptions.closebuttoncaption != nil) {
166 [self.inAppBrowserViewController setCloseButtonTitle:browserOptions.closebuttoncaption];
167 }
168 // Set Presentation Style
169 UIModalPresentationStyle presentationStyle = UIModalPresentationFullScreen; // default
170 if (browserOptions.presentationstyle != nil) {
171 if ([[browserOptions.presentationstyle lowercaseString] isEqualToString:@"pagesheet"]) {
172 presentationStyle = UIModalPresentationPageSheet;
173 } else if ([[browserOptions.presentationstyle lowercaseString] isEqualToString:@"formsheet"]) {
174 presentationStyle = UIModalPresentationFormSheet;
175 }
176 }
177 self.inAppBrowserViewController.modalPresentationStyle = presentationStyle;
178
179 // Set Transition Style
180 UIModalTransitionStyle transitionStyle = UIModalTransitionStyleCoverVertical; // default
181 if (browserOptions.transitionstyle != nil) {
182 if ([[browserOptions.transitionstyle lowercaseString] isEqualToString:@"fliphorizontal"]) {
183 transitionStyle = UIModalTransitionStyleFlipHorizontal;
184 } else if ([[browserOptions.transitionstyle lowercaseString] isEqualToString:@"crossdissolve"]) {
185 transitionStyle = UIModalTransitionStyleCrossDissolve;
186 }
187 }
188 self.inAppBrowserViewController.modalTransitionStyle = transitionStyle;
189
190 // prevent webView from bouncing
191 if (browserOptions.disallowoverscroll) {
192 if ([self.inAppBrowserViewController.webView respondsToSelector:@selector(scrollView)]) {
193 ((UIScrollView*)[self.inAppBrowserViewController.webView scrollView]).bounces = NO;
194 } else {
195 for (id subview in self.inAppBrowserViewController.webView.subviews) {
196 if ([[subview class] isSubclassOfClass:[UIScrollView class]]) {
197 ((UIScrollView*)subview).bounces = NO;
198 }
199 }
200 }
201 }
202
203 // UIWebView options
204 self.inAppBrowserViewController.webView.scalesPageToFit = browserOptions.enableviewportscale;
205 self.inAppBrowserViewController.webView.mediaPlaybackRequiresUserAction = browserOptions.mediaplaybackrequiresuseraction;
206 self.inAppBrowserViewController.webView.allowsInlineMediaPlayback = browserOptions.allowinlinemediaplayback;
207 if (IsAtLeastiOSVersion(@"6.0")) {
208 self.inAppBrowserViewController.webView.keyboardDisplayRequiresUserAction = browserOptions.keyboarddisplayrequiresuseraction;
209 self.inAppBrowserViewController.webView.suppressesIncrementalRendering = browserOptions.suppressesincrementalrendering;
210 }
211
212 [self.inAppBrowserViewController navigateTo:url];
213 if (!browserOptions.hidden) {
214 [self show:nil];
215 }
216}
217
218- (void)show:(CDVInvokedUrlCommand*)command
219{
220 if (self.inAppBrowserViewController == nil) {
221 NSLog(@"Tried to show IAB after it was closed.");
222 return;
223 }
224 if (_previousStatusBarStyle != -1) {
225 NSLog(@"Tried to show IAB while already shown");
226 return;
227 }
228
229 _previousStatusBarStyle = [UIApplication sharedApplication].statusBarStyle;
230
231 __block CDVInAppBrowserNavigationController* nav = [[CDVInAppBrowserNavigationController alloc]
232 initWithRootViewController:self.inAppBrowserViewController];
233 nav.orientationDelegate = self.inAppBrowserViewController;
234 nav.navigationBarHidden = YES;
235 nav.modalPresentationStyle = self.inAppBrowserViewController.modalPresentationStyle;
236
237 __weak CDVInAppBrowser* weakSelf = self;
238
239 // Run later to avoid the "took a long time" log message.
240 dispatch_async(dispatch_get_main_queue(), ^{
241 if (weakSelf.inAppBrowserViewController != nil) {
242 CGRect frame = [[UIScreen mainScreen] bounds];
243 UIWindow *tmpWindow = [[UIWindow alloc] initWithFrame:frame];
244 UIViewController *tmpController = [[UIViewController alloc] init];
245 [tmpWindow setRootViewController:tmpController];
246 [tmpWindow setWindowLevel:UIWindowLevelNormal];
247
248 [tmpWindow makeKeyAndVisible];
249 [tmpController presentViewController:nav animated:YES completion:nil];
250 }
251 });
252}
253
254- (void)hide:(CDVInvokedUrlCommand*)command
255{
256 if (self.inAppBrowserViewController == nil) {
257 NSLog(@"Tried to hide IAB after it was closed.");
258 return;
259
260
261 }
262 if (_previousStatusBarStyle == -1) {
263 NSLog(@"Tried to hide IAB while already hidden");
264 return;
265 }
266
267 _previousStatusBarStyle = [UIApplication sharedApplication].statusBarStyle;
268
269 // Run later to avoid the "took a long time" log message.
270 dispatch_async(dispatch_get_main_queue(), ^{
271 if (self.inAppBrowserViewController != nil) {
272 _previousStatusBarStyle = -1;
273 [self.viewController dismissViewControllerAnimated:YES completion:nil];
274 }
275 });
276}
277
278- (void)openInCordovaWebView:(NSURL*)url withOptions:(NSString*)options
279{
280 NSURLRequest* request = [NSURLRequest requestWithURL:url];
281
282#ifdef __CORDOVA_4_0_0
283 // the webview engine itself will filter for this according to <allow-navigation> policy
284 // in config.xml for cordova-ios-4.0
285 [self.webViewEngine loadRequest:request];
286#else
287 if ([self.commandDelegate URLIsWhitelisted:url]) {
288 [self.webView loadRequest:request];
289 } else { // this assumes the InAppBrowser can be excepted from the white-list
290 [self openInInAppBrowser:url withOptions:options];
291 }
292#endif
293}
294
295- (void)openInSystem:(NSURL*)url
296{
297 [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]];
298 [[UIApplication sharedApplication] openURL:url];
299}
300
301// This is a helper method for the inject{Script|Style}{Code|File} API calls, which
302// provides a consistent method for injecting JavaScript code into the document.
303//
304// If a wrapper string is supplied, then the source string will be JSON-encoded (adding
305// quotes) and wrapped using string formatting. (The wrapper string should have a single
306// '%@' marker).
307//
308// If no wrapper is supplied, then the source string is executed directly.
309
310- (void)injectDeferredObject:(NSString*)source withWrapper:(NSString*)jsWrapper
311{
312 // Ensure an iframe bridge is created to communicate with the CDVInAppBrowserViewController
313 [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:@"(function(d){_cdvIframeBridge=d.getElementById('_cdvIframeBridge');if(!_cdvIframeBridge) {var e = _cdvIframeBridge = d.createElement('iframe');e.id='_cdvIframeBridge'; e.style.display='none';d.body.appendChild(e);}})(document)"];
314
315 if (jsWrapper != nil) {
316 NSData* jsonData = [NSJSONSerialization dataWithJSONObject:@[source] options:0 error:nil];
317 NSString* sourceArrayString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
318 if (sourceArrayString) {
319 NSString* sourceString = [sourceArrayString substringWithRange:NSMakeRange(1, [sourceArrayString length] - 2)];
320 NSString* jsToInject = [NSString stringWithFormat:jsWrapper, sourceString];
321 [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:jsToInject];
322 }
323 } else {
324 [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:source];
325 }
326}
327
328- (void)injectScriptCode:(CDVInvokedUrlCommand*)command
329{
330 NSString* jsWrapper = nil;
331
332 if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) {
333 jsWrapper = [NSString stringWithFormat:@"_cdvIframeBridge.src='gap-iab://%@/'+encodeURIComponent(JSON.stringify([eval(%%@)]));", command.callbackId];
334 }
335 [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper];
336}
337
338- (void)injectScriptFile:(CDVInvokedUrlCommand*)command
339{
340 NSString* jsWrapper;
341
342 if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) {
343 jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('script'); c.src = %%@; c.onload = function() { _cdvIframeBridge.src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId];
344 } else {
345 jsWrapper = @"(function(d) { var c = d.createElement('script'); c.src = %@; d.body.appendChild(c); })(document)";
346 }
347 [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper];
348}
349
350- (void)injectStyleCode:(CDVInvokedUrlCommand*)command
351{
352 NSString* jsWrapper;
353
354 if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) {
355 jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('style'); c.innerHTML = %%@; c.onload = function() { _cdvIframeBridge.src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId];
356 } else {
357 jsWrapper = @"(function(d) { var c = d.createElement('style'); c.innerHTML = %@; d.body.appendChild(c); })(document)";
358 }
359 [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper];
360}
361
362- (void)injectStyleFile:(CDVInvokedUrlCommand*)command
363{
364 NSString* jsWrapper;
365
366 if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) {
367 jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %%@; c.onload = function() { _cdvIframeBridge.src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId];
368 } else {
369 jsWrapper = @"(function(d) { var c = d.createElement('link'); c.rel='stylesheet', c.type='text/css'; c.href = %@; d.body.appendChild(c); })(document)";
370 }
371 [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper];
372}
373
374- (BOOL)isValidCallbackId:(NSString *)callbackId
375{
376 NSError *err = nil;
377 // Initialize on first use
378 if (self.callbackIdPattern == nil) {
379 self.callbackIdPattern = [NSRegularExpression regularExpressionWithPattern:@"^InAppBrowser[0-9]{1,10}$" options:0 error:&err];
380 if (err != nil) {
381 // Couldn't initialize Regex; No is safer than Yes.
382 return NO;
383 }
384 }
385 if ([self.callbackIdPattern firstMatchInString:callbackId options:0 range:NSMakeRange(0, [callbackId length])]) {
386 return YES;
387 }
388 return NO;
389}
390
391/**
392 * The iframe bridge provided for the InAppBrowser is capable of executing any oustanding callback belonging
393 * to the InAppBrowser plugin. Care has been taken that other callbacks cannot be triggered, and that no
394 * other code execution is possible.
395 *
396 * To trigger the bridge, the iframe (or any other resource) should attempt to load a url of the form:
397 *
398 * gap-iab://<callbackId>/<arguments>
399 *
400 * where <callbackId> is the string id of the callback to trigger (something like "InAppBrowser0123456789")
401 *
402 * If present, the path component of the special gap-iab:// url is expected to be a URL-escaped JSON-encoded
403 * value to pass to the callback. [NSURL path] should take care of the URL-unescaping, and a JSON_EXCEPTION
404 * is returned if the JSON is invalid.
405 */
406- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
407{
408 NSURL* url = request.URL;
409 BOOL isTopLevelNavigation = [request.URL isEqual:[request mainDocumentURL]];
410
411 // See if the url uses the 'gap-iab' protocol. If so, the host should be the id of a callback to execute,
412 // and the path, if present, should be a JSON-encoded value to pass to the callback.
413 if ([[url scheme] isEqualToString:@"gap-iab"]) {
414 NSString* scriptCallbackId = [url host];
415 CDVPluginResult* pluginResult = nil;
416
417 if ([self isValidCallbackId:scriptCallbackId]) {
418 NSString* scriptResult = [url path];
419 NSError* __autoreleasing error = nil;
420
421 // The message should be a JSON-encoded array of the result of the script which executed.
422 if ((scriptResult != nil) && ([scriptResult length] > 1)) {
423 scriptResult = [scriptResult substringFromIndex:1];
424 NSData* decodedResult = [NSJSONSerialization JSONObjectWithData:[scriptResult dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error];
425 if ((error == nil) && [decodedResult isKindOfClass:[NSArray class]]) {
426 pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:(NSArray*)decodedResult];
427 } else {
428 pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_JSON_EXCEPTION];
429 }
430 } else {
431 pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:@[]];
432 }
433 [self.commandDelegate sendPluginResult:pluginResult callbackId:scriptCallbackId];
434 return NO;
435 }
436 }
437 //if is an app store link, let the system handle it, otherwise it fails to load it
438 else if ([[ url scheme] isEqualToString:@"itms-appss"] || [[ url scheme] isEqualToString:@"itms-apps"]) {
439 [theWebView stopLoading];
440 [self openInSystem:url];
441 return NO;
442 }
443 else if ((self.callbackId != nil) && isTopLevelNavigation) {
444 // Send a loadstart event for each top-level navigation (includes redirects).
445 CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
446 messageAsDictionary:@{@"type":@"loadstart", @"url":[url absoluteString]}];
447 [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
448
449 [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
450 }
451
452 return YES;
453}
454
455- (void)webViewDidStartLoad:(UIWebView*)theWebView
456{
457}
458
459- (void)webViewDidFinishLoad:(UIWebView*)theWebView
460{
461 if (self.callbackId != nil) {
462 // TODO: It would be more useful to return the URL the page is actually on (e.g. if it's been redirected).
463 NSString* url = [self.inAppBrowserViewController.currentURL absoluteString];
464 CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
465 messageAsDictionary:@{@"type":@"loadstop", @"url":url}];
466 [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
467
468 [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
469 }
470}
471
472- (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error
473{
474 if (self.callbackId != nil) {
475 NSString* url = [self.inAppBrowserViewController.currentURL absoluteString];
476 CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
477 messageAsDictionary:@{@"type":@"loaderror", @"url":url, @"code": [NSNumber numberWithInteger:error.code], @"message": error.localizedDescription}];
478 [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
479
480 [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
481 }
482}
483
484- (void)browserExit
485{
486 if (self.callbackId != nil) {
487 CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
488 messageAsDictionary:@{@"type":@"exit"}];
489 [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
490 self.callbackId = nil;
491 }
492 // Set navigationDelegate to nil to ensure no callbacks are received from it.
493 self.inAppBrowserViewController.navigationDelegate = nil;
494 // Don't recycle the ViewController since it may be consuming a lot of memory.
495 // Also - this is required for the PDF/User-Agent bug work-around.
496 self.inAppBrowserViewController = nil;
497
498 if (IsAtLeastiOSVersion(@"7.0")) {
499 if (_previousStatusBarStyle != -1) {
500 [[UIApplication sharedApplication] setStatusBarStyle:_previousStatusBarStyle];
501 }
502 }
503
504 _previousStatusBarStyle = -1; // this value was reset before reapplying it. caused statusbar to stay black on ios7
505}
506
507@end
508
509#pragma mark CDVInAppBrowserViewController
510
511@implementation CDVInAppBrowserViewController
512
513@synthesize currentURL;
514
515- (id)initWithUserAgent:(NSString*)userAgent prevUserAgent:(NSString*)prevUserAgent browserOptions: (CDVInAppBrowserOptions*) browserOptions
516{
517 self = [super init];
518 if (self != nil) {
519 _userAgent = userAgent;
520 _prevUserAgent = prevUserAgent;
521 _browserOptions = browserOptions;
522#ifdef __CORDOVA_4_0_0
523 _webViewDelegate = [[CDVUIWebViewDelegate alloc] initWithDelegate:self];
524#else
525 _webViewDelegate = [[CDVWebViewDelegate alloc] initWithDelegate:self];
526#endif
527
528 [self createViews];
529 }
530
531 return self;
532}
533
534// Prevent crashes on closing windows
535-(void)dealloc {
536 self.webView.delegate = nil;
537}
538
539- (void)createViews
540{
541 // We create the views in code for primarily for ease of upgrades and not requiring an external .xib to be included
542
543 CGRect webViewBounds = self.view.bounds;
544 BOOL toolbarIsAtBottom = ![_browserOptions.toolbarposition isEqualToString:kInAppBrowserToolbarBarPositionTop];
545 webViewBounds.size.height -= _browserOptions.location ? FOOTER_HEIGHT : TOOLBAR_HEIGHT;
546 self.webView = [[UIWebView alloc] initWithFrame:webViewBounds];
547
548 self.webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
549
550 [self.view addSubview:self.webView];
551 [self.view sendSubviewToBack:self.webView];
552
553 self.webView.delegate = _webViewDelegate;
554 self.webView.backgroundColor = [UIColor whiteColor];
555
556 self.webView.clearsContextBeforeDrawing = YES;
557 self.webView.clipsToBounds = YES;
558 self.webView.contentMode = UIViewContentModeScaleToFill;
559 self.webView.multipleTouchEnabled = YES;
560 self.webView.opaque = YES;
561 self.webView.scalesPageToFit = NO;
562 self.webView.userInteractionEnabled = YES;
563
564 self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
565 self.spinner.alpha = 1.000;
566 self.spinner.autoresizesSubviews = YES;
567 self.spinner.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin);
568 self.spinner.clearsContextBeforeDrawing = NO;
569 self.spinner.clipsToBounds = NO;
570 self.spinner.contentMode = UIViewContentModeScaleToFill;
571 self.spinner.frame = CGRectMake(CGRectGetMidX(self.webView.frame), CGRectGetMidY(self.webView.frame), 20.0, 20.0);
572 self.spinner.hidden = NO;
573 self.spinner.hidesWhenStopped = YES;
574 self.spinner.multipleTouchEnabled = NO;
575 self.spinner.opaque = NO;
576 self.spinner.userInteractionEnabled = NO;
577 [self.spinner stopAnimating];
578
579 self.closeButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(close)];
580 self.closeButton.enabled = YES;
581
582 UIBarButtonItem* flexibleSpaceButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
583
584 UIBarButtonItem* fixedSpaceButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
585 fixedSpaceButton.width = 20;
586
587 float toolbarY = toolbarIsAtBottom ? self.view.bounds.size.height - TOOLBAR_HEIGHT : 0.0;
588 CGRect toolbarFrame = CGRectMake(0.0, toolbarY, self.view.bounds.size.width, TOOLBAR_HEIGHT);
589
590 self.toolbar = [[UIToolbar alloc] initWithFrame:toolbarFrame];
591 self.toolbar.alpha = 1.000;
592 self.toolbar.autoresizesSubviews = YES;
593 self.toolbar.autoresizingMask = toolbarIsAtBottom ? (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin) : UIViewAutoresizingFlexibleWidth;
594 self.toolbar.barStyle = UIBarStyleBlackOpaque;
595 self.toolbar.clearsContextBeforeDrawing = NO;
596 self.toolbar.clipsToBounds = NO;
597 self.toolbar.contentMode = UIViewContentModeScaleToFill;
598 self.toolbar.hidden = NO;
599 self.toolbar.multipleTouchEnabled = NO;
600 self.toolbar.opaque = NO;
601 self.toolbar.userInteractionEnabled = YES;
602
603 CGFloat labelInset = 5.0;
604 float locationBarY = toolbarIsAtBottom ? self.view.bounds.size.height - FOOTER_HEIGHT : self.view.bounds.size.height - LOCATIONBAR_HEIGHT;
605
606 self.addressLabel = [[UILabel alloc] initWithFrame:CGRectMake(labelInset, locationBarY, self.view.bounds.size.width - labelInset, LOCATIONBAR_HEIGHT)];
607 self.addressLabel.adjustsFontSizeToFitWidth = NO;
608 self.addressLabel.alpha = 1.000;
609 self.addressLabel.autoresizesSubviews = YES;
610 self.addressLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin;
611 self.addressLabel.backgroundColor = [UIColor clearColor];
612 self.addressLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters;
613 self.addressLabel.clearsContextBeforeDrawing = YES;
614 self.addressLabel.clipsToBounds = YES;
615 self.addressLabel.contentMode = UIViewContentModeScaleToFill;
616 self.addressLabel.enabled = YES;
617 self.addressLabel.hidden = NO;
618 self.addressLabel.lineBreakMode = NSLineBreakByTruncatingTail;
619
620 if ([self.addressLabel respondsToSelector:NSSelectorFromString(@"setMinimumScaleFactor:")]) {
621 [self.addressLabel setValue:@(10.0/[UIFont labelFontSize]) forKey:@"minimumScaleFactor"];
622 } else if ([self.addressLabel respondsToSelector:NSSelectorFromString(@"setMinimumFontSize:")]) {
623 [self.addressLabel setValue:@(10.0) forKey:@"minimumFontSize"];
624 }
625
626 self.addressLabel.multipleTouchEnabled = NO;
627 self.addressLabel.numberOfLines = 1;
628 self.addressLabel.opaque = NO;
629 self.addressLabel.shadowOffset = CGSizeMake(0.0, -1.0);
630 self.addressLabel.text = NSLocalizedString(@"Loading...", nil);
631 self.addressLabel.textAlignment = NSTextAlignmentLeft;
632 self.addressLabel.textColor = [UIColor colorWithWhite:1.000 alpha:1.000];
633 self.addressLabel.userInteractionEnabled = NO;
634
635 NSString* frontArrowString = NSLocalizedString(@"►", nil); // create arrow from Unicode char
636 self.forwardButton = [[UIBarButtonItem alloc] initWithTitle:frontArrowString style:UIBarButtonItemStylePlain target:self action:@selector(goForward:)];
637 self.forwardButton.enabled = YES;
638 self.forwardButton.imageInsets = UIEdgeInsetsZero;
639
640 NSString* backArrowString = NSLocalizedString(@"◄", nil); // create arrow from Unicode char
641 self.backButton = [[UIBarButtonItem alloc] initWithTitle:backArrowString style:UIBarButtonItemStylePlain target:self action:@selector(goBack:)];
642 self.backButton.enabled = YES;
643 self.backButton.imageInsets = UIEdgeInsetsZero;
644
645 [self.toolbar setItems:@[self.closeButton, flexibleSpaceButton, self.backButton, fixedSpaceButton, self.forwardButton]];
646
647 self.view.backgroundColor = [UIColor grayColor];
648 [self.view addSubview:self.toolbar];
649 [self.view addSubview:self.addressLabel];
650 [self.view addSubview:self.spinner];
651}
652
653- (void) setWebViewFrame : (CGRect) frame {
654 NSLog(@"Setting the WebView's frame to %@", NSStringFromCGRect(frame));
655 [self.webView setFrame:frame];
656}
657
658- (void)setCloseButtonTitle:(NSString*)title
659{
660 // the advantage of using UIBarButtonSystemItemDone is the system will localize it for you automatically
661 // but, if you want to set this yourself, knock yourself out (we can't set the title for a system Done button, so we have to create a new one)
662 self.closeButton = nil;
663 self.closeButton = [[UIBarButtonItem alloc] initWithTitle:title style:UIBarButtonItemStyleBordered target:self action:@selector(close)];
664 self.closeButton.enabled = YES;
665 self.closeButton.tintColor = [UIColor colorWithRed:60.0 / 255.0 green:136.0 / 255.0 blue:230.0 / 255.0 alpha:1];
666
667 NSMutableArray* items = [self.toolbar.items mutableCopy];
668 [items replaceObjectAtIndex:0 withObject:self.closeButton];
669 [self.toolbar setItems:items];
670}
671
672- (void)showLocationBar:(BOOL)show
673{
674 CGRect locationbarFrame = self.addressLabel.frame;
675
676 BOOL toolbarVisible = !self.toolbar.hidden;
677
678 // prevent double show/hide
679 if (show == !(self.addressLabel.hidden)) {
680 return;
681 }
682
683 if (show) {
684 self.addressLabel.hidden = NO;
685
686 if (toolbarVisible) {
687 // toolBar at the bottom, leave as is
688 // put locationBar on top of the toolBar
689
690 CGRect webViewBounds = self.view.bounds;
691 webViewBounds.size.height -= FOOTER_HEIGHT;
692 [self setWebViewFrame:webViewBounds];
693
694 locationbarFrame.origin.y = webViewBounds.size.height;
695 self.addressLabel.frame = locationbarFrame;
696 } else {
697 // no toolBar, so put locationBar at the bottom
698
699 CGRect webViewBounds = self.view.bounds;
700 webViewBounds.size.height -= LOCATIONBAR_HEIGHT;
701 [self setWebViewFrame:webViewBounds];
702
703 locationbarFrame.origin.y = webViewBounds.size.height;
704 self.addressLabel.frame = locationbarFrame;
705 }
706 } else {
707 self.addressLabel.hidden = YES;
708
709 if (toolbarVisible) {
710 // locationBar is on top of toolBar, hide locationBar
711
712 // webView take up whole height less toolBar height
713 CGRect webViewBounds = self.view.bounds;
714 webViewBounds.size.height -= TOOLBAR_HEIGHT;
715 [self setWebViewFrame:webViewBounds];
716 } else {
717 // no toolBar, expand webView to screen dimensions
718 [self setWebViewFrame:self.view.bounds];
719 }
720 }
721}
722
723- (void)showToolBar:(BOOL)show : (NSString *) toolbarPosition
724{
725 CGRect toolbarFrame = self.toolbar.frame;
726 CGRect locationbarFrame = self.addressLabel.frame;
727
728 BOOL locationbarVisible = !self.addressLabel.hidden;
729
730 // prevent double show/hide
731 if (show == !(self.toolbar.hidden)) {
732 return;
733 }
734
735 if (show) {
736 self.toolbar.hidden = NO;
737 CGRect webViewBounds = self.view.bounds;
738
739 if (locationbarVisible) {
740 // locationBar at the bottom, move locationBar up
741 // put toolBar at the bottom
742 webViewBounds.size.height -= FOOTER_HEIGHT;
743 locationbarFrame.origin.y = webViewBounds.size.height;
744 self.addressLabel.frame = locationbarFrame;
745 self.toolbar.frame = toolbarFrame;
746 } else {
747 // no locationBar, so put toolBar at the bottom
748 CGRect webViewBounds = self.view.bounds;
749 webViewBounds.size.height -= TOOLBAR_HEIGHT;
750 self.toolbar.frame = toolbarFrame;
751 }
752
753 if ([toolbarPosition isEqualToString:kInAppBrowserToolbarBarPositionTop]) {
754 toolbarFrame.origin.y = 0;
755 webViewBounds.origin.y += toolbarFrame.size.height;
756 [self setWebViewFrame:webViewBounds];
757 } else {
758 toolbarFrame.origin.y = (webViewBounds.size.height + LOCATIONBAR_HEIGHT);
759 }
760 [self setWebViewFrame:webViewBounds];
761
762 } else {
763 self.toolbar.hidden = YES;
764
765 if (locationbarVisible) {
766 // locationBar is on top of toolBar, hide toolBar
767 // put locationBar at the bottom
768
769 // webView take up whole height less locationBar height
770 CGRect webViewBounds = self.view.bounds;
771 webViewBounds.size.height -= LOCATIONBAR_HEIGHT;
772 [self setWebViewFrame:webViewBounds];
773
774 // move locationBar down
775 locationbarFrame.origin.y = webViewBounds.size.height;
776 self.addressLabel.frame = locationbarFrame;
777 } else {
778 // no locationBar, expand webView to screen dimensions
779 [self setWebViewFrame:self.view.bounds];
780 }
781 }
782}
783
784- (void)viewDidLoad
785{
786 [super viewDidLoad];
787}
788
789- (void)viewDidUnload
790{
791 [self.webView loadHTMLString:nil baseURL:nil];
792 [CDVUserAgentUtil releaseLock:&_userAgentLockToken];
793 [super viewDidUnload];
794}
795
796- (UIStatusBarStyle)preferredStatusBarStyle
797{
798 return UIStatusBarStyleDefault;
799}
800
801- (BOOL)prefersStatusBarHidden {
802 return NO;
803}
804
805- (void)close
806{
807 [CDVUserAgentUtil releaseLock:&_userAgentLockToken];
808 self.currentURL = nil;
809
810 if ((self.navigationDelegate != nil) && [self.navigationDelegate respondsToSelector:@selector(browserExit)]) {
811 [self.navigationDelegate browserExit];
812 }
813
814 __weak UIViewController* weakSelf = self;
815
816 // Run later to avoid the "took a long time" log message.
817 dispatch_async(dispatch_get_main_queue(), ^{
818 if ([weakSelf respondsToSelector:@selector(presentingViewController)]) {
819 [[weakSelf presentingViewController] dismissViewControllerAnimated:YES completion:nil];
820 } else {
821 [[weakSelf parentViewController] dismissViewControllerAnimated:YES completion:nil];
822 }
823 });
824}
825
826- (void)navigateTo:(NSURL*)url
827{
828 NSURLRequest* request = [NSURLRequest requestWithURL:url];
829
830 if (_userAgentLockToken != 0) {
831 [self.webView loadRequest:request];
832 } else {
833 __weak CDVInAppBrowserViewController* weakSelf = self;
834 [CDVUserAgentUtil acquireLock:^(NSInteger lockToken) {
835 _userAgentLockToken = lockToken;
836 [CDVUserAgentUtil setUserAgent:_userAgent lockToken:lockToken];
837 [weakSelf.webView loadRequest:request];
838 }];
839 }
840}
841
842- (void)goBack:(id)sender
843{
844 [self.webView goBack];
845}
846
847- (void)goForward:(id)sender
848{
849 [self.webView goForward];
850}
851
852- (void)viewWillAppear:(BOOL)animated
853{
854 if (IsAtLeastiOSVersion(@"7.0")) {
855 [[UIApplication sharedApplication] setStatusBarStyle:[self preferredStatusBarStyle]];
856 }
857 [self rePositionViews];
858
859 [super viewWillAppear:animated];
860}
861
862//
863// On iOS 7 the status bar is part of the view's dimensions, therefore it's height has to be taken into account.
864// The height of it could be hardcoded as 20 pixels, but that would assume that the upcoming releases of iOS won't
865// change that value.
866//
867- (float) getStatusBarOffset {
868 CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
869 float statusBarOffset = IsAtLeastiOSVersion(@"7.0") ? MIN(statusBarFrame.size.width, statusBarFrame.size.height) : 0.0;
870 return statusBarOffset;
871}
872
873- (void) rePositionViews {
874 if ([_browserOptions.toolbarposition isEqualToString:kInAppBrowserToolbarBarPositionTop]) {
875 [self.webView setFrame:CGRectMake(self.webView.frame.origin.x, TOOLBAR_HEIGHT, self.webView.frame.size.width, self.webView.frame.size.height)];
876 [self.toolbar setFrame:CGRectMake(self.toolbar.frame.origin.x, [self getStatusBarOffset], self.toolbar.frame.size.width, self.toolbar.frame.size.height)];
877 }
878}
879
880#pragma mark UIWebViewDelegate
881
882- (void)webViewDidStartLoad:(UIWebView*)theWebView
883{
884 // loading url, start spinner, update back/forward
885
886 self.addressLabel.text = NSLocalizedString(@"Loading...", nil);
887 self.backButton.enabled = theWebView.canGoBack;
888 self.forwardButton.enabled = theWebView.canGoForward;
889
890 [self.spinner startAnimating];
891
892 return [self.navigationDelegate webViewDidStartLoad:theWebView];
893}
894
895- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
896{
897 BOOL isTopLevelNavigation = [request.URL isEqual:[request mainDocumentURL]];
898
899 if (isTopLevelNavigation) {
900 self.currentURL = request.URL;
901 }
902 return [self.navigationDelegate webView:theWebView shouldStartLoadWithRequest:request navigationType:navigationType];
903}
904
905- (void)webViewDidFinishLoad:(UIWebView*)theWebView
906{
907 // update url, stop spinner, update back/forward
908
909 self.addressLabel.text = [self.currentURL absoluteString];
910 self.backButton.enabled = theWebView.canGoBack;
911 self.forwardButton.enabled = theWebView.canGoForward;
912
913 [self.spinner stopAnimating];
914
915 // Work around a bug where the first time a PDF is opened, all UIWebViews
916 // reload their User-Agent from NSUserDefaults.
917 // This work-around makes the following assumptions:
918 // 1. The app has only a single Cordova Webview. If not, then the app should
919 // take it upon themselves to load a PDF in the background as a part of
920 // their start-up flow.
921 // 2. That the PDF does not require any additional network requests. We change
922 // the user-agent here back to that of the CDVViewController, so requests
923 // from it must pass through its white-list. This *does* break PDFs that
924 // contain links to other remote PDF/websites.
925 // More info at https://issues.apache.org/jira/browse/CB-2225
926 BOOL isPDF = [@"true" isEqualToString :[theWebView stringByEvaluatingJavaScriptFromString:@"document.body==null"]];
927 if (isPDF) {
928 [CDVUserAgentUtil setUserAgent:_prevUserAgent lockToken:_userAgentLockToken];
929 }
930
931 [self.navigationDelegate webViewDidFinishLoad:theWebView];
932}
933
934- (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error
935{
936 // log fail message, stop spinner, update back/forward
937 NSLog(@"webView:didFailLoadWithError - %ld: %@", (long)error.code, [error localizedDescription]);
938
939 self.backButton.enabled = theWebView.canGoBack;
940 self.forwardButton.enabled = theWebView.canGoForward;
941 [self.spinner stopAnimating];
942
943 self.addressLabel.text = NSLocalizedString(@"Load Error", nil);
944
945 [self.navigationDelegate webView:theWebView didFailLoadWithError:error];
946}
947
948#pragma mark CDVScreenOrientationDelegate
949
950- (BOOL)shouldAutorotate
951{
952 if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(shouldAutorotate)]) {
953 return [self.orientationDelegate shouldAutorotate];
954 }
955 return YES;
956}
957
958- (NSUInteger)supportedInterfaceOrientations
959{
960 if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(supportedInterfaceOrientations)]) {
961 return [self.orientationDelegate supportedInterfaceOrientations];
962 }
963
964 return 1 << UIInterfaceOrientationPortrait;
965}
966
967- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
968{
969 if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(shouldAutorotateToInterfaceOrientation:)]) {
970 return [self.orientationDelegate shouldAutorotateToInterfaceOrientation:interfaceOrientation];
971 }
972
973 return YES;
974}
975
976@end
977
978@implementation CDVInAppBrowserOptions
979
980- (id)init
981{
982 if (self = [super init]) {
983 // default values
984 self.location = YES;
985 self.toolbar = YES;
986 self.closebuttoncaption = nil;
987 self.toolbarposition = kInAppBrowserToolbarBarPositionBottom;
988 self.clearcache = NO;
989 self.clearsessioncache = NO;
990
991 self.enableviewportscale = NO;
992 self.mediaplaybackrequiresuseraction = NO;
993 self.allowinlinemediaplayback = NO;
994 self.keyboarddisplayrequiresuseraction = YES;
995 self.suppressesincrementalrendering = NO;
996 self.hidden = NO;
997 self.disallowoverscroll = NO;
998 }
999
1000 return self;
1001}
1002
1003+ (CDVInAppBrowserOptions*)parseOptions:(NSString*)options
1004{
1005 CDVInAppBrowserOptions* obj = [[CDVInAppBrowserOptions alloc] init];
1006
1007 // NOTE: this parsing does not handle quotes within values
1008 NSArray* pairs = [options componentsSeparatedByString:@","];
1009
1010 // parse keys and values, set the properties
1011 for (NSString* pair in pairs) {
1012 NSArray* keyvalue = [pair componentsSeparatedByString:@"="];
1013
1014 if ([keyvalue count] == 2) {
1015 NSString* key = [[keyvalue objectAtIndex:0] lowercaseString];
1016 NSString* value = [keyvalue objectAtIndex:1];
1017 NSString* value_lc = [value lowercaseString];
1018
1019 BOOL isBoolean = [value_lc isEqualToString:@"yes"] || [value_lc isEqualToString:@"no"];
1020 NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
1021 [numberFormatter setAllowsFloats:YES];
1022 BOOL isNumber = [numberFormatter numberFromString:value_lc] != nil;
1023
1024 // set the property according to the key name
1025 if ([obj respondsToSelector:NSSelectorFromString(key)]) {
1026 if (isNumber) {
1027 [obj setValue:[numberFormatter numberFromString:value_lc] forKey:key];
1028 } else if (isBoolean) {
1029 [obj setValue:[NSNumber numberWithBool:[value_lc isEqualToString:@"yes"]] forKey:key];
1030 } else {
1031 [obj setValue:value forKey:key];
1032 }
1033 }
1034 }
1035 }
1036
1037 return obj;
1038}
1039
1040@end
1041
1042@implementation CDVInAppBrowserNavigationController : UINavigationController
1043
1044- (void) dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion {
1045 if ( self.presentedViewController) {
1046 [super dismissViewControllerAnimated:flag completion:completion];
1047 }
1048}
1049
1050- (void) viewDidLoad {
1051
1052 CGRect statusBarFrame = [self invertFrameIfNeeded:[UIApplication sharedApplication].statusBarFrame];
1053 statusBarFrame.size.height = STATUSBAR_HEIGHT;
1054 // simplified from: http://stackoverflow.com/a/25669695/219684
1055
1056 UIToolbar* bgToolbar = [[UIToolbar alloc] initWithFrame:statusBarFrame];
1057 bgToolbar.barStyle = UIBarStyleDefault;
1058 [bgToolbar setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1059 [self.view addSubview:bgToolbar];
1060
1061 [super viewDidLoad];
1062}
1063
1064- (CGRect) invertFrameIfNeeded:(CGRect)rect {
1065 // We need to invert since on iOS 7 frames are always in Portrait context
1066 if (!IsAtLeastiOSVersion(@"8.0")) {
1067 if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])) {
1068 CGFloat temp = rect.size.width;
1069 rect.size.width = rect.size.height;
1070 rect.size.height = temp;
1071 }
1072 rect.origin = CGPointZero;
1073 }
1074 return rect;
1075}
1076
1077#pragma mark CDVScreenOrientationDelegate
1078
1079- (BOOL)shouldAutorotate
1080{
1081 if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(shouldAutorotate)]) {
1082 return [self.orientationDelegate shouldAutorotate];
1083 }
1084 return YES;
1085}
1086
1087- (NSUInteger)supportedInterfaceOrientations
1088{
1089 if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(supportedInterfaceOrientations)]) {
1090 return [self.orientationDelegate supportedInterfaceOrientations];
1091 }
1092
1093 return 1 << UIInterfaceOrientationPortrait;
1094}
1095
1096- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
1097{
1098 if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(shouldAutorotateToInterfaceOrientation:)]) {
1099 return [self.orientationDelegate shouldAutorotateToInterfaceOrientation:interfaceOrientation];
1100 }
1101
1102 return YES;
1103}
1104
1105
1106@end
1107