How to bridge React Native Promise to Swift - objective-c

Hi fellow software enthousiasts,
I am currently working on a React native project for which I need to add some logic which has been written in swift. I am able to trigger a basic swift function through the bridging to Objective C an then to Swift.
The problem occurs when I try to do something with promises. The page I describing this is clear on the Objective C part for Promises and also for bridging to Swift, but not so on promises to swift: https://facebook.github.io/react-native/docs/native-modules-ios.html
This is what I have:
Project-Bridging-Header.h
#import <React/RCTBridgeModule.h>
MyLoginBridge.m
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#interface RCT_EXTERN_REMAP_MODULE(MyCustomLoginJSName, MyLoginModule, NSObject)
RCT_EXTERN_REMAP_METHOD(loginWithEmail,
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(testMethod)
#end
MyLoginModule.swift
import Foundation
#objc(TripleASDKModule)
class TripleASDKModule: NSObject {
#objc
func loginWithEmail(resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) -> Void {
resolve("This method is troublesome")
}
#objc func testMethod() -> Void {
print("This Does appear")
}
}
When i trigger the testMethod, the print is shown in Xcode, so that swift code is executed. But when I call the loginWithEmail method, I get the infamous red React Native error screen saying:
Exception 'resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject is not a recognized Objective-C method.' was thrown while invoking loginWithEmail on target MyCustomLoginJSName with params (
30,
31
)
And for the sake of completeness, the Javascript side:
const loginWithEmail = () => NativeModules.TripleA.loginWithEmail()
.then(result => console.log(result));
I tried almost all variations of RCT_EXTERN_REMAP_METHOD and the like I could find, both with and without Remapping repeating the name, etc.
So if this problem sound familiar, or you could guide me in the right direction, please do so, any help is appreciated.

Taken from the answers at Got "is not a recognized Objective-C method" when bridging Swift to React-Native; the fact that it doesn't work, is because of the difference in the first argument labels.
To make it work with your initial code you should write the first argument of your Swift to be without a name, like this:
#objc
func loginWithEmail(_ resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) -> Void {
// the trick ^
resolve("This method is no longer troublesome")
}

