diff --git a/ReactBrownfield.podspec b/ReactBrownfield.podspec index a4442c79..b65c4cf6 100644 --- a/ReactBrownfield.podspec +++ b/ReactBrownfield.podspec @@ -15,8 +15,13 @@ Pod::Spec.new do |spec| spec.module_name = "ReactBrownfield" spec.source = { :git => "git@github.com:callstack/react-native-brownfield.git", :tag => "#{spec.version}" } spec.source_files = "ios/**/*.{h,m,mm,swift}" - spec.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } + spec.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + 'OTHER_SWIFT_FLAGS' => "-enable-experimental-feature AccessLevelOnImport" + } spec.dependency 'ReactAppDependencyProvider' + add_dependency(spec, "React-RCTAppDelegate") + install_modules_dependencies(spec) end diff --git a/docs/OBJECTIVE_C.md b/docs/OBJECTIVE_C.md index b1b43fc2..89c26cda 100644 --- a/docs/OBJECTIVE_C.md +++ b/docs/OBJECTIVE_C.md @@ -35,7 +35,6 @@ Examples: | `entryFile` | `NSString` | `index` | Path to JavaScript root. | | `fallbackResource` | `NSString` | `nil` | Path to bundle fallback resource. | | `bundlePath` | `NSString` | `main.jsbundle`| Path to bundle fallback resource. | -| `reactNativeFactory` | `RCTReactNativeFactory` | `nil` | React Native factory instance. | --- @@ -70,6 +69,24 @@ Examples: }, launchOptions]; ``` +`view` + +Creates a React Native view for the specified module name. + +Params: + +| Param | Required | Type | Description | +| ----------------------- | -------- | ----------------- | ----------------------------------------------------- | +| `moduleName` | Yes | `NSString` | Name of React Native component registered to `AppRegistry`. | +| `initialProps` | No | `NSDictionary` | Initial properties to be passed to React Native component. | +| `launchOptions` | No | `NSDictionary` | Launch options, typically passed from AppDelegate. | + +Examples: + +```objc +UIView *view = [[ReactNativeBrownfield shared] viewWithModuleName:@"ReactNative" initialProps:@{@"score": @12}]; +``` + --- #### `ReactNativeViewController` diff --git a/docs/SWIFT.md b/docs/SWIFT.md index 1bf032eb..a38bbd1a 100644 --- a/docs/SWIFT.md +++ b/docs/SWIFT.md @@ -35,12 +35,12 @@ ReactNativeBrownfield.shared | `entryFile` | `String` | index | Path to JavaScript root. | | `fallbackResource` | `String?` | nil | Path to bundle fallback resource. | | `bundlePath` | `String` | main.jsbundle | Path to bundle fallback resource. | -| `reactNativeFactory` | `RCTReactNativeFactory?` | nil | React Native factory instance. | --- **Methods:** + `startReactNative` Starts React Native. You can use it to initialize React Native in your app. @@ -70,6 +70,27 @@ ReactNativeBrownfield.shared.startReactNative(onBundleLoaded: { }, launchOptions: launchOptions) ``` +`view` + +Creates a React Native view for the specified module name. + +Params: + +| Param | Required | Type | Description | +| ----------------------- | -------- | ------------------- | ----------------------------------------------------- | +| `moduleName` | Yes | `String` | Name of React Native component registered to `AppRegistry`. | +| `initialProps` | No | `[AnyHashable: Any]?` | Initial properties to be passed to React Native component. | +| `launchOptions` | No | `[AnyHashable: Any]?` | Launch options, typically passed from AppDelegate. | + +Examples: + +```swift +let view = ReactNativeBrownfield.shared.view( + moduleName: "ReactNative", + initialProps: ["score": 12] +) +``` + --- #### Initialization Approaches @@ -238,5 +259,3 @@ NavigationLink("Open React Native Screen") { ### Example You can find an example app [here](../example/swift). - - diff --git a/example/swift/Podfile b/example/swift/Podfile index 9333a16c..d6cb328d 100644 --- a/example/swift/Podfile +++ b/example/swift/Podfile @@ -28,7 +28,7 @@ target 'SwiftExample' do installer, config[:reactNativePath], :mac_catalyst_enabled => false, - :ccache_enabled => true + # :ccache_enabled => true ) end end diff --git a/example/swift/Podfile.lock b/example/swift/Podfile.lock index 22dbedbd..a4409f7f 100644 --- a/example/swift/Podfile.lock +++ b/example/swift/Podfile.lock @@ -1527,7 +1527,7 @@ PODS: - React-jsi (= 0.78.0) - ReactAppDependencyProvider (0.78.0): - ReactCodegen - - ReactBrownfield (1.0.0-rc.0): + - ReactBrownfield (1.0.0-rc.1): - DoubleConversion - glog - hermes-engine @@ -1541,6 +1541,7 @@ PODS: - React-graphics - React-ImageManager - React-NativeModulesApple + - React-RCTAppDelegate - React-RCTFabric - React-rendererdebug - React-utils @@ -1944,13 +1945,13 @@ SPEC CHECKSUMS: React-timing: bb220a53a795ed57976a4855c521f3de2f298fe5 React-utils: 3b054aaebe658fc710a8d239d0e4b9fd3e0b78f9 ReactAppDependencyProvider: a1fb08dfdc7ebc387b2e54cfc9decd283ed821d8 - ReactBrownfield: e05f198df083698ed9942ace80fd90da6e9298de + ReactBrownfield: f2e119f0241af9303f55556a63385efc58ce49b7 ReactCodegen: 008c319179d681a6a00966edfc67fda68f9fbb2e ReactCommon: 0c097b53f03d6bf166edbcd0915da32f3015dd90 RNScreens: 0d4cb9afe052607ad0aa71f645a88bb7c7f2e64c SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: afd04ff05ebe0121a00c468a8a3c8080221cb14c -PODFILE CHECKSUM: b5dc5f822e98018cbffd7384516e146bba9f6d99 +PODFILE CHECKSUM: dd9bed4f3821ab08d739dbded562f749348cd4d7 COCOAPODS: 1.15.2 diff --git a/example/swift/SwiftExample.xcodeproj/project.pbxproj b/example/swift/SwiftExample.xcodeproj/project.pbxproj index 2de5fbd9..8272e04e 100644 --- a/example/swift/SwiftExample.xcodeproj/project.pbxproj +++ b/example/swift/SwiftExample.xcodeproj/project.pbxproj @@ -328,7 +328,10 @@ ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(inherited)"; - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; @@ -392,7 +395,10 @@ MTL_FAST_MATH = YES; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(inherited)"; - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/ios/ReactNativeBrownfield.swift b/ios/ReactNativeBrownfield.swift index b0fcafc2..3dd07c95 100644 --- a/ios/ReactNativeBrownfield.swift +++ b/ios/ReactNativeBrownfield.swift @@ -1,16 +1,44 @@ -import React -import React_RCTAppDelegate -import ReactAppDependencyProvider +import UIKit +internal import React +internal import React_RCTAppDelegate +internal import ReactAppDependencyProvider -@objc public class ReactNativeBrownfield: RCTDefaultReactNativeFactoryDelegate { - @objc public static let shared = ReactNativeBrownfield() - private var onBundleLoaded: (() -> Void)? +class ReactNativeBrownfieldDelegate: RCTDefaultReactNativeFactoryDelegate { + var entryFile = "index" + // MARK: - RCTReactNativeFactoryDelegate Methods + + override func sourceURL(for bridge: RCTBridge) -> URL? { + return bundleURL() + } + + public override func bundleURL() -> URL? { +#if DEBUG + return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: entryFile) +#else + let resourceURLComponents = bundlePath.components(separatedBy: ".") + let withoutLast = resourceURLComponents[..<(resourceURLComponents.count - 1)] + let resourceName = withoutLast.joined() + let fileExtension = resourceURLComponents.last ?? "" + + return Bundle.main.url(forResource: resourceName, withExtension: fileExtension) +#endif + } +} +@objc public class ReactNativeBrownfield: NSObject { + public static let shared = ReactNativeBrownfield() + private var onBundleLoaded: (() -> Void)? + private var delegate = ReactNativeBrownfieldDelegate() + /** * Path to JavaScript root. * Default value: "index" */ - @objc public var entryFile: String = "index" + @objc public var entryFile: String = "index" { + didSet { + delegate.entryFile = entryFile + } + } /** * Path to bundle fallback resource. * Default value: nil @@ -25,14 +53,14 @@ import ReactAppDependencyProvider * React Native factory instance created when starting React Native. * Default value: nil */ - @objc public var reactNativeFactory: RCTReactNativeFactory? = nil + private var reactNativeFactory: RCTReactNativeFactory? = nil /** * Root view factory used to create React Native views. */ - @objc lazy public var rootViewFactory: RCTRootViewFactory? = { + lazy private var rootViewFactory: RCTRootViewFactory? = { return reactNativeFactory?.rootViewFactory }() - + /** * Starts React Native with default parameters. */ @@ -40,9 +68,21 @@ import ReactAppDependencyProvider startReactNative(onBundleLoaded: nil) } + @objc public func view( + moduleName: String, + initialProps: [AnyHashable: Any]?, + launchOptions: [AnyHashable: Any]? = nil + ) -> UIView? { + reactNativeFactory?.rootViewFactory.view( + withModuleName: moduleName, + initialProperties: initialProps, + launchOptions: launchOptions + ) + } + /** * Starts React Native with optional callback when bundle is loaded. - * + * * @param onBundleLoaded Optional callback invoked after JS bundle is fully loaded. */ @objc public func startReactNative(onBundleLoaded: (() -> Void)?) { @@ -51,15 +91,15 @@ import ReactAppDependencyProvider /** * Starts React Native with optional callback and launch options. - * + * * @param onBundleLoaded Optional callback invoked after JS bundle is fully loaded. * @param launchOptions Launch options, typically passed from AppDelegate. */ @objc public func startReactNative(onBundleLoaded: (() -> Void)?, launchOptions: [AnyHashable: Any]?) { guard reactNativeFactory == nil else { return } - self.dependencyProvider = RCTAppDependencyProvider() - self.reactNativeFactory = RCTReactNativeFactory(delegate: self) + delegate.dependencyProvider = RCTAppDependencyProvider() + self.reactNativeFactory = RCTReactNativeFactory(delegate: delegate) if let onBundleLoaded { self.onBundleLoaded = onBundleLoaded @@ -86,25 +126,6 @@ import ReactAppDependencyProvider onBundleLoaded = nil NotificationCenter.default.removeObserver(self) } - - // MARK: - RCTReactNativeFactoryDelegate Methods - - @objc public override func sourceURL(for bridge: RCTBridge) -> URL? { - return bundleURL() - } - - public override func bundleURL() -> URL? { -#if DEBUG - return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: entryFile) -#else - let resourceURLComponents = bundlePath.components(separatedBy: ".") - let withoutLast = resourceURLComponents[..<(resourceURLComponents.count - 1)] - let resourceName = withoutLast.joined() - let fileExtension = resourceURLComponents.last ?? "" - - return Bundle.main.url(forResource: resourceName, withExtension: fileExtension) -#endif - } } extension Notification.Name { diff --git a/ios/ReactNativeBrownfieldModule.h b/ios/ReactNativeBrownfieldModule.h index b14f9024..b29e9533 100644 --- a/ios/ReactNativeBrownfieldModule.h +++ b/ios/ReactNativeBrownfieldModule.h @@ -1,6 +1,5 @@ #ifdef __cplusplus -#import #import @interface ReactNativeBrownfieldModule : NSObject diff --git a/ios/ReactNativeBrownfieldModule.swift b/ios/ReactNativeBrownfieldModule.swift index 5c7ced1f..d783cb9d 100644 --- a/ios/ReactNativeBrownfieldModule.swift +++ b/ios/ReactNativeBrownfieldModule.swift @@ -1,4 +1,4 @@ -import React +internal import React @objcMembers public class ReactNativeBrownfieldModuleImpl: NSObject { @@ -8,7 +8,7 @@ public class ReactNativeBrownfieldModuleImpl: NSObject { NotificationCenter.default.post(name: Notification.Name.togglePopGestureRecognizer, object: nil, userInfo: userInfo) } } - + static public func popToNative(animated: Bool) { let userInfo = ["animated": animated] DispatchQueue.main.async { diff --git a/ios/ReactNativeViewController.swift b/ios/ReactNativeViewController.swift index d16f0d6d..07a31f47 100644 --- a/ios/ReactNativeViewController.swift +++ b/ios/ReactNativeViewController.swift @@ -1,38 +1,37 @@ import UIKit -import React +internal import React @objc public class ReactNativeViewController: UIViewController { private var moduleName: String private var initialProperties: [String: Any]? - + @objc public init(moduleName: String, initialProperties: [String: Any]? = nil) { self.moduleName = moduleName self.initialProperties = initialProperties super.init(nibName: nil, bundle: nil) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + public override func viewDidLoad() { super.viewDidLoad() - - guard let factory = ReactNativeBrownfield.shared.rootViewFactory else { - print("Error: You need to start React Native in order to use ReactNativeViewController, make sure to run BridgeManager.shared.startReactNative() before instantiating it.") - return - } - + if !moduleName.isEmpty { - view = factory.view(withModuleName: moduleName, initialProperties: initialProperties) - + view = ReactNativeBrownfield.shared.view( + moduleName: moduleName, + initialProps: initialProperties, + launchOptions: nil + ) + NotificationCenter.default.addObserver( self, selector: #selector(togglePopGestureRecognizer(_:)), name: NSNotification.Name.togglePopGestureRecognizer, object: nil ) - + NotificationCenter.default.addObserver( self, selector: #selector(popToNative(_:)), @@ -41,24 +40,24 @@ import React ) } } - + deinit { NotificationCenter.default.removeObserver(self) } - + @objc private func togglePopGestureRecognizer(_ notification: Notification) { guard let userInfo = notification.userInfo, let enabled = userInfo["enabled"] as? Bool else { return } - + DispatchQueue.main.async { [weak self] in self?.navigationController?.interactivePopGestureRecognizer?.isEnabled = enabled } } - + @objc private func popToNative(_ notification: Notification) { guard let userInfo = notification.userInfo, let animated = userInfo["animated"] as? Bool else { return } - + DispatchQueue.main.async { [weak self] in self?.navigationController?.popViewController(animated: animated) }