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.
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();
}
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];
I'm trying to manually (programmatically) lay out views in a UITabBarViewController. I instantiate my UITabBarViewController like this:
MYAppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UITabBarController *tabBarController = [[UITabBarController alloc] initWithNibName:nil bundle:nil];
MYViewController1 *myViewController1 = [[MYViewController1 alloc] init];
myViewController1.title = #"My VC 1";
[tabBarController addChildViewController:myViewController1];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.window.rootViewController = tabBarController;
return YES;
}
MYViewController1.m
- (void)viewDidLoad
{
[super viewDidLoad];
_myView = [[MYView alloc] initWithFrame:self.view.frame];
[self.view addSubview:_myView];
}
MYView.m
- (void)layoutSubviews
{
CGRect descriptionRect, buttonRect;
CGRectDivide(self.frame, &buttonRect, &descriptionRect, 50.f, CGRectMaxYEdge);
_descriptionTextView.frame = descriptionRect;
[self addSubview:_descriptionTextView];
_myButton.frame = buttonRect;
[self addSubview:_myButton];
}
The problem I'm having is that when I get to layoutSubviews, the superview's frame is the full size of the window, so the button is hidden by the tab bar. What am I doing wrong?
Without seeing more code, it appears that you may have some confusion about the use of a UITabBarController.
In the code you have posted above you are initializing a UITabBarController, then a UIViewController, and then you are calling 'addChildViewController:' on the tabBarController. (however, addChildViewController: is an instance method on UIViewController).
Is this in attempt to add the ViewController as a tab of the TabBarController? If so, then try the following code in place of addChildViewController: to see if it gives you the functionality that you are looking for:
tabBarController.viewControllers = #[myViewController1]; // Passing an array of viewControllers will set them as a tabs on the TabBar in the order they are added to the array.
If that doesn't help, then please comment on this answer with more details regarding the desired functionality of your code, and I'll update my answer to assist you as much as I can.
EDIT: Looks like I may have misunderstood your problem. Can you try the following code in play of your subview's initialization:
_myView = [[MYView alloc] initWithFrame:self.view.bounds]; // Try bounds instead of frame.
Is it possible to display a modal view over the top of a UIWebView? I have a UIViewController that loads a WebView. I then want to push a Modal View Controller over the top so that a modal view covers up the WebView temporarily...
The WebView is working fine; here's how it's loaded in the View Controller:
- (void)loadView {
// Initialize webview and add as a subview to LandscapeController's view
myWebView = [[[UIWebView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease];
myWebView.scalesPageToFit = YES;
myWebView.autoresizesSubviews = YES;
myWebView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
myWebView.delegate = self;
self.view = myWebView;
}
If I attempt to load a Modal View controller from within viewDidLoad, however, no modal view appears:
- (void)viewDidLoad {
[super viewDidLoad];
// Edit dcftable.html with updated figures
NSMutableString *updated_html = [self _updateHTML:#"dcftable"];
// Load altered HTML file as an NSURL request
[self.myWebView loadHTMLString:updated_html baseURL:nil];
// If user hasn't paid for dcftable, then invoke the covering modal view
if (some_condition) {
LandscapeCoverController *landscapeCoverController = [[[LandscapeCoverController alloc] init] autorelease ];
[self presentModalViewController:landscapeCoverController animated:YES];
}
}
I suspect that there's something that needs to be done with the UIWebView delegate to get it to receive the new modal view...but can't find any discussion or examples of this anywhere...again, the objective is to invoke a modal view that covers over the top of the WebView.
Thanks for any thoughts in advance!
I ran into this same problem. Moving the presentModalViewController call from viewDidLoad to viewDidAppear did the trick.
I couldn't get it to work in viewDidLoad either, but I got it to work in a different method. Here's what I did:
- (void)viewDidLoad {
...
if (some_condition) {
[NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:#selector(showModal) userInfo:nil repeats:NO];
}
}
- (void)showModal {
LandscapeCoverController *landscapeCoverController = [[[LandscapeCoverController alloc] init] autorelease ];
[self presentModalViewController:landscapeCoverController animated:YES];
}
Basically, it works if it's not being called in viewDidLoad. I wish I could explain why, I'm curious myself, but this should at least fix your problem.
Hope this helps!