Implementing Swift in Unity

Kevin H
7 min readJun 7, 2018

--

This is a follow up on my previous article about implementing CoreML in Unity together with ARKit.

What you can do after this tutorial.

  • You can use any Swift code in your Unity project.
  • You can call Swift void’s from Unity but also actual return types.
  • You can pass on variables from Unity to the Swift Functions.
  • You can go from a Swift string to a C# string (more complicated then it sounds).

To start off: Using Swift in Unity.

Let’s start off easy and with the most essential thing, using Swift in Unity. This has been done by many people before me, especially for Unity versions 5.6 and below. Lately people struggle getting it to work in Unity 2017 or later. By the help of many tutorials I got it to work in Unity 2017 and later, all the way till Unity 2018.2 beta.

So here we go:

Step 1: Create a Cocoa Touch Framework

When opening Xcode, create a new project and select “Cocoa Touch Framework” and name it wathever you want. I named mine “SwiftPlugin”. And of course the language is “Swift”, you can leave “Include Unit Tests” on.

To keep things organised, create a new folder in the “SwiftPlugin” folder. And call it “Source”.

Step 2: Start writing your Swift code.

To start of we create a Swift file in our “Source” folder. I called mine “SwiftForUnity.swift”.

Paste the following code in your .swift file:

import Foundation
import UIKit
@objc public class SwiftForUnity: UIViewController { @objc static let shared = SwiftForUnity()
@objc func SayHiToUnity() -> String{
return "Hi, I'm Swift"
}
}

The @objc indicates that this class and function can be used in Objective-c, we’ll need to do so later on in order to use it in C#. Also all classes that you want to be visible for objective-c need to inherit from NSObject, since UIViewController inherits from NSObject already, there is no need to add it another time here.

After you’ve written your Swift code you need to make an Objective-C++ file and call it “SwiftForUnityBridge.mm”. You’ll need to rename it from “SwiftForUnityBridge.m” to “SwiftForUnityBridge.mm”.

Now add the following code to the .mm file:

#include "SwiftPlugin-Swift.h"#pragma mark - C interfaceextern "C" {     char* _sayHiToUnity() {          NSString *returnString = [[SwiftForUnity shared]       SayHiToUnity];          char* cStringCopy(const char* string);          return cStringCopy([returnString UTF8String]);     }
}
char* cStringCopy(const char* string){ if (string == NULL){
return NULL;
}
char* res = (char*)malloc(strlen(string)+1);
strcpy(res, string);
return res;
}

This is going to give you some errors because the “SwiftPlugin-Swift.h” file doesn’t exist yet, also the reason why it will tell you that it can’t find “SwiftForUnity shared”. This file will automatically be created once we build. If you just want to execute a void you can use the following:

#include "SwiftPlugin-Swift.h"#pragma mark - C interfaceextern "C" {     void _sayHiToUnity(){
[[SwiftForUnity shared]SayHiToUnity];
}
}

The cStringCopy function is to change the stringformatting to make it readable for C# all the other return types work exactly the same as a void.

If you want to pass on some variables to the Swift function, it would look like this:

In this case I’m passing on a PixelBuffer (needed for CoreML) and a string.

Step 3: Creating the bridging header

In our source folder we create a new header file and call it “SwiftPlugin-Bridging-Header.h”.

Xcode requires that the bridging header is always formatted as follows: [PROJECTNAME-Bridging-Header].

add the following to the header file:

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

So it looks like this:

Once you’ve done that you build the framework to a simulator device. The build will fail but under products you’ll have a .framework file. Right click it and press Show in finder.

Copy the file to somewhere you’ll find it back because tho you won’t need it in Unity, you will need it later on in the Xcode project that Unity generates.

Step 4: The Unity Project.

Let’s start by creating a new Unity project, we all know how to do that hopefully. In our Unity project’s asset folder we create a new folder called “Plugins”, in “Plugins” we create a folder called “iOS” and in there we create a folder called “SwiftPlugin” in which we create 2 folders, one named “Editor” and the other named “Source”. It should look like this:

In source we drag our “SwiftForUnity.Swift” file, our “SwiftForUnityBridge.mm” file and our “SwiftPlugin-Bridging-Header.h” file. Now we create a C# file in the Source folder named “SwiftForUnity.cs”.

This is what it should look like:

using System;
using System.Runtime.InteropServices;
using UnityEngine;

