Bridge Class Functions in React Native - objective-c

Say I have a class called ExampleClass.
Say I then write code like so:
#objc(ExampleClass)
class ExampleClass: NSObject {
#objc class func exampleFunc() -> Void {
}
}
With an Objective-C file header like so:
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#interface RCT_EXTERN_MODULE(ExampleClass, NSObject)
RCT_EXTERN_METHOD(exampleFunc)
#end
Which I then consume in my React Native app like so:
console.log('exampleClass', React.NativeModules.ExampleClass);
console.log('exampleFunc', React.NativeModules.ExampleClass.exampleFunc)
The first console log results in {exampleFunc: f}
The second results in undefined,
Calling the function: React.NativeModules.ExampleClass.exampleFunc() results in an app crash with:
Exception 'exampleFunc is not a recognized Objective-C method.' was thrown while invoking setupLogger on target ExampleClass with params (
While changing only the Swift so that it reads:
#objc(ExampleClass)
class ExampleClass: NSObject {
#obj func exampleFunc() -> Void {
}
}
results in calling the function (which, yes) does nothing at present.
How can I expose class level variables? I am trying to write functional Swift, and I am using class methods to simulate structs.

I believe the problem is that RCT_EXPORT_METHOD() only works on instance methods, not class methods, according to my own similar problem and some discussion here: https://github.com/facebook/react-native/issues/2311
My use case is trying to bridge a getInstance() method for a Swift singleton class. This is problematic because when you reference NativeModules.ExampleClass from javascript, which has been exported with RCT_EXTERN_MODULE(), RN calls init() on its own, which you don't want for a singleton (init() reference: https://samwize.com/2017/02/09/calling-a-view-controller-function-from-react-native/)
The best way I've found to accomplish this is pretty ugly. I have a dummy wrapper class that does nothing except call methods on the singleton, and this is the class I export to Objective C (and therefore to React Native). It's basically like this:
#objc(StupidWrapperClass)
class StupidWrapperClass : NSObject {
#objc(pseudoSingletonSomeMethod)
public func pseudoSingletonSomeMethod() {
let singleton = ActualClass.getInstance()
singleton.someMethod()
}
}
and then in the .m bridge file:
#interface RCT_EXTERN_MODULE(StupidWrapperClass, NSObject)
RCT_EXTERN_METHOD(pseudoSingletonSomeMethod)
#end
You could do something like this for a class method, too:
#objc(StupidWrapperClass)
class StupidWrapperClass : NSObject {
#objc(pseudoClassMethod)
public func pseudoClassMethod() {
ActualClass.theRealClassMethod()
}
}

I know I'm kinda late to the party but I recently faced the same problem and I fixed it using a different approach. Adding to the answer given above by #thejoelpatrol, a different approach would be to store the object's reference created by react native in some static variable that would be accessible by you. Then we can use the variable to access the object created by react-native anytime.
Whenever React Native tries to instantiate the class, it would come to the init. inside the init, we can save the reference to the object created by RN.
#objc public class MyClass {
#objc public static var shared: MyClass?
init() {
MyClass.shared = self
}
}
The .m bridge file is as follows:
#interface RCT_EXTERN_MODULE(MyClass)
RCT_EXTERN_METHOD(myClassMethod)
#end

Related

Call Swift func from Objective-C with parameter

