How can I present a native UIViewController in React Native? (Can't use just a UIView)
Solution 1
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.
Solution 2
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;
}
}
Solution 3
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];
}
Related videos on Youtube
Mark Estefanos
Updated on June 06, 2022Comments
-
Mark Estefanos almost 2 years
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 π
-
ngoue about 5 yearsDoes exactly what it needs to! Thanks for the help.
-
PostCodeism about 5 yearsSo we have UIViews responsible for presenting UIViewControllers. This doesn't seem right to me.
-
Ilia Sidorenko almost 5 yearsMUCH more straightforward than the first answer. Thank you.
-
Nader Ghanbari over 4 years@PostCodeism It certainly does not seem right but RN's rigid interface forces us towards workarounds like this. Would be great if RN worked directly with a
UIViewController
. -
Ephrim Daniel about 3 years#import <AddressBookUI/AddressBookUI.h> is this storyboard ?? could you please explain this in detail
-
Wils almost 3 yearsIt has been a while since the answer above, React Native has improved a lot...I'm wondering if is there a better way to do this nowadays...ty
-
jspizziri about 2 yearsThanks for this answer! Are there any open-source libs you can point me to that use this approach?
-
Chaim Paneth about 2 yearsHi @jspizziri, I use it in my library here github.com/chaimPaneth/react-native-jw-media-player/blob/masββter/β¦