avatarMatt Croak Code

Summary

The article provides a technical guide on how to close a UIViewController from within a React Native application by using native modules to access Objective-C code for iOS.

Abstract

The author discusses the implementation of a feature in the Socket Mobile React Native CaptureSDK to support the SocketCamC860 on iOS. The challenge involved closing a native SocketCam window within a React Native app, which required integrating native modules to access native APIs not available in JavaScript. The article explains the use of NativeModules to communicate between the React Native UI and the iOS SDK, specifically detailing how to "grab" and dismiss a UIViewController using Objective-C. The process involves creating Objective-C files to represent the iOS native module, exporting methods to JavaScript, and then invoking those methods to dismiss the view controller asynchronously on the main thread. The author concludes by providing a code snippet that demonstrates how to trigger the dismissal from the React Native UI and mentions an updated method for achieving this in a more efficient manner.

Opinions

  • The author emphasizes the importance of using native modules to access platform-specific APIs that are not exposed to JavaScript by default in React Native.
  • The author suggests that the method of "grabbing" the UIViewController via the root view controller is particularly effective for their use case with the SocketCam SDK.
  • It is noted that UI updates, such as dismissing a view controller, should always be performed on the main thread to prevent issues with UI updates from background threads.
  • The author provides a personal update about a more efficient method for closing a UIViewController from a React Native app, indicating a commitment to sharing improved practices and encouraging readers to check out the newer approach.
  • The article encourages reader interaction by inviting alternative methods for removing a UIViewController from the React Native UI and offers a subscription option for readers interested in future content.

How to Close A UIViewController From Your React Native App

How to run dismissViewController from the JavaScript side

Image from Thinkwik

Recently, I was working on the Socket Mobile React Native CaptureSDK, trying to add support for the new SocketCamC860 on iOS.

In the React Native SDK, Both SocketCamC820 and SocketCamC860 are implemented via the respective native SocketCam view controllers from the iOS and Android SDKs. To utilize the iOS native SocketCam, I use ObjectiveC for the native modules. Fore Android, I am using Java.

In this post I’ll go over how I was able to implement a means from the JavaScript portion of the React Native to “close” the SocketCam window that was delivered to the React Native UI via iOS.

To do this, I first needed to incorporate native modules.

Native Modules

Sometimes a React Native app needs to access a native platform API that is not available by default in JavaScript, for example the native APIs to access Apple or Google Pay. The NativeModule system exposes instances of Java/Objective-C/C++ (native) classes to JavaScript (JS) as JS objects, thereby allowing you to execute arbitrary native code from within JS.

Think of my scenario.

I need to be able to initiate (or open) our SocketCam window in a React Native app. Since we have a means of using these windows in our iOS and Android apps already, it makes sense to implement the SocketCam window already provided by the respective native sdks.

This means I need to communicate from the React Native UI, to the iOS SDK, grab the SocketCam viewController, and pull back into the React Native UI so it can be used in the context of the React Native app.

I won’t go into details on how I was able to get all of that communication to work as it’s rather complex. I also won’t go into detail about how this works on the Android side of things as it’s not related to this blog topic.

What you should take away is that in order to achieve this, I needed to make use of the NativeModule system provided by React Native.

Below is how you can reference the NativeModule in a React Native app.

import React, {useState, useEffect, useRef} from 'react';

import { NativeModules } from 'react-native';

const {OurNativeModule} = NativeModules;

const App = () =>{
  useEffect(()=>{
    OurNativeModule.ourNativeModuleFunction();
  })
}

In your React Native app’s ios project folder, you will need two files to use ObjectiveC in this project that will represent your iOS native module. We will creat OurNativeModule.h and OurNativeModule.m.

The .h file is the header file, it is the declaration of the .m file. Together these files make up a class declaration.

Below is what our header file would look like.

//  OurNativeModule.h
#import <React/RCTBridgeModule.h>
@interface OurNativeModule : NSObject <RCTBridgeModule>
@end

And below is what our .m file (or our “implementation” file).

// OurNativeModule.m
#import "OurNativeModule.h"

@implementation OurNativeModule

RCT_EXPORT_MODULE(OurNativeModule);

@end 

For more on using Native Modules in React Native for iOS, you can check out these docs.