I want to integrate a Swift class into an UIViewController class.
I think I made all settings correct. I have a class rURLTask:NSObject with a function:
#objc public func primer() {
print("primer")
}
In my .swift file and can call it from my Objective-C-class with:
[URLTask primer];
and it prints nicely.
Another function is:
#objc public func ladeURL(url: URL?) {
print("loadURL")
}
but this one I cannot call from Objective-C. I try to write:
NSURL* testURL = [NSURL URLWithString:#"http://www.google.com"];
[URLTask ladeURL:testURL];
I get the error:
No visible #interface for 'rURLTask' declares the selector 'ladeURL:'
I think there is a very basic mistake. Using Objective-C in Swift 3 in another project worked well.
The reason you cannot call
#objc public func ladeURL(url: URL?) {
print("loadURL")
}
by saying
[URLTask ladeURL:testURL];
is that ladeURL: is not the name of this method as far as Objective-C is concerned. That is what the compiler means when it says that "No visible #interface for 'rURLTask' declares the selector ladeURL:".
Its name as far as Objective-C is concerned is ladeURLWithUrl:. That is because you have exposed the url: parameter name as an external label.
If it was important to you to be able to say
[URLTask ladeURL:testURL];
in Objective-C, you could have declared ladeURL like this:
#objc public func ladeURL(_ url: URL?) {
print("loadURL")
}
See the underscore? That hides the external label of the parameter.
Another solution, allowing you to keep the url: external label, would be to declare the Objective-C name as part of the objc attribution:
#objc(ladeURL:) public func ladeURL(url: URL?) {
print("loadURL")
}
That says to the compiler: "I know you would like to translate the name of this method into Objective-C as ladeURLwithUrl:, but don't; translate it as ladeURL: instead."
When you import the class to OC , the name of the method written in swift is translated concatenated with the withParameterType , as prime
method
#objc public func primer() {
print("primer")
}
has no parameters it can be called like this
[URLTask primer];
but this
#objc public func ladeURL(url: URL?) {
print("loadURL")
}
is translated to
[URLTask ladeURLWithUrl:<#NSURL#>];

How to use pointer from other classes in swift

I'm immigrating my old ObjectiveC code to swift. In ObjcC had a separate class to handle my Admob activity.
In this class I've created a pointer in the init func, and when changing scene, I could use this pointer to change the location of the ads banner.
#implementation MyAdsSupport
+(id)ShowAds:(My_Ads_Position)posIndex
{
if (_adsBannerPointer == nil)
{
_adsBannerPointer = [[KTFAdsSupport alloc]initAds:posIndex];
}
else
{
[_adsBannerPointer setAdsPosition:posIndex];
}
return _adsBannerPointer;
}
In Swift I created the Admob class, and managed to present ads on screen but when I try to call the pointer from another class it returns always nil.
Here is my Swift Code:
var adsPointer: My_Ads_Support!
func initAds(myView: UIViewController, atPos: My_Ads_Position) -> KTF_Ads_Support {
if adsPointer == nil {
adsPointer = self
adsPointer.ShowAds(myView: myView, atPos: atPos)
}
else
{
print("adsPointer ALIVE")
adsPointer.setAdsPos( atPos: atPos)
}
return self.adsPointer!
}
How can I set a pointer in Swift to be able to reach the ads banner from any scene?
In your Objective-C code you have three methods, the instance methods initAds: and setAdsPosition:, and the class method ShowAds:. The latter uses a variable, presumably declared static, called _adsBannerPointer.
Your Swift code is not the same. It has two methods, the instance methods initAds and setAdsPos, and one variable, the instance variable adsPointer.
In Swift class methods are termed type methods (as they can belong to classes, structs and enumerations) and are indicated by the use of the keyword static, type (class) variables are also indicated with static. So to follow your Objective-C model you need something along the lines of:
static var adsPointer: My_Ads_Support!
// instance init
init(startingPos : My_Ads_Position) { ... }
// instance set position
fun setAdsPos(atPos : My_Ads_Position) { ... }
static func showAds(myView: UIViewController, atPos: My_Ads_Position) -> KTF_Ads_Support { ... }
HTH

Property 'sharedInstance' not found on object of type ClassA

I am creating a swift framework. In that one class is like this as shown below.
import Foundation
#objc public class classA: NSObject {
public override init (){
super.init();
}
/**
Singleton intance is returned.
*/
public class var sharedInstance: classA {
struct Static {
static let instance = popeye();
}
return Static.instance
}
}
Now when i add this framework into a Objective c project and try to access "sharedInstance" i get this error.
Property 'sharedInstance' not found on object of type ClassA.
Fix it Replace 'sharedInstance' with 'sharedInstance'
But even if i try use Fix it, this issue isnt solved.
NOTE: This issue doesn't happen when i integrate this framework with a swift project!!!
I AM STUCK.. :(
I tried to reproduce your problem. At first the syntax highlighter in Xcode flagged the same error in Objective-C that you mentioned, but the code actually was built and ran fine.
However, there is a cleaner way of doing this. In your code you are using a computed type property, which is evaluated every time you access it! You work around this by introducing the struct Static, where you essentially do what could be done in classA itself, like this:
/**
Singleton intance is returned.
*/
public static var sharedInstance: classA = popeye()
Here we used a stored type property, which is a recommended way to implement singletons, see here:
https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html
And here is some documentation on different kinds of properties:
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html
Finally i was able to fix this with a minor change !! :)
Swift framework code
#objc class SingletonTest: NSObject {
// swiftSharedInstance is not accessible from ObjC
class var swiftSharedInstance: SingletonTest {
struct Singleton {
static let instance = SingletonTest()
}
return Singleton.instance
}
// the sharedInstance class method can be reached from ObjC
class func sharedInstance() -> SingletonTest {
return SingletonTest.swiftSharedInstance
}
// Some testing
func testTheSingleton() -> String {
return "Hello World"
}
}
Objective C parent project code
SingletonTest *aTest = [SingletonTest sharedInstance];
NSLog(#"Singleton says: %#", [aTest testTheSingleton]);

Extending a Swift class with Objective-C category

Im in a situation where I need to use Objective-C category to extend a Swift class. I've done something as follows:
In "SomeClass.swift":
class SomeClass: NSObject {
}
In "SomeClass+Extension.h":
#import "Project-Swift.h"
#interface SomeClass (Extension)
-(void)someMethod();
#end
This has worked well. And if I try to use the SomeClass extension in my Objective C code, it is fine.
The problem is, if I want to use someMethod() in a another Swift class, I will need to put the SomeClass+Extension.h file into my ObjC-BridgingHeader.h file.
But doing this will cause a circular dependency, because SomeClass+Extension.h also imports Project-Swift.h.
Does anyone have a good way to get around this?
Please note that simply forward declaring the class in the category header will not work, as categories cannot use forward declarations for it's own implementation as so:
#class SomeClass without importing Project-Swift.h will give a compile error.
The Bad
i too have been fighting this issue a bunch. unfortunately the documentation pretty explicitly states that this pattern is not allowed:
To avoid cyclical references, don’t import Swift code into an
Objective-C header (.h) file. Instead, you can forward declare a Swift
class or protocol to reference it in an Objective-C interface.
Forward declarations of Swift classes and protocols can only be used
as types for method and property declarations.
also throughout the the linked page you will notice it keeps mentioning to import the generated header specifically into the .m file:
To import Swift code into Objective-C from the same target
Import the Swift code from that target into any Objective-C .m file
within that target
The Good
one solution that may work for you is to create a swift extension that redefines each method you need in the category. it is fragile and ugly, but arguably the cleanest solution.
/**
Add category methods from objc here (since circular references prohibit the ObjC extension file)
*/
extension SomeClass {
#nonobjc func someMethod() {
self.performSelector(Selector("someMethod"))
}
}
adding the #noobjc to the front allows the
same method signature to be used w/o overriding the ObjC implementation
now the import "SomeClass+Extension.h" from the bridging
header can be removed
if support for more than two input params is needed, or tighter type coupling is desired i would recommend using the runtime to call the underlying function. a great description is here.
From the Interoperability guide, we cannot directly access the subclassed / categorized / extensioned Objc-objects for the .swift [SomeClass] class.
But as a turn-around, we can do this:
For Variables , we can do this:
extension Class {
private struct AssociatedKeys {
static var DescriptiveName = "sh_DescriptiveName"
}
var descriptiveName: String? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.DescriptiveName) as? String
}
set {
if let newValue = newValue {
objc_setAssociatedObject(
self,
&AssociatedKeys.DescriptiveName,
newValue as NSString?,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
}
}
}
}
For Methods, we can use method_swizzling which is not recommended.
As one simple solution, you can move the extension to your Swift code. Then you won't have any dependency problems.

