I have an existing app that I am working on integrating React-Native for a portion of it. I am
having trouble understanding how to 'exit' react-native and get back to a native view.
Here's some code:
// Main objective-c code that bootstraps the react-native view. This view is loaded as a modal view.
MainViewController.m:
- (void)viewDidLoad {
[super viewDidLoad];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:#"IndexApp" initialProperties:props launchOptions:nil];
rootView.frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height-49);
[self.view addSubview:rootView];
}
My initial react views are such:
render() {
return (
<Navigator
style={styles.container}
...
I have a right-nav button on the Navigator that I would like to "dismiss" the react view and the underlying MainViewController native view.
I have tried a callback to MainViewController from the react view like so, but without avail:
RCT_EXPORT_METHOD(dismiss:(NSString *)name location:(NSString *)location)
{
NSLog(#"dismiss...");
// these don't do anything
//[self dismissViewControllerAnimated:YES completion:nil];
//[self.navigationController popViewControllerAnimated:YES];
// anything with _rootView doesn't do anything, i.e. _rootView removeFromSuperview];
}
Any help with an approach to 'exit' the react-native view and get back into native views would be appreciated.
The only way I've found this works is this
The gist of it is:
Create a NotificationManager class in Obj-C and expose it as React Module
In the ViewController register for a notification which when receives triggers [self dismissViewController..]
You need to run the pop or dismiss on the main thread:
RCT_EXPORT_METHOD(dismiss:(NSString *)name location:(NSString *)location)
{
NSLog(#"dismiss...");
dispatch_async(dispatch_get_main_queue(), ^{
[self dismissViewControllerAnimated:YES completion:nil];
// use pop instead if this view controller was pushed onto a navigation controller
//[self.navigationController popViewControllerAnimated:YES];
}
}
NOTE: THIS DOESN"T WORK - I've left this here as an example of what doesn't work. Check my other answer for an approach that works.
If you present MainViewController this way, popViewController: should work
NSURL *jsCodeLocation = [NSURL URLWithString:#"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];
RCTRootView *reactView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:#"SimpleApp"
initialProperties:nil
launchOptions:nil];
MainViewController *rootViewController = [MainViewController new];
rootViewController.view = reactView;
[[self navigationController] pushViewController:rootViewController animated:YES];
Related
I have an iOS native framework written in swift, which eventually needs to be wrapped as a react native component for a client. I managed to show the base view and make interactions with the buttons work (toast messages and date pickers displayed in the same view), but I cannot make it push a new view controller (which is internally pushed in the SDK).
I created a react native library with react-native-create-library and I installed my framework through the library's .podspec file as vendor_framework.
I linked the library to a sample react-native app, and so I started working on the .xcworkspace of the app.
Then I wrote some code in the default myLib.m and myLib.h to expose the module to JS.
myLib.h:
#interface RNHellLib : RCTViewManager <RCTBridgeModule>
+ (instancetype)sharedInstance;
- (void)configureSDK: (UIViewController *)rootController;
#end
myLib.m:
#implementation RNHellLib
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
RCT_EXPORT_MODULE(RNSearchBox)
RCT_EXPORT_VIEW_PROPERTY(rootController, UIViewController)
SearchBox *mySearchBox;
- (UIView *)view {
return mySearchBox;
}
+ (instancetype)sharedInstance {
static RNHellLib *sharedInstance = nil;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
sharedInstance = [[RNHellLib alloc] init];
});
return sharedInstance;
}
- (void) configureSDK: (UINavigationController *)rootController {
mySearchBox = [[SearchBox alloc] init];
mySearchBox.rootController = rootController;
}
#end
The framework requires a UINavigationController to be assigned form the host app, so I modified a bit the appDelegate.m from SampleApp to use a UINavigationController instead of the default UIViewManager, and assigned it to my component as you see in myLib.m > configureSDK. The code in appDelegate.h:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
UIViewController *rootViewController = [UIViewController new];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
[[RNHellLib sharedInstance] configureSDK: navigationController];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:#"HellApp"
initialProperties:nil];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
navigationController.view = rootView;
self.window.rootViewController = navigationController;
[self.window makeKeyAndVisible];
return YES;
}
Inside the framework, I have a public class SearchBox: UIView as entry door for the view stack. Then, to perform the segues, I do a vc.show as follows:
func show(_ viewControllerToShow:UIViewController, sender: Any?) {
guard let vc = self.rootController else {
return print("ERROR: Please set a rootController for the SearchBox");
}
vc.show(viewControllerToShow, sender: sender)
}
Depending on the element I interact with in the base view, I push a different controller from the function above. Among them, I have a UITextField that should push a new controller that manages an autocompletion view. Instead, the simulator access it as a regular TextInput and lets me write inside. Apart from it, I have a "Search" button that should, again, push another controller that manages a results view (which now is just throwing me a toast saying that I need to select something from the autocompletion).
I have thought of two possible problems here:
(Most likely) The UINavigationController I configured at react-native is not able to manage the segues with vc.show.
Actually the problem is related with the fact that the UITextField is being considered a TextInput for some reason.
Any ideas? I'm I approaching the whole thing wrongly? Thank you in advance for your support.
I'm in the middle of trying to make multiple frameworks to work together in an iOS environment and I got stuck while trying to navigate using a native method triggered by React Native (RN).
After trying multiple ways of doing this (one of them being exposing the rootViewController and change the UIViewController in the triggered method) I just can't change the view controller, even though the method is being called.
I'll leave some code that I tried below and hopefully better explain what I'm trying to do.
Disclaimer: I'm a beginner with obj-c and a complete noob with iOS development - learning as I go
This is my AppDelegate implementation:
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[super applicationDidFinishLaunching:application];
NSURL *jsCodeLocation;
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:#"index.ios" fallbackResource:nil];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:#"SomethingMobile"
initialProperties:nil
launchOptions:launchOptions];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
UIViewController *rootViewController = [[UIViewController alloc] init];
rootViewController.view = rootView;
self.navigationController = [[UINavigationController alloc] initWithRootViewController: rootViewController];
[self.window setRootViewController:self.navigationController];
//--- style the UINavigationController
self.navigationController.navigationBar.barStyle = UIBarStyleBlackTranslucent;
self.navigationController.navigationBar.topItem.title = #"Home";
return YES;
}
-(void) changeView {
// SimpleViewController is a valid UIViewController that I tested separately and it works
[self.navigationController pushViewController:[[SimpleViewController alloc] init] animated:YES];
self.navigationController.navigationBar.topItem.title = #"OF";
printf("Got here");
}
#end
Then I have a class that acts as a bridge between RN and the native code:
#implementation OFStarter
// The React Native bridge needs to know our module
RCT_EXPORT_MODULE()
- (NSDictionary *)constantsToExport {
return #{#"greeting": #"Welcome to native"};
}
RCT_EXPORT_METHOD(squareMe:(int)number:(RCTResponseSenderBlock)callback) {
AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[delegate changeView]; // this is getting called when I press a button in the RN view
callback(#[[NSNull null], [NSNumber numberWithInt:(number*number)]]);
}
#end
Now for some reason unknown to me, when the changeView method is called nothing is happening. From my understanding, the pushViewController is the method to call when changing the view using a UINavigationController.
Now if I want to change the view controller in the didFinishLaunchingWithOptions method using the same line of code as in the changeView, it works perfectly.
[self.navigationController pushViewController:[[SimpleViewController alloc] init] animated:YES];
To make matters even weirder, one time I was clicking on the RN button annoyed for about 20 times and then all of a sudden the view started to change ~20 times.
Also, I want to mention that the changeView is always getting called when I press the RN button.
I would appreciate any help on this matter. I was stuck on this for some time and I'm pretty sure I might be missing something obvious. Thanks!
I figured out the problem in the end. When changing the ViewController you have to do so from the main thread otherwise it doesn't work as expected.
All I needed to do was to add this method to the OFStarter implementation:
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
in my app i am trying to make a slider using the LWSlideShow LWSlideShow
the source of images are fetched from my server after trying the solution here i stuck with error that said unbalanced call and it means that i am presenting a modal view on a view that did not completed his animation after solving this problem by putting animation to no the splashView that i present will be dismissed before the images are downloaded here is my code for further explanation:
- (IBAction)goDownload {
UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"Splash"];
[self.navigationController presentViewController:vc animated:YES completion:nil];
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableArray *array = [#[] mutableCopy];
LWSlideItem *item = [LWSlideItem itemWithCaption:#""
imageUrl:#"http://code-bee.net/geeks/images/cover-1.jpg"];
[array addObject:item];
item = [LWSlideItem itemWithCaption:#""
imageUrl:#"http://code-bee.net/geeks/images/cover-2.jpg"];
[array addObject:item];
item = [LWSlideItem itemWithCaption:#""
imageUrl:#"http://code-bee.net/geeks/images/cover-3.jpg"];
[array addObject:item];
LWSlideShow *slideShow = [[LWSlideShow alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 120)];
slideShow.autoresizingMask = UIViewAutoresizingFlexibleWidth;
//slideShow.delegate = self;
[self.view addSubview:slideShow];
slideShow.slideItems = array;
if ([slideShow.slideItems count] == [array count]) {
[self dismissViewControllerAnimated:YES completion:nil];
}
});
}
//
//-(void)viewWillAppear:(BOOL)animated
//{
//
// UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"Splash"];
// [self.navigationController presentViewController:vc animated:YES completion:nil];
//}
- (void)viewDidLoad {
[super viewDidLoad];
[self goDownload];
}
also you can see from the code that also i try to use viewWillAppear same thing happened what i want is when the images are downloaded the splashView need to be dismissed i dont know what i am doing wrong
Running that code from a VC anytime before viewDidAppear (like viewDidLoad, viewWillAppear) will cause the problem you describe. But you probably don't want the slide show view to appear - even for an instant - until you're done fetching the assets. This is a common problem.
The solution is to realize that the "splash screen" and the network tasks aren't just preamble, they are as much a part of your application as the slide show.
EDIT
Make that Splash vc the app's initial view controller in storyboard. Right now, the slide show vc probably looks like this:
Uncheck the "Is Initial View Controller" checkbox, find your splash view controller (in the same storyboard, I hope) and check it's box to be the initial view controller. Now your app will start up on the splash vc, like you want it.
When the splash vc done, it can present the slide show vc, or it can even replace itself (with the slide show ) as the app window's root.
To replace the UI, I use variations of this snippet...
// in the splash vc, after all of the asset loading is complete
// give what used to be your initial view controller a storyboard id
// like #"MySlideShowUI"
UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"MySlideShowUI"];
UIWindow *window = [UIApplication sharedApplication].delegate.window;
window.rootViewController = vc;
[UIView transitionWithView:window
duration:0.3
options:UIViewAnimationOptionTransitionCrossDissolve
animations:nil
completion:nil];
I am using AddressBookUI Framework for Adding contact, when I tried to pushing this view controller then cancel and done button not working properly, I don't want to present it
Here is my code
ABNewPersonViewController *abnpvc = [[ABNewPersonViewController alloc] init];
[abnpvc setNewPersonViewDelegate: self];
[self.navigationController pushViewController:abnpvc animated:YES];
I am also tried add as subview rather then pushing it but when I am adding as subview then it was not added
As per comment i have tried like
UINavigationController *navigation = [[UINavigationController alloc] initWithRootViewController:abnpvc];
[self presentViewController:navigation animated:YES completion:nil];
Can anyone help me out why properly not working ?
You can implement that too considering also the other answers and the deprecations to ABNewPersonViewController in iOS 9.
As per your remarks:
cancel and done button not working properly
They are working if you have included the ABNewPersonViewControllerDelegate on interface like this:
#interface ViewController () <ABNewPersonViewControllerDelegate>
Pushing the viewController on navigation stack like this:
ABNewPersonViewController *controller = [[ABNewPersonViewController alloc] init];
controller.newPersonViewDelegate = self;
[self.navigationController pushViewController:controller animated:YES];
And by conforming to the protocol by implementing this method:
- (void)newPersonViewController:(ABNewPersonViewController *)newPersonView didCompleteWithNewPerson:(nullable ABRecordRef)person {
// Trick to go back to your view by popping it from the navigation stack when done or cancel button is pressed
[self.navigationController popViewControllerAnimated:YES];
}
The tricky line is to pop the newPersonController from the navigation stack when either Done or Cancel button are pressed.
Enjoy it
Why can't you just do it as the docs say?
It is recommended that you present a new-person view controller modally.
Use
ABNewPersonViewController *abnpvc = [[ABNewPersonViewController alloc] init];
[abnpvc setNewPersonViewDelegate: self];
[self presentViewController:abnpvc animated:YES completion:nil];
That should work fine.
Edit
On second thought, did you set your delegate correctly and do the implementations get called? I suspect they are not implemented or the delegate is not set correctly.
Apple guideline(IMPORTANT) :: New-person view controllers must be used with a navigation controller in order to function properly. It is recommended that you present a new-person view controller modally.
Add Delegate
#interface ViewController () <ABNewPersonViewControllerDelegate>
Pushing the viewController
ABNewPersonViewController *abnpvc = [[ABNewPersonViewController alloc] init];
[abnpvc setNewPersonViewDelegate: self];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:abnpvc];
[self presentModalViewController:navController animated:YES];
And Now Add Delegate Method
#pragma mark ABNewPersonViewControllerDelegate methods
- (void)newPersonViewController:(ABNewPersonViewController *)newPersonViewController didCompleteWithNewPerson:(ABRecordRef)person
{
[self dismissViewControllerAnimated:YES completion:NULL];
}
That will work fine.
We're adding react native to an existing app and are having trouble pushing a native view controller on top of another native view controller housing a react native view.
The main view controller's viewDidLoad looks like this:
-(void)viewDidLoad {
...
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName: #"ListingsView" launchOptions:nil];
self.view = rootView;
}
The react view is a ListView where the TouchableHighlight's onPress is exported to this method on the same view controller:
RCT_EXPORT_METHOD(productClicked:(NSDictionary *)productDict)
{
dispatch_async(dispatch_get_main_queue(),
^{
SNKProduct *product = [[SNKProduct alloc] initWithDictionary:productDict];
SNKProductViewController *vc = [[SNKProductViewController alloc] initWithProduct:product];
[self.navigationController pushViewController:vc animated:YES];
});
}
The method is definitely called, but the SNKProductViewController is never pushed onto the screen (no log messages). I also tried modally presenting the view controller, and am getting this console message:
Warning: Attempt to present <SNKProductViewController: 0x7feadf247d10> on <SNKProductsViewController: 0x7feada5e2a20> whose view is not in the window hierarchy!
Any help would be appreciated, thanks much!
After call RCT_EXPORT_MODULE macros by Reac-Native this created new instance of this module. And after call RCT_EXPORT_METHOD method this method called from another instance of module. To solve this problem, I found only one solution yet. in called method place from js search my ViewController in Hierarchy controllers:
RCT_EXPORT_MODULE(AuthController);
- (instancetype)init {
self = [super init];
if (self) {
}
return self;
}
- (void)loadView {
self.view = [[RCTRootView alloc] initWithBundleURL:[[BCEConfigs sharedInstance] getBundleOfReactResources]
moduleName:#"AuthorizationView"
launchOptions:nil];
}
- (void)viewDidLoad {
[super viewDidLoad];
}
RCT_EXPORT_METHOD(a_registrationButton) {
[self presentRegistrationViewController];
}
- (void)presentRegistrationViewController {
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIViewController *mainViewController = keyWindow.rootViewController.childViewControllers[0];
BCERegistrationViewController *bceRegistrationViewController = [BCERegistrationViewController new];
dispatch_async(dispatch_get_main_queue(), ^{
[mainViewController presentViewController:bceRegistrationViewController animated:YES completion:nil];
});
}
In this example my controller, which i needed, has a place this window>rootViewController>authViewController
UPD
this i found some solution how we can get current ViewController
I think you should change the -viewDidLoad method in you main view controller like this:
-(void)viewDidLoad{
[super viewDidLoad];
......
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName: #"ListingsView" launchOptions:nil];
[self.view addSubview:rootView];
}
That is , don't set the RCTRootView as 'self.view'; make it a subview.