How can I present a native UIViewController in React Native? (Can’t use just a UIView)
I’m trying to use ABNewPersonViewController in my React Native app. This is how it’s used in Objective-C:
ABNewPersonViewController *picker = [[ABNewPersonViewController alloc] init];
picker.newPersonViewDelegate = self;
UINavigationController *navigation = [[UINavigationController alloc] initWithRootViewController:picker];
[self presentViewController:navigation animated:NO completion:nil];
How would I do this in React Native? I can’t write a bridged UI component since it’s a UIViewController, not a UIView.
Please don’t tell me to reimplement it 😟
Here’s what ended up working for me.
CreateContact.h:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <AddressBook/AddressBook.h>
#import <AddressBookUI/AddressBookUI.h>
#import "RCTBridgeModule.h"
@interface CreateContact : NSObject <ABNewPersonViewControllerDelegate, RCTBridgeModule>
@end
CreateContact.m:
#import "CreateContact.h"
#import "AppDelegate.h"
@implementation CreateContact
RCT_EXPORT_MODULE(CreateContact);
RCT_EXPORT_METHOD(presentContact) {
dispatch_async(dispatch_get_main_queue(), ^{
ABNewPersonViewController *picker = [[ABNewPersonViewController alloc] init];
picker.newPersonViewDelegate = self;
UINavigationController* contactNavigator = [[UINavigationController alloc] initWithRootViewController:picker];
AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[delegate.window.rootViewController presentViewController:contactNavigator animated:NO completion:nil];
});
}
- (void)newPersonViewController:(ABNewPersonViewController *)newPersonViewController didCompleteWithNewPerson:(ABRecordRef)person
{
[newPersonViewController dismissViewControllerAnimated:YES completion:nil];
}
@end
This tutorial has more detail: http://moduscreate.com/leverage-existing-ios-views-react-native-app/
I’ll update as I implement the best way to communicate information back to RN.
You want to implement a bridged UI component that mounts an empty UIView and is responsible primarily for presenting your UIViewController. The simplest example of this technique is in RCTModalHostView; check out the source code.
Notably, React Native defines a category on UIView that adds a property called reactViewController
which climbs the view hierarchy to find the closest UIViewController. Use this UIViewController to present your custom view controller:
- (void)didMoveToWindow
{
[super didMoveToWindow];
if (!_isPresented && self.window) {
[self.reactViewController presentViewController:_customViewController
animated:NO
completion:nil];
_isPresented = YES;
}
}
What you can actually do is have the UIViewController added as a child to the key window view controller but still have the view of the UIViewController added as a subview to the native module view so it’s contained in the frame and lifecycle of the native module view without presenting the UIViewController itself outside the life of the native module view as so ->
UIWindow *window = (UIWindow*)[[UIApplication sharedApplication] keyWindow];
[window.rootViewController addChildViewController:_myViewController];
_myViewController.view.frame = self.superview.frame;
[self addSubview:_myViewController.view];
[_myViewController didMoveToParentViewController:window.rootViewController];
Here is the full implementation ->
In your MyViewManager.m add
#import "RCTUIManager.h"
@interface MyViewManager ()
@end
@implementation MyViewManager
RCT_EXPORT_MODULE()
- (UIView*)view
{
return [[MyView alloc] init];
}
@end
and in your MyView.h add
#if __has_include("React/RCTViewManager.h")
#import "React/RCTViewManager.h"
#else
#import "RCTViewManager.h"
#endif
#import <UIKit/UIKit.h>
#import <AVKit/AVKit.h>
@interface MyView : UIView
@property(nonatomic, strong)UIViewController* myViewController;
@end
and in your MyView.m add
#import "MyView.h"
#import <AVFoundation/AVFoundation.h>
#import <AVKit/AVKit.h>
@implementation MyView
- (instancetype)init
{
self = [super init];
return self;
}
- (void)dealloc
{
}
- (void)layoutSubviews
{
[super layoutSubviews];
if (_myViewController != nil) {
_myViewController.view.frame = self.frame;
}
}
- (void)removeFromSuperview {
if (_myViewController != nil) {
[_myViewController willMoveToParentViewController:nil];
[_myViewController.view removeFromSuperview];
[_myViewController removeFromParentViewController];
_myViewController = nil;
[super removeFromSuperview];
}
}
-(void)addViewControllerAsSubView
{
_myViewController = [UIViewController new];
UIWindow *window = (UIWindow*)[[UIApplication sharedApplication] keyWindow];
[window.rootViewController addChildViewController:_myViewController];
_myViewController.view.frame = self.superview.frame;
[self addSubview:_myViewController.view];
[_myViewController didMoveToParentViewController:window.rootViewController];
}