Недавно возникла задача интеграции кода, который был написан только на Swift в проект на UE4. При этом вариант "давайте быстренько перепишем все на Objective C" не рассматривался. Готового решения нигде не нашел, пришлось, как это часто бывает, копать и вширь и вглубь. Задача была успешно решена и чтобы помочь кому-то еще, решил написать туториал.
Обычно, если нужно интегрировать какой-либо специфичный для iOS/macOS платформы код, используется сборка библиотеки в виде Framework, который должен быть статический. Сам же код пишется на Objective C. Далее, зависимость прописывается в скрипте сборки проекта MyProject.Build.cs
, например, так:
if (Target.Platform == UnrealTargetPlatform.IOS)
{
string ExtraPath = Path.GetFullPath(Path.Combine(ModuleDirectory, "../ThirdParty"));
PublicAdditionalFrameworks.Add(
new Framework(
"MyFramework",
ExtraPath + "/" + "MyFramework.framework.zip"
)
);
}
Для Swift можно попробовать пойти этим же путем, но возникнет проблема — зависимость от некоторых библиотек языка. Предупреждение при линковке будет выглядеть так:
ld: warning: Could not find auto-linked library 'swiftFoundation'
ld: warning: Could not find auto-linked library 'swiftDarwin'
ld: warning: Could not find auto-linked library 'swiftCoreFoundation'
ld: warning: Could not find auto-linked library 'swiftCore'
ld: warning: Could not find auto-linked library 'swiftCoreGraphics'
ld: warning: Could not find auto-linked library 'swiftObjectiveC'
ld: warning: Could not find auto-linked library 'swiftDispatch'
ld: warning: Could not find auto-linked library 'swiftSwiftOnoneSupport'
После чего последуют стандартные ошибки о том что не найдены некоторые символы в объектных файлах. Можно попробовать найти где находится каждая библиотека, упомянутая выше, и указать путь к ней через ключ L, но я решил перейти на динамический тип Framework, который сам подгрузит эти библиотеки.
Сборка проект пройдет нормально, однако при запуске приложение будет падать со знакомой iOS разработчикам ошибкой:
dyld: Library not loaded: @rpath/MyFramework.framework/MyFramework
Referenced from: /private/var/mobile/Containers/Bundle/Application/.../MyApp.app/MyApp
Reason: image not found
Это связано с тем, что UE4 не умеет работать с динамическим типом Framework, и не копирует его при создании пакета приложения. Это можно было бы решить путем изменения кода IOS Toolchain и сборкой своей версии движка, но хотелось всего этого избежать и на помощь пришел IOSPlugin.
Это расширение обычно используется для модификации plist
файла приложения, например, добавления описания для прав доступа:
<iosPListUpdates>
<addElements tag="dict" once="true">
<key>NSCameraUsageDescription</key>
<string>The camera is used for live preview.</string>
</addElements>
</iosPListUpdates>
Нам же пригодится элемент copyDir
. Он позволяет задать путь к директории, которую мы хотим скопировать, и место назначения:
<init>
<log text="Copy MyFramwork..."/>
<copyDir src="/.../MyFramework.framework" dst="$S(BuildDir)/Frameworks/MyFramework.framework" />
</init>
Важно, чтобы команда копирования располагалась в блоке init
. После этого приложение начинает работать.
Итоговый файл MyProject.Build.cs
будет выглядеть так:
using UnrealBuildTool;
using System.IO;
public class MyProject : ModuleRules
{
public MyProject(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });
if (Target.Platform == UnrealTargetPlatform.IOS)
{
string ExtraPath = Path.GetFullPath(Path.Combine(ModuleDirectory, "../ThirdParty"));
PublicAdditionalFrameworks.Add(
new Framework(
"MyFramework",
ExtraPath + "/" + "MyFramework.framework.zip"
)
);
string PluginPath = Utils.MakePathRelativeTo(ModuleDirectory, Target.RelativeEnginePath);
AdditionalPropertiesForReceipt.Add("IOSPlugin", Path.Combine(PluginPath, "MyProject_IOS_UPL.xml"));
}
}
}
А файл MyProject_IOS_UPL.xml
так:
<?xml version="1.0" encoding="utf-8"?>
<root>
<init>
<log text="Copy MyFramework..."/>
<copyDir src="/.../MyFramework.framework" dst="$S(BuildDir)/Frameworks/MyFramework.framework" />
</init>
</root>
Это решение в некотором роде обходной путь, пока не появится нормальной поддержки динамического Framework в UE4, но меня оно полностью устроило.