Upgrading the PhoneGap Library Used by the iOS Hybrid Web Container

Sybase Unwired Platform comes with PhoneGap 1.4.1 libraries linked in; to upgrade to a later version of the PhoneGap library used by the Hybrid Web Container, perfom these steps.

The PhoneGap library that is included with the Hybrid Web Container uses source code that has been modified slightly from the source available from PhoneGap, mainly because the original source does not support some Hybrid Web Container user interface.

Beginning with PhoneGap 1.5.0, PhoneGap rebranded the name PhoneGap to Cordova, which means that when you upgrade, changes to internal class names must be updated.

In addition to the name change, version 1.5.0 ushered in a reorganization of core classes in the PhoneGap implementation. Because of the coupling between the Hybrid Web Container UI code and PhoneGap classes, upgrading requires a careful replacement of the existing PhoneGap integration.

Note: This document describes the process of upgrading the iOS Hybrid Web Container from PhoneGap 1.4.1 to Cordova 1.5.0. Because of its rapid release cycle, Cordova remains a somewhat volatile platform. These instructions are up to date at the time of this writing, but no guarantee is made about how the Cordova implementation may change in the future.
  1. Go to http://www.phonegap.com and download the Cordova package you are upgrading to.
  2. Install the Cordova .dmg file.
    This typically places a set of Cordova files under ~/Documents/CordovaLib.
  3. In Xcode, open the CordovaLib.xcodeproj.
  4. In the Xcode Project Navigator, find and open the CDVViewController.h file, which is in the Classes/Cleaver group folder.
    1. In the interface declaration, remove UIWebViewDelegate from the list of implemented protocols.
    2. Add a UIViewController property declaration:
      @property (nonatomic, retain) UIViewController* viewController;
    3. Find and remove the declaration of the createGapView function:
      #if 0
      -(void) createGapView;
      #endif
    4. Add these function declarations:
      -(void) reset;
      -(void)setTheWebView: (UIWebView*) theWebView;
      -(void)setTheViewController: (UIViewController*) theViewController;
  5. In Xcode Project Navigator, find and open the CDVViewController.m file, which is next to the CDVViewController.h file.
    1. Synthesize the viewController property:
      @synthesize viewController;
    2. In order to avoid memory leak issues, you must move a portion of the viewDidLoad function, around line 94, to the init function. First, remove the following code from viewDidLoad:
      #if 0        
      // read from Cordova.plist in the app bundle    
      NSString* appPlistName = @"Cordova";    
      NSDictionary* cordovaPlist = [[self class] getBundlePlist:appPlistName];    
      if (cordovaPlist == nil) {        
      NSLog(@"WARNING: %@.plist is missing.", appPlistName);  
      return;    
      }    
      self.settings = [[[NSDictionary alloc] initWithDictionary:cordovaPlist] autorelease];     
      
      // read from Plugins dict in Cordova.plist in the app bundle    
      NSString* pluginsKey = @"Plugins";    
      NSDictionary* pluginsDict = [self.settings objectForKey:@"Plugins"];    
      if (pluginsDict == nil) {         
      NSLog(@"WARNING: %@ key in %@.plist is missing! Cordova will not  work, you need to have this key.", pluginsKey, appPlistName);        
      return;    
      }     
      
      // set the whitelist    
      self.whitelist = [[[CDVWhitelist alloc] initWithArray:[self.settings objectForKey:@"ExternalHosts"]] autorelease];     
      
      self.pluginsMap = [pluginsDict dictionaryWithLowercaseKeys];
      #endif
      If you do not move this portion of the viewDidLoad function, it is called automatically by the OS every time a workflow application is opened. This function contains code that initializes some Cordova components that need to be initialized only once.
    3. Move the code you just removed into the init function inside the "if (self != nil)" block, at the very end of this block.
      Since the init function does not return void, change the two return statements in the code you just moved so they read "return nil;", to avoid compiler warnings.
    4. There are portions of the viewDidLoad function that do some UI initialization that is unnecessary, and which clashes with UI behavior of the Hybrid Web Container. Around line 118, remove this code:
      #if 0
      	NSString* startFilePath = [self pathForResource:self.startPage];
      	NSURL* appURL  = nil;
          NSString* loadErr = nil;
          
          if (startFilePath == nil) {
              loadErr = [NSString stringWithFormat:@"ERROR: Start Page at '%@/%@' was not found.", self.wwwFolderName, self.startPage];
              NSLog(@"%@", loadErr);
              self.loadFromString = YES;
              appURL = nil;
          } else {
              appURL = [NSURL fileURLWithPath:startFilePath];
          }
      
          [ self createGapView];
      #endif
      In the same function, around line 181, remove this code:
      #if 0
          if (!loadErr) {
              NSURLRequest *appReq = [NSURLRequest requestWithURL:appURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0];
              [self.webView loadRequest:appReq];
          } else {
              NSString* html = [NSString stringWithFormat:@"<html><body> %@ </body></html>", loadErr];
              [self.webView loadHTMLString:html baseURL:nil];
          }
      #endif
    5. Remove the implementation of the createGapView function:
      #if 0
      - (void) createGapView
      {
          CGRect webViewBounds = self.view.bounds;
          webViewBounds.origin = self.view.bounds.origin;
      	
          if (!self.webView) 
      	{
              self.webView = [[ [ CDVCordovaView alloc ] initWithFrame:webViewBounds] autorelease];
      		self.webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
      		
      		[self.view addSubview:self.webView];
      		[self.view sendSubviewToBack:self.webView];
      		
      		self.webView.delegate = self;
          }
      }
      #endif
    6. Since you added a UIViewController property to the CDVViewController class, you must ensure that view controller gets used at the appropriate time. In the getCommandInstance function, there is code that creates an instance of a CDVPlugin object. Find the if block that attempts to set the view controller of this object, and change it to use self.viewController instead of self, for example:
      if ([obj isKindOfClass:[CDVPlugin class]] && [obj respondsToSelector:@selector(setViewController:)]) { 
      [obj setViewController:self.viewController];
      }
    7. Add these implementations for the reset function:
      -(void) reset
      {   
      [self onAppWillTerminate:nil];   
      self.pluginObjects = nil;   
      self.webView = nil;   
      self.commandDelegate = nil;   
      self.view = nil;
      }
      -(void) setTheWebView: (UIWebView*) theWebView {
      self.webView = theWebView;
      }
      -(void) setTheViewController: (UIViewController *)theViewController {
      self.viewController = (CDVViewController*)theViewController;
      }
      This ensures that the plug-in objects that are saved by Cordova for later use are destroyed when the Workflow application is closed. The plug-in objects each contain a reference to the WebView, and this causes problems if they are retained after closing a Workflow application and then opening a new one.
    8. In the dealloc function, add the following line, just before [super dealloc];
      self.whitelist = nil;
  6. In the Xcode Project Navigator, find and open the CDVPlugin.m file, which is in the Classes/Commands group folder, and add this code at the very top of the dealloc function:
    if (self.viewController != nil)
    {
       self.viewController = nil;
    }
    
  7. In the Xcode Project Navigator, find and open the CDVConnection.m file, which is located in the Classes/Commands group folder, and in the dealloc function, locate the line that calls the removeObserver function:
    [[NSNotificationCenter defaultCenter] removeObserver:self 
    		name:kReachabilityChangedNotification object:nil];
    
    Change the line to:
    [[NSNotificationCenter defaultCenter] removeObserver:self];

    This causes the connection plug-in object to remove itself as an observer for all of these events when a Workflow application is closed, rather than only for specified events. This is necessary because during initialization, Cordova creates a CDVConnection object, then adds this object as an observer to NSNotificationCenter. It adds itself as the callback delegate for online/offline connection events, as well as for background/foreground processing notifications. The Hybrid Web Container implementation of Cordova is somewhat nonstandard, in that it expects Cordova to initialize and de-initialize when a Workflow application is opened and closed. If the observers are left, they remain even after the Workflow application is closed, and may cause memory issues.

  8. As of PhoneGap version 2.1.0, supporf for the Contacts API does not yet exist for iOS 6 devices. To add this support:
    1. In Xcode, in the Project Navigator, find and open the CDVContacts.h file in the Classes/Commands group folder.
      Add this protocol definition BEFORE the CDVContacts interface declaration:
      @protocol MissingFeaturesProvider <NSObject>
      - (void) requestContactsAccess;
      @end
      Add this function declaration inside the CDVContacts interface declaration:
      + (void) setContactsAccessDelegate: (id<MissingFeaturesProvider>)accessProvider;
    2. In Xcode, in the Project Navigator, find and open the CDVContacts.m file in the Classes/Commands group folder.
      At the very TOP of the CDVContacts implementation block, just after @implementation CDVContacts, add:
      static id<MissingFeaturesProvider> s_contactsAccessDelegate = nil;
      In the initWithView: function, before return self, add
      if (s_contactAccessDelegate != nil)
      {
      [s_contactsAccessDelegate requestContactsAccess];
      }
      Somewhere within the CDVContacts implementation block, add this function definition:
      + (void) setContactsAccessDelegate:(id<MissingFeaturesProvider>)accessProvider
      {
      s_contactsAccessDelegate = accessProvider;
      }
      There is one final modification necessary to prevent a crash due to freeing unallocated memory. In the save:withDict: function, you will see:
      [aContact release];
      CFRelease(addrBook);
      Modify the code so that it checks whether addrBook is nil before trying to release it, like this:
      [aContact release];
      if (addrBook != nil)
      {
      CFRelease(addrBook);
      }
  9. Build all configurations of the CordovaLib target (Debug-iphoneos, Debug-iphonesimulator, Release-iphoneos, and Release-iphonesimulator), which produces files named libCordova.a.
  10. Copy the libCordova.a file for each configuration to the corresponding libs folder in WorkFlow/libs/<configuration>.
    These folders already have libMo.a, existing PhoneGap libraries, and other Sybase Unwired Platform libraries in them. Delete the existing libPhoneGap.a for each configuration.
  11. You must now include the PhoneGap javascript file, which is under the javascripts folder where PhoneGap was installed, in any workflow application that is built using the new Hybrid Web Container with the new PhoneGap library.
    Starting with PhoneGap 1.5.0, this file is called cordova-<version>.js.
    1. For each workflow package that is to be generated, copy this file to the js folder in the Generated Workflow folder of the Eclipse WorkSpace where the Sybase Mobile SDK is installed.
    2. Remove any old instances of this file, and regenerate the workflow package.