One of the biggest issues and limitations faced by Ionic developers, and other “hybrid” application developers, is the reliance upon third-party solutions for accessing native functionality.
Tools like Capacitor (as well as Cordova) provide us with a way to access Native APIs and run native code from within the browser environment that most of the mobile application is contained within. Typically, we are able to do the vast majority of our work using web code - however, if you do not know how to write and run some native code yourself, you could find yourself getting stuck and relying on solutions created by other people.
In this tutorial, we are first going to highlight how relying on a third-party for native functionality can cause issues and roadblocks. We will then walk through how we can, reasonably simply, write and run our own Native iOS code from an Ionic application using Capacitor. Although understanding Native iOS code (Swift) will obviously be a big benefit here, Swift is similar enough to JavaScript/TypeScript such that with a few searches and some sample code to reference, you will likely be able to build out your own simple solutions even if you have no Swift knowledge.
Before We Get Started
This will be an advanced tutorial and I will be assuming that you already have a basic understanding of using Capacitor, and a reasonably solid understanding of whatever you are using to build your Ionic applications. This particular example will be using StencilJS with Ionic, but if you are using Angular, React, Vue or something else that will be of little consequence.
The Problem with Core APIs
The issue with relying on the core set of Native APIs maintained by Capacitor/Cordova is that they are limited in scope, and you would typically just find “core” functionality like:
- Camera
- Splash Screen
- Storage
- Push Notifications
- Keyboard
- Filesystem
- Geolocation
This is to be expected, because the amount of “native stuff” you might want to do is almost limitless, and it’s just not feasible for a project to provide (and maintain) a way to access everything you might ever want to use.
The Problem with Community Plugins
If we find ourselves wanting to use something that isn’t covered by one of the Core API’s, we can then turn to community published/maintained plugins. These are plugins that the general community (not the Ionic/Capacitor team) have built and shared for others to use. You will generally find that the community creates a much broader range of plugins to access native functionality.
The issue with community plugins is that these are most often built and maintained by people volunteering their time to help others. There are no guarantees that the plugin you find will work or that if there are any issues or updates required that the person who created it will be willing or able to fix it.
There is also no guarantee that you will even find a community plugin that offers the functionality you are looking for.
The Solution
A lot of the time, probably in the majority of cases for most people, you can build what you need using one of the two options above. But, if you find yourself in a position where neither of those options are working, your application development could come to a quick halt.
If you really want to take charge of your application, and not have to worry about relying on others to provide the solution you need, it is important to understand how to write and run your own native code through Capacitor. The general concept is actually quite simple - although the implementation itself does require a bit of work.
Capacitor acts as a messenger between your web code and the native platform (iOS in this case). If we want to execute some native code, then we just add that native code to the native project that Capacitor creates for us, and then we make a call to that native code from our web code through Capacitor. In general, a method to execute some native code in a Capacitor plugin would look something like this:
SomePlugin.swift
@objc func myMethod(_ call: CAPPluginCall){
// get anything passed in from the web code from Capacitor's "call"
// do whatever native stuff you need
// pass data back to the web code through Capacitor's "call"
}
No matter what you are building, this same basic concept will usually apply:
- Pass in data to the native code through Capacitor
- Execute the native code
- Pass data back to the web code through Capacitor
We are going to walk through building out a solution using this concept. What we will be building is a “simple” way to add some native code to your project that you can run. This will not be a “proper” plugin in the sense that it could be easily installed into another project, or published as your own community plugin, this is just a quick way to write and access native code in an individual project.
If you would like a little more context as to how Capacitor “plugins” work before we begin, I would recommend watching this video: Exploring the Source Code: Understanding Capacitor’s Storage API