I'm following the example posted in the official doc for native module ios. I've set up everything, build it and run the application.
// CAL.h
#import <React/RCTBridgeModule.h>
#interface CAL : NSObject <RCTBridgeModule>
#end
// CAL.m
#import <React/RCTLog.h>
#import "CAL.h"
#implementation CAL
RCT_EXPORT_MODULE(CAL);
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name location:(NSString *)location)
{
RCTLogInfo(#"Pretending to create an event %# at %#", name, location);
}
#end
But when I check NativeModules from react-native it shows an empty object - {}.
I'm not sure what I'm missing.
Like #chengsam mentioned, when I access CAL directly in the following manner it works.
const { CAL } = NativeModules;
or
NativeModules.CAL
CAL still held the native module while NativeModules directly displayed {}
Related
Trying to get ObjC app project to call Swift function within ObjC static lib..........
My ObjC app project build gets build error for reference to a Swift function that is within an ObjC static lib (.a) that is imported into the app project.
The file Hub_lib-Bridging-Header.h has no code.
OBJ-C APP PROJECT..............................................
ViewController.mm within the ObjC app project...
#import "ViewController.h"
#import "Hub_lib.h"
#import "Hub_lib-Swift.h"
#import "hublib.hpp"
#interface ViewController ()
#end
#implementation ViewController
. . .
- (IBAction)run_simple_central:(id)sender {
[self BLE.start_central];
}
BLE.h within ObjC app project...........
#import <CoreBluetooth/CoreBluetooth.h>
#import "Hub_lib-Swift.h"
#interface BLE: NSObject
//< CBPeripheralManagerDelegate >
#property(strong, nonatomic) CBPeripheralManager* peripheralManager;
#property(strong, nonatomic) CBMutableCharacteristic* transferCharacteristic;
#property(strong, nonatomic) NSData* dataToSend;
#property(nonatomic, readwrite) NSInteger sendDataIndex;
-(void)start_central;
#end /* BLE_h */
BLE.m within app; a wrapper for call to swift..........................
#import "BLE.h"
#import "Hub_lib-Swift.h"
#interface BLE ()
#end
#implementation BLE
-(void)start_central
{
Hub_lib* BLE_central = [Hub_lib new];
[BLE_central centralManager.run_central];
}
made a test project to be able to replicate your errors.
You are close but you need to take care of how your static lib has its internal methods and classes exposed in its header so you can use them elsewhere.
let's begin with the Objective-C Static Library project. Hub_lib.h
#import <Foundation/Foundation.h>
// we want the lib header as clean as possible
// it will then be imported in your project with '#import <Hub_lib/Hub_lib.h>'
// auto-generated swift -> objc bridging header imported here will
// mess it up when imported somewhere else.
// so when using swift->objc bridging place next line in .m file instead
//#import "Hub_lib-Swift.h"
//#class BLE_Central; // pre-declaration of a later fully declared Class.
// as we moved the property into .m file we dont need it here.
#interface Hub_lib : NSObject
// to make this work you would need pre-declaration of BLE_Central, see above interface
//#property BLE_Central *ble; // placed in .m interface extension instead.
-(void)run_central;
#end
static lib counterpart / implementation Hub_lib.m
#import "Hub_lib.h"
#import "Hub_lib-Swift.h"
#interface Hub_lib ()
#property BLE_Central *ble_central;
#end;
#implementation Hub_lib
-(instancetype)init {
if (!(self=[super init])) return nil;
_ble_central = [[BLE_Central alloc] init];
return self;
}
-(void)run_central {
[_ble_central run_central];
}
#end
notice BLE_Central property is placed in the class interface extension and when you want to use swift module stuff that exposes back to objc you need to declare the auto-generated bridge somewhere (best done in .m file #import "Hub_lib-Swift.h")
your BLE_central.swift with its protocol method implementation
import Foundation
import UIKit
import CoreBluetooth
import os
var centralManager: CBCentralManager = CBCentralManager()
class BLE_Central: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
os_log("centralManagerDidUpdateState")
}
var discoveredPeripheral: CBPeripheral?
var transferCharacteristic: CBCharacteristic?
var writeIterationsComplete = 0
var connectionIterationsComplete = 0
let defaultIterations = 5 // change this value based on test usecase
var data = Data()
// as you figured out before we need to expose to #objc
#objc public func run_central()
{
os_log("run_central")
// out-commented for testing purposes
//mobile_sys_hub_lib.centralManager = CBCentralManager(delegate: self, queue: nil, options: [CBCentralManagerOptionShowPowerAlertKey: true])
os_log("Scanning started")
}
}
As long no extra code from objc is used in swift inside the static lib, the
Hub_lib-Bridging-Header.h is empty
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
Next let's see how to import your static lib in your "sim backend UI" Objective-C Project app.
Goto "sim backend UI" App Target settings > General > Framework, Libraries, and.. > hit the + button and search for your compiled libHub_lib.a file. > select it > hit ok. Should be very much in your framework list by now.
Yes, thats not enough! You have to declare its header in your app project somewhere. Where exactly is up to you. Instead of implementing it multiple times we do the following in BLE.h
#import <Foundation/Foundation.h>
#import <CoreBluetooth/CoreBluetooth.h>
//#import "Hub_lib-Swift.h" // no no no no
#import <Hub_lib/Hub_lib.h> // much better. test-compile once if it is crying
NS_ASSUME_NONNULL_BEGIN
#interface BLE: NSObject
// <CBPeripheralManagerDelegate> // "//<Protocol>" will confuse doxygen
#property (strong, nonatomic) CBPeripheralManager* peripheralManager;
#property (strong, nonatomic) CBMutableCharacteristic* transferCharacteristic;
#property (strong, nonatomic) NSData* dataToSend;
#property (nonatomic, readwrite) NSInteger sendDataIndex;
-(void)start_central;
#end /* BLE_h */
NS_ASSUME_NONNULL_END
counterpart BLE.m
#import "BLE.h"
#implementation BLE
-(void)start_central
{
NSLog(#"invoked BLE start_central");
Hub_lib *Hub_central = [Hub_lib new];
[Hub_central run_central];
}
#end
Your ViewController.m or .mm makes use of BLE.h with its already imported static lib header
#import "ViewController.h"
#import "BLE.h"
#interface ViewController ()
#property (nonatomic) BLE *ble;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
btn.frame = CGRectMake(100, 100, 200, 50);
[btn setTitle:#"run simple central" forState:(UIControlStateNormal)];
[btn setTitleColor:UIColor.greenColor forState:(UIControlStateNormal)];
btn.layer.backgroundColor = UIColor.orangeColor.CGColor;
btn.layer.cornerRadius = 5.0f;
[self.view addSubview:btn];
[btn addTarget:self action:#selector(run_simple_central:) forControlEvents:(UIControlEventTouchUpInside)];
}
- (IBAction)run_simple_central:(id)sender {
// BLE needs to be allocated to take effect.
if (!_ble) _ble = [[BLE alloc] init];
// testing.. should log "run_central" + "Scanning started"
[self.ble start_central];
}
#end
Made a testButton so you can see if the implementation invokes what you expect. Compile!
And? Aoutsch! Does not work. What happened?
If your App is a plain Objective-C project it doesn't know about swift yet and will complain in a weirdo way, possibly via something like
Link: Could not find or use auto-linked library 'swiftCoreImage'
Undefined symbol: value witness table for Builtin.UnknownObject*
Solution: Most easy way is to create a swift file in your app project and allow Xcode to make a bridging header for you. (Alternatively change project settings, search for "bridge" and go step by step thru the properties)
The swift file does not have to have special content.
//
// MakeProjectSwiftCompatible.swift
//
import Foundation
Compile again. now it should work because the partly implemented swift module from within your static lib can properly work when your Objc-App-Project is able to work with swift stuff.
Edit as you asked to work with Objective-C++/C++ in your static library things change a little bit.. so here some additional example code to proof it.
In Hub_lib project (targeting your "framework") add some files which will keep some random testing c++ code
//HubCPP.h
#import <Foundation/Foundation.h>
#include <vector>
NS_ASSUME_NONNULL_BEGIN
class SomeCPPClass
{
public:
SomeCPPClass(id<NSObject> obj, size_t size);
~SomeCPPClass();
id<NSObject> getBuffer() { return buffers[bufferIdx]; }
unsigned int getCurrentIdx();
private:
std::vector <id <NSObject>> buffers;
unsigned int bufferIdx;
bool isReady;
};
NS_ASSUME_NONNULL_END
To make Xcode know that you work with C++ you need to have implementation file ending with .mm and also need to change HubCpp.h "Identity and Type (right Xcode panel while file selected)" to C++ Header
// HubCpp.mm
#import "HubCPP.h"
SomeCPPClass::SomeCPPClass (id<NSObject> obj, size_t size) :
bufferIdx (0),
isReady(false)
{
uint8_t ringSize = 255;
assert (ringSize > 0);
for (uint8_t i = 0; i < ringSize; i++)
{
//buffers.push_back ();
bufferIdx = (unsigned int)size;
}
}
SomeCPPClass::~SomeCPPClass() {
// cleanup allocated stuff here.
}
unsigned int SomeCPPClass::getCurrentIdx() {
return bufferIdx;
}
Rename Hub_lib.m to .mm and change its import rules accordingly to the following ..
#import "Hub_lib.h"
#import <CoreBluetooth/CoreBluetooth.h> //needed because the -Swift.h bridge will cry in the next line
#import "Hub_lib-Swift.h"
#import "HubCPP.h"
lets change the proofing method in Hub_lib.mm so it really uses C++
-(void)run_central {
[_ble_central run_central];
SomeCPPClass *cpp = new SomeCPPClass(#"justSomeNSStringObject",2);
unsigned int idx = cpp->getCurrentIdx();
NSLog(#"objectiveCplusplus testIdx = %u", idx);
}
Compile Hub_lib (scheme). It should work by now and also accept the use of #import <vector>.
If this works go on and change your Objc-Project-App.
Switch your compile Scheme to target your Objc-App.
Change file name BLE.m to BLE.mm (makes it a Objective C++ Source)
Change file name BLE.h to BLE.hh (makes it a C++ header)
Change in BLE.mm #import "BLE.h to #import "BLE.hh
in ViewController.m kick out the line #import "BLE.h" and replace it into ViewController.h instead as #import "BLE.hh"
(In general its much easier to keep your compiler informed what language to expect in implementation when you place import headers in header files.)
Compile. Thats it! Your Objective-C++ static lib should properly work at this point.
Edit
You can find a ready made workspace for Xcode here...github: combine cpp swift and objective-c in static lib
Solution from StackO user: Asperi
Emptied top folder
Created workspace at top folder
1.1 Copied clean app and lib to top folder
Add ObjC lib .xcodeproj to workspace using Xcode > File > Add files ...
Add ObjC app .xcodeproj to workspace
Added dependency of sim_backend_UI to lib via workspace
a. app proj > General tab > Frameworks, Libs.. > +
b. Select lib .a
c. Add.
Add Some.swift (any swift file you want) to sim_backend_UI, just for the purpose Xcode add required system swift dynamic libraries (which will be needed for swift part in static library as well)... and confirm creating bridge in appeared dialog
a. new file > Swift > Some.swift
b. Create Bridging Header
c. Added to Some.swift ...
import Foundation
struct Some{}
Set Xcode active scheme to app and target device
Build
"succeeded"
I'm fairly new to objective-c and swift so forgive me if this sounds dumb. Basically what I'm trying to do is *expose** a swift function to both react-native(so it can be used in JS) and to be used in objective-c. The issue I'm having is this dreaded "Duplicate interface definition for class...". I have researched and tried everything it seems but can not get rid of this error. I'm starting to wonder if is possible to do this. It seems so simple yet I just can't figure it out!
Here is my code:
AppDelegate.m
#import "MyApp-Swift.h"
#import "MyApp-Bridging-Header.h"
MyApp-Swift.h
#import "React/RCTEventEmitter.h"
#interface Counter : RCTEventEmitter
- (void)start;
- (void)stop;
#end
MyApp.swift
import Foundation
import UIKit
#objc(Counter)
class Counter: RCTEventEmitter {
#objc func start(){
}
#objc func end(){
}
}
MyApp-Bridging-Header.h
#import <Foundation/Foundation.h>
#import "React/RCTBridgeModule.h"
#import "React/RCTEventEmitter.h"
#interface RCT_EXTERN_MODULE(Counter, RCTEventEmitter)
RCT_EXTERN_METHOD(start);
RCT_EXTERN_METHOD(end);
#end
Inside AppDelegate.m didFinishLaunchingWithOptions() function
Counter* CounterInstance = [[Counter alloc] init];
[CounterInstance start];
If I remove the code from MyApp-Swift.h then I get the error "No visible #interface for..." BUT fixes the "Duplicate interface error" in MyApp-Bridging-Header.h. It seems like it contradicts each other!? How are you supposed to call a swift function from objective-c while also exposing the same function to JS?
I'm doing something similar in a project, and my setup looks like this:
Counter.m
#import "React/RCTBridgeModule.h"
#import "React/RCTViewManager.h"
#interface RCT_EXTERN_MODULE(Counter, NSObject)
RCT_EXTERN_METHOD(start)
RCT_EXTERN_METHOD(stop)
#end
Counter.swift
import React
#objc(Counter)
class Counter: RCTEventEmitter {
#objc func start() { /* ... */ }
#objc func stop() { /* ... */ }
}
and at the call site:
#import "YOURAPP-Swift.h" // the compiler creates this for you. don't edit!
-(void) foo {
Counter* cnt = [Counter new];
[cnt start];
}
(no other files used or modified. In particular, the bridging header isn't needed here)
I've found https://teabreak.e-spres-oh.com/swift-in-react-native-the-ultimate-guide-part-1-modules-9bb8d054db03 very helpful when I started doing this.
Firstly, let me explain the app structure of how it is maintained
I have here two applications
First, a native objective-c based iOS application which is working perfectly, the task of the native application is to launch the camera once the application is launched, capture image and do some OpenGL processing, display the image captured.
This is done by calling ViewController class within my main.m file as shown below
#import <UIKit/UIKit.h>
#import "ViewController.h"
int main(int argc, char * argv[]) {
#autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([ViewController class]));
}
}
This ViewController class has some properties which gets the app up & running & launches a storyboard
Now for the cordova app, I created a plugin and integrated all the native files into the plugin, so once we add the plugin it adds all the resources including source files, resource files, assets, storyboard etc.
When we trigger the plugin within javascript, it will call the native class CustomPlugin which is the starting point of the plugin, below are the header & implementation code of the same
CustomPlugin.h
#import <Cordova/CDV.h>
#import "ViewController.h"
#interface CustomPlugin : CDVPlugin
- (void) pluginInitialize;
- (void) process:(CDVInvokedUrlCommand*)command;
#end
CustomPlugin.h
#import "CustomPlugin.h"
#import "ViewController.h"
#interface CustomPlugin()
#end
#implementation CustomPlugin
NSString *_routeChangedCallbackId;
#synthesize viewc;
- (void) pluginInitialize {
NSLog(#"CustomPlugin:pluginInitialize");
_routeChangedCallbackId = nil;
}
- (void) process:(CDVInvokedUrlCommand*)command
{
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:#"test"];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
#end
This implementation currently just sends back the response sample message to the caller
What I want to achieve is to be able to invoke/launch/initialize the ViewController feature as is working in native
Please oblige me for the lengthy question but it was required since I'm too much new to iOS.
The pluginInitialize method should be called between the CDVPluginResult and commandDelegate methods:
- (void) process:(CDVInvokedUrlCommand*)command
{
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:#"test"];
[self pluginInitialize];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithObjects/WorkingwithObjects.html
Successfully able to resolve my issue of launching a ViewController from Cordova plugin, refer the below post
Plugin with UIViewController
Setup:
react-native v0.41.2
react-native-cli v2.0.1
xcode v8.2.1
node v6.9.5
I started using RN v0.41.2 and found that v0.40 introduced a namespace breaking change stating that all react imports should be prepended with React/.
But the documentation shows otherwise.
So, is doing this the only thing that I have to do:
// RNLib.h
#import "RCTBridgeModule.h"
#interface RNLib : NSObject <RCTBridgeModule>
#end
to
// RNLib.h
#import <React/RCTBridgeModule.h>
#interface RNLib : NSObject <RCTBridgeModule>
#end
Or do I have to do it for my imports as well:
// RNLib.m
#import "RNLib.h"
#implementation RNLib
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(helloWorld:(NSString *)world)
{
return [NSString stringWithFormat:#"hello %#", world];
}
#end
to
// RNLib.m
#import <React/RNLib.h>
#implementation RNLib
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(helloWorld:(NSString *)world)
{
return [NSString stringWithFormat:#"hello %#", world];
}
#end
I'm currently unable to create a library and link it correctly (I've tried multiple things).
// somthing.m
#import "something.h"
This above line refers to something.h file which is present in same directory of the implementation file.
Only Modules from the React should be prepended with "React/RCTWhatever.h".
This has effective change in Header Search Paths of Xcode when you are linking the Native Libraries.
Thanks
I want access to the device name (from UIDevice) with React Native. Is there a good way to do that?
You need to make a Native Modules (iOS). Here are the steps:
Make native iOS component for getting device name
Use it in JS
1. Make Component
// Device.h
#import UIKit;
#import "RCTBridgeModule.h"
#interface Device : NSObject <RCTBridgeModule>
#end
// Device.m
#import "Device.h"
#implementation Device
RCT_EXPORT_MODULE()
RCT_EXPORT_METHOD(deviceName:(RCTResponseSenderBlock)callback) {
NSString *deviceName = [[UIDevice currentDevice] name];
callback(#[deviceName]);
}
#end
2. Use it in JS
var Device = require('react-native').NativeModules.Device;
Device.deviceName( (name) => {
console.log(name)
});