I tweaked some of the Unity iOS support code to make it work with SwiftUI as a UIViewRepresentable. Will share the process of getting there if people are interested.
Yes Please!
Can you put this into a github and share it? Iāve done a bunch of work to get this to work as a window above a swift UI, but it has itās issues. This seems much more seamless.
@Neonlyte maybe also post it into the UAAL forum page. But please show us a github for it!
I checked my changes and itās bits and pieces everywhere that I donāt think I can just share a git repo and itāll be clear by itself. Iāll do a write-up here instead and update the main post.
The anatomy of an iOS app is essential to understand what we need to do. An iOS app would consist of one or more UIWindow, in each window, one or more UIViewController will be presented, and in each view controller, one or more UIView will be presented.
Unityās UaaL initialization for iOS is essentially the same as if it was running as a standalone application, where on initialization, Unityās iOS ātrampolineā code would create one of each UI components internally so that it can have exclusive control over those components, rather than reusing any of those from the host application.
What we need to do is to hijack the creation of these UI components, and along the way, we will also need to move around some code to make things visible under Swift, so that we can embed the Unity View into SwiftUI.
- Prevent Unity creating its own window and view controller. A newly UIWindow will cause it to be placed on top of the host application, and you would have to manually place the SwiftUI app window in front of it again, or all touch events will be blocked by that new window. The UnityViewControllerBase contains code related to auto screen orientation, status bar and home bar, which we want to avoid letting Unity changing those.
The following changes also removes snapshots VC and splash screen usage. My anecdotal observation is that removing the splash screen code actually makes the app launches perceivably faster even for non-UaaL purpose, as it seems that Unity would manually present the splash screen VC, even though iOS would present those automatically.
This flag enabled auto-rotation which would try to access Unityās own view controller that would not exist, plus we automatically get auto-rotation when embedded into other view controllers.
This change may be optional as itās part of the auto-rotation code.
- Make Unity View accessible in Swift. By default, Unity does not export the UnityView class, and its header file structure prevents Swift from importing the relevant methods and symbols.
Create a new header file called āUnityView+Private.hā, and move the 3 private fields deleted in the previous image into here.
Then, update header includes
Finally, export the header file in the framework umbrella header, and configure Xcode project to copy the header into the framework:
- Integrate with SwiftUI in a way that SwiftUI Preview still works.
import SwiftUI
#if targetEnvironment(simulator)
#else
import UnityFramework
import MachO
private var isUnityStarted: Bool = false
func initializeUnityIfNeeded() {
guard !isUnityStarted else {
return
}
UnityInstance.setDataBundleId("com.unity3d.framework")
UnityInstance.runEmbedded(withArgc: CommandLine.argc, argv: CommandLine.unsafeArgv, appLaunchOpts: [:])
isUnityStarted = true
}
private let UnityBundle = Bundle(path: Bundle.main.privateFrameworksPath! + "/UnityFramework.framework")!
let UnityInstance = {
guard UnityBundle.load() else {
fatalError("Unity Bundle failed to load")
}
let unityFrameworkInstance = UnityBundle.principalClass!.getInstance()!
if unityFrameworkInstance.appController() == nil {
let machineHeader = UnsafeMutablePointer<MachHeader>.allocate(capacity: 1)
machineHeader.pointee = _mh_execute_header
unityFrameworkInstance.setExecuteHeader(machineHeader)
}
return unityFrameworkInstance
}()
#endif
@main
struct Partial_View_TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
import SwiftUI
#if targetEnvironment(simulator)
// Does not import UnityFramework for simulator
#else
import UnityFramework
#endif
struct ContentView: View {
var body: some View {
VStack {
Text("Hello from Swift UI!")
.padding()
UnityWrapperView()
.aspectRatio(16/9, contentMode: .fit)
Button("Render") {
#if targetEnvironment(simulator)
// Does nothing if simulator
#else
UnityInstance.sendMessageToGO(
withName: "Message Receiver",
functionName: "Receive",
message: "C"
)
#endif
}
.padding()
}
.padding()
}
}
#if targetEnvironment(simulator)
struct UnityWrapperView: View {
var body: some View {
Rectangle()
.foregroundColor(.blue)
}
}
#else
struct UnityWrapperView: UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
initializeUnityIfNeeded()
let view = UnityInstance.appController().unityView!
view.removeFromSuperview()
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
}
}
#endif
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Make sure that the framework is not linked via the build phase.
Linker flag:
@Neonlyte ! Thereās some great info there!
This is a completely different setup than how I have it. Iāll give it a spin soon and see if that helps solving some things.
One question though, how does your UnityInstance object / file looks like? Iām using a version of the UnityBridge idea from the other thread. or is UnityInstance at that point imported from the library?
I feel just by reading this that Iām missing something.
Thank you for taking the time to write this up!
Iāve tried replicating this in my own project, but iām getting some odd error in an unrelated class (lifecycle).
There are also some lines that werenāt in my project, so my questions are:
- Can you please share this as a repo? This will be easier to compare where discrepencies are.
- Which version of Unity is this based off? Iām using 2021.3
This is based off Unity 2022.2.10.
I cannot share this as a repository because of the changes I have made is very fragmented, and since I canāt reasonably bundle the IL2CPP sources and Unity library, you wonāt be able to build it to run anyway. All the screenshots above contained all of my changes from a clean export.
The UnityInstance global variable is an instance of UnityFramework. You get that by calling [UnityFramework getInstance] in Objective-C. The UnityFramework class is the principal class of the UnityFramework.framework. See the first code chunk of this post on Wednesday at 6:26 AM .
Ok thanks. Might have to try a clean export out into a new project or something. I got all the changes down yet it wonāt compile due to some wierd error happening on the LifeCycleListener protocol. This happens after I add the last parts, of the view / view+private. All of a sudden Iām getting some errors in that class.
But, unfortunatly those errors donāt make any sense, especially as this is a class that hasnāt even been edited, and compiles correctly without the other changesā¦
@Neonlyte . Thanks again for this write up and your help. hopefully this will benefit other users in the future. Iāll post updates if I manage to get this working.
Update : Did a clean XCode 14.3 project, with a clean Unity Project 2022.2.11, getting the same error. Looks like a dead cause atm unfortunately.
You may have messed up a header file somewhere which can cause this kind of bogus errors. These changes are indeed not easy to replicate due to all the moving pieces and I was only comfortable doing it because my day job is iOS Development.
Yeah we use this in an IOS app ourselves however Iām new to it and never worked with ObjC. What irks me is that it happens with a file that isnāt releated and Iāve double and triple checked. It has to be some odd order of includes that breaking it or something along those lines.
A clean project and build also didnāt work and iāve gone through this multiple times, commiting each step and seeing where it breaks.
Could be some config thing, xcode issue or god knows what, and at this point, I canāt spend anymore time troubleshooting it. Unity really needs to step up and update their codebase at this point so we arenāt relying on the community to solve these issues.
In any case, @Neonlyte , thanks for the effort, maybe someone else will have better success and will be able to get it working.
Itās alive! It is working. @Neonlyte Thank you many times.
One thing is that you missed, you have to add UnityFramework in General settings Frameworks, Libraries, and Embedded Content.
And probably there a bit more things that can be cleaned up before making Framework, I will try and maybe post a github link with changes on empty project
Oh. I forgot to mention that I have a small problem, probably it is related to headers as well. When I follow exact steps from your instruction I am getting an error (on the screen)
If I comment that line - everything builds fine.
BUT in Native iOS project where I am trying to use resulted framework I am getting an opposite error in that file that Screenorientation canāt be found. I have to go inside the framework (fortunately itās just a folder basically) and uncomment that line manually.
May be the order of headers or quirks of compiler idk. But for future followers - worth mentioning
Hello, I followed along and got stuck on the LifeCycleListener.h warnings identical to @SKArctop , how were you able to get past those?
Glad you were able to make it work. I hope my posts have served the necessary inspiration.
One comment on āFrameworks, Libraries, and Embedded Contentā. The reason that I did not set it there is because I manually configured the build settings to achieve the same effect. That setting is usually convenience, but in my case it interferes with building SwiftUI in Xcode Preview, since the Unity Xcode project can only link with either Simulator or Device SDK at one time, so if I export the framework with Device SDK, it canāt link with Simulator targets, which is what Xcode Preview is underneath.
I am not sure. I just followed the steps ĀÆ_(ć)_/ĀÆ
The only difference is that I made a universal framework (xcframework) which includes a build for simulator and device.
I made two separate builds and after that, I joined the results via command:
xcodebuild -create-xcframework -framework <deviceFrameworkPath> -framework <simulatorFrameworkPath> -output <xcframeworkPath>
Hi, Can you please share the github link for the working copy of this development