Adding an Apple Watch App or Widget to Cordova

Intro

If you want to add native code to Cordova you’re pretty much on your own because the framework won’t help you with it.
Trying to add an iOS 14 widget and an Apple Watch app cost me 2 weeks of digging around in Cordovas source code and figuring out the Xcode project file. This article hopes to save you those 2 weeks.

The method

So obviously the first thing you would try when doing anything that has to do with a native feature in Cordova is you wold look for a plugin that does it for you. Unfortunately the few plugins that exist don’t work anymore. So we need to get our hands dirty ourselves.

The biggest pain point when integrating native features in Cordova is that the Xcode project file is created on every build overwriting any change you did manually.

The birds eye view of what I did is:
1. Build the Cordova app to generate the Xcode config
2. Add the iOS 14 widget and the Apple Watch target
3. See what parts of the Xcode config step 2 changed
4. Write a script to make those changes on every build

Build the Cordova app to generate the Xcode config

This part is very simple. Just build your app the way you always do. After you build the app, Cordova will have regenerated the Xcode project file at platforms/ios/yourapp.xcodeproj/project.pbxproj. Store this file at a different location or check it in to git, you will compare it with a later version.

Add the iOS 14 widget and the Apple Watch target

Now add your iOS 14 or Apple Watch target. If you have both I would recommend doing them one by one.
Since there are plenty of tutorials on this part I won’t go into detail here.

See what parts of the Xcode config step 2 changed

Now that you have two Xcode config files we need to compare the contents. Since the files are sort of json (they aren’t real json and can’t be parsed as such) you could just compare them with a graphical diffing tool such as Xcode -> Open Developer Tool > FileMerge. It is helpful to have an understanding of how the Xcode config file works.
The way I found very helpful though was to convert both Xcode config files into json and then using an online tool to compare them. Apple provides a tool to convert legacy plist (which is what the Xcode config is) into json.
– convert with plutil -convert json project.pbxproj
– diff the json with http://jsondiff.com/

Write a script to make those changes on every build

Birds eye view:
– Cordova build
– Runs add watch / widget hook
– Hook converts Xcode config to json
– Hook adds stuf to the Xcode config as json
– Hook converts the Xcode config back to the legacy plist format so Cordova is happy

Now this is the hard part. You might want to break it into steps and keep comparing the result of your script with the Xcode config you got after adding the Watch app or iOS 14 widget.

Because there are no good parsers for the Xcode config (except the one of CocaPods maybe but that’s Ruby and I don’t know Ruby yet) I opted to just convert the Xcode config in a Cordova hook to json and edit it.

Since the Xcode config is just a legacy plist file Xcode will recognize it if it is converted to json. So we just leave it as json right? Well I wish it was that easy but there is also Cordova and that has it’s own Xcode config parser that is actually quite bad cordova-node-xcode. It must have the legacy plist format and to make matters even worth it also needs the comments preserved.

What I ended up doing was converting the json back to the legacy plist format that Cordova know how to handle. Since there is no code for that already I wrote it.

Converting json Xcode config to legacy plist format

The Xcode config in legacy plist format looks like this

// !$*UTF8*$!
{
    archiveVersion = 1;
    classes = {
    };
    objectVersion = 46;
    objects = {

    // add sections here

    };
    rootObject = "...projectid..." /* Project object */;
}

The objects object holds Xcode config sections. Each object has a property named isa. For Cordova to recognize this file you need to loop through all the objects in your json and sort them by the isa. Then wrap each ISA section with the following comments.

/* Begin PBXBuildFile section */

// ...

/* End PBXBuildFile section */

Where PBXBuildFile in this example is just on ISA but you would have to do this for all the ISAs in your Xcode config.

If you’re curios why on earth we’d need to retain the comments https://github.com/apache/cordova-node-xcode/blob/master/lib/parser/pbxproj.js#L235. This is unfortunately the way the “parser” for Cordova is build.

Also see http://www.monobjc.net/xcode-project-file-format.html for information on the Xcode config file format.

Adding the script as Cordova hook

The script you just wrote needs to be run by Cordova on each build process.

In your config.xml add

    <hook src="hooks/convert.py" type="before_run" />
    <hook src="hooks/convert.py" type="before_build" />

Cordova bugs

There are plenty of bugs in Cordova making our lives harder here that we need to adress.

Cordovas ProjectFile.js has code that overwirtes the bundle id of every Xcode build target to the one defined in config.xml. This is bad because the widget and watch targets need differing bundle ids.
Some upcoming version of Cordova might include a fix but for now an easy solution is to:
– Fix the file on the fly with our hook. See https://github.com/apache/cordova-ios/issues/764#issuecomment-669317730 for details on how the file needs to be patched.

  • If you are using the iOS 14 widget or if your Apple Watch app is written in Swift you also need to patch a line in Cordovas config that breaks Swift code.
    Comment out the following line
    SWIFT_OBJC_BRIDGING_HEADER = $(PROJECT_DIR)/$(PROJECT_NAME)/Bridging-Header.h in build.xcconfig. This has to do with https://issues.apache.org/jira/browse/CB-10072.
    Note though that if you have a Cordova plugin that is written in swift it might break by changing that line and you’d need a different solution. Since support for most Cordova plugins was dropped long ago and are written in ObjC it wasn’t an issue for me.

There is one more bug worth mentioning although I didn’t find a way to do anything about it https://github.com/apache/cordova-ios/issues/1049 it basically prevents building the app without a real smartphone attached if you have the watch target.

And certainly not the last Cordova bug only the last I ran into. You can’t set a version lower than iOS 14 because for Cordova you need to set every target to the same minal version and the widget (at least if you wrote it with SwiftUI) requies iOS 14.

Resources

Leave a Reply

Your email address will not be published.