Wed Nov 16 2022
Running Custom Native IOS Code in Ionic With Capacitor
To access native functionality with Cordova, "hybrid" applications must rely on a primarily community-driven plugin ecosystem. You can usually locate a Cordova plugin for your needs. However, for specialist needs, you may not be able to discover a plugin or find one that is no longer developed.
If you can't develop native functionality yourself, you'll reach a wall and have to adjust your application's needs or quit. Ionic, Cordova, and Capacitor shouldn't be judged by open source plugin flaws. When community plugins function, they save us time, but depending on software maintained by one individual in their own time is a formula for catastrophe. Capacitor simplifies native code integration into ionic mobile app development (or whatever you're using it with). Capacitor plugins let you add native functionality to your online app, but you'll still require platform-specific code (just as you did with Cordova).
This guide will show you how to integrate your own Objective-C/Swift iOS code into an existing Ionic/Capacitor app. To show the most recent picture in the user's photo library, we'll build a custom local Capacitor plugin and integrate it into an Ionic application.
Context
Many programmers use Ionic because it allows them to more easily create web-based applications. Doesn't it kind of undermine the point if we have to learn Objective-C/Swift/Java to add native code to our applications? In response, I would say:
The time savings alone that result from employing a hybrid strategy rather than writing native code are worth considering. One of the key advantages is that you can write an app once and have it work on whatever platform the web supports.
Most of the time, your needs may be met by using already plugins rather than writing native code. However, if there isn't, you can get around the problem by writing some native code.
My background in native iOS/Android coding is minimal. Several times ago, I worked with Java and built some native Android apps, but my only exposure to Objective-C/Swift was via the lens of utilising Ionic. It would be fascinating to see how challenging it would be for someone like myself, who has largely worked with online technology, to effectively incorporate some bespoke native functionality with a little of Googling.
Before we get started with this lesson, I want to stress that I don't believe it's a good idea to piece together solutions you don't understand from StackOverflow to create your application; doing so is nearly sure to result in a problematic and unmaintainable programme. But like I said, this isn't something you'll need to do very often. To get around a snag and go back to the web realm, I wouldn't worry too much if you had to add 50 lines of Swift or Java to your project that you didn't fully understand. Over time, you'll gain familiarity with the underlying native code.
For the Time Being, Let's
Lets Understand this lesson as it is intended for those who are familiar with Ionic at the beginner level. You can get by without knowing anything about Capacitor, but it will help to know what function it serves.
How Does a Plugin Appear?
I believe it would be beneficial to have a look at the already available Capacitor plugins to get a broad concept of how they function before we construct our own local plugin. Browse the following directory to see all of Capacitor's pre installed plugins:
ios > App > Pods > Capacitor > ios > Capacitor > Capacitor > Plugins
There is a DefaultPlugins.m file that registers all of the predefined plugins, and then there is a.swift file for each plugin that describes the plugin's functionality. View the camera interface by opening the Camera.swift file.
Confused? It would surprise me if you weren't familiar with Swift, however. Swift's syntax is not wholly alien, since it is relatively close to standard JavaScript; yet, there are several peculiarities. Fortunately, we just need to know enough to get our plugin functioning, so you don't need to grasp much of it.
The Following Are the Most Essential Elements of This File:
@objc(CAPCameraPlugin)
public class CAPCameraPlugin : CAPPlugin, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIPopoverPresentationControllerDelegate {
The @objc decorator is crucial since it is what makes this plugin visible to Capacitor. The only other truly significant element of this file to comprehend is the function, which is likewise prefixed with a @objc decorator:
@objc func getPhoto(_ call: CAPPluginCall) {
}
This is the function that will be available to our Ionic application in the web view. Other functions specified inside the class that do not utilise the @objc decorator will be used internally and will not be available to your application. A call argument is supplied into this function. This is how we send the findings to our hire ionic developers. You may alternatively use call.reject:
call.reject('User denied access to photos');
To signify that an error happened Alternatively, you may use call.resolve to return the appropriate data:
call.resolve([('someData': 'hello!')]);
The fundamental premise is that you:
- Create a function that may be called from your web view.
- Run any native code that is necessary.
- Using a call, return the information to the web view.
A simple plugin may look like this:
import Capacitor
import SomeLibrary
@objc(MyCoolPlugin)
public class MyCoolPlugin: CAPPlugin {
@objc func getLastPhotoTaken(_ call: CAPPluginCall) {
// do something native
call.resolve([
"someResult": "hello!"
])
}
}
It is worth mentioning that there is no need to develop a plugin if you just need to execute some native code and do not need to trigger it from your application or disclose the outcome to your application running in the web view. You may just add the native code to your project.
Plugin Creation
Let's install a Capacitor plugin now that we know how they function. Just add a plugin to our local project. The Capacitor CLI makes standalone plugins that you can publish to npm and install like any other plugin. This lesson will concentrate on manually adding code to the local project, and I will likely discuss constructing the plugin "correctly" in another post.
Our plugin requires a new.swift file in our iOS app's main folder (i.e. the same folder where the AppDelegate.swift, Info.plist, and config.xml files are). Registering the plugin requires a new.m Objective-C file. We need to find out how to retrieve the newest picture from the user's library using Swift before implementing that plugin.
I still don't know how. I found this code while Googling and browsing at Capacitor plugins:
func getLastPhotoTaken() {
let photos = PHPhotoLibrary.authorizationStatus()
if photos == .notDetermined {
PHPhotoLibrary.requestAuthorization({status in
if status == .authorized{
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
fetchOptions.fetchLimit = 1
let fetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
let image = self.getAssetThumbnail(asset: fetchResult.object(at: 0))
let imageData:Data = image.pngData()!
let base64String = imageData.base64EncodedString()
// base64String should now contain the photo
} else {
// failed
}
})
}
}
func getAssetThumbnail(asset: PHAsset) -> UIImage {
let manager = PHImageManager.default()
let option = PHImageRequestOptions()
var thumbnail = UIImage()
option.isSynchronous = true
manager.requestImage(for: asset, targetSize: CGSize(width: 500, height: 500), contentMode: .aspectFit, options: option, resultHandler: {(result, info)->Void in
thumbnail = result!
})
return thumbnail
}
These routines create a base64 string with the user's newest photo's image data. We need to add the code to a Capacitor plugin now that we have it.
Create App/PluginTest.swift in the same folder as AppDelegate.swift and add the following:
import Capacitor
import Photos
@objc(PluginTest)
public class PluginTest: CAPPlugin {
@objc func getLastPhotoTaken(_ call: CAPPluginCall) {
let photos = PHPhotoLibrary.authorizationStatus()
if photos == .notDetermined {
PHPhotoLibrary.requestAuthorization({status in
if status == .authorized{
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
fetchOptions.fetchLimit = 1
let fetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
let image = self.getAssetThumbnail(asset: fetchResult.object(at: 0))
let imageData:Data = image.pngData()!
let base64String = imageData.base64EncodedString()
call.resolve([
"image": base64String
])
} else {
call.reject("Not authorised to access Photos")
}
})
}
}
func getAssetThumbnail(asset: PHAsset) -> UIImage {
let manager = PHImageManager.default()
let option = PHImageRequestOptions()
var thumbnail = UIImage()
option.isSynchronous = true
manager.requestImage(for: asset, targetSize: CGSize(width: 500, height: 500), contentMode: .aspectFit, options: option, resultHandler: {(result, info)->Void in
thumbnail = result!
})
return thumbnail
}
}
We've just included the components Capacitor needs to recognise this as a plugin and provide the function/result to our web view. PluginTest.getLastPhotoTaken() will eventually return the base64 encoded picture from this method.
Just generate the.m file to register the plugin. Right-click the App folder in XCode and choose New file...
Plugin Use
Now our plugin should work! We only need to add code to our Ionic app to leverage it.
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { Plugins } from '@capacitor/core';
import { DomSanitizer } from '@angular/platform-browser';
const { PluginTest } = Plugins;
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
private lastPhoto: string = 'http://placehold.it/500x500';
constructor(public navCtrl: NavController, private domSanitizer: DomSanitizer) {
}
async getLatestPhoto(){
const result = await PluginTest.getLastPhotoTaken()
this.lastPhoto = "data:image/png;base64, " + result.image;
}
}
Our template calls our getLatestPhoto method, which uses our new PluginTest plugin like any other Capacitor plugin. This.lastPhoto will show the picture in our template (which is why we added the DomSanitizer—base64 images can't be displayed by default).
<ion-header>
<ion-navbar color="primary">
<ion-title> Custom iOS Native </ion-title>
</ion-navbar>
</ion-header>
<ion-content class="ion-padding">
<ion-button color="primary" expand="full" (click)="getLatestPhoto()"
>Get Latest Photo</ion-button
>
<img [src]="domSanitizer.bypassSecurityTrustUrl(lastPhoto)" />
</ion-content>
Final Thought
Executing custom native code was amazing, and having confidence in your abilities helped. You can create straightforward plugins like this even if you are unfamiliar with native code. Best is Java or Swift.