Unity iOS Interop

Most of the sources I've found that explain how to call Swift functions from Unity, and vice versa, use Objective-C bridging code. This isn't strictly necessary, since we have the @_cdecl attribute that forces Swift functions to use the C calling convention, and prevents name mangling. However, the attribute is undocumented, so you should be aware it might break in the future. Discussion for formalizing this attribute have been going on for a long time.

Calling Swift functions from Unity

Calling Swift functions from Unity is reasonably simple. Put your Swift source file in Assets/Plugins/iOS and add the @_cdecl attribute to the function you want to call. This makes the function callable using the C calling convention.

import Foundation

@_cdecl("testFunction")
public func testFunction(num: Int32) -> UnsafeMutablePointer<CChar> 
{
    return strdup("Hello World! The number is \(num)");
}

UnsafeMutablePointer<CChar> is a C style string. The strdup function allocates memory for the string, but the Mono runtime takes ownership of the memory and cleans it up for us.

To call the function from Unity we need to import it using InteropServices. Then we can call it like any other function.

using UnityEngine;
using System.Runtime.InteropServices;

public class TestClass : MonoBehaviour 
{
    [DllImport ("__Internal")]
    private static extern string testFunction(int num);

    void Start () 
    {
        Debug.Log(testFunction(-12)); //Prints "Hello World! The number is -12"
    }
}

Calling Unity functions from Swift

Swift can't find Unity functions on its own, so we need to pass them to Swift ourselves. Then we can save the function to call it again later. We'll start by declaring the function type that Swift will accept and a function to take in the function from Unity.

import Foundation

public typealias Delegate = @convention(c) () -> Void;

@_cdecl("callDelegate")
public func callDelegate(delegate: Delegate)
{
    delegate();
}

The function doesn't have to be a void function, it can return a value and also take in arguments.

We need to declare an equivalent delegate type in our Unity script and also add an annotation to the function we want Swift to call. This makes the function available to call using the C calling convention. Then we just call our Swift function to pass the delegate to Swift. Swift can then call the delegate at any time.

using UnityEngine;
using System.Runtime.InteropServices;

public class TestClass : MonoBehaviour 
{
    delegate void DelegateFunc();
    [AOT.MonoPInvokeCallback(typeof(DelegateFunc))]
    static void TestDelegate()
    {
        Debug.Log("Delegate Called");
    }

    [DllImport ("__Internal")]
    private static extern void callDelegate(DelegateFunc func);

    void Start () 
    {
        callDelegate(TestDelegate);
    }

    void Update (){}
}

Opening a browser from Unity

Sometimes we want to be able to open a browser without leaving our app. We'll start by creating the Swift function. We need to get Unity's view controller so we can make the browser a child of it.

import Foundation
import SafariServices

@_cdecl("openBrowser")
public func openBrowser(urlString: UnsafeMutablePointer)
{
    // Get the app's root view controller so we can make the web view a child of it
    if let unityViewController = UnityFramework.getInstance()?.appController()?.rootViewController
    {
        // The mono runtime will handle freeing the memory allocated for the c-string
        if let url = URL(string: String.init(cString: urlString)) 
        {
            let webview = SFSafariViewController(url: url);
            // Shows the webview, the user can dismiss it
            unityViewController.present(webview, animated: true);
        }
    }
}

In our Unity script we just need to import the function and call it.

using UnityEngine;
using System.Runtime.InteropServices;
public class TestClass : MonoBehaviour 
{
    [DllImport ("__Internal")]
    private static extern void openBrowser(string url);

    void Start () 
    {
        openBrowser("https://google.ca");
    }

    void Update(){}
}