Swift class extends Objective C delegate

I am trying to extend Objective C class in my Swift class. This is where I got so far:
SINMessageClientDelegate is Objective C class. ViewController is written in Swift. I already have Bridging Header, so I can use Objective C object in my Swift class.
This is how my Swift code class definition looks like:
class ViewController: UIViewController, SINMessageClientDelegate {
I am getting the following error:
Type "ViewController does not conform to protocol SINMessageClientDelegate"
This is how definition of SINMessageClientDelegate looks like:
#protocol SINMessageClientDelegate <NSObject>
- (void)messageClient:(id<SINMessageClient>)messageClient didReceiveIncomingMessage:(id<SINMessage>)message;
- (void)messageSent:(id<SINMessage>)message recipientId:(NSString *)recipientId;
- (void)messageDelivered:(id<SINMessageDeliveryInfo>)info;
- (void)messageFailed:(id<SINMessage>)message info:(id<SINMessageFailureInfo>)messageFailureInfo;
I tried to create these methods using Swift in ViewController:
// Tells the delegate that a message has been received.
func messageClient(id: SINMessageClient, didReceiveIncomingMessage:SINMessage)
{
}
// Tells the delegate that a message for a specific recipient has been sent by the local user.
func messageSent(id: SINMessage, recipientId: NSString)
{
}
// Tells the delegate that a message has been delivered (to a particular recipient).
func messageDelivered(id: SINMessageDeliveryInfo)
{
}
func messageFailed(id: SINMessage, info: SINMessageFailureInfo)
{
}
Can someone advice what is the proper way of doing this, since I am getting the same error after adding my code?
Thanks!
One problem in the way you pose your question is that you're using the wrong words. You are not "extending a class". You are conforming to (or adopting) a protocol.
The trouble, however, is that you are not adopting it (conforming to it), as the error message rightly tells you. The reason apparently is that you don't know how to read Objective-C. You'll need to learn to do that in order to proceed. For example, given this Objective-C declaration:
- (void)messageClient:(id<SINMessageClient>)messageClient
didReceiveIncomingMessage:(id<SINMessage>)message;
The Swift implementation will need to be:
func messageClient(SINMessageClient,
didReceiveIncomingMessage message: SINMessage) { /* ... */ }
Whereas what you have is not at all the same thing. You have this:
func messageClient(id: SINMessageClient,
didReceiveIncomingMessage:SINMessage) { /* ... */ }
That is not a match, so you are not implementing the required method, but rather some totally different method. That's legal, but it has nothing to do with the protocol you are supposed to be conforming to. And the same for the rest of your declarations.