Get variables from Swift file with Native Modules for React Native? - objective-c

I have React Native app and I need to implement native modules on IOS with RCT_EXTERN_METHOD macros.
My steps:
Created Settings.swift file in XCode.
Created Bridging - Header.
Created Settings.m file in XCode.
Declared Class component and inside variable and function in Settings.swift file in Xcode.
Registered functions in Settings.m file with RCT_EXTERN_METHOD macros in XCode.
Imported Native Modules and used it in React Native project.
I need to use variables from my swift file like state in my React Native project and manage it with declared function from Swift file. When i call the function it works, but i can not get variables from Native Modules like NativeModules.Settings.getSwitchGeneralTagLog
i get undefined. Can you tell me please how can i get variable from Native Module? And is it good way to use it for state? Which the best way to manage state in Swift?
Settings.swift:
import Foundation
#objc(Settings)
class Settings: NSObject {
private var generalTagLog = false
#objc
func getSwitchGeneralTagLog() -> Bool {
return generalTagLog
}
#objc
func switchGeneralTagLog() -> Bool {
return generalTagLog ? false : true
}
#objc
static func requiresMainQueueSetup() -> Bool {
return true
}
}
Settings.m:
#import <Foundation/Foundation.h>
#import "React/RCTBridgeModule.h"
#interface RCT_EXTERN_MODULE(Settings, NSObject)
RCT_EXTERN_METHOD(switchGeneralTagLog)
#end

I am going to focus on one function and you can extend it based on that
Settings.m
RCT_EXTERN_METHOD(getSwitchGeneralTagLog:(RCTPromiseResolveBlock)promise rejector: (RCTPromiseRejectBlock)reject)
In the below scenario, we are not processing any exception
Settings.swift
#objc func getSwitchGeneralTagLog(_ promise: RCTPromiseResolveBlock, rejector reject: RCTPromiseRejectBlock) {
var isSwitch = false
// figure out what need to be done for the flag
promise(isSwitch)
}
in your RN Accept you can extract the result from promise and use it.

Related

Creating Dynamic Framework Xcode

I am trying to create a dynamic framework project, I can use to inject code and for reverse engineering purposes.
I've been successfully able to inject code using Objective C file "(.m)" file.
#import <Foundation/Foundation.h>
#import "CodeInjection-Swift.h" // This line gives error.
#interface CodeInjection: NSObject
#end
#implementation CodeInjection
static void __attribute__((constructor)) initialize(void){
NSLog(#"==== Code Injection in Action lolz====");
[[CodeInjectionSwift shared] performTask];
}
#end
I also have a file called "CodeInjectionSwift.swift"
import Foundation
import NetworkInterceptor
#objc class CodeInjectionSwift: NSObject {
#objc public static let shared = CodeInjectionSwift()
override private init(){}
#objc func performTask(){
let requestSniffers: [RequestSniffer] = [
RequestSniffer(requestEvaluator: AnyHttpRequestEvaluator(), handlers: [
SniffableRequestHandlerRegistrable.console(logginMode: .nslog).requestHandler()
])
]
let requestRedirectors: [RequestRedirector] = [
RequestRedirector(requestEvaluator: DomainHttpRequestEvaluator(domain: "www.antennahouse.com"), redirectableRequestHandler: AlternateUrlRequestRedirector(url: URL(string: "https://www.rhodeshouse.ox.ac.uk/media/1002/sample-pdf-file.pdf")!))
]
let networkConfig = NetworkInterceptorConfig(requestSniffers: requestSniffers,
requestRedirectors: requestRedirectors)
NetworkInterceptor.shared.setup(config: networkConfig)
NetworkInterceptor.shared.startRecording()
}
}
If I get rid of the #import "CodeInjection-Swift.h" and the line [[CodeInjectionSwift shared] performTask];
The NSLog prints and am able to build successfully. I was told that I have to import -Swift.h file to use swift classes in CondeInjection-swift.h
Error message is
"-Swift.h not found"
My goal is to build the framework, and be able to utilize "CodeInjectionSwift" functionality that uses import NetworkInterceptor

Issue with RCT_EXTERN_METHOD while converting React Native app with Objective-C to Swift

