iOS React Native - navigation doesn't work using native code - ios

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();
}

Related

iOS native framework wrapped as native component into react native project won't perform segues

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.

React-Native: Dismiss/Exit React-Native View back to Native

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];

Can't push a native view controller from a react-native click

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.

Getting Address Book permission crashes app iOS

I'm working on an iOS 7.1 app on Xcode 5.1.1 (can't be upgraded currently), with ARC and without a StoryBoard, and when I call an empty method in the viewDidLoad method, the app crashes at the end of my custom method. Currently, I'm thinking that it's either my older version of Xcode, or the fact that I'm not using a StoryBoard, but I've simplified the code as much as possible and still cannot find the error. if someone could point out what I'm doing wrong, that would be great, thanks!
The crash just says Thread 1: breakpoint 1.1, crashing when [self.window makeKeyAndVisible] calls [viewController viewDidLoad].
ViewController.h
#interface XYZContactsTableViewController : UITableViewController
#end
ViewController.m:
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self getAddressBook];
}
- (void)getAddressBook {
} // App crashes at line point exactly
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
XYZContactsTableViewController *viewController = [[XYZContactsTableViewController alloc] init];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = navigationController;
[self.window makeKeyAndVisible];
return YES;
}
Edits:
Also, calling pure C functions in the viewDidLoad method works, so the problem has something to do with the viewController object.
The crash just says Thread 1: breakpoint 1.1
Aha. You are not crashing at all. You are just pausing at a breakpoint. If you don't want to pause, or if breakpoints confuse you, take the breakpoint away or turn breakpoints off. Breakpoints are great, but you clearly don't understand them, so turn them off for now (but do learn to use them eventually, as they are extremely cool!).
why don't you use some already implemented component? :)
Check KBContactsSelection which allows you to search and select multiple contacts and is easily customizable using elegant Builder Pattern.

How to open a ViewController before application starts in iOS

I am working in iOS 5,and before loading my application,I want to open a another view controller,where the user should enter some data,for eg.password and when the password matches ,application will be opened,I am not getting how to do this..I tried some code ,which I have written below
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if(somecondition)
{
ViewController *View =[[ViewController alloc]initWithNibName:#"ViewController" bundle:nil];
[_window addSubview:View.view];
}
return YES;
}
But I dont know whether it is a right way,so friends,please help me out..
Regards
Ranjit
You should use
[self.window setRootViewController:yourViewController]
instead of addSubview to your window.
BTW, searching before asking is a good habit. ;)
If you want to show a view like the loginView or loadingView, you can set it as your rootViewController, when did loaded, you can reset your rootViewController.
Note, in your ProjectAppDelegate.m, you can get window
by self.window, and in other child view controller's, you'll need
[[[UIApplication sharedApplication] delegate] window]
to get your main window.
Another simple way to meet your requirement is that you can just present a modalView before showing your app. Dismiss it after done and then start your app.
You can get more suggestion HERE.
BTW, I'm sorry I didn't get your comments' notification when you are write at other users comment area a few days ago. :( You should add # before the user's name when you comment at somewhere else.
You can create some bool variable for checking is this a first start or another. The best place to store this bool is NSUserDefaults. Well, if this is a first start then show your LoginViewController, if not - execute regular code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UIViewController *startVC = nil;
if (isFirstLaunch){
startVC = [[[LoginViewController alloc] initWithNibName:#"LoginView" bundle:nil] autorelease];
}
else{
startVC = [[[WorkspaceViewController alloc] initWithNibName:#"WorkspaceView" bundle:nil] autorelease];
}
navController = [[UINavigationController alloc] initWithRootViewController:startVC];
[self.window makeKeyAndVisible];
[self.window addSubview:navController.view];
return YES;
}

Resources