Adding so that the complete solution is shown
.m - note that the resolve parameter is not "named"
RCT_EXTERN_METHOD(loginWithEmail: (RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
.swift - same as #Koen
#objc func loginWithEmail(_ resolve: #escaping RCTPromiseResolveBlock,
rejecter reject: #escaping RCTPromiseRejectBlock ) -> Void {}

I did eventually come up with a solution. It is not exactly like React native people intended it I believe, but it works. So i can continue and maybe someone else is set on the right track with my solution. Although, please post the way it is supposed to be.
So I decided to start with the Objective-C way first. So I created a .h file for my module.
MyLoginBridge.h
#import <React/RCTBridgeModule.h>
#interface MyLoginBridge : NSObject <RCTBridgeModule>
#end
Then alter the .m file
#import "MyLoginBridge.h"
#import "MyProject-Swift.h" // Include the swift header manually
#implementation MyLoginBridge
RCT_EXPORT_MODULE(MyCustomLoginJSName);
RCT_EXPORT_METHOD(loginWithEmail:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
// Manually init the module and call swift function
MyLoginModule* module = [[MyLoginModule alloc] init];
[module loginWithEmailWithResolver:resolve rejecter:reject];
}
#end
The swift file and the bridging header remained the same. This works.

Related

can't access Objective C class from swift controller code

I am getting an Undefined symbol: OBJC_CLASS$_Intro at link time when trying to build.
I can access the class Intro inside a swift file in Xcode using Xcode's intellisense, but it doesn't seem to recognize it at build time. Can someone with more experience with swift and Xcode give me a hand.
class OldLoginViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
NotificationCenter.default.addObserver(self, selector: #selector(OldLoginViewController.goIntro), name: NSNotification.Name("ROOTGOINTRO"), object: nil)
ClsUtil.skip(toViewController: self.navigationController!, viewControllerName: "Intro", isViewDidLoad: true)
}
var style : UIStatusBarStyle = UIStatusBarStyle.default;
#objc
override func viewDidAppear(_ animated: Bool) {
let intro = Intro()
self.navigationController?.pushViewController(intro, animated:false)
}
Obj C code:
#import "BaseViewController.h"
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface Intro : BaseViewController
#end
NS_ASSUME_NONNULL_END
Bridging header:
#ifndef ObjectiveCBridgingHeader_h
#define ObjectiveCBridgingHeader_h
#import "Base/Util/ClsUtil.h"
#import "Base/ViewController/BaseViewController.h"
#import "Intro/Intro.h"
#endif /* ObjectiveCBridgingHeader_h */
builds fine and accesses ClsUtil fine without the code involving intro, so I assume that the bridging header is set up correctly in project settings
ps - forgive the noobness. 4th day of iOS. Prior developer left and we don't have a developer to take care of iOS related issues or finish porting the prior app. I wanted to switch to using swift instead of obj c cause it looks cleaner/will give access to the latest tools.
figured it out. It's because when I copy over folders from OS X finder, the files are not added to the "Compile Sources" section of the Build Phases Tab of the project settings.
I'm still a little confused as to what the proper method of importing folders from another project is. I assume I need to use an Xcode command of some kind instead of OS X finder

How to implement objective C protocol in swift class?

#protocol LibraryPaymentStatusProtocol <NSObject>
#required
-(void)paymentStatus:(NSString*)message;
#optional
-(void)onError:(NSException *)exception;
-(void)tryAgain;
-(void)cancelTransaction;
#end
This protocol I want to implement in my swift class. This protocol is present in BDViewController.h file which is inside the Library. I imported the library successfully in my swift project not getting access to this protocol. What is the additional thing I am missing? Thanks in advance.
In my Swift class, I simply want to implement like normal protocol like below
class mainTabViewController: LibraryPaymentStatusProtocol {
// MARK : - Payment status protocol method
func paymentStatus(_ message: String!) {
}
}
Add this line in the bridging header
#import "BDViewController.h"
maybe it will solve your problem. Let me know if it is helpful.
You need to create a Swift bridging header and include your Objective-C file there.
// <ProjectName>-Bridging-Header.h
#import "BDViewController.h" // In which you have written LibraryPaymentStatusProtocol
Then you can implement the required method as
func paymentStatus(_ message: String?) {
}

Combinating Swift & Objective C files

I have a bit complicated classes in swift and Objective-C combinated together:
Keypad.h:
#import "MyApp-Swift.h"
#interface Keypad : UIViewController {
...
SwiftViewController *swiftViewController; // this is written in swift
...
}
This worked well.
Then I created a new swift file:
AnotherSwiftViewController.swift
#objc class AnotherSwiftViewController: UITableViewController {
func myMethod() {
let keypad = appDelegate.getTabs().selectedViewController as! Keypad // I need get ObjC Keypad class
}
}
And I need to use there the ObjcC Keypad class.
So I added it to the MyApp-Bridging-Header.h:
MyApp-Bridging-Header.h
...
// lot of other Obj-C files imported
...
#import "Keypad.h"
...
And I get the error:
> .../MyApp-Bridging-Header.h:31:9: note: in file included from .../MyApp-Bridging-Header.h:31: #import "Keypad.h"
> .../Keypad.h:13:9: error: 'My_App-Swift.h' file not found \#import "My_App-Swift.h"
> <unknown>:0: error: failed to import bridging header '.../MyApp-Bridging-Header.h'
Any ideas?
/// EDIT:
Maybe will help:
I'm using
#import "My_App-Swift.h"
In the Keypad.h file, not in standard Keypad.m, because I have there that SwiftViewController *swiftViewController; property
Maybe it will help
You have a good point in your ///Edit.
In Keypad.h remove #import "MyApp-Swift.h" and add a forward declaration of your Swift class as follows:
...
#class SwiftViewController;
#interface Keypad : UIViewController {
...
This should do it assuming Keytab.h references SwiftViewController only by pointer and your bridging header is imported correctly otherwise.
See section Referencing a Swift Class or Protocol in an Objective-C Header in https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html.

Cloudinary SDK in Swift project

