I am trying to create an IOS plugin to do some API calls for my Unity game.
I started with this article and got it running so that basic structuring is done.
Now to call any API and get its response in Unity, I looked up this question but I am unable to figure out how to declare my _addTwoNumberInIOS
function in Objective-C so that it can take a completionHandler
as a parameter (basically a function taking string
as argument and returning void
) and pass it to Swift code which will later call it when it has something to return.
My Unity code is:
using System.Runtime.InteropServices;
using UnityEngine;
using TMPro;
using AOT;
using System;
public class PluginHelper : MonoBehaviour
{
public static TextMeshProUGUI myText;
[DllImport("__Internal")]
private static extern void _addTwoNumberInIOS(int a, int b, Action<string> completionHandler);
[MonoPInvokeCallback ( typeof ( Action<string> ) )]
private static void completionHandler ( string s )
{
myText.text = "25 5 is : " s;
}
void Start()
{
AddTwoNumber();
}
public void AddTwoNumber()
{
_addTwoNumberInIOS(25, 5, completionHandler);
}
}
My swift code is:
import Foundation
@objc public class UnityPlugin : NSObject {
@objc public static let shared = UnityPlugin()
@objc public func AddTwoNumber(a:Int,b:Int,onComplete:@escaping(String)->Void) -> Void {
let url : String = "https://restcountries.eu/rest/v2/all"
var result: String = "1111"
result = "2222"
URLSession.shared.dataTask(with: NSURL(string: url) as! URL, completionHandler: { (data, response, error) in
// Handle result
result = "data "
// do api call
onComplete(result)
}).resume();
}
}
Now my Objective-C code (where I am having trouble):
#import <Foundation/Foundation.h>
#include "UnityFramework/UnityFramework-Swift.h"
extern "C" {
#pragma mark - Functions
void _addTwoNumberInIOS(int a , int b, void onComplete(NSString*)) {
[[UnityPlugin shared] AddTwoNumberWithA:a b:b onComplete:onComplete]; // problem here
}
}
I am getting
Cannot initialize a parameter of type 'void (^ _Nonnull)(NSString * _Nonnull __strong)' with an lvalue of type 'void (*)(NSString *__strong)'
on line[[UnityPlugin shared] AddTwoNumberWithA:a b:b onComplete:onComplete];
If I change my function to:
void _addTwoNumberInIOS(int a , int b, void (^_Nonnull)onComplete(NSString*)) {
[[UnityPlugin shared] AddTwoNumberWithA:a b:b onComplete:onComplete];
}
Then I get
Block pointer to non-function type is invalid
on linevoid _addTwoNumberInIOS(int a , int b, void (^_Nonnull)onComplete(NSString*))
Question: How do I declare and define _addTwoNumberInIOS
in Objective-C to use shared UnityPlugin Swift code so that I can return and use API response in Unity?
Thanks.
CodePudding user response:
The problem is Swift closures aren't exactly Objective-C blocks even though there is a huge degree of similiarity.
Keep your
void _addTwoNumberInIOS(int a , int b, void (^_Nonnull)onComplete(NSString*))
But add @convention(block)
to the Swift signature like this:
@objc public func AddTwoNumber(a: Int, b: Int, onComplete: @escaping @convention(block)(String) -> Void) -> Void {
CodePudding user response:
When I wrote the native communications for an app of mine, I was using callbacks extensively, so a shortcut I used was to do this:
extern "C"
{
typedef void (*CallbackFunction)();
typedef void (*CallbackFunctionWithString)( const char * );
typedef void (*CallbackFunctionWithInt)( NSInteger );
typedef void (*CallbackFunctionWithFloat)( float );
// Sometimes I also wanted to send back a 'success' bool, with the string data, so I did that like this:
typedef void (*CallbackFunctionWithBoolAndString)( bool, const char * );
// etc ...
#pragma mark - Functions
void _addTwoNumberInIOS( int a , int b, CallbackFunctionWithBoolAndString onComplete )
{
[UnityPlugin shared] AddTwoNumberWithA:a:b:onComplete;
}
}
The Swift file would then look like this:
import Foundation
typealias CCallbackFunction = @convention(c) () -> Void;
typealias CCallbackFunctionWithString = @convention(c) (UnsafePointer<CChar>) -> Void;
typealias CCallbackFunctionWithInt = @convention(c) (Int) -> Void;
typealias CCallbackFunctionWithFloat = @convention(c) (Float) -> Void;
typealias CCallbackFunctionWithBool = @convention(c) (Bool) -> Void;
typealias CCallbackFunctionWithBoolAndString = @convention(c) (Bool, UnsafePointer<CChar>) -> Void;
// etc ..
@objc public class UnityPlugin : NSObject
{
@objc public static let shared = UnityPlugin()
@objc public func AddTwoNumber( _ a:Int, _ b:Int, _ onComplete:@escaping CCallbackFunctionWithString ) -> Void {
let url : String = "https://restcountries.eu/rest/v2/all"
var result: String = "1111"
result = "2222"
URLSession.shared.dataTask(with: NSURL(string: url) as! URL, completionHandler: { (data, response, error) in
// Handle result
result = "data "
onComplete(result)
}).resume();
}
}
Mind you this was back in the wild west days of Swift 4, so YMMV. But this worked a treat because, as mentioned, I relied heavily on callbacks to tell Unity when tasks in the native libraries had finished.