How can I send event's to JavaScript in Swift?
There is examples of Objc code how to send event to JavaScript, but I need to do in swift?
#import "RCTBridge.h"
#import "RCTEventDispatcher.h"
#implementation CalendarManager
#synthesize bridge = _bridge;
- (void)calendarEventReminderReceived:(NSNotification *)notification
{
NSString *eventName = notification.userInfo[#"name"];
[self.bridge.eventDispatcher sendAppEventWithName:#"EventReminder"
body:#{#"name": eventName}];
}
#end
I was just trying to figure this out myself. It was actually surprisingly easy. Heres how I did it:
EventTests.m
#import "RCTBridgeModule.h"
#interface RCT_EXTERN_MODULE(EventTests, NSObject)
RCT_EXTERN_METHOD( testEvent:(NSString *)eventName )
#end
EventTests.Swift
import UIKit
#objc( EventTests )
class EventTests: NSObject {
// Swift doesn't have synthesize - just define the variable
var bridge: RCTBridge!
#objc func testEvent( eventName: String ) {
self.bridge.eventDispatcher.sendAppEventWithName( eventName, body: "Woot!" )
}
}
MyModule.js
var React = require( 'react-native' );
var EventTests = require( 'NativeModules' ).EventTests;
var {
Component,
NativeAppEventEmitter
} = React;
var testEventName = 'test';
class MyModule extends Component {
constructor( options ) {
super( options );
// Register for our test event
NativeAppEventEmitter.addListener( testEventName, ( body ) => {
console.log( body );
});
// Call objective c function, which will emit our test event
EventTests.testEvent( testEventName );
}
}
module.exports = MyModule;
Also make sure to include a few imports in your bridging header:
#import "RCTBridge.h"
#import "RCTEventDispatcher.h"
Ok, things had already changed, so just to update what I did:
I have my swift module and I need to make it be a RCTEventEmitter
import React
#objc(MyModule)
class MyModule: RCTEventEmitter {
var hasListener: Bool = false
override func startObserving() {
hasListener = true
}
override func stopObserving() {
hasListener = false
}
#objc
func sendMyEvent() {
if hasListener {
self.sendEvent(withName:"MyEvent", body:["name": eventName]];
}
}
#objc
override func supportedEvents() -> [String]! {
return ["MyEvent","MyEvent2"];
}
}
This is how my bridging header looks like:
#import <React/RCTBridgeModule.h>
#import <React/RCTBridge.h>
#import <React/RCTEventDispatcher.h>
And in the .m file I changed it to:
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
#interface RCT_EXTERN_MODULE(MyModule, RCTEventEmitter)
It is insane. How to pass anything as an event to Javascript, if this event is generated by Swift?
I am stuck with the same issue here: "Subclass RCTEventEmitter instead"
The following does not work anymore.
self.bridge.eventDispatcher.sendAppEventWithName( eventName, body: "Woot!" )
I tried:
var emitter: RCTEventEmitter = RCTEventEmitter()
Instead of:
var bridge: RCTBridge!
Conesquently:
self.emitter.sendEvent(withName: eventName, body: "Woot!")
Instead of
self.bridge.eventDispatcher.sendAppEventWithName( eventName, body: "Woot!" )
From bad to worst, I go from a warning to the following hard error:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Error when sending event: Location with body: woot. Bridge is not set. This is probably because you've explicitly synthesized the bridge in RCTEventEmitter, even though it's inherited from RCTEventEmitter.'
The documentation is very poor and old. It looks that not many people is using this hot garbage. Very frustrating.
This is an old implementation and it no longer works:
example1
Another article, not very useful:
example2
Very thorough and complex implementation, no one knows if it still works. In my case it doesn't, I get the same error above:
example3
Then there are several objective-c swift cocktails, which I cannot drink at my age:
example4
Related
I'have a project where i have multiple targets, and in one of those targets i have added a custom class to download some Values from Firebase Remote Config.
Custom class is declared as follows:
#objc (Test)
final class CheckUpdate: NSObject {
#objc static let sharedInstance = CheckUpdate()
#objc func fetchCloudValues() {
// Grab remote config values and do things
}
}
I've added this class as target membership only on one target and in the code I've add
Class asdasd = NSClassFromString(#"Test");
if (asdasd) {
[[asdasd sharedInstance] fetchCloudValues];
}
My issue is that, at line where I call fetchCloudValues I get a compiling error saying that:
no visible #interface GIDSignIn declares the selector fetchCloudValues
but it's obviously an issue because asdasd should not be of type GIDSignIn
I'm trying to bind a SWIFT library to Xamarin iOS.
Source code has class:
#objc(ClassA)
public class ClassA: NSObject
{
<...>
public init(param1: StructA, param2: EnumStringA = .defaultValueA) {
<...>
}
}
but in *-Swift.h file it looks like this:
SWIFT_CLASS_NAMED("ClassA")
#interface ClassA: NSObject
- (nonnull instancetype)init SWIFT_UNAVAILABLE;
+ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable");
#end
So, of course, Sharpie tool make the interface empty, without constructor:
[BaseType(typeof(NSObject))]
[DisableDefaultCtor]
interface ClassA
{
}
I thought maybe its because both StructA and EnumStringA are not exposed, but #objc attribute is not applicable for struct and for enum : String
How to make the constructor is visible?
Please help me with the following issue. I am using the swift argument parser library https://github.com/apple/swift-argument-parser
Suppose I have this swift code in some swift file:
#objc public class Math: NSObject, ParsableCommand {
static var configuration = CommandConfiguration(
abstract: "A utility for performing maths.",
subcommands: [Add.self],
defaultSubcommand: Add.self)
}
extension Math {
struct Add: ParsableCommand {
static var configuration = CommandConfiguration(abstract: "Print the sum of the values.")
mutating func run() {
print("0")
}
}
}
` and I want to call to an objective-c++ function named "func" with an argument which is of type Math.Add like so from main.swift file:
var status: Int32 = 0
do {
// try to parse application arguments
var args = try Math.parseAsRoot()
switch args {
case let args as Math.Add:
status = func(args)
default:
try args.run()
}
} catch {
//Some code...
}
What should be the signature of the function and how the bridging-header files should look like? I made the signature be:
extern "C" int func(Math.Add *args);
And my bridging header is like so:
#ifndef __BRIDGING_HEADER_H__
#define __BRIDGING_HEADER_H__
#class Math;
#ifdef __cplusplus
extern "C" {
#endif
int func(Math.Add *args);
#ifdef __cplusplus
}
#endif
#endif /* __BRIDGING_HEADER_H__ */
but is doesn't work and the bridging header doesn't compile (Xcode writes the error: Interface type 'Math' cannot be passed by value; did you forget * in 'Math'?
Objective-C(++) doesn't support nested types, what you can do if you want to keep the nested structure on Swift, is to export a non-nested type for Objective-C:
extension Math {
#objc(Math_Add) // or MathAdd, or whatever name you like
struct Add: ParsableCommand {
, which can then be referenced in your header:
int func(Math_Add *args);
As a side note, I would also change the name of the func function to something that doesn't collide with Swift keywords. Even if you'll be able to call it, it will be confusing for other readers of your code.
I'm new in Swift and I would like if someone could tell me how can I translate the following code fragment written in Objective-C to Swift.
Code:
id< JavaUtilConcurrentConcurrentNavigableMap > mapVu = mmcache->kvmap_;
The corresponding header:
#interface MemCache : NSObject {
JavaUtilConcurrentConcurrentLinkedDeque *kvqueue_;
}
A bit of confusion later: You cannot access the instance variables of an Objective-C class from within Swift in a regular / proper manner. Take a look at the answer by Leo Natan for how to work around that.
The previous answer below basically focused on the protocol rather than the instance variables
In swift you can just have the protocol as the type:
let mapVu:JavaUtilConcurrentConcurrentNavigableMap = mmcache.kvmap_
See a working example:
protocol JavautilBlaBla {
}
class Class1: JavautilBlaBla {
override init() {
}
}
class Class2 {
var kvmap_:Class1
override init() {
kvmap_ = Class1()
}
}
let mmcache = Class2()
let mapVu:JavautilBlaBla = mmcache.kvmap_
The only current way to access an instance variable is to use valueForKey().
So,
let mapVu = mmcache.valueForKey("kvmap_") as! JavaUtilConcurrentConcurrentNavigableMap!
I am using the wahoo fitness API and it defines the following objective-C enum:
typedef enum
{
/** No active connection. */
WF_SENSOR_CONNECTION_STATUS_IDLE,
/** The connection is in process of being established. */
WF_SENSOR_CONNECTION_STATUS_CONNECTING,
/** The sensor connection is established and active. */
WF_SENSOR_CONNECTION_STATUS_CONNECTED,
/** The connection was interrupted (usually occurs when fisica is disconnected). */
WF_SENSOR_CONNECTION_STATUS_INTERRUPTED,
/** The connection is in process of being disconnected. */
WF_SENSOR_CONNECTION_STATUS_DISCONNECTING,
} WFSensorConnectionStatus_t;
I can't find a way to use it in swift. I first tried to do a switch/case on it without success. I am at a point I just want to carry on and tried the following:
var connState : WFSensorConnectionStatus_t = WF_SENSOR_CONNECTION_STATUS_IDLE
...
if( connState == WF_SENSOR_CONNECTION_STATUS_IDLE){
But it does not compile:
'WFSensorConnectionStatus_t' is not convertible to 'NSObject'
Any workaround? I read to use WFSensorConnectionStatus_t.WF_SENSOR_CONNECTION_STATUS_IDLE or WF_SENSOR_CONNECTION_STATUS_IDLE.value but it does not work in xcode beta-4.
The workaround to use .value to get the underlying integer doesn't work anymore as of Beta 4, as you said.
Unfortunately an enum is not transferrable to Swift from Objective-C, it needs to be an NS_ENUM.
I have the same setup as you in a project where I need the enum from an Objective-C framework and use it in Swift.
The workaround I did was to create an Objective-C category that contains an NS_ENUM and there I transfer the values from the framework enum to my own NS_ENUM.
Import the category in your bridging header and you should be able to use the enum as you normally would do.
Something like this:
typedef NS_ENUM(NSUInteger, ConnectionStatus) {
ConnectionStatusIdle
}
- (ConnectionStatus)connectionStatus {
if [self getConnectionStatus] == WF_SENSOR_CONNECTION_STATUS_IDLE {
return ConnectionStatusIdle
}
}
Then you should be able to use it like this:
switch myObject.connectionStatus() {
case .Idle:
// do something
break
}
Here is the final complete solution:
WFSensorConnection+SensorConnectionEnumCategory.h
:
#import <Foundation/Foundation.h>
#import <WFConnector/WFConnector.h>
#interface WFSensorConnection (SensorConnectionEnumCategory)
typedef NS_ENUM(NSUInteger, ConnectionStatus) {
ConnectionStatusIdle,
ConnectionStatusConnecting,
ConnectionStatusConnected,
ConnectionStatusInterrupted,
ConnectionStatusDisconnecting
};
- (ConnectionStatus) swift_connectionStatus;
#end
WFSensorConnection+SensorConnectionEnumCategory.m
:
#import "WFSensorConnection+SensorConnectionEnumCategory.h"
#implementation WFSensorConnection (SensorConnectionEnumCategory)
- (ConnectionStatus) swift_connectionStatus{
if ( [self connectionStatus] == WF_SENSOR_CONNECTION_STATUS_IDLE ){
return ConnectionStatusIdle;
} else if ( [self connectionStatus] == WF_SENSOR_CONNECTION_STATUS_CONNECTING ){
return ConnectionStatusConnecting;
} else if ( [self connectionStatus] == WF_SENSOR_CONNECTION_STATUS_CONNECTED ){
return ConnectionStatusConnected;
} else if ( [self connectionStatus] == WF_SENSOR_CONNECTION_STATUS_DISCONNECTING ){
return ConnectionStatusDisconnecting;
} else if ( [self connectionStatus] == WF_SENSOR_CONNECTION_STATUS_INTERRUPTED ){
return ConnectionStatusInterrupted;
}
return 0;
}
#end
Bridging-Header.h
:
#import "WFSensorConnection+SensorConnectionEnumCategory.h"
Usage:
var sensorConnection: WFSensorConnection?
var connState : ConnectionStatus = ConnectionStatus.Idle
connState = sensorConnection!.swift_connectionStatus()
switch connState {
case ConnectionStatus.Idle:
...
}
C-style enums import in Swift like UInt32. So you can do something like:
let state = unsafeBitCast(WF_SENSOR_CONNECTION_STATUS_IDLE, UInt32.self)
if state == unsafeBitCast(WF_SENSOR_CONNECTION_STATUS_IDLE, UInt32.self) {
//do something
}
Upd: In Swift 2.1 (Xcode 7.1 beta 2) all C-style enums conforms Equatable and you can now use it like:
let state = WF_SENSOR_CONNECTION_STATUS_IDLE
if state == WF_SENSOR_CONNECTION_STATUS_IDLE {
//do something
}
Profit :)
Note that there is a CoreFoundation type that is similar to NS_ENUM called CF_ENUM. I've used it on my framework that is mainly C. And yes, Swift does translate it to a Swift enum.
There is also something similar for NS_OPTIONS called CF_OPTIONS.