We have an existing React Native app that was written using Objective-C. I have been tasked with converting the Objective-C syntax to Swift.
Part of the work has been ensuring that the React Native bridge is encapsulated in one place and references external methods in Swift "Manager" classes.
Previously, we had a file called "CameraManager", which was exposed to React Native and had the following method:
RCT_REMAP_METHOD(takePicture, imageSaved:(RCTPromiseresolveBlock)resolve failedSavingImage:(RCTPromiseRejectBlock)reject) { ... }
I want to convert that to something like
#interface RCT_EXTERN_MODULE(CameraViewManager, NSObject)
RCT_EXTERN_METHOD(takePicture: imageSaved:(RCTPromiseResolveBlock *)resolve failedSavingImage:(RCTPromiseRejectBlock *)reject)
#end
This compiles fine, but when the call is made in the application to takePicture, the following exception is thrown.
Exception: 'takePicture::failedSavingImage: is not a recognized Objective-C method'. was thrown while invoking takePicture on target CameraViewManager with params ( ... )
In my CameraViewManager.swift file I have the following:
#objc
func takePicture(imageSaved resolve: #escaping RCTPromiseResolveBlock, failedSavingImage reject: RCTPromiseRejectBlock) -> Void { ... }
My overall familiarity with React Native is still a little less than optimal, so I'm not sure exactly what it's going to take to satisfy this.
Any ideas?
So the issue turns out to be that, despite exposing the method with the imageSaved parameter name, it's not looking for that name. It's looking for
#objc
func takePicture(_ resolve: #escaping RCTPromiseResolveBlock, failedSavingImage reject: RCTPromiseRejectBlock) -> Void { ... }
I have also changed my export in the bridge to:
RCT_EXTERN_METHOD(takePicture: (RCTPromiseResolveBlock *)resolve failedSavingImage:(RCTPromiseRejectBlock *)reject)

Bridge Class Functions in React Native

Say I have a class called ExampleClass.
Say I then write code like so:
#objc(ExampleClass)
class ExampleClass: NSObject {
#objc class func exampleFunc() -> Void {
}
}
With an Objective-C file header like so:
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#interface RCT_EXTERN_MODULE(ExampleClass, NSObject)
RCT_EXTERN_METHOD(exampleFunc)
#end
Which I then consume in my React Native app like so:
console.log('exampleClass', React.NativeModules.ExampleClass);
console.log('exampleFunc', React.NativeModules.ExampleClass.exampleFunc)
The first console log results in {exampleFunc: f}
The second results in undefined,
Calling the function: React.NativeModules.ExampleClass.exampleFunc() results in an app crash with:
Exception 'exampleFunc is not a recognized Objective-C method.' was thrown while invoking setupLogger on target ExampleClass with params (
While changing only the Swift so that it reads:
#objc(ExampleClass)
class ExampleClass: NSObject {
#obj func exampleFunc() -> Void {
}
}
results in calling the function (which, yes) does nothing at present.
How can I expose class level variables? I am trying to write functional Swift, and I am using class methods to simulate structs.
I believe the problem is that RCT_EXPORT_METHOD() only works on instance methods, not class methods, according to my own similar problem and some discussion here: https://github.com/facebook/react-native/issues/2311
My use case is trying to bridge a getInstance() method for a Swift singleton class. This is problematic because when you reference NativeModules.ExampleClass from javascript, which has been exported with RCT_EXTERN_MODULE(), RN calls init() on its own, which you don't want for a singleton (init() reference: https://samwize.com/2017/02/09/calling-a-view-controller-function-from-react-native/)
The best way I've found to accomplish this is pretty ugly. I have a dummy wrapper class that does nothing except call methods on the singleton, and this is the class I export to Objective C (and therefore to React Native). It's basically like this:
#objc(StupidWrapperClass)
class StupidWrapperClass : NSObject {
#objc(pseudoSingletonSomeMethod)
public func pseudoSingletonSomeMethod() {
let singleton = ActualClass.getInstance()
singleton.someMethod()
}
}
and then in the .m bridge file:
#interface RCT_EXTERN_MODULE(StupidWrapperClass, NSObject)
RCT_EXTERN_METHOD(pseudoSingletonSomeMethod)
#end
You could do something like this for a class method, too:
#objc(StupidWrapperClass)
class StupidWrapperClass : NSObject {
#objc(pseudoClassMethod)
public func pseudoClassMethod() {
ActualClass.theRealClassMethod()
}
}
I know I'm kinda late to the party but I recently faced the same problem and I fixed it using a different approach. Adding to the answer given above by #thejoelpatrol, a different approach would be to store the object's reference created by react native in some static variable that would be accessible by you. Then we can use the variable to access the object created by react-native anytime.
Whenever React Native tries to instantiate the class, it would come to the init. inside the init, we can save the reference to the object created by RN.
#objc public class MyClass {
#objc public static var shared: MyClass?
init() {
MyClass.shared = self
}
}
The .m bridge file is as follows:
#interface RCT_EXTERN_MODULE(MyClass)
RCT_EXTERN_METHOD(myClassMethod)
#end

Got "is not a recognized Objective-C method" when bridging Swift to React-Native

I'm trying to bridge my React-Native 0.33 code to a super simple Swift method, following this guide but all I'm getting is show:(NSString *)name is not a recognized Objective-C method.
Here's my code:
SwitchManager.swift
import Foundation
#objc(SwitchManager)
class SwitchManager: NSObject {
#objc func show(name: String) -> Void {
NSLog("%#", name);
}
}
SwitchManagerBridge.h
#import "RCTBridgeModule.h"
#interface RCT_EXTERN_MODULE(SwitchManager, NSObject)
RCT_EXTERN_METHOD(show:(NSString *)name)
#end
SwitchManager-Bridging-Header.h
#import "RCTBridgeModule.h"
Then on my index.ios.js file I'm importing SwitchManager with import { SwitchManager } from 'NativeModules'; and calling SwitchManager.show('One');. This is where the error happened.
Not sure what's wrong.
This is a part of Swift 3's changes and can be solved by adding an underscore:
import Foundation
#objc(SwitchManager)
class SwitchManager: NSObject {
#objc func show(_ name: String) {
NSLog("%#", name);
}
}
See Swift 3's 0046 Proposal: Establish consistent label behavior across all parameters including first labels that is called out in the Swift.org migration guide under "Consistent first argument labels".
Basically, how Objective-C sees Swift methods has changed with Swift 3.
EDIT: This is still the case in Swift 4, see docs here under Omitting Argument Labels.
this worked for me in xcode 8.0 and swift 3
#objc func openPresentedViewController(_ name: String,name1: String,name2: String){
}
add _ to non labelled members
RCT_EXTERN_METHOD(methodName:(NSString *)name name1:(NSString *)name1 name2:(NSString *)name2)
as you can see in the objective c method name is nonlabeled parameter add _ to it in the swift method
I was fighting with this issue all day. Resolved by setting the Swift Compiler to use Legacy versions (XCode 8 is prefers Swift 3), so in:
Build Settings > Scroll down to 'Use Legacy Swift Language Version' set as Yes.
I had the same error because I had forgotten to put the decorator #objc before the function declaration

Is it possible to use Swift's Enum in Obj-C?

I'm trying to convert some of my Obj-C class to Swift. And some other Obj-C classes still using enum in that converted class. I searched In the Pre-Release Docs and couldn't find it or maybe I missed it. Is there a way to use Swift enum in Obj-C Class? Or a link to the doc of this issue?
This is how I declared my enum in my old Obj-C code and new Swift code.
my old Obj-C Code:
typedef NS_ENUM(NSInteger, SomeEnum)
{
SomeEnumA,
SomeEnumB,
SomeEnumC
};
#interface SomeClass : NSObject
...
#end
my new Swift Code:
enum SomeEnum: NSInteger
{
case A
case B
case C
};
class SomeClass: NSObject
{
...
}
Update: From the answers. It can't be done in Swift older version than 1.2. But according to this official Swift Blog. In Swift 1.2 that released along with XCode 6.3, You can use Swift Enum in Objective-C by adding #objc in front of enum
As of Swift version 1.2 (Xcode 6.3) you can. Simply prefix the enum declaration with #objc
#objc enum Bear: Int {
case Black, Grizzly, Polar
}
Shamelessly taken from the Swift Blog
Note: This would not work for String enums or enums with associated values. Your enum will need to be Int-bound
In Objective-C this would look like
Bear type = BearBlack;
switch (type) {
case BearBlack:
case BearGrizzly:
case BearPolar:
[self runLikeHell];
}
To expand on the selected answer...
It is possible to share Swift style enums between Swift and Objective-C using NS_ENUM().
They just need to be defined in an Objective-C context using NS_ENUM() and they are made available using Swift dot notation.
From the Using Swift with Cocoa and Objective-C
Swift imports as a Swift enumeration any C-style enumeration marked with the NS_ENUM macro. This means that the prefixes to enumeration value names are truncated when they are imported into Swift, whether they’re defined in system frameworks or in custom code.
Objective-C
typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};
Swift
let cellStyle: UITableViewCellStyle = .Default
From the Using Swift with Cocoa and Objective-C guide:
A Swift class or protocol must be marked with the #objc attribute to
be accessible and usable in Objective-C. [...]
You’ll have access to anything within a class or protocol that’s
marked with the #objc attribute as long as it’s compatible with
Objective-C. This excludes Swift-only features such as those listed
here:
Generics Tuples / Enumerations defined in Swift / Structures defined in
Swift / Top-level functions defined in Swift / Global variables defined in
Swift / Typealiases defined in Swift / Swift-style variadics / Nested types /
Curried functions
So, no, you can't use a Swift enum in an Objective-C class.
Swift 4.1, Xcode 9.4.1:
1) Swift enum must be prefixed with #objc and be Int type:
// in .swift file:
#objc enum CalendarPermission: Int {
case authorized
case denied
case restricted
case undetermined
}
2) Objective-C name is enum name + case name, eg CalendarPermissionAuthorized:
// in .m file:
// point to something that returns the enum type (`CalendarPermission` here)
CalendarPermission calPermission = ...;
// use the enum values with their adjusted names
switch (calPermission) {
case CalendarPermissionAuthorized:
{
// code here
break;
}
case CalendarPermissionDenied:
case CalendarPermissionRestricted:
{
// code here
break;
}
case CalendarPermissionUndetermined:
{
// code here
break;
}
}
And, of course, remember to import your Swift bridging header as the last item in the Objective-C file's import list:
#import "MyAppViewController.h"
#import "MyApp-Swift.h"
If you prefer to keep ObjC codes as-they-are, you could add a helper header file in your project:
Swift2Objc_Helper.h
in the header file add this enum type:
typedef NS_ENUM(NSInteger, SomeEnum4ObjC)
{
SomeEnumA,
SomeEnumB
};
There may be another place in your .m file to make a change: to include the hidden header file:
#import "[YourProjectName]-Swift.h"
replace [YourProjectName] with your project name. This header file expose all Swift defined #objc classes, enums to ObjC.
You may get a warning message about implicit conversion from enumeration type... It is OK.
By the way, you could use this header helper file to keep some ObjC codes such as #define constants.
If you (like me) really want to make use of String enums, you could make a specialized interface for objective-c. For example:
enum Icon: String {
case HelpIcon
case StarIcon
...
}
// Make use of string enum when available:
public func addIcon(icon: Icon) {
...
}
// Fall back on strings when string enum not available (objective-c):
public func addIcon(iconName:String) {
addIcon(Icon(rawValue: iconName))
}
Of course, this will not give you the convenience of auto-complete (unless you define additional constants in the objective-c environment).
After researching this, I kept finding only partial answers, so I created an entire example of a Swift App bridged to Objective C that has Swift enums used by Objective C code and Objective C enums used by Swift code. It is a simple Xcode project that you can run and experiment with. It was written using Xcode 10.3 with Swift 5.0
Example Project
In case you are trying to observe an enum which looks like this:
enum EnumName: String {
case one = "One"
case two = "Two"
}
this workaround helped me.
Observable Class:
create #objc dynamic var observable: String?
create your enum instance like this:
private var _enumName: EnumName? {
didSet {
observable = _enumName!.rawValue
}
}
Observer Class:
create private var _enumName: EnumName?
create private let _instance = ObservableClass()
create
private var _enumObserver: NSKeyValueObservation = _instance.observe(\.observable, options: .new, changeHandler: { [weak self] (_, value) in
guard let newValue = value.newValue else { return }
self?._enumName = EnumName(rawValue: period)!
})
Than's it. Now each time you change the _enumName in the observable class, an appropriate instance on the observer class will be immediately updated as well.
This is of course an oversimplified implementation, but it should give you an idea of how to observe KVO-incompatible properties.
this might help a little more
Problem statement :- I have enum in swift class, which I am accessing form other swift classes, and Now I need to access it form my one of the objective C class.
Before accessing it from objective-c class :-
enum NTCType {
case RETRYNOW
case RETRYAFTER
}
var viewType: NTCType?
Changes for accessing it from objective c class
#objc enum NTCType :Int {
case RETRYNOW
case RETRYAFTER
}
and add a function to pass it on the value
#objc func setNtc(view:NTCType) {
self.viewType = view; // assign value to the variable
}