How to Close A UIViewController From Your React Native App — The BETTER Way
Refining our previous solution

Previously, I wrote a blog post about how to close a UIViewController from your React Native app on iOS.
The solution I provided makes use of the rootViewController. This is the UIViewController instance that is first displayed on the iOS platform of a React Native app.
Now, this worked for us, but it’s not always the ideal solution. This post will cover a safer way to do it. But first, let’s talk about why it’s unpredictable to do it this way, and why it worked for our use case.
Why It’s Unpredictable

The problem with using the rootViewController is that it is, when talking about the context of a RN app on iOS, the first UIViewController instance that is placed in the UI of the app (hence the term, “root”).
By grabbing the rootViewController, you run the risk of grabbing the first parent view, if you will. When you dismiss this view, you run the risk of subsequently dismissing all of the views within the root view.
In theory, what should have happened in our app is the screen should have been rendered completely blank. Yet, when I tested it numerous times as I always do before committing, it worked completely as expected.
Why?
Why It Worked
The solution worked because we were using Apple’s dismissViewControllerAnimated method to properly “dismiss” a view controller. The rootViewController is just that — a UIViewController.
Now, the reason it didn’t render the app blank, I think can be boiled down to one of two reasons (or both concurrently).
The Native iOS Context
First, the SocketCamC820 view UIViewController was being “injected” into the React Native UI. This is because we were piggy backing off of the iOS SDK’s native code.
So, in essence, we had two “root” views in the same UI: the React Native context and the iOS context. The method used to dismiss the UIViewController was being called from the React Native side, but within the context of the Native Module (i.e. the piggy-backed iOS UI).
The Modal Presentation Style
The second reason is because of modal behavior. In the Native Modules, I was presenting the SocketCam UI window as a modal. This was because in our native code, we were instantiating a UIViewController for SocketCam, and then we assigned the value UIModalPresentationOverFullScreen to the modalPresentationStyle property.
socketCamViewController.modalPresentationStyle = UIModalPresentationOverFullScreen;This presentation style could in a sense pull the UIViewController instance for the iOS code further out of the context of the rootViewController of the React Native app.
Another important consideration for why this worked is to discuss how/when the dismiss method was executed.
How and When

You already know this was being invoked within the React Native UI, but I was using it in a function that gets executed after data is scanned.
I won’t dive too far into the specifics, but if you scan any data, there is a condition that checks to make sure the device type is SocketCam instance (either C820 or C860 for advanced camera scanning). If it is, then it runs this command to remove the view controller. If it’s not (i.e. there is no view controller) then this method is ignored.
The only issue is, if I, say, added a button to call the native method on it’s own — like even when there is no SocketCam UIViewController — it would probably find the React Native root view controller. This would ultimately render the React Native app blank.
So how was I able to refine this and make it more predictable?
The Solution
Below is how we initially got the rootViewController.
UIViewController *rootViewController = UIApplication.sharedApplication.keyWindow.rootViewController;However, our SocketCam UIViewController was an instance of the Socket Cam view controller class — provided by our iOS SDK. So in the dismissViewController method, I was able to check to see if this class existed on the ReactPresentedViewController — the currently presented view used in our React Native app.
If it existed, then remove it. See below.
RCT_EXPORT_METHOD(dismissViewController) {
dispatch_async(dispatch_get_main_queue(), ^{
UIViewController* currentViewController = RCTPresentedViewController();
if ([currentViewController isKindOfClass:NSClassFromString(@"SOCKET_CAM_VIEW_CONTROLLER_CLASS")]) {
[currentViewController dismissViewControllerAnimated:YES completion:nil];
}
});
} If the current view is not of this class type, then the dismissal code is ignored. This way we only dismiss the UIViewController associated with SocketCam and not any other important UIViewController instance — such as the one associated with the React Native app’s root view!

Do you have another way of achieving this logic? Or any other reason why the first method worked at all? Let me know in the comments?
🙏 You can also subscribe via email and get notified whenever I post something new!






