Недавно возникла задача интеграции кода, который был написан только на 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="http://personeltest.ru/aways/habr.com/.../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="http://personeltest.ru/aways/habr.com/.../MyFramework.framework" dst="$S(BuildDir)/Frameworks/MyFramework.framework" /> </init></root>
Это решение в некотором роде обходной путь, пока не появится нормальной поддержки динамического Framework в UE4, но меня оно полностью устроило.