From 1c3a5b3d70ede5853f1cae35a6159d48192685cb Mon Sep 17 00:00:00 2001 From: Todd Stellanova Date: Fri, 11 Nov 2011 14:20:52 -0800 Subject: [PATCH 1/2] refractor PhoneGapDelegate to allow teardown and reinit of web view --- PhoneGapLib/Classes/PhoneGapDelegate.h | 79 +++++- PhoneGapLib/Classes/PhoneGapDelegate.m | 247 +++++++++++-------- PhoneGapLib/Classes/PhoneGapViewController.m | 15 ++ 3 files changed, 235 insertions(+), 106 deletions(-) diff --git a/PhoneGapLib/Classes/PhoneGapDelegate.h b/PhoneGapLib/Classes/PhoneGapDelegate.h index eb1e837..9a26f6b 100644 --- a/PhoneGapLib/Classes/PhoneGapDelegate.h +++ b/PhoneGapLib/Classes/PhoneGapDelegate.h @@ -30,29 +30,100 @@ @property (nonatomic, readonly, retain) NSDictionary *settings; @property (nonatomic, readonly, retain) PGWhitelist* whitelist; // readonly for public + +#pragma mark - App settings + + (NSDictionary*)getBundlePlist:(NSString *)plistName; + (NSString*) wwwFolderName; + (NSString*) pathForResource:(NSString*)resourcepath; + (NSString*) phoneGapVersion; + (NSString*) applicationDocumentsDirectory; + +/** + @return URL string for the first page that should be loaded in the web view: defaults to www/index.html + */ + (NSString*) startPage; +/** + @return NSString The first URL scheme supported by this app, if any registered with CFBundleURLSchemes in the app .plist + */ +- (NSString*) appURLScheme; + +/** + @return NSDictionary A set of device and app properties + */ +- (NSDictionary*) deviceProperties; + + +#pragma mark - Command Queue + - (int)executeQueuedCommands; - (void)flushCommandQueue; +- (BOOL) execute:(InvokedUrlCommand*)command; + +#pragma mark - Plugin Management + +/** + + */ +- (void)reinitializePlugins; +/** + Get an instance of the named plugin. This method creates a new instance if + one does not already exist. If there is an existing instance, this method returns it. + Thus, plugins are essentially singletons. + + @param pluginName Class name for plugin, eg "com.salesforce.com.foo" + @return Singleton instance of the named plugin + */ - (id) getCommandInstance:(NSString*)pluginName; -- (void) javascriptAlert:(NSString*)text; -- (BOOL) execute:(InvokedUrlCommand*)command; -- (NSString*) appURLScheme; -- (NSDictionary*) deviceProperties; + +#pragma mark - Embedded UIWebView management + +/** +Tear down the existing web view. + @see reinitializeWebView + */ +- (void)teardownWebView; + +/** + (re)Initialize the embedded web view. + @see teardownWebView + */ +- (void)reinitializeWebView; + + +/** + Configure the web view from self.settings. + @param aWebView The web view to configure; + */ +- (void)configureWebViewFromSettings:(UIWebView*)aWebView; + + +#pragma mark - Application state transitions - (void)applicationDidEnterBackground:(UIApplication *)application; - (void)applicationWillEnterForeground:(UIApplication *)application; - (void)applicationWillResignActive:(UIApplication *)application; - (void)applicationWillTerminate:(UIApplication *)application; + +#pragma mark - Public + + +/** + Force a javascript alert to be shown in the embedded UIWebView + @param text Message to be shown in the web view in a javascript alert. + */ +- (void) javascriptAlert:(NSString*)text; + + + + @end + + @interface NSDictionary (LowercaseKeys) - (NSDictionary*) dictionaryWithLowercaseKeys; diff --git a/PhoneGapLib/Classes/PhoneGapDelegate.m b/PhoneGapLib/Classes/PhoneGapDelegate.m index 58c3f16..e7f56f7 100644 --- a/PhoneGapLib/Classes/PhoneGapDelegate.m +++ b/PhoneGapLib/Classes/PhoneGapDelegate.m @@ -26,6 +26,10 @@ #define degreesToRadian(x) (M_PI * (x) / 180.0) + +NSString * const kAppPlistName = @"PhoneGap"; +NSString * const kAppPlist_PluginsKey = @"Plugins"; + // class extension @interface PhoneGapDelegate () @@ -353,57 +357,143 @@ - (void) receivedOrientationChange } } + + /** * This is main kick off after the app inits, the views and Settings are setup here. */ -// - (void)applicationDidFinishLaunching:(UIApplication *)application - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions -{ - // read from UISupportedInterfaceOrientations (or UISupportedInterfaceOrientations~iPad, if its iPad) from -Info.plist - NSArray* supportedOrientations = [self parseInterfaceOrientations: - [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UISupportedInterfaceOrientations"]]; +{ + NSDictionary *settingsDict = [[self class] getBundlePlist:kAppPlistName]; + self.settings = settingsDict; + + [self reinitializePlugins]; - // read from PhoneGap.plist in the app bundle - NSString* appPlistName = @"PhoneGap"; - NSDictionary* phonegapPlist = [[self class] getBundlePlist:appPlistName]; - if (phonegapPlist == nil) { - NSLog(@"WARNING: %@.plist is missing.", appPlistName); - return NO; + // set the external hosts whitelist + PGWhitelist *hostWhitelist = [[PGWhitelist alloc] initWithArray:[settingsDict objectForKey:@"ExternalHosts"]]; + self.whitelist = hostWhitelist; + [hostWhitelist release]; + + [self reinitializeWebView]; + + + NSString *startPage = [[self class] startPage]; //TODO make this more configurable, and/or instance method? + NSURL *appURL = [NSURL URLWithString:startPage]; + NSString *loadErr = nil; + + if (nil == [appURL scheme]) { + NSString* startFilePath = [[self class] pathForResource:startPage]; + if (nil == startFilePath) { + loadErr = [NSString stringWithFormat:@"ERROR: Start Page at '%@/%@' was not found.", [[self class] wwwFolderName], startPage]; + NSLog(@"%@", loadErr); + appURL = nil; + } + else { + appURL = [NSURL fileURLWithPath:startFilePath]; + } } - self.settings = [[[NSDictionary alloc] initWithDictionary:phonegapPlist] autorelease]; + + if (nil == loadErr) { + NSLog(@"loading appURL: %@",appURL); + //TODO make timeoutInterval and cachePolicy configurable? + NSURLRequest *appReq = [NSURLRequest requestWithURL:appURL + cachePolicy:NSURLRequestUseProtocolCachePolicy + timeoutInterval:20.0]; + [self.webView loadRequest:appReq]; + } else { + NSString* html = [NSString stringWithFormat:@" %@ ", loadErr]; + [self.webView loadHTMLString:html baseURL:nil]; + self.loadFromString = YES; + } + + [self.window makeKeyAndVisible]; + if (self.loadFromString) { + self.imageView.hidden = YES; + } + + return YES; + +} + +#pragma mark - Plugins management + +- (void)reinitializePlugins +{ + NSLog(@"reinitializePlugins"); // read from Plugins dict in PhoneGap.plist in the app bundle - NSString* pluginsKey = @"Plugins"; - NSDictionary* pluginsDict = [self.settings objectForKey:@"Plugins"]; + NSDictionary* pluginsDict = [self.settings objectForKey:kAppPlist_PluginsKey]; if (pluginsDict == nil) { - NSLog(@"WARNING: %@ key in %@.plist is missing! PhoneGap will not work, you need to have this key.", pluginsKey, appPlistName); - return NO; + NSLog(@"WARNING: %@ key in %@.plist is missing! PhoneGap will not work, you need to have this key.", kAppPlist_PluginsKey, kAppPlistName); + NSAssert(nil != pluginsDict,@"Plugins key required in plist"); } - // set the whitelist - self.whitelist = [[[PGWhitelist alloc] initWithArray:[self.settings objectForKey:@"ExternalHosts"]] autorelease]; - + //reinit the plugins class map self.pluginsMap = [pluginsDict dictionaryWithLowercaseKeys]; + //reinit the plugins instance map + self.pluginObjects = [[[NSMutableDictionary alloc] initWithCapacity:4] autorelease]; - self.viewController = [[[PhoneGapViewController alloc] init] autorelease]; + //setup the Location plugin + //Fire up the GPS Service right away as it takes a moment for data to come back. + BOOL enableLocation = [[self.settings objectForKey:@"EnableLocation"] boolValue]; + if (enableLocation) { + [[self getCommandInstance:@"com.phonegap.geolocation"] startLocation:nil withDict:nil]; + } + +} + +#pragma mark - Embedded UIWebView management + + +- (void)teardownWebView { + NSLog(@"teardownWebView"); + [self.webView setDelegate:nil]; + + self.viewController.webView = nil; + self.viewController = nil; + self.webView = nil; + [self.window setRootViewController:nil]; +} + +- (void)configureWebViewFromSettings:(UIWebView*)aWebView +{ + NSLog(@"configureWebViewFromSettings"); + + //configure the web view + BOOL enableViewportScale = [[self.settings objectForKey:@"EnableViewportScale"] boolValue]; + BOOL allowInlineMediaPlayback = [[self.settings objectForKey:@"AllowInlineMediaPlayback"] boolValue]; + BOOL mediaPlaybackRequiresUserAction = [[self.settings objectForKey:@"MediaPlaybackRequiresUserAction"] boolValue]; - NSNumber *enableLocation = [self.settings objectForKey:@"EnableLocation"]; - NSString *enableViewportScale = [self.settings objectForKey:@"EnableViewportScale"]; - NSNumber *allowInlineMediaPlayback = [self.settings objectForKey:@"AllowInlineMediaPlayback"]; - NSNumber *mediaPlaybackRequiresUserAction = [self.settings objectForKey:@"MediaPlaybackRequiresUserAction"]; + + aWebView.scalesPageToFit = enableViewportScale; - // Set the supported orientations for rotation. If number of items in the array is > 1, autorotate is supported - viewController.supportedOrientations = supportedOrientations; + if (allowInlineMediaPlayback && [aWebView respondsToSelector:@selector(allowsInlineMediaPlayback)]) { + aWebView.allowsInlineMediaPlayback = YES; + } + if (mediaPlaybackRequiresUserAction && [aWebView respondsToSelector:@selector(mediaPlaybackRequiresUserAction)]) { + aWebView.mediaPlaybackRequiresUserAction = YES; + } + +} + +- (void)reinitializeWebView +{ + + NSLog(@"reinitializeWebView"); + //check whether the current orientation is supported: if it is, keep it, rather than forcing a rotation + NSArray* supportedOrientations = [self parseInterfaceOrientations: + [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UISupportedInterfaceOrientations"]]; + BOOL forceStartupRotation = YES; UIDeviceOrientation curDevOrientation = [[UIDevice currentDevice] orientation]; - + if (UIDeviceOrientationUnknown == curDevOrientation) { //UIDevice isn't firing orientation notifications yet...go look at status bar - curDevOrientation = [[UIApplication sharedApplication] statusBarOrientation]; + curDevOrientation = (UIDeviceOrientation)[[UIApplication sharedApplication] statusBarOrientation]; } - + if (UIDeviceOrientationIsValidInterfaceOrientation(curDevOrientation)) { for (NSNumber *orient in supportedOrientations) { if ([orient intValue] == curDevOrientation) { @@ -421,86 +511,36 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( [[UIApplication sharedApplication] setStatusBarOrientation:newOrient]; } - CGRect screenBounds = [ [ UIScreen mainScreen ] bounds ]; - self.window = [ [ [ UIWindow alloc ] initWithFrame:screenBounds ] autorelease ]; - - - self.window.autoresizesSubviews = YES; - CGRect webViewBounds = [ [ UIScreen mainScreen ] applicationFrame ] ; - webViewBounds.origin = screenBounds.origin; - if (!self.webView) { - self.webView = [[ [ UIWebView alloc ] initWithFrame:webViewBounds] autorelease]; - } - self.webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); - self.webView.scalesPageToFit = [enableViewportScale boolValue]; - - viewController.webView = self.webView; - [self.viewController.view addSubview:self.webView]; - - /* - * Fire up the GPS Service right away as it takes a moment for data to come back. - */ - if ([allowInlineMediaPlayback boolValue] && [self.webView respondsToSelector:@selector(allowsInlineMediaPlayback)]) { - self.webView.allowsInlineMediaPlayback = YES; - } - if ([mediaPlaybackRequiresUserAction boolValue] && [self.webView respondsToSelector:@selector(mediaPlaybackRequiresUserAction)]) { - self.webView.mediaPlaybackRequiresUserAction = YES; - } - - /* - * This is for iOS 4.x, where you can allow inline