4

I'm trying to create a native (iOS) module for a React Native app using Swift. I want to expose a method on the module that returns a promise, using functions of type RCTPromiseResolveBlock and RCTPromiseRejectBlock, both of which are declared in React Native's RCTBridgeModule.h header. However, my build fails with the message "Use of undeclared type..." for both of these types.

I had already created a bridging header (automatically using Xcode) for another purpose, so I believe importing React/RCTBridgeModule.h is all that I'd need to supply the types mentioned above. Presumably I've misconfigured something, as this doesn't fix the issue. I've tried setting up a fresh project and everything works as expected there, but I can't seem to find a difference that would cause my project's build to fail.

Some relevant configuration details:

  • Bridging header named <ProjectName>-Bridging-Header.h and stored in my source directory. I moved it around to confirm that Xcode is finding it (the build fails differently when it's not in the right place).
  • The contents of the bridging header is simply: #import <React/RCTBridgeModule.h>
  • In my project build settings:
    • <ProjectName>-Bridging-Header.h is configured as the "Objective-C Bridging Header"
    • "Install Objective-C Compatibility Header" is set to "Yes"
    • "Precompile Bridging Header" is set to "Yes"
  • When I type alias the missing types the project builds and runs as expected
  • I have some third-party Swift libraries installed (one using react-native link and one using a Git submodule)
  • I've tried cleaning the build folder, reinstalling node_modules and deleting Xcode derived data, all to no effect.

Is my project misconfigured or have I otherwise missed something important?

Christophe
  • 68,716
  • 7
  • 72
  • 138
Karl L.
  • 41
  • 1
  • 2

1 Answers1

7

This tutorial is pretty good at explaining how to set up a native module using Swift. It breaks everything down into steps and it is quite easy to follow.

https://teabreak.e-spres-oh.com/swift-in-react-native-the-ultimate-guide-part-1-modules-9bb8d054db03

  1. Setup
  2. How to expose a Swift class to JS
  3. How to expose static Swift data
  4. How to expose a Swift method
  5. How to expose a method with a callback
  6. How to expose a method as a Promise
  7. How to expose an Event Emitter
  8. How to extract your React Native Module

Clearly it is step 6 that you are looking to do.

Here is a code example. It is very similar to what is done in the above link. Your bridging header should look like this:

// <ProjectName>-Bridging-Header.h

#import "React/RCTBridgeModule.h"
#import "React/RCTEventEmitter.h"

You should have files named ModuleName.m and ModuleName.swift.

// ModuleName.m
#import "React/RCTBridgeModule.h"
#import "React/RCTEventEmitter.h"
@interface RCT_EXTERN_MODULE(ModuleName, NSObject)
// this is how we expose the promise to the javascript side.
RCT_EXTERN_METHOD(functionWithPromise: (RCTPromiseResolveBlock)resolve rejecter: (RCTPromiseRejectBlock)reject)
@end

// ModuleName.swift

@objc(ModuleName)
class ModuleName: NSObject {
  @objc
  func constantsToExport() -> [AnyHashable : Any]! {
    return ["projectName": "ModuleName"]
  }

  @objc
  static func requiresMainQueueSetup() -> Bool {
    return true
  }

  @objc
  func functionWithPromise(
    _ resolve: RCTPromiseResolveBlock,
    rejecter reject: RCTPromiseRejectBlock
  ) -> Void {
    if (//something bad happens) {
      let error = NSError(domain: "", code: 200, userInfo: nil)
      reject("ERROR_FOUND", "failure", error)
    } else {
      resolve("success")
    }
  }
}

Then on the Javascript side you can access it like this:

import { NativeModules } from 'react-native'

NativeModules.Counter.functionWithPromise()
    .then(res => console.log(res))
    .catch(e => console.log(e.message, e.code))
Andrew
  • 26,706
  • 9
  • 85
  • 101
  • While that is a great tutorial (thanks for posting), it didn't fix my issue. As far as I can tell, I've set everything up as the tutorial described; I have a sneaking suspicion I broke something in my build settings, but I'm not sure what. I've been hoping there's a way to delete the bridging header and have Xcode regenerate it for me, but I haven't been able to make that happen. Also, for what it's worth `RCTEventEmitter.h` is only necessary if you're coding up an event emitter in swift; `RCTBridgeModule.h` has the promise symbols. – Karl L. Jan 31 '19 at 18:20
  • @Andrew How to resolve a HKStatisticsQuery?? I need to read the query in my JS file. I tried your above method to access it in my JS file but it didn't work. Any inputs? – grooot Jan 20 '21 at 02:40
  • @grooot I don’t do much with React-Native anymore so I cannot answer your question. I would suggest writing up your question and posting it properly on SO. You may find someone who is able to help you with it. Good luck. – Andrew Jan 20 '21 at 13:49