public class SwiftForUnity : MonoBehaviour {

#region Declare external C interface

#if UNITY_IOS && !UNITY_EDITOR

[DllImport("__Internal")]
private static extern string _sayHiToUnity();

#endif

#endregion

#region Wrapped methods and properties

public static string HiFromSwift()
{
#if UNITY_IOS && !UNITY_EDITOR
return _sayHiToUnity();
#else
return "No Swift found!";
#endif
}

#endregion

#region Singleton implementation

private static SwiftForUnity _instance;

public static SwiftForUnity Instance
{
get
{
if (_instance == null)
{
var obj = new GameObject("SwiftUnity");
_instance = obj.AddComponent<SwiftForUnity>();
}

return _instance;
}
}

private void Awake()
{
if (_instance != null)
{
Destroy(gameObject);
return;
}

DontDestroyOnLoad(gameObject);
}

#endregion
}

We need the System.Runtime.InteropsServices to import extern functions. also make sure that with the [DllImport ….] you spell your .mm function correct, if you don’t it won’t work.



[DllImport("__Internal")]
private static extern string _sayHiToUnity();

#endif

#endregion

#region Wrapped methods and properties

public static string HiFromSwift()
{
#if UNITY_IOS && !UNITY_EDITOR
return _sayHiToUnity();
#else
return "No Swift found!";
#endif
}

#endregion

The “_sayHiToUnity()” must be correctly spelled at both places.

Now you can use “SwiftForUnity.HiFromSwift()” anywhere you like and it will return the string specified in your Swift code.

This is what it looks like for more return types and when you want to pass on variables to swift:

Note that while a String in Swift is a char* in Objective C++ it still is a string in C#, this might be the most confusing variable to pass on, that’s why I used it as an example, that way you should be able to figure out all the other stuff.

TIP: Remember that .framework file I told you to place somewhere you’d remember. You can actually place it in Unity in a “StreamingAssets” folder. That way it won’t compile with the other files but it will still be in your Xcode project.

Step 5: The crucial part.

Okay so you have implemented some Swift functionality in Unity and are eager to test it, so you build your Xcode project, open it up and try to build it … but hold on … So many errors! The most important part is yet to happen. The post processing, in order for this to work you have to adjust a couple of settings in Xcode.

Remember that folder named “Editor” we made in our “SwiftPlugin”, well it’s time to use it. In here we add a C# scripted named: “SwiftPostProcess.cs”

This file will contain the following code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
using System.Diagnostics;

using System.IO;
using System.Linq;

public static class SwiftPostProcess {

[PostProcessBuild]
public static void OnPostProcessBuild(BuildTarget buildTarget, string buildPath)
{
if (buildTarget == BuildTarget.iOS)
{
var projPath = buildPath + "/Unity-Iphone.xcodeproj/project.pbxproj";
var proj = new PBXProject();
proj.ReadFromFile(projPath);

var targetGuid = proj.TargetGuidByName(PBXProject.GetUnityTargetName());


proj.SetBuildProperty(targetGuid, "ENABLE_BITCODE", "NO");
proj.SetBuildProperty(targetGuid, "SWIFT_OBJC_BRIDGING_HEADER", "Libraries/Plugins/iOS/SwiftPlugin/Source/SwiftPlugin-Bridging-Header.h");
proj.SetBuildProperty(targetGuid, "SWIFT_OBJC_INTERFACE_HEADER_NAME", "SwiftPlugin-Swift.h");
proj.AddBuildProperty(targetGuid, "LD_RUNPATH_SEARCH_PATHS", "@executable_path/Frameworks $(PROJECT_DIR)/lib/$(CONFIGURATION) $(inherited)");
proj.AddBuildProperty(targetGuid, "FRAMERWORK_SEARCH_PATHS",
"$(inherited) $(PROJECT_DIR) $(PROJECT_DIR)/Frameworks");
proj.AddBuildProperty(targetGuid, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES", "YES");
proj.AddBuildProperty(targetGuid, "DYLIB_INSTALL_NAME_BASE", "@rpath");
proj.AddBuildProperty(targetGuid, "LD_DYLIB_INSTALL_NAME",
"@executable_path/../Frameworks/$(EXECUTABLE_PATH)");
proj.AddBuildProperty(targetGuid, "DEFINES_MODULE", "YES");
proj.AddBuildProperty(targetGuid, "SWIFT_VERSION", "4.0");
proj.AddBuildProperty(targetGuid, "COREML_CODEGEN_LANGUAGE", "Swift");



proj.WriteToFile(projPath);
}
}

}

Note: Make sure to check the header file names in the postprocess scripted if you used other names then I did.

What it does is basically change a whole bunch of Xcode settings. If you’re not using ARKit or CoreML a lot of these don’t have to be changed but it can’t hurt. Once you’ve added this you’re ready to build.

Once you open up the Xcode project you have to do the usual with the bundle identifier stuff but also have to add .framework to your application. Because we put it in the “StreamingAssets” folder we can find it back in our Xcode project and move it to the Framework folder in there.

Alright, now you should be able to build and use the Swift code in Unity.

If you have any questions, don’t hesitate to contact me.

--

--