Due to the addition of ** "Unity as a Library" ** as a new feature from Unity 2019.3
, the configuration of .xcodeproj
after building for iOS has changed significantly.
Perhaps because of that, ** For example, even if I tried to implement the Swift code by the method described in the following article, the build did not pass **, so this time I will note the various solutions while comparing it with the conventional method. I would like to summarize it in.
conventional method
.-Native plugin for writing Unity iOS native plugin in Swift
There may be an oversight such as "I couldn't actually make this pattern", so I will add it as soon as I understand it. (If you have any information that says "I can't do that with the content here", I would appreciate it if you could let me know in the comments and edit requests: pray :: sweat_drops :)
version
** Public repository **
The set of projects used for this verification is available on GitHub. We have prepared two projects, "a project that applies the conventional method" and "a project that applies the method from 2019.3."
In the past, all code and data were aggregated in the target called Unity-iPhone
, but from ** 2019.3, a new framework target called" Unity Framework "has been added. So, ** Classes
, Libraries folders
, and other dependent frameworks are now separated here.
The official document also has a description of these details, so please see the following for details.
-** Structure of a Unity Xcode Project --2019.4 (after change) ** -Structure of a Unity XCode Project --2018.4 (before change)
The part that will be greatly affected by this change is the update process of PBX Project
, which will probably be processed by[PostProcessBuild]
, and it is necessary to redirect the GUID to Unity Framework
as follows. It came out.
[PostProcessBuild]
static void OnPostProcessBuild(BuildTarget target, string path)
{
if (target != BuildTarget.iOS) return;
var projectPath = PBXProject.GetPBXProjectPath(path);
var project = new PBXProject();
project.ReadFromString(File.ReadAllText(projectPath));
#if UNITY_2019_3_OR_NEWER
// NOTE: 2019.From 3`UnityFramework`Apply settings to
var targetGUID = project.GetUnityFrameworkTargetGuid();
#else
var targetName = PBXProject.GetUnityTargetName();
var targetGUID = project.TargetGuidByName(targetName);
#endif
project.AddFrameworkToProject(targetGUID, "MonafuwaLowMemoryWarningFramework.framework", false);
File.WriteAllText(projectPath, project.WriteToString());
}
** However, when dealing with Swift, simply changing the direction of the GUID to Uinty Framework
as shown in ↑ will not solve the problem, so we will explain the specific solution later. ** **
First, as a preliminary knowledge, I will describe the conventional implementation method up to 2019.2.
That said, what you're doing is pretty much the same as in the article below. If you are familiar with this, you can skip it.
-Native plugin for writing Unity iOS native plugin in Swift
Here, I will explain based on Exsamples in the following project.
Almost as described in the article. I will summarize only a few points in a bulleted list.
--Set UnitySwift-Bridging-Header.h
to Objective-C Bridging Header
-* UnitySwift-Bridging-Header.h
is applied so that the one imported as a plug-in in advance is set during the project.
- Assets/UnitySwift/UnitySwift-Bridging-Header.h
--Set unityswift-Swift.h
to Objective-C Generated Interface Header Name
--Set @ executable_path/Frameworks
to Runpath Search Paths
--Set Swift version to 5.0
-* As mentioned in the comment, if you do not specify this explicitly, the area around 3.0
will be set by default? Due to the influence, it may be treated as Unspecified
depending on Xcode.
-* By the way, since 5.0
is the latest, it is only specified for the time being, and there is no particular reason to say this. For this version, set the required value according to your requirements.
XcodePostProcess.cs
/// <summary>
///Automatically apply the settings required to implement Swift
/// </summary>
sealed class XcodePostProcess
{
[PostProcessBuild]
static void OnPostProcessBuild(BuildTarget target, string path)
{
if (target != BuildTarget.iOS) return;
var projectPath = PBXProject.GetPBXProjectPath(path);
var project = new PBXProject();
project.ReadFromString(File.ReadAllText(projectPath));
var targetGuid = project.TargetGuidByName(PBXProject.GetUnityTargetName());
project.SetBuildProperty(targetGuid, "SWIFT_OBJC_BRIDGING_HEADER", "Libraries/UnitySwift/UnitySwift-Bridging-Header.h");
project.SetBuildProperty(targetGuid, "SWIFT_OBJC_INTERFACE_HEADER_NAME", "unityswift-Swift.h");
project.AddBuildProperty(targetGuid, "LD_RUNPATH_SEARCH_PATHS", "@executable_path/Frameworks");
// Swift version: 5.0
// NOTE:3 if not explicitly specified.It seems that the old one is set to about 0, so depending on Xcode`Unspecified`Be treated
project.AddBuildProperty(targetGuid, "SWIFT_VERSION", "5.0");
File.WriteAllText(projectPath, project.WriteToString());
}
}
In addition to Foundation.h
and UIKit.h
, UnityInterface.h
is imported to UnitySwift-Bridging-Header.h
in advance.
(UnitySendMessage
etc. are declared in UnityInterface.h
, so if you want to call UnitySendMessage
from Swift, you need to import it)
UnitySwift-Bridging-Header.h
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "UnityInterface.h"
The Swift code is implemented as follows.
callSwiftMethod
only prints the string passed from the C # side with print
, and callUnityMethod
calls UnitySendMessage
and sends the string "Hello, Unity!" To the C # side. I will.
Exsample.swift
import Foundation
class Example : NSObject {
// ObjC++Call a Swift method from
@objc static func callSwiftMethod(_ message: String) {
print("\(#function) is called with message: \(message)")
}
//Call SendMessage from a Swift method
@objc static func callUnityMethod() {
// Call a method on a specified GameObject.
UnitySendMessage("CallbackTarget", "OnCallFromSwift", "Hello, Unity!")
}
}
Since it is not possible to call from C # with only Swift code, prepare Obj C ++ code so that it can be called with P/Invoke. The content is just calling the method on the Swift side as it is.
ExsampleBridge.mm
#import <Foundation/Foundation.h>
// ObjC++Required to access Swift classes from
// NOTE: `unityswift-Swift.h`The actual situation at build time`DerivedData`Automatically generated below
#import "unityswift-Swift.h" // Required
// P/Invoke
extern "C" {
void callSwiftMethod(const char *message) {
[Example callSwiftMethod:[NSString stringWithUTF8String:message]];
}
void callUnityMethod() {
[Example callUnityMethod];
}
}
Implement the Swift code implemented above and the ObjC ++ code call processing for P/Invoke.
In Exsample, you can call callSwiftMethod
and callUnityMethod
on the Swift side from two Buttons on the screen.
For callUnityMethod
, for the convenience of receiving the result with SendMessage, the received character string is output to the log by matching the GameObject name and method name.
Exsample.cs
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.UI;
namespace UnitySwift.Exsamples
{
/// <summary>
///iOS Native Plugin call sample
/// </summary>
public sealed class Exsample : MonoBehaviour
{
[SerializeField] Button _callSwiftMethod = default;
[SerializeField] Button _callUnityMethod = default;
void Start()
{
//Set the name of the GameObject specified when calling SendMessage from Swift
this.name = "CallbackTarget";
_callUnityMethod.onClick.AddListener(() =>
{
#if !UNITY_EDITOR && UNITY_IOS
CallUnityMethod();
#endif
});
_callSwiftMethod.onClick.AddListener(() =>
{
#if !UNITY_EDITOR && UNITY_IOS
CallSwiftMethod("Gorilla");
#endif
});
}
/// <summary>
///A method of action called by SendMessage from Swift
/// </summary>
/// <param name="message"></param>
void OnCallFromSwift(string message)
{
Debug.Log(message);
}
/// <summary>
/// ObjC++Call a Swift method from
/// </summary>
/// <remarks>[C# -> ObjC++ -> Swift]Called in the flow of</remarks>>
[DllImport("__Internal", EntryPoint = "callSwiftMethod")]
static extern void CallSwiftMethod(string message);
/// <summary>
///Call SendMessage from a Swift method
/// </summary>
/// <remarks>* There is an intention to call the method defined on the Unity side from Swift</remarks>>
[DllImport("__Internal", EntryPoint = "callUnityMethod")]
static extern void CallUnityMethod();
}
}
If you build .xcodeproj
using the conventional method described above, you will definitely get an error and get angry.
One of the reasons is that the target called Framework, which is the actual state of Unity Framework
, cannot set Bridging-Header.h
, so maybe an error related to this is output at an early timing. It seems.
In addition to this, there are some minor changes that have been made, so I will explain them step by step.
Here, I will explain based on Exsamples in the following project.
I explained the changes of PBX Project
earlier, but from the conclusion, it seemed that it was unnecessary except for **" Swift version specification ". ** **
Since UnitySwift-Bridging-Header.h
cannot be set in the first place, it is also deleted from the project.
XcodePostProcess.cs
sealed class XcodePostProcess
{
/// <summary>
///Automatically apply the settings required to implement Swift
/// </summary>
[PostProcessBuild]
static void OnPostProcessBuild(BuildTarget target, string path)
{
if (target != BuildTarget.iOS) return;
var projectPath = PBXProject.GetPBXProjectPath(path);
var project = new PBXProject();
project.ReadFromString(File.ReadAllText(projectPath));
// 2019.From 3`UnityFramework`Since it is separated into, targetGuid needs to be printed here.
// NOTE:If you want to coexist with the previous version,#if UNITY_2019_3_OR_It is also possible to divide by "NEWER"
var targetGuid = project.GetUnityFrameworkTargetGuid();
// NOTE:
//I had set it before`Bridging-Header.h`The kind of setting is 2019.It seems unnecessary from 3.
//Rather, Cocoa touch Framework is Bridging-Since it does not support Header, it gets angry when it is set.
// Swift version: 5.0
// NOTE:3 if not explicitly specified.It seems that the old one is set to about 0, so depending on Xcode`Unspecified`Be treated
project.AddBuildProperty(targetGuid, "SWIFT_VERSION", "5.0");
File.WriteAllText(projectPath, project.WriteToString());
}
}
There are two changes as follows.
-** Set the access level of the class/method exposed to ObjC ++ to public
**
-** Use sendMessageToGO
implemented in Unity Framework
instead of UnitySendMessage
**
Regarding the latter change, I referred to the following issue/PR.
(In my memory, sendMessageToGO
was used around the exchange between" Native → Unity "in UaaL [^ 1])
[^ 1]: UaaL ・ ・ ・ Abbreviation for Unity as a Library
It seems that the conventional UnityInterface.h
is not included in the target in the first place and can not even be referenced ...?
Exsample.swift
import Foundation
// NOTE:Access level for items published in ObjC`public` or `open`Must be set to
public class Example : NSObject {
// ObjC++Call a Swift method from
@objc public static func callSwiftMethod(_ message: String) {
print("\(#function) is called with message: \(message)")
}
//Call SendMessage from a Swift method
@objc public static func callUnityMethod() {
// NOTE:Traditional`UnityInterface.h`It is in`SendMessage`Can't be referred to? So, use the following sendMessageToGO.
// Call a method on a specified GameObject.
if let uf = UnityFramework.getInstance() {
uf.sendMessageToGO(
withName: "CallbackTarget",
functionName: "OnCallFromSwift",
message: "Hello, Unity!")
}
}
}
The main change here is the ** import header, which changes the target from unityswift-Swift.h
to UnityFramework/UnityFramework-Swift.h
. ** **
ExsampleBridge.mm
#import <Foundation/Foundation.h>
// 2019.You need to import this from 3
#import <UnityFramework/UnityFramework-Swift.h>
// P/Invoke
extern "C" {
void callSwiftMethod(const char *message) {
[Example callSwiftMethod:[NSString stringWithUTF8String:message]];
}
void callUnityMethod() {
[Example callUnityMethod];
}
}
There is no change in the calling code on the C # side, so it is omitted.
-It's not good to mix Swift and Objective-C in Framework -Native plugin for writing Unity iOS native plugin in Swift -[Unity] Shortest implementation to run Swift from C # in recent Unity