react-native-unimodules
react-native-unimodules copied to clipboard
Using React Native Unimodules in Swift
Hello!
I would like to use this Unimodules project in my React Native project. However, the files that need to be modified under the Configure for iOS section are AppDelegate.h and AppDelegate.m
However, I converted my AppDelegate into a Swift file (with a bridge file to accompany it). I was wondering what is the equivalent of these two files shown in this gist (https://gist.github.com/brentvatne/1ece8c32a3c5c9d0ac3a470460c65603) in Swift?
Here is my code:
import Foundation
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var bridge: RCTBridge!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let jsCodeLocation: URL
jsCodeLocation = RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index", fallbackResource:nil)
let rootView = RCTRootView(bundleURL: jsCodeLocation, moduleName: "PROJECT", initialProperties: nil, launchOptions: launchOptions)
let rootViewController = UIViewController()
rootViewController.view = rootView
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = rootViewController
self.window?.makeKeyAndVisible()
return true
}
}
@brentvatne Since you authored the gist, I believe you might have some insight!
Hi! I'm having a similar issue, is there anywhere to look for using this with swift?
@sjchmiela @tsapeta Hey, sorry to be a pain :( I wouldn't be bothering y'all if I hadn't spent like four days bashing my head trying to convert the AppDelegate.h/m into swift. Unfortunately, my understanding of what's happening close to the metal isn't so good.
Here's a starting point that I may have found, but I'm not sure how to interpret it https://stackoverflow.com/questions/37142310/dependency-injection-in-react-native-modules/47468905#47468905
Any information at this point would be hugely appreciated, even if that information were "right now we don't support swift"
Hey @nmutalik, could you please provide a working AppDelegate.swift that you're trying to integrate with unimodules?
Here you are!
import Foundation
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var bridge: RCTBridge!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
let jsCodeLocation: URL
jsCodeLocation = RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index", fallbackResource:nil)
let rootView = RCTRootView(bundleURL: jsCodeLocation, moduleName: "FireflyHealth", initialProperties: nil, launchOptions: launchOptions)
let rootViewController = UIViewController()
rootViewController.view = rootView
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = rootViewController
self.window?.makeKeyAndVisible()
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
AppEvents.activateApp()
}
// Orientation control library
// https://github.com/yamill/react-native-orientation
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return Orientation.getOrientation()
}
// DEEPLINKING
func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
return RCTLinkingManager.application(application, open: url, options: options)
}
// Only if your app is using [Universal Links](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/UniversalLinks.html).
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
return RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler as! ([Any]?) -> Void)
}
}
@sjchmiela we're very excited to be able to use this package, but unfortunately we're still stuck on this step
Step-by-step
Add moduleRegistryAdapter instance variable
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var bridge: RCTBridge!
+ var moduleRegistryAdapter: UMModuleRegistryAdapter!
Initialize moduleRegistryAdapter at the top of application:didFinishLaunching:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+ self.moduleRegistryAdapter = UMModuleRegistryAdapter(moduleRegistryAdapter: UMModuleRegistryProvider())
FirebaseApp.configure()
Add RCTBridgeDelegate protocol to AppDelegate
-class AppDelegate: UIResponder, UIApplicationDelegate {
+class AppDelegate: UIResponder, UIApplicationDelegate, RCTBridgeDelegate {
Initialize RCTBridge ourselves so we can set ourselves as the delegate
+ let bridge = RCTBridge(delegate: self, launchOptions: launchOptions)
- let rootView = RCTRootView(bundleURL: jsCodeLocation, moduleName: "FireflyHealth", initialProperties: nil, launchOptions: launchOptions)
+ let rootView = RCTRootView(bridge: bridge, moduleName: "FireflyHealth", initialProperties: nil)
Implement extraModulesForBridge method
Add the following method to inside of AppDelegate
func extraModules(for bridge: RCTBridge!) -> [RCTBridgeModule]! {
var extraModules = self.moduleRegistryAdapter.extraModulesForBridge(bridge)
return extraModules
}
Move url to RCTBridgeDelegate method
Add the following method
func sourceURL(for bridge: RCTBridge!) -> URL! {
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index", fallbackResource:nil)
}
and remove let url… from inside application:didFinishLaunching
End result
import Foundation
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, RCTBridgeDelegate {
var window: UIWindow?
var bridge: RCTBridge!
var moduleRegistryAdapter: UMModuleRegistryAdapter!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.moduleRegistryAdapter = UMModuleRegistryAdapter(moduleRegistryAdapter: UMModuleRegistryProvider())
FirebaseApp.configure()
let bridge = RCTBridge(delegate: self, launchOptions: launchOptions)
let rootView = RCTRootView(bridge: bridge, moduleName: "FireflyHealth", initialProperties: nil)
let rootViewController = UIViewController()
rootViewController.view = rootView
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = rootViewController
self.window?.makeKeyAndVisible()
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
AppEvents.activateApp()
}
// Orientation control library
// https://github.com/yamill/react-native-orientation
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return Orientation.getOrientation()
}
// DEEPLINKING
func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
return RCTLinkingManager.application(application, open: url, options: options)
}
// Only if your app is using [Universal Links](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/UniversalLinks.html).
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
return RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler as! ([Any]?) -> Void)
}
func extraModules(for bridge: RCTBridge!) -> [RCTBridgeModule]! {
var extraModules = self.moduleRegistryAdapter.extraModulesForBridge(bridge)
return extraModules
}
func sourceURL(for bridge: RCTBridge!) -> URL! {
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index", fallbackResource:nil)
}
}
Let me know if there are any compile errors after those changes (there probably will be).
@nmutalik - if this works for you, can you open a PR with some instructions on how to use the library with swift?
Can confirm that this does not work (SDK 37). But I have an easy fix.
Steps
- Bootstrap a new project
npx crna- select the default template.
- Create new file (⌘N)
- select swift
- name it
AppDelegate.swift
- Confirm generating a bridging header
myproject-Bridging-Header.h - Delete
AppDelegate.m,AppDelegate.h,main.m - Populate files:
AppDelegate.swift
import Foundation
@UIApplicationMain
class AppDelegate: UMAppDelegateWrapper, EXUpdatesAppControllerDelegate {
var launchOptions: [UIApplication.LaunchOptionsKey: Any]?
var moduleRegistryAdapter: UMModuleRegistryAdapter!
override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
moduleRegistryAdapter = UMModuleRegistryAdapter(moduleRegistryProvider: UMModuleRegistryProvider())
self.launchOptions = launchOptions
window = UIWindow(frame: UIScreen.main.bounds)
#if DEBUG
initializeReactNativeApp()
#else
let controller = EXUpdatesAppController.sharedInstance()
controller.delegate = self
controller.startAndShowLaunchScreen(window!)
#endif
super.application(application, didFinishLaunchingWithOptions: launchOptions);
return true
}
@discardableResult
func initializeReactNativeApp() -> RCTBridge? {
guard let bridge = RCTBridge(delegate: self, launchOptions: launchOptions) else { return nil }
let rootView = RCTRootView(bridge: bridge, moduleName: "main", initialProperties: nil)
let rootViewController = UIViewController()
rootView.backgroundColor = UIColor.white;
rootViewController.view = rootView
window?.rootViewController = rootViewController
window?.makeKeyAndVisible()
return bridge
}
func appController(_ appController: EXUpdatesAppController, didStartWithSuccess success: Bool) {
appController.bridge = initializeReactNativeApp();
}
}
// MARK: - RCTBridgeDelegate
extension AppDelegate: RCTBridgeDelegate {
func sourceURL(for bridge: RCTBridge!) -> URL! {
#if DEBUG
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index", fallbackResource:nil)
#else
return EXUpdatesAppController.sharedInstance().launchAssetUrl!
#endif
}
func extraModules(for bridge: RCTBridge!) -> [RCTBridgeModule]! {
let extraModules = moduleRegistryAdapter.extraModules(for: bridge)
// You can inject any extra modules that you would like here, more information at:
// https://facebook.github.io/react-native/docs/native-modules-ios.html#dependency-injection
return extraModules
}
}
myproject-Bridging-Header.h
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
// Unimodules
#import <UMCore/UMModuleRegistry.h>
#import <UMReactNativeAdapter/UMNativeModulesProxy.h>
#import <UMReactNativeAdapter/UMModuleRegistryAdapter.h>
#import <UMCore/UMAppDelegateWrapper.h>
// OTA Updates
#import <EXUpdates/EXUpdatesAppController.h>
// React
#import <React/RCTBridgeModule.h>
#import <React/RCTBridge.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTRootView.h>
#import <React/RCTUtils.h>
#import <React/RCTConvert.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTBridgeDelegate.h>
Issue
The project builds properly but throws a runtime error:
2020-05-27 15:23:25.171761-0700 swiftdemo[40384:6054772] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[swiftdemo.AppDelegate setWindow:]: unrecognized selector sent to instance 0x600000905a60'
With exception breakpoints enabled, the project fails on UMAppDelegateWrapper.m#L23.
Solution
Simply add the following to your pod @unimodules/core/ios/UMCore/UMAppDelegateWrapper.h
// Copyright © 2018 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface UMAppDelegateWrapper : UIResponder <UIApplicationDelegate>
+ @property (nullable, nonatomic, strong) UIWindow *window;
@end
NS_ASSUME_NONNULL_END
- Notice
DEBUGwill not work unless this is configured: https://stackoverflow.com/a/24112024/4047926 - Upstream solution https://github.com/expo/expo/pull/8526