Is there a way to use Obj-C SDK in a Swift file?
I've tried to add cloudinary SDK to my project. I found a way to add libcloudinary.a to my project Frameworks but its not working.
UPDATE: Cloudinary have just published new SDKs written in the Swift language: https://github.com/cloudinary/cloudinary_ios (Swift 3 and Swift 2.3 on separate brunches)
These are working for me:
Create or append to the Podfile file: pod "Cloudinary"
Run pod install and then open the workspace file, not the project.
Add an Obj-C file to your project (http://cl.ly/image/272V1Z2Q3g0e) and when asked say yes to adding an Objective-C bridging header
In this file add #import "Cloudinary/Cloudinary.h"
You may now use the Cloudinary API. Here is some code to upload:
let cloudinary_url = "cloudinary://API_KEY:API_SECRET#CLOUD_NAME"
var uploader:CLUploader = CLUploader(cloudinary, delegate: self)
uploader.upload(UIImageJPEGRepresentation(new_image, 0.8), options: ["format":"jpg"], withCompletion: { ([NSObject : AnyObject]!, errorResult:String!, code:Int, context:AnyObject!) -> Void in
}, andProgress: { (p1:Int, p2:Int, p3:Int, p4:AnyObject!) -> Void in
})
Yes, you can.
First, you will need to get your xcode project setup as per their instructions found here. You will then need to create a bridging header file as defined in the Swift docs.
example
#ifndef Fun_Bridging_Header_h
#define Fun_Bridging_Header_h
#import "Cloudinary.h"
#endif
Unfortunately their objective-c API is not fully interoperable with Swift, namely their upload API (CLUploader).
What I've had to do to make it work was to create a factory class in objective-c for it
The interface (CloudinaryFactory.h)
#ifndef Fun_Wrappy_h
#define Fun_Wrappy_h
#import "Cloudinary.h"
#interface CloudinaryFactory : NSObject
+ (CLUploader*)create:(CLCloudinary*)cloudinary delegate:(id <CLUploaderDelegate> )delegate;
#end
#endif
The implementation (CloudinaryFactory.m):
#import "CloudinaryFactory.h"
#implementation CloudinaryFactory
+ (CLUploader*)create:(CLCloudinary*)cloudinary delegate:(id <CLUploaderDelegate> )delegate
{
return [[CLUploader alloc] init:cloudinary delegate:delegate];
}
#end
And the updated bridging file
#ifndef Fun_Bridging_Header_h
#define Fun_Bridging_Header_h
#import "CloudinaryFactory.h"
#endif
Now, to use it:
var image:UIImage? //todo: i'm assuming you would set this somewhere
#IBAction func uploadGarment(sender: AnyObject) {
let clouder = CLCloudinary(url: "cloudinary://your:cloudinary#url")
let forUpload = UIImagePNGRepresentation(image) as NSData
let uploader = CloudinaryFactory.create(clouder, delegate: self)
uploader.upload(forUpload, options: ["public_id":"testo"])
}
I hope it helps! for more, here's the related blog post
You can have a look at the iOS sample code here:
https://github.com/cloudinary/cloudinary_ios#upload.
In addition, here's a code reference:
https://github.com/cloudinary/cloudinary_ios/blob/master/CloudinaryTests/UploaderTests.m
Also, you can use Cloudinary's unsigned upload. Here's some more information including an iOS example: http://cloudinary.com/blog/direct_upload_made_easy_from_browser_or_mobile_app_to_the_cloud#api_example_2
Create Bridging Header File in your project. After that import .h file into bridging-header file.
See here :
1 step: xyz-Bridging-header.h
Import Cloudinary.h file into Bridging-header file
#import "Cloudinary.h"
2 Step : ViewController.swift
Class viewController : UIViewController {
cloudinary: CLCloudinary = CLCloudinary()
override func viewDidLoad() {
super.viewDidLoad()
// Cloudinary Setup
cloudinary.config().setValue("xxxxxxxx", forKey: "cloud_name")
cloudinary.config().setValue("xxxxxxxx", forKey: "api_key")
cloudinary.config().setValue("xxxxxxxx", forKey: "api_secret")
}
}
Your Cloudinary setup is done. now you can able to use.

Swift protocol in Objective-C class

