Note
This example is focused on writing logic/functions and may not be applicable to creating user interfaces.
Proof of concept/exemplification of how to write React Native Modules for macOS using Swift. Includes asynchronous and synchronous examples interacting with Apple MusicKit.
This almost entirely also applies to iOS, though for iOS you should probably instead use the Expo Modules API.
- App.tsx
- macos/macOSNativeModuleExample-macOS/MusicKitModule.swift
- macos/macOSNativeModuleExample-macOS/MusicKitModule.m
Screen.Recording.2023-12-22.at.6.53.20.PM.mov
Instructions
-
Install React Native for macOS
Do you want to install CocoaPods now? y
You'll want to make sure your project can build/run using Xcode.
⚠️ Build error: "Command PhaseScriptExecution failed with a nonzero exit code"There may be other better solutions for this such as changing Node related configuration or updating CocoaPods, but this worked for me:
Modify
node_modules/react-native/scripts/find-node.sh
@ L7- set -e + set +e
-
From project root dir run
xed -b macos
to open Xcode. -
Navigate to the folder containing
AppDelegate
. -
Create a new macOS Swift file.
The name you use for this file will be reused throughout the project including in your React code. Leave the options to default and create. I'm naming mine
MusicKitModule
as I'll be exporting some methods that utilize Apple MusicKit. Suffixed withModule
to prevent confusion, but use whatever naming you like. -
Create the bridging header automatically.
The name of this file is automatically prefixed by your Xcode project name.
-
Add
#import <React/RCTBridgeModule.h>
to the...-Bridging-Header.h
file. -
Add the following boilerplate to your Swift file
@objc(YourFileName) class YourFileName: NSObject { @objc static func requiresMainQueueSetup() -> Bool { return true } }
-
Create a new Objective-C file with the same name
-
Add
#import <React/RCTBridgeModule.h>
to theYourFileName.m
file. -
In
macos/YourProjectName-macOS/Info.plist
, add the following key/string pair<key>NSSupportsSuddenTermination</key> <true/> + <key>NSAppleMusicUsageDescription</key> + <string>A message that tells the user why the app is requesting access to the user's media library.</string> </dict> </plist>
-
Test by running the Xcode project.
-
Congratulations! You've completed all boilerplate. See below for examples on creating methods.
Example
ℹ️ There also exists the ability to create callback based methods using
RCTResponseSenderBlock, RCTResponseErrorBlock
, but I will not be using those here.
- Expose the function using
@objc
- Last two function parameters must be
RCTPromiseResolveBlock, RCTPromiseRejectBlock
- Use
@escaping
to useresolve
orreject
in aTask
@objc(MusicKitModule) class MusicKitModule: NSObject {
@objc static func requiresMainQueueSetup() -> Bool { return true }
/// Asynchronous
@objc func requestAuthorization(_ resolve: @escaping(RCTPromiseResolveBlock), rejecter reject: RCTPromiseRejectBlock) {
if #available(macOS 12.0, *) {
Task {
let status = (await MusicAuthorization.request()).rawValue
resolve(status)
}
} else {
resolve("Unsupported iOS")
}
}
}
- Register the module once using
RCT_EXTERN_MODULE
- Register a method using
RCT_EXTERN_METHOD
, providing the method signature.
@interface RCT_EXTERN_MODULE(MusicKitModule, NSObject)
RCT_EXTERN_METHOD(requestAuthorization: (RCTPromiseResolveBlock)resolve
rejecter: (RCTPromiseRejectBlock)reject)
@end
This is a minimal example, you could expand this by following this guide.
- Import
NativeModules
- Your module is a property on the
NativeModules
import, corresponds to the same file name used in ObjC/Swift. - Use
await
(or chain.then()
)
import {NativeModules} from 'react-native';
// Optionally destructure
const {MusicKitModule} = NativeModules;
const status = await MusicKitModule.requestAuthorization();
Example
⚠️ Runs a blocking function on the main thread. Highly discouraged by React Native. Use at own risk and please know what you're doing.
- Expose the function using
@objc
@objc(MusicKitModule) class MusicKitModule: NSObject {
@objc static func requiresMainQueueSetup() -> Bool { return true }
/// Synchronous (main thread)
@objc func currentAuthorizationStatus() -> String {
if #available(macOS 12.0, *) {
let status = MusicAuthorization.currentStatus.rawValue
return status
} else {
return "Unsupported iOS"
}
}
}
- Register the module once using
RCT_EXTERN_MODULE
- Register a method using
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD
, providing the method signature.
@interface RCT_EXTERN_MODULE(MusicKitModule, NSObject)
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(currentAuthorizationStatus)
@end
This is a minimal example, you could expand this by following this guide.
- Import
NativeModules
- Your module is a property on the
NativeModules
import, corresponds to the same file name used in ObjC/Swift.
import {NativeModules} from 'react-native';
// Optionally destructure
const {MusicKitModule} = NativeModules;
const status = MusicKitModule.currentAuthorizationStatus();