Now that we have this setup, let’s dive in to how to declare a function in the native module, and how to call it from the React Native side.

Our Function

First, implementing functions that can be used by our React Native app in ObjectiveC side of our code is rather simple. All you need to do is make use of RCT_EXPORT_METHOD. See below.

// OurNativeModule.m
#import "OurNativeModule.h"

@implementation OurNativeModule

RCT_EXPORT_MODULE(OurNativeModule);

RCT_EXPORT_METHOD(ourNativeModuleFunction)
{
  // YOUR CODE HERE
}

@end

Here we are exporting ourNativeModuleFunction as a React usable function so log as the React Native App has a connection to the native modules. This connection can be used by accessing it NativeModules.OurNativeModule.

Now that we have that settled, let’s figure out how SocketCam’s window is created and used in the iOS SDK.

UIViewController

In the case of SocketCam, the iOS SDK makes use of a subclass of something called UIViewController, which is a class provided by the Apple UIKit.

A view controller’s main responsibilities include the following:

  • Updating the contents of the views, usually in response to changes to the underlying data
  • Responding to user interactions with views
  • Resizing views and managing the layout of the overall interface
  • Coordinating with other objects — including other view controllers — in your app

It’s also particularly useful for accessing the device’s camera for data capture purposes.

So our next question is, how can we “grab” the UIViewController?

“Grabbing” the UIViewController

There are a few ways to do this, but the way we’re going to “grab” it is by root view controller of the application. This one works for our case because there is really only one UIViewController instance we’re going after — the one from the iOS SDK.

You can access the root view controller by making use of the UIApplication class and basically working our way down to rootViewController. See below.

 UIViewController *rootViewController = UIApplication.sharedApplication.keyWindow.rootViewController;

Now that we know how to gran the view, how can we “close” it?

“Closing” the UIViewController

Closing it is actually the simplest part of this process. We can do this by making use of Apple’s dismissViewControllerAnimated method. This method allows us to dismiss (i.e. “close”) a UIViewController subclass that was presented modally.

You can execute this on the rootViewController like so.

[rootViewController dismissViewControllerAnimated:YES completion:nil];

Put it all together, and our method looks like this.

RCT_EXPORT_METHOD(dismissViewController) {
  UIViewController *rootViewController = UIApplication.sharedApplication.keyWindow.rootViewController;
  [rootViewController dismissViewControllerAnimated:YES completion:nil];
}

Now, to be safe you should wrap the body of the method in dispatch_async(dispatch_get_main_queue(), ^{…}). By wrapping it in this dispatcher, you ensure that you only fire this method asynchronously on the main thread.

RCT_EXPORT_METHOD(dismissViewController) {
  dispatch_async(dispatch_get_main_queue(), ^{
    UIViewController *rootViewController = UIApplication.sharedApplication.keyWindow.rootViewController;
    [rootViewController dismissViewControllerAnimated:YES completion:nil];
  });
}

UI updates should always be done on the main thread. This way you prevent issues with UI updated from background threads.

Finally, in our React Native App, add the below line wherever you want in your React Native Code. Maybe you add it as a function for a button or some other event.

import React, {useState, useEffect, useRef} from 'react';

import { NativeModules } from 'react-native';

const {OurNativeModule} = NativeModules;

const App = () =>{
  useEffect(()=>{
    console.log("Hello World");
  })

  const closeUiViewControllerFromRNApp = () =>{
    OurNativeModule.dismissViewController()
  }

  return (
    <View>
       <Pressable onPress={onPress}>
         <Text>Close UIViewContrller</Text>
       </Pressable>
    </View>
  )
}

There you have it! By leveraging native modules to communicate between the React Native UI and the ObjectiveC code, and knowing how to effectively “grab” and “c̶l̶o̶s̶e̶ dismiss” the UIViewController, you can know close a native view from your React Native UI.

UPDATE!

I have since published a post that shows a more efficient way to accomplish this goal should you need one. You can read here!

Do you have another way to remove a UIViewController from React Native UI? Let me know in the comments!

You can also subscribe via email and get notified whenever I post something new!

References

Software Development
Software Engineering
React Native
Objective C
React
Recommended from ReadMedium