I wrote SearcherProtocol in Swift and need to implement an Objective-C class FileSearcher which has to use this protocol.
So I tried this:
#import <Foundation/Foundation.h>
#interface FileSearcher : NSObject <SearcherProtocol>
// ... class content
#end
The compiler tells me
Cannot find protocol declaration for 'SearcherProtocol'
The corresponding bridged header file (modulename-Swift.h) is being imported within FileSearcher.m.
Importing SearcherProtocol into FileSearcher.h throws another compiler error: module name-swift.h file not found
Does anybody have any clue what I'm doing wrong?
I'm using Xcode 6 Beta 5.
Edit
Here is the protocol declaration in Swift:
#objc protocol SearcherProtocol
{
var searchNotificationTarget: SearchCompletedProtocol? { get }
var lastSearchResults: [AnyObject] { get set }
func search(searchParam: String, error: NSErrorPointer) -> Bool
}
And the SearchCompletedProtocol:
#objc protocol SearchCompletedProtocol
{
func searchCompletedNotification(sender: AnyObject!)
}
There are two common reasons for this occuring:
Getting the module name wrong, see my answer.
Having a circular reference - see mitrenegades answer below.
1. Get the module name right:
If both the swift protocol and and Objective C are in the same project then according to apple you should just need to make sure you get the correct module name.
For Xcode6 beta 5 you can find it under BuildSettings->Packaging->Product Module Name
A common mistake would be to think that each swift file/class gets its own file, but instead they are all put into one big one that is the name of the project.
A further mistakes are if the module name has spaces, these should be replaced with underscores.
Edit:
With your protocol I created a test project called 'Test' which compiles perfectly and it has the files:
TestObjClass.h
#import <Foundation/Foundation.h>
#import "Test-Swift.h"
#interface TestObjCClass : NSObject <SearcherProtocol>
#end
TestObjClass.m
#import "TestObjCClass.h"
#implementation TestObjCClass
#end
TestProtocol.swift
import Foundation
#objc protocol SearcherProtocol
{
var searchNotificationTarget: SearchCompletedProtocol? { get }
var lastSearchResults: [AnyObject] { get set }
func search(searchParam: String, error: NSErrorPointer) -> Bool
}
#objc protocol SearchCompletedProtocol
{
func searchCompletedNotification(sender: AnyObject!)
}
2. Avoid circular reference:
Mitrenegades answer explains this, but if your project needs to use the explicit objc class that uses the swift protocol, (rather than just using the protocol) then you will have circularity issues. The reason is that the swift protocol is defined to the swift-objc header, then to your obj-c class definition, which then goes again to the swift-objc header.
Mitrenegades solution is to use an objective-c protocol, is one way, but if you want a swift protocol, then the other would be to refactor the code so as to not use the objective-c class directly, but instead use the protocol (e.g. some protocol based factory pattern). Either way may be appropriate for your purposes.
When you have
#import "moduleName-Swift.h"
in the .h file that you want to be a delegate, and you have that .h file also in the bridging headers file, there's a circular reference that causes the moduleName-Swift.h to fail compilation. for #james_alvarez's test project, it's probably working because you don't need to include TestObjClass.h into the bridging header.
The best way for me to combine objc files that need to be the delegate for a class written in swift, but that also needs to be included in the bridging header so other swift files can access this objc class, is to create a separate protocol file in objc:
MyProtocol.h:
#protocol MyDelegate <NSObject>
-(void)didDoThis;
-(void)didDoThat;
#end
ViewController.h:
#import "MyProtocol.h"
#interface ViewController : UIViewController <MyDelegate>
MyProject-Bridging-Header.h
#import "MyProtocol.h"
#import "ViewController.h"
I know this was a long time ago, but I just struggled with the same problem when adding a protocol to my Swift code, and it wasn't being added to the -Swift.h header file, hence "Cannot find protocol declaration"
The problem was my protocol wasn't marked as Public. I changed my protocol from this:
#objc protocol MyProtocol { //etc.... }
to this:
#objc public protocol MyProtocol { //etc.... }
I'm still not entirely sure why I need 'Public' but nobody else seems to, but hey it works...
Make sure you are including the auto generated Swift header in your ObjectiveC file. It will have the same name as your project module followed by -Swift.h.
For instance if your Project Module is MyTarget, then you would use:
#import "MyTarget-Swift.h"
If you are typing in the import into your Objective C file, it will not autocomplete. You can verify that you have the correct file by Command-clicking on the header after typing it in.
You can do the conformance part from Swift side 😁
So you have a swift protocol and want to make an Objective-C type conforms to it,
Swift Side
Add #objc to your protocol to make it visible to Objective-C world.
#objc protocol IndianCooking {
func cookChicken()
}
Objective-C Side
In the implementation .m file you do the following:
#import "YourProject-Swift.h"
#interface Cheef ()<IndianCooking> {
}
and in the header file .h add the method
cookChicken()
Import delegate as like this in .h file
#protocol AnalyticProtocol;
and add this in to .swift file
#objc public protocol AnalyticProtocol {
}
Try adding #import "Product_Module_Name-Swift.h" to your Product_Module_Name-Prefix.pch file. That fixed it for me, plus you will now have access to your swift files from any objc file.