use Siri intent in react-native - ios

I am going to make a relation between Siri Intent and React-native, but I have an error when I am going to call a class from Intent Extension.
how can I call swift class from different target in Intent Extension. The target of my ReactIntegration.swift is main project which is going to be called from GetCommandIntentHandler.swift (intent extension). Here is the source of two files.
The root and Target of ReactIntegration.swift
the source code is
import Foundation
#objc(ReactIntegration)
class ReactIntegration: RCTEventEmitter {
private var count = 0
#objc
func AskRequestFromEnergica(request: String) {
sendEvent(withName: "AskRequestFromEnergica", body: ["request": request])
}
override func supportedEvents() -> [String]! {
return ["AskRequestFromEnergica"]
}
override func constantsToExport() -> [AnyHashable : Any]! {
return ["initialCount": count]
}
override static func requiresMainQueueSetup() -> Bool {
return true
}
}
The target of GetCommandIntentHandler.swift
the source code is
import Foundation
class GetCommandInentHandler: NSObject, GetCommandIntentHandling{
func handle(intent: GetCommandIntent, completion: #escaping (GetCommandIntentResponse) -> Void) {
let req = intent.request!
let res = "the result is " + req
let ri = ReactIntegration()
ri.AskRequestFromEnergica(res);
let response = GetCommandIntentResponse.success(result: res)
completion(response)
}
the error is 'Cannot find 'ReactIntegration' in scope'.
Please let me know how to solve it. Thanks

Related

How to implement Paytm all in one sdk in react native giving error after adding AllInOneSDKSwiftWrapper.swift

I have created this AllInOneSDKSwiftWrapper.swift class then xcode asked me for bridging header I allowed it after adding code which is given in paytm doc it is giving multiple errors in file. i have also added AllInOneSDKSwiftWrapper.m file. i am not ios developer so i don't know how to fix and what should i write in empty generated bridging header file.
Paytm react native support documentation for both iOS and Android is very poor as it doesn't have the complete set of information required to setup and run the code in native end.
Below is the implementation for iOS running Xcode v12.0, Swift 5, RN v0.63.2
Download AppInvokeSDK.framework
Copy and paste the framework inside "ios/YourApp" folder
Open your workspace and click on File -> Add Files
Navigate to the framework, select it and click on Add (This is important, don't drag and drop as mentioned in the Paytm documentation)
In TARGETS/YourApp under General tab, the framework should be linked in the section Frameworks, Libraries, and Embedded Content
Change the Embed option to Embed and Sign
Add the below path link in PROJECT -> YourApp -> Build settings. Search for Header Search Paths and add the below path
$(SRCROOT)/../node_modules/react-native/Libraries/LinkingIOS
Import headers in Bridging-Header.h
#import "React/RCTBridgeModule.h"
#import "React/RCTEventEmitter.h"
AllInOneSDKSwiftWrapper.h
#import <Foundation/Foundation.h>
#import "React/RCTBridgeModule.h"
#if __has_include("RCTEventEmitter.h")
#import "RCTEventEmitter.h"
#else
#import <React/RCTEventEmitter.h>
#endif
#interface RCT_EXTERN_MODULE(AllInOneSDKSwiftWrapper, RCTEventEmitter)
RCT_EXTERN_METHOD(openPaytm:(NSString *)mid
orderId:(NSString *)oid
transactionToken:(NSString *)txnTkn
amount:(NSString *)amt
callbackUrl:(NSString *)url
isStaging: (BOOL)isStaging)
#end
AllInOneSDKSwiftWrapper.swift
import Foundation
import AppInvokeSDK
import UIKit
class EventEmitter {
/// Shared Instance.
public static var sharedInstance = EventEmitter()
// ReactNativeEventEmitter is instantiated by React Native with the bridge.
private static var eventEmitter: AllInOneSDKSwiftWrapper!
private init() {}
// When React Native instantiates the emitter it is registered here.
func registerEventEmitter(eventEmitter: AllInOneSDKSwiftWrapper) {
EventEmitter.eventEmitter = eventEmitter
}
func dispatch(name: String, body: Any?) {
EventEmitter.eventEmitter.sendEvent(withName: name, body: body)
}
/// All Events which must be support by React Native.
lazy var allEvents: [String] = {
var allEventNames: [String] = ["responseIfNotInstalled", "responseIfPaytmInstalled"]
// Append all events here
return allEventNames
}()
}
#objc(AllInOneSDKSwiftWrapper)
class AllInOneSDKSwiftWrapper: RCTEventEmitter, AIDelegate {
private let handler = AIHandler()
var viewController = UIApplication.shared.windows.first?.rootViewController
override static func moduleName() -> String! {
return "AllInOneSDKSwiftWrapper"
}
override init() {
super.init()
EventEmitter.sharedInstance.registerEventEmitter(eventEmitter: self)
NotificationCenter.default.addObserver(self, selector: #selector(getAppInvokeResponse(notification:)), name: NSNotification.Name(rawValue: "appInvokeNotification"), object: nil)
}
#objc func getAppInvokeResponse(notification: NSNotification) {
if let userInfo = notification.userInfo {
let url = userInfo["appInvokeNotificationKey"] as? String
let response = self.separateDeeplinkParamsIn(url: url, byRemovingParams: nil)
let alert = UIAlertController(title: "Response", message: response.description, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
self.viewController?.present(alert, animated: true, completion: nil)
sendEvent(withName: "responseIfPaytmInstalled", body: response)
}
}
/// Base overide for RCTEventEmitter.
///
/// - Returns: all supported events
#objc open override func supportedEvents() -> [String] {
return EventEmitter.sharedInstance.allEvents
}
#objc override static func requiresMainQueueSetup() -> Bool {
return true
}
#objc(openPaytm:orderId:transactionToken:amount:callbackUrl:isStaging:)
func openPaytm(_ mid: String, orderId: String, transactionToken: String, amount: String, callbackUrl: String?, isStaging: Bool) {
DispatchQueue.main.async {
var env:AIEnvironment = .production
if isStaging {
env = .staging
} else {
env = .production
}
self.handler.openPaytm(merchantId: mid, orderId: orderId, txnToken: transactionToken, amount: amount, callbackUrl: callbackUrl, delegate: self, environment: env)
}
}
#objc func separateDeeplinkParamsIn(url: String?, byRemovingParams rparams: [String]?) -> [String: String] {
guard let url = url else {
return [String : String]()
}
/// This url gets mutated until the end. The approach is working fine in current scenario. May need a revisit.
var urlString = stringByRemovingDeeplinkSymbolsIn(url: url)
var paramList = [String : String]()
let pList = urlString.components(separatedBy: CharacterSet.init(charactersIn: "&?"))
for keyvaluePair in pList {
let info = keyvaluePair.components(separatedBy: CharacterSet.init(charactersIn: "="))
if let fst = info.first , let lst = info.last, info.count == 2 {
paramList[fst] = lst.removingPercentEncoding
if let rparams = rparams, rparams.contains(info.first!) {
urlString = urlString.replacingOccurrences(of: keyvaluePair + "&", with: "")
//Please dont interchage the order
urlString = urlString.replacingOccurrences(of: keyvaluePair, with: "")
}
}
}
if let trimmedURL = pList.first {
paramList["trimmedurl"] = trimmedURL
}
return paramList
}
func stringByRemovingDeeplinkSymbolsIn(url: String) -> String {
var urlString = url.replacingOccurrences(of: "$", with: "&")
// This may need a revisit. This is doing more than just removing the deeplink symbol.
if let range = urlString.range(of: "&"), urlString.contains("?") == false {
urlString = urlString.replacingCharacters(in: range, with: "?")
}
return urlString
}
func openPaymentWebVC(_ controller: UIViewController?) {
if let vc = controller {
DispatchQueue.main.async {[weak self] in self?.viewController?.present(vc, animated: true, completion: nil)}
}
}
func didFinish(with status: AIPaymentStatus, response: [String : Any]) {
sendEvent(withName: "responseIfNotInstalled", body: response)
let alert = UIAlertController(title: "(status)", message: String(describing: response), preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
DispatchQueue.main.async { self.viewController?.present(alert, animated: true, completion: nil) }
}
}
AppDelegate.m
#import "RCTLinkingManager.h" // Import this header
// Add the below function
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
NSString *urlString = url.absoluteString;
NSDictionary *userInfo =
[NSDictionary dictionaryWithObject:urlString forKey:#"appInvokeNotificationKey"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"appInvokeNotification" object:nil userInfo:userInfo];
return [RCTLinkingManager application:app openURL:url options:options];
}
PaytmT.ts
import { NativeModules, NativeEventEmitter, Alert } from 'react-native'
export class PaytmT {
/** Call this function to invoke the Paytm Flow for iOS */
invoke() {
const allInOnePaytmSDK = NativeModules.AllInOneSDKSwiftWrapper
allInOnePaytmSDK.openPaytm('MERCHANT_ID', 'ORDER_ID', 'TXN_TOKEN', 'AMOUNT', 'CALLBACKURL', true) // If production environment, change it to false
const CounterEvents = new NativeEventEmitter(NativeModules.AllInOneSDKSwiftWrapper)
CounterEvents.addListener('responseIfNotInstalled', (response) => {
Alert.alert(PaytmT.name, JSON.stringify(response))
})
CounterEvents.addListener('responseIfPaytmInstalled', (response) => {
Alert.alert(PaytmT.name, JSON.stringify(response))
})
}
}

Cannot set value conforming to protocol to property with protocol type

I'm trying to create a fake authenticator for my unit tests that can manually set the user as logged in or logged out and bypass the API my code actually uses where I'd need a real accessToken to log the user in.
I've wrapped the Authentication API my app uses in to the following class:
API Wrapper
import OIDC
protocol Authenticator {
func getValidAccessToken(completionHandler: #escaping (Error?, String?) -> Void)
}
struct OIDCAuthenticator: Authenticator {
func getValidAccessToken(completionHandler: #escaping (Error?, String?) -> Void) {
//API call
OIDCHelper.getValidAccessToken { (error, accessToken) in
DispatchQueue.main.async {
completionHandler( error, accessToken)
}
}
}
}
Then I create a Fake Authenticator using the same protocol for testing purposes
Fake/Testing Authenticator
import OIDC
import Foundation
///Mocks the user being logged in our logged out for Testing purposes
struct FakeAuthenticator: Authenticator {
let error: OIDCError?
let accessToken: String?
func getValidAccessToken(completionHandler: #escaping (Error?, String?) -> Void) {
completionHandler(error, accessToken)
}
init(loggedIn: Bool) {
if loggedIn {
error = .tokenNotFoundInKeychainData
accessToken = nil
} else {
error = nil
accessToken = "abcdefg"
}
}
}
Settings the OIDCAuthenticator API Wrapper works fine when settings the authenticator in the ViewController subclass.
TableViewController Implementation
import UIKit
import OIDC
class SettingsPageTableViewController: UITableViewController{
// MARK: - Outlets and variables
var authenticator: Authenticator!
private var isUserLoggedIn = false
// MARK: - LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
authenticator = OIDCAuthenticator()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
authenticator.getValidAccessToken { [weak self] (error, _) in
self?.isUserLoggedIn = (error == nil)
self?.setLogginStatus()
}
}
}
However when I try to do the same thing with the FakeAuthenticator in my Unit tests I get the following error:
SettingsViewController Test Class
import XCTest
import UIKit
#testable import MyProject
class SettingsViewControllerTests: XCTestCase {
var viewController: SettingsPageTableViewController!
override func setUp() {
super.setUp()
configureViewControllerForTesting()
}
private func configureViewControllerForTesting() {
let storyboard = UIStoryboard(name: "SettingsPage", bundle: nil)
let navigationController = storyboard.instantiateInitialViewController() as! UINavigationController
viewController = navigationController.topViewController as! SettingsPageTableViewController
_ = viewController.view
}
func testSignInButtonIsAvailableWhenUnauthenticated() {
viewController.authenticator = FakeAuthenticator(loggedIn: false)
}
}
The same things happens when I swap out FakeAuthenticator with OIDCAuthenticator. I've also attempted to cast the FakeAuthenticator to Authenticator but this merely alters the Error to Cannot assign value of type 'Authenticator' to type 'Authenticator!'.
Why am I getting this error and what is the best approach to fixing this?
You need to remove the files from your test target since you're already importing your whole project with #testable.

Declaration 'subscribe' cannot override more than one superclass declaration (ReSwift)

I'm having a problem when overriding a function from the ReSwift Pod. I've got the following mock class:
import Foundation
import Quick
import Nimble
import RxSwift
#testable import MainProject
#testable import ReSwift
class MockReSwiftStore: ReSwift.Store<MainState> {
var dispatchDidRun: Bool = false
var subscribeWasTriggered: Bool = false
init() {
let reducer: Reducer<MainState> = {_, _ in MainState() }
super.init(reducer: reducer, state: nil)
}
required init(
reducer: #escaping (Action, State?) -> State,
state: State?,
middleware: [(#escaping DispatchFunction, #escaping () -> State?) -> (#escaping DispatchFunction) -> DispatchFunction]) {
super.init(reducer: reducer, state: state, middleware: middleware)
}
override func subscribe<SelectedState, S>(
_ subscriber: S,
transform: ((Subscription<MainState>) -> Subscription<SelectedState>)?)
where S: StoreSubscriber,
S.StoreSubscriberStateType == SelectedState {
subscribeWasTriggered = true
}
}
}
And when overriding the subscribe method I'm getting following errors
Then when using autocomplete it also shows 2 occurences:
However when looking for the original function there's only one which looks like this
open func subscribe<SelectedState, S: StoreSubscriber>(
_ subscriber: S, transform: ((Subscription<State>) -> Subscription<SelectedState>)?
) where S.StoreSubscriberStateType == SelectedState
{
// Create a subscription for the new subscriber.
let originalSubscription = Subscription<State>()
// Call the optional transformation closure. This allows callers to modify
// the subscription, e.g. in order to subselect parts of the store's state.
let transformedSubscription = transform?(originalSubscription)
_subscribe(subscriber, originalSubscription: originalSubscription,
transformedSubscription: transformedSubscription)
}
This is my compiler output
I'm out of ideas so any help is greatly appreciated
Thanks!
Here is your issue:
class Some<T> {
func echo() {
print("A")
}
}
extension Some where T: Equatable {
func echo() {
print("B")
}
}
class AnotherSome: Some<String> {
override func echo() {
print("Doesn't compile")
}
}
The problem is: ReSwift developers declare Store.subscribe behavior as a part of interface and as a part of extension (I am not sure why they chose to do it instead of introducing other objects). Swift can't figure out which part you are trying to override and thus it doesn't compile. Afaik there are no language instruments which allow you to resolve this issue.
A possible solution is to implement MockStore as a StoreType and use Store object to implement behavior for StoreType interface.

Swift protocol as an init parameter called from Objective C class

I have a swift protocol which i have defined for testing openUrl functionality in iOS. It looks something like this:
protocol URLOpener {
func open(_ url: URL, options: [String : Any], completionHandler completion: ((Bool) -> Void)?)
func canOpenURL(_ url: URL) -> Bool
}
extension UIApplication: URLOpener {}
Note that UIApplication is class is conformed to this protocol.
Now i have a class which takes an object URLOpener type to initilize that class
import Foundation
class GenericHandler : NSObject {
required init(urlOpener: URLOpener) {
super.init()
}
func aaa() {
NBLog("aaaaa")
}
}
I want to use this GenericHandler class from Objective C but i gives me an error that it could not find the initilizer.
GenericHandler *handler = [[GenericHandler alloc] initWithUrlOpener:[UIApplication sharedApplication]];
[handler aaa];
No visible #interface for 'GenericHandler' declares the selector 'initWithUrlOpener:'
However if i change the initizer so that it accepts String parameter then it starts to work fine.
import Foundation
class GenericHandler : NSObject {
required init(urlOpener: String) {
super.init()
}
func aaa() {
NBLog("aaaaa")
}
}
GenericHandler *handler = [[GenericHandler alloc] initWithUrlOpener:#"test"];
[handler aaa];
This just works fine. Can anyone guide me whats the issue withURLOpener protocol or how i can make it working with URLOpener parameter.
A bit late, but I just struggled with the same question.
You have to expose both the protocol and the init to Objective-C.
#objc protocol URLOpener {
func open(_ url: URL, options: [String : Any], completionHandler completion: ((Bool) -> Void)?)
func canOpenURL(_ url: URL) -> Bool
}
extension UIApplication: URLOpener {}
-
import Foundation
class GenericHandler : NSObject {
#objc required init(urlOpener: URLOpener) {
super.init()
}
func aaa() {
NBLog("aaaaa")
}
}
You can always check your generated header file to see if your Swift code is available in Objective-C. See the paragraph "Importing Swift into Objective-C" from the Apple docs.

SFSafariViewController: how to provide custom activities?

The WWDC session of Safari View Controller mentioned that apps could provide custom activities through the method func safariViewController(controller: SFSafariViewController, activityItemsForURL URL: NSURL, title: String?) -> [UIActivity] of the delegate SFSafariViewControllerDelegate. I have tried to implement this method, but it is not called after I present the SFSafariViewCntroller. I also implemented another optional method of that delegate, func safariViewControllerDidFinish(_: SFSafariViewController), which does get called. I tried to add the "#objc" keyword to my method (required by some other protocols), but it seems not to change anything.
I am wondering what could go wrong.
Thanks!
Here's the example code for your reference. In your main view:
func safariViewController(controler: SFSafariViewController, activityItemsForURL: NSURL, title: String?) -> [UIActivity] {
//global variable for the url to be shared
webPageUrl = activityItemsForURL.absoluteString
//global variable for the title to be shared
webPageTitle = title!
let wcActivity = WeChatActivity()
let wcMoment = WeChatMoment()
return [wcActivity, wcMoment]
}
Custom activities 1
import UIKit
class WeChatActivity : UIActivity{
override init() {
self.text = ""
}
var text:String?
override func activityType()-> String {
return "WeChat"
}
override func activityImage()-> UIImage?
{
return UIImage(named: "WeChat")!
}
override func activityTitle() -> String
{
return "微信好友"
}
override class func activityCategory() -> UIActivityCategory{
return UIActivityCategory.Action
//you can change to .Share and it'll appear in the share line
}
func getURLFromMessage(message:String)-> NSURL
{
var url = "whatsapp://"
if (message != "")
{
url = "\(url)send?text=\(message)"
}
return NSURL(string: url)!
}
override func canPerformWithActivityItems(activityItems: [AnyObject]) -> Bool {
return true;
}
override func performActivity() {
shareToWeChat("ftcweixin://?url=\(webPageUrl)&title=\(webPageTitle)&description=\(webPageDescription)&img=\(webPageImageIcon)&to=chat")
}
}
Custom Activity 2:
import UIKit
class WeChatMoment : UIActivity{
override init() {
self.text = ""
}
var text:String?
override func activityType()-> String {
return "WeChatMoment"
}
override func activityImage()-> UIImage?
{
return UIImage(named: "Moment")!
}
override func activityTitle() -> String
{
return "微信朋友圈"
}
override class func activityCategory() -> UIActivityCategory{
return UIActivityCategory.Action
}
func getURLFromMessage(message:String)-> NSURL
{
var url = "whatsapp://"
if (message != "")
{
url = "\(url)send?text=\(message)"
}
return NSURL(string: url)!
}
override func canPerformWithActivityItems(activityItems: [AnyObject]) -> Bool {
return true;
}
override func performActivity() {
shareToWeChat("ftcweixin://?url=\(webPageUrl)&title=\(webPageTitle)&description=\(webPageDescription)&img=\(webPageImageIcon)&to=moment")
}
}
You'll be able to see the two new icons in the action line of the action sheet. You can also change it to appear in the share line, as explained in the code.
One final note, there are pitfalls for WeChat sharing on Safari View, as WeChat doesn't conform to Safari's sharing standard. You can click the WeChat Share icon and WeChat will be able to share. But you can only get the page title and url in Safari View's page, unlike with WKWebView where you can get everything using evaluateJavaScript. So you'll need to get the share image and description (for sharing to friends) from other places.

Resources