PhotoEditor SDK + React Native


5 min read
PhotoEditor SDK + React Native

Often our users ask whether it’s possible to use the PhotoEditor SDK for iOS and Android with React Native (the good news right away: Yes, it is possible and fairly easy as well). So, we set out to create a demo app and put together a guide on how to easily set up the PhotoEditor SDK with React Native and how to avoid eventual pitfalls.

Setup

We started our demo project by creating a new React Native project using react-native init PESDKDemo. We then added react-native-navigation, a navigation library from the awesome folks at Wix, to get a base for our demo app. However, react-native-navigation is not required for embedding the PESDK into your React Native application.

Launching the PhotoEditor SDK from React Native

To successfully launch our editor from React Native we needed to do three things:

  1. Add the PESDK library to our iOS project.
  2. Create a native module that bridges between React Native and the PhotoEditor SDK.
  3. Add a method to create a ToolbarController, push a PhotoEditController and present them from the current view controller.
  4. Call the method, wherever we want to edit an image in our React Native code

The first step was rather easy. We just followed the steps for a manual installation and had the PESDK library ready in minutes.

Creating a native module in React Native was fairly easy as well. We simply created PESDKModule.h and PESDKModule.m and defined a PESDKModule class that inherits from NSObject and implements the RCTBridgeModule protocol. In the classes implementation we registered our module with React Native by calling RCT_EXPORT_MODULE(PESDK):

#import <React/RCTBridgeModule.h>
@interface PESDKModule : NSObject <RCTBridgeModule>
@end
// PESDKModule.m
@implementation PESDKModule
RCT_EXPORT_MODULE(PESDK);
view raw PESDKModule.h hosted with ❤ by GitHub

In order to create a new photo editor view controller we needed to create a new ToolbarController and push a PhotoEditController that loads a sample image. As we'll always use such a hierarchy, we bundled this setup into the present() method of our native module and exported it to React Native using the RCT_EXPORT_METHOD macro:

RCT_EXPORT_METHOD(present) {
IMGLYToolbarController *toolbarController = [IMGLYToolbarController new];
IMGLYPhotoEditViewController *photoEditViewController = [[IMGLYPhotoEditViewController alloc] initWithImage:[UIImage named:"test"]];
UIViewController *currentViewController = RCTPresentedViewController();
dispatch_async(dispatch_get_main_queue(), ^{
[toolbarController pushViewController:photoEditViewController animated:NO completion:NULL];
[currentViewController presentViewController:toolbarController animated:YES completion:NULL];
});
}
view raw PESDKModule.m hosted with ❤ by GitHub

The final step was putting our newly created module to good use and actually pushing our photo editor from React Native. To do so, we imported the PESDK native module using var PESDK = NativeModules.PESDK. We could then call PESDK.present() and the native module created the ToolbarController and PhotoEditViewController objects and pushed them from the current view controller. If you were to use a simple TouchableHighlight, this could look like this:

const PESDK = NativeModules.PESDK
// ...
render () {
return (
<View style={{flex: 1, padding: 20, backgroundColor: '#e6e6e6'}}>
<TouchableHighlight onPress={this.onOpenEditorPress.bind(this)}>
<Text style={styles.button}>Push PESDK Editor</Text>
</TouchableOpacity>
</View>
)
}
onOpenEditorPress () {
PESDK.present()
}
view raw StartScreen.js hosted with ❤ by GitHub

Bringing it all together, we were now able to open the native iOS PESDK PhotoEditController from our React Native code. Surprisingly easy, isn't it?

Our initial result: The button pushes our SDK with a sample image.

Passing an image to the editor

Opening an image editor with a sample image is nice but wasn’t really our intention, when we started integrating the PhotoEditor SDK. Instead, we wanted to be able to edit any image and save the result into our camera roll or share it with our friends. For our little demo, we decided to present a grid of images from unsplash.com to the user, so he can choose any image from the list and edit it with our editor.

The JavaScript for creating the image grid is a little out of scope for this article, so we won’t cover it in detail. It basically uses a ListView to create rows of three cells and fills them with images loaded from the unsplash.it service. All image fetching, scrolling, etc. is handled by React Native, so we only needed to handle the user's taps on an image:

_onItemPressed (item) {
const imageUrl = 'https://unsplash.it/2048/2048?image=' + item.index
const imagePath = RNFS.DocumentDirectoryPath + '/image.jpeg'
this.setState({ downloading: true })
RNFS.downloadFile({ fromUrl: imageUrl, toFile: imagePath }).promise.then(result => {
PESDK.present(imagePath)
this.setState({ output: 'Select an image to edit...' })
})
}
view raw StartScreen.js hosted with ❤ by GitHub

We used react-native-fs to download a larger resolution image to the local filesystem, pass the path of the local file to our present() call and modify our iOS native module:

RCT_EXPORT_METHOD(present:(NSString *)path) {
...
[[IMGLYPhotoEditViewController alloc] initWithData:[NSData dataWithContentsOfFile:path]];
...
}
view raw PESDKModule.h hosted with ❤ by GitHub

We then had a nice little app, that shows a grid of images, loads a high-resolution image upon selection and opens the PhotoEditor SDK:

The iOS demo app running on a device.

Android implementation

As we have seen, opening the PESDK from React Native can easily be done on iOS. But why use React Native, if the app only runs on iOS? It was time to add Android support to our little demo app. To accomplish this we needed to repeat some of the previous steps for Android:

  1. Add the PESDK to our Android project.
  2. Create a native module that bridges between React Native and the PESDK.
  3. Add a method to launch an ImglyIntent using the PhotoEditorBuilder from the current Activity.

Installing the SDK is again done by following the instructions for integrating the PESDK and shouldn’t take more than a few minutes. Creating a native module on Android is quite similar to iOS, although a little more setup code is required: We created our PESDKModule that recreates the present(path)method from iOS, a PESDKPackage containing our module and finally added the package to our Application:

public class PESDKModule extends ReactContextBaseJavaModule {
public PESDKModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "PESDK";
}
@ReactMethod
public void present(@NonNull String image) {
if (getCurrentActivity() != null) {
SettingsList settingsList = new SettingsList();
settingsList.getSettingsModel(EditorLoadSettings.class)
.setImageSourcePath(image, true)
.getSettingsModel(EditorSaveSettings.class)
.setExportDir(Directory.DCIM, "test")
.setExportPrefix("result_")
.setSavePolicy(
EditorSaveSettings.SavePolicy.KEEP_SOURCE_AND_CREATE_ALWAYS_OUTPUT
);
new PhotoEditorBuilder(getCurrentActivity())
.setSettingsList(settingsList)
.startActivityForResult(getCurrentActivity(), PESDK_EDITOR_RESULT);
}
}
}
public class PESDKPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new PESDKModule(reactContext));
return modules;
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
@NonNull
@Override
public List<ReactPackage> createAdditionalReactPackages() {
// Add the packages you require here.
// No need to add RnnPackage and MainReactPackage
return Arrays.<ReactPackage>asList(
new RNFSPackage(),
new PESDKPackage()
);
}

This time, we prepared the desired settings for our editor, added our image path and passed everything to a PhotoEditorBuilder. The builder creates an intent and we launch it from our current Activity. That's it! Running our sample app on an Android device now leads to this:

The example app running on an Android device.

Nifty details

Experimenting with our new app, we noticed an issue: While the Android app closed the editor just fine when canceling the editor, the iOS app did nothing at all. That is because the iOS module doesn’t handle the PESDKs delegate methods. However, this can be fixed easily:

@interface PESDKModule () <IMGLYPhotoEditViewControllerDelegate>
@end
RCT_EXPORT_METHOD(present:(NSString *)path) {
...
photoEditViewController.delegate = self;
...
}
- (void)photoEditViewControllerDidCancel:(IMGLYPhotoEditViewController *)photoEditViewController {
[photoEditViewController.toolbarController.presentingViewController dismissViewControllerAnimated:YES completion:NULL];
}
...

It could be taken even further by turning our native iOS module into a RCTEventEmitter and sending events to React Native when the delegate methods are called. Just take a look into our GitHub repository to find the corresponding code.

Summary

To sum it up: Using the PhotoEditor SDK from React Native was rather easy to do and we were very happy with the results. Handling the data flows between React Native and the Java or Objective-C classes can be tricky, but when dealing with a specific use case, it should be easy to adjust the native modules to meet your requirements. Feel free to adapt our code and add the PhotoEditor SDK to your React Native app. We’re looking forward to your feedback and any pull requests, that further optimize our implementation.

Thanks for reading! To stay in the loop, subscribe to our Newsletter.

GO TOP