title: Embedding Godot games in iOS apps is easy now
Authors: Christian Selig
contentPublished:
noteCreated: 2025-06-01
tags:
- clippings
- article
description: Recently thereās been very exciting developments in the Godot game engine, that have allowed really easy and powerful integration into an existing normal iOS or Mac app. I couldnāt find a lot of documentation or discussion about this, so I wanted to shine some light on why this is so cool, and how easy it is to do!Whatās Godot? For the uninitiated, Godot is an engine for building games, other common ones you might know of are Unity and Unreal Engine.
takeaways:
Status: Reference
url: https://christianselig.com/2025/05/godot-ios-interop/
subjects:
- "[[Swift]]"
- "[[Godot]]"Original URL: https://christianselig.com/2025/05/godot-ios-interop/

Recently thereās been very exciting developments in the Godot game engine, that have allowed really easy and powerful integration into an existing normal iOS or Mac app. I couldnāt find a lot of documentation or discussion about this, so I wanted to shine some light on why this is so cool, and how easy it is to do!
For the uninitiated, Godot is an engine for building games, other common ones you might know of are Unity and Unreal Engine.
Itās risen in popularity a lot over the last couple years due to its open nature: itās completely open source, MIT licensed, and worked on in the open. But beyond that, itās also a really well made tool for building games with (both 2D and 3D), with a great UI, beautiful iconography, a ton of tutorials and resources, and as a bonus, itās very lightweight.
Iāve had a lot of fun playing around with it (considering potentially integrating it into Pixel Pals), and while Unity and Unreal Engine are also phenomenal tools, Godot has felt lightweight and approachable in a really nice way. As an analogy, Godot feels closer to Sketch and Figma whereas Unity and Unreal feel more like Photoshop/Illustrator or other kinda bulky Adobe products.
Even Apple has taken interest in it, contributing a substantial pull request for visionOS support in Godot.
Youāve always been able to build a game in Godot and export it to run on iOS, but recently thanks to advancements in the engine and work by amazing folks like Miguel de Icaza, you can now embed a Godot game in an existing normal SwiftUI or UIKit app just as you would an extra UITextView or ScrollView.
Why is this important? Say you want to build a game or experience, but you donāt want it to feel just like another port, you want it to integrate nicely with iOS and feel at home there through use of some native frameworks and UI here and there to anchor the experience (share sheets, local notifications, a simple SwiftUI sheet for adding a friend, etc.). Historically your options have been very limited or difficult.
You no longer have to have āa Godot gameā or āan iOS appā, you can have the best of both worlds. A fun game built entirely in Godot, while having your share sheets, Settings screens, your paywall, home screen widgets, onboarding, iCloud sync, etc. all in native Swift code. Dynamically choosing which tool you want for the job.
(Again, this was technically possible before and with other engines, but was much, much more complicated. Unityās in particular seems to have been last updated during the first Obama administration.)
And truly, this doesnāt only benefit āgame appsā. Heck, if the user is doing something that will take awhile to complete (uploading a video, etc.) you could give them a small game to play in the interim. Or just for some fun you could embed a little side scroller easter egg in one of your Settings screens to delight spelunking users. Be creative!
A quick aside. It wouldnāt be an article about game dev on iOS without mentioning SpriteKit, Appleās native 2D game framework (Apple also has SceneKit for 3D).
SpriteKit is well done, and actually what I built most of Pixel Pals in. But it has a lot of downsides versus a proper, dedicated game engine:
Iām a big fan of using the right tool for the job. For iOS apps, most of the time thatās building something incredible in SwiftUI and UIKit. But for building a game, be it small or large, using something purpose built to be incredible at that seems like the play to me, and Godot feels like a great candidate there.
Simply add the SwiftGodotKit package to your Xcode project by selecting your project in the sidebar, ensuring your project is selected in the new sidebar, selecting the Package Dependencies tab, click the +, then paste the GitHub link.
After adding it, you will also need to select the target that you added it to in the sidebar, select the Build Settings tab, then select āOther Linker Flagsā and add -lc++.
Lastly, with that same target, under the General tab add MetalFX.framework to Frameworks, Libraries, and Embedded Content. (Yeah you got me, I donāt know why we have to do that.)
After that, you should be able to import SwiftGodotKit.
Now weāre ready to use Godot in our iOS app! What excites me most and I want to focus on is embedding an existing Godot game in your iOS app and communicating back and forth with it from your iOS app.
This way, you can do the majority of the game development in Godot without even opening Xcode, and then sprinkle in delightful iOS integration by communicating between iOS and Godot where needed.

To start, weāll build a very simple game called IceCreamParlor, where we select from a list of ice cream options in SwiftUI, which then gets passed into Godot. Godot will have a button the user can tap to send a message back to SwiftUI with the total amount of ice cream. This will not be an impressive āgameā by any stretch of the imagination, but should be easy to set up and understand the concepts so you can apply it to an actual game.
To accomplish our communication, in essence weāll be recreating iOSā NotificationCenter to send messages back and forth between Godot and iOS, and like NotificationCenter, weāll create a simple singleton to accomplish this.
Those messages will be sent via Signals. This is Godotās system for, well, signaling an event occurred, and can be used to signify everything from a button press, to a player taking damage, to a timer ending. Keeping with the NotificationCenter analogy, this would the be Notification that gets posted (except in Godot, itās used for everything, where in iOS land you really wouldnāt use NotificationCenter for a button press.)
And similar to Notification that has a userInfo field to provide more information about the notification, Godot signals can also take an argument that provides more information. (For example if the notification was āplayer took damageā the argument might be an integer that includes how much damage they took.) Like userInfo, this is optional however and you can also fire off a signal with no further information, something like āuserUnlockedProā for when they activate Pro after your SwiftUI paywall.
For our simple example, weāre going to send a āselectedIceCreamā signal from iOS to Godot, and a āupdatedIceCreamCountā signal from Godot to iOS. The former will have a string argument for which ice cream was selected, and the latter will have an integer argument with the updated count.
Open Godot.app (available to download from their website) and create a new project, Iāll type in IceCreamParlor, choose the Mobile renderer, then click Create.
Godot defaults to a 3D scene, so Iāll switch to 2D at the top, and then in the left sidebar click 2D Scene to create that as our root node. Iāll right-click the sidebar to add a child node, and select Label. Weāll set the text to the āIce cream:ā. In the right sidebar, weāll go to Theme Overrides and increase the font size to 80 to make it more visible, and weāll also rename it in the left sidebar from Label to IceCreamLabel.
Weāll also do the same to add a Button to the scene, which weāll call UpdateButton and sets its text to āUpdate Ice Cream Countā.

If you click the Play button in the top right corner of Godot, it will run and you can click the button, but as of now it doesnāt do anything.
Weāll select our root node (Node2D) in the sidebar, right click, and select āAttach Scriptā. Leave everything as default, and click Create. This will now present us with an area where we can actually code in GDScript, and we can refer to the objects in our scene by prefixing their name with a dollar sign.
Inside our script, weāll implement the _ready function, which is essentially Godotās equivalent of viewDidLoad, and inside weāll connect to our simple signal we discussed earlier. Weāll do this by grabbing a reference to our singleton, then reference the signal we want, then connect to it by passing a function we want to be called when the signal is received. And of course the function takes a String as a parameter because our signal includes what ice cream was selected.
extends Node2D
var ice_cream: Array[String] = []
func _ready() -> void:
var singleton = Engine.get_singleton("GodotSwiftMessenger")
singleton.ice_cream_selected.connect(_on_ice_cream_selected_signal_received)
func _on_ice_cream_selected_signal_received(new_ice_cream: String) -> void:
# We received a signal! Probably should do somethingā¦
pass
Note that we havenāt actually created the singleton yet, but we will shortly. Also note that normally in Godot, you have to declare custom signals like the ones weāre using, but weāre going to declare them in Swift. As long as theyāre declared somewhere, Godot is happy!
Letās also hook up our button by going back to our scene, selecting our button in the canvas, selecting the āNodeā tab in the right sidebar, and double-clicking the pressed() option. We can then select that same Node2D script and name the function _on_update_button_pressed to add a function that executes when the button is pressed (fun fact: the button being pressed event is also powered by signals).
func _on_update_button_pressed() -> void:
pass
Letās jump over to Xcode and create a new SwiftUI project there as well, also calling it IceCreamParlor. Weāll start by adding the Swift package for SwiftGodotKit to Swift Package Manager, and as mentioned above weāll add -lc++ to our āOther Linker Flagsā under āBuild Settingsā, add MetalFX, then go to ContentView.swift and add import SwiftGodotKit at the top.

From here, letās create a simple SwiftUI view so we can choose from some ice cream options.
var body: some View {
HStack {
Button {
} label: {
Text("Chocolate")
}
Button {
} label: {
Text("Strawberry")
}
Button {
} label: {
Text("Vanilla")
}
}
.buttonStyle(.bordered)
}
Weāll also create a new file in Xcode called GodotSwiftMessenger.swift. This will be where we implement our singleton that is akin to NotificationCenter.
We first import SwiftGodot (minus the Kit), essentially because this part is purely about interfacing with Godot through Godot, and doesnāt care about whether or not itās embedded in an iOS app. For more details on SwiftGodot see its section below.
Then, we annotate our class with the @Godot Swift Macro, which basically just says āHey make Godot aware that this class existsā. The class is a subclass of Object as everything in Godot needs to inherit from Object, itās essentially the parent class of everything.
Following that is your bog standard Swift singleton initialization.
Then, with another Swift Macro, we annotate a variable we want to be our signal which signifies that itās a Signal to Godot. You can either specify its type as Signal or SignalWithArguments<T> depending on whether or not the specific signal also sends any data alongside it. Weāll use that āsomethingHappenedā signal we mentioned early, which includes a string for more details on what happened.
Note that we used āice_cream_selectedā in Godot but āiceCreamSelectedā in Swift, this is because the underscore convention is used in Godot, and SwiftGodotKit will automatically map the camelCase Swift convention to it.
Now we need to tell Godot about this singleton we just made. We want Godot to know about it as soon as possible, otherwise if things arenāt hooked up, Godot might emit a signal that we wouldnāt receive in Swift, or vice-versa.
So, weāll hook it up very early in our app cycle. In SwiftUI, you might do this in the init of your main App struct as Iāll show below, and in UIKit in applicationDidFinishLaunching.
In addition to the boilerplate code Xcode gives us, weāve added an extra step to the initializer, where we set a callback on initHookCb. This is just a callback that fires as Godot is setup, and it specifies what level of setup has occurred. We want to wait until the level setup is reached, which means the game is ready to go (you could set it up at an even earlier level if you see that as beneficial).
Then, we just tell Godot about this type by calling register, and then we register the singleton itself with a name we want it to be accessible under.
Again, we want to do this early, as if Godot was already setup in our app, and then we set initHookCb, its contents would never fire and thus we wouldnāt register anything. But donāt worry, this hook wonāt fire until we first initialize our Godot game in iOS ourself, so as long as this code is called before then, weāre golden.
Lastly, everything is registered in iOS land, but thereās still nothing that emits or receives signals. Letās change that by going to ContentView.swift, and change our body to the following:
Thereās quite a bit going on here, but letās break it down because itās really quite simple.
We have two new state variables, the first is to keep track of the new ice cream count. Could we just do this ourselves purely in SwiftUI? Totally, but for fun weāre going to be totally relying on Godot to keep us updated there, and weāll just reflect that in SwiftUI to show the communication. Secondly and more importantly, we need to declare a variable for our actual game file so we can embed it.
We do this embedding at the top of the VStack by creating a GodotAppView, a handy SwiftUI view we can now leverage, and we do so by just setting its environment variable to the game we just declared.
Then, we change our buttons to actually emit the selections via signals, and when the view appears, we make sure we connect to the signal that keeps us updated on the count so we can reflect that in the UI. Note that we donāt also connect to the iceCreamSelected signal, because we donāt care to receive it in SwiftUI, weāre just firing that one off for Godot to handle.
Letās update our gdscript in Godot to take advantage of these changes.
func _on_ice_cream_selected_signal_received(new_ice_cream: String) -> void:
ice_cream.append(new_ice_cream)
$IceCreamLabel.text = "Ice creams: " + ", ".join(ice_cream)
func _on_update_button_pressed() -> void:
var singleton = Engine.get_singleton("GodotSwiftMessenger")
singleton.ice_cream_count_updated.emit(ice_cream.size())
Not too bad! We now receive the signal from SwiftUI and update our UI and internal state in Godot accordingly, as well as the UI by making our ice cream into a comma separated list. And then when the user taps the update button, we then send (emit) that signal back to SwiftUI with the updated count.
To actually see this live, first make sure you have an actual iOS device plugged in. Unfortunately Godot doesnāt work with the iOS simulator.
Secondly, in Godot, select the Project menu bar item, then Export, then click the Add button and select āiOSā. This will bring you to a screen with a bunch of options, but my understanding is that this is 99% if youāre building your app entirely in Godot, you can plug in all the things youād otherwise plug into Xcode here instead, and Godot will handle them for you. That doesnāt apply to us, weāre going to do all that normally in Xcode anyway, we just want the game files, so ignore all that and select āExport PCK/ZIPā¦ā at the bottom. Itāll ask you where you want to save it, and I just keep it in the Godot project directory, make sure āGodot Project Pack (*.pck)ā is selected in the dropdown, and then save it as main.pck.

Thatās our āgameā bundled up, as meager as it is!
Weāll then drop that into Xcode, making sure to add it to our target, then we can run it on the device!
Here weāll see choosing the ice cream flavor at the bottom in SwiftUI beams it into the Godot game thatās just chilling like a SwiftUI view, and then we can tap the update button in Godot land to beam the new count right back to SwiftUI to be displayed.

Not exactly a AAA game but enough to show the basics of communication š
Look at you go! Take this as a leaping off point for all the cool SwiftUI and Godot interoperability that you can accomplish, be it tappings a Settings icon in Godot to bring up a beautifully designed, native SwiftUI settings screen, or confirmation to you your game when the user updated to the Pro version of your game through your SwiftUI paywall.
An additional fun option (that sits at the heart of SwiftGodotKit) is SwiftGodot, which allows you to actually build your entire Godot game with Swift as the programming language if you so choose. Swift for iOS apps, Swift on the server, Swift for game dev. Swift truly is everywhere.
For me, Iām liking playing around in GDScript, which is Godotās native programming language, but itās a really cool option to know about.
A fear might be that embedding Godot into your app might bloat the binary and result in an enormous app download size. Godot is very lightweight, adding it to your codebase adds a relatively meager (at least by 2025 standards) 30MB to your binary size. Thatās a lot larger than SpriteKitās 0MB, but for all the benefits Godot offers thatās a pretty compelling trade.
(30MB was measured by handy blog sponsor, Emerge Tools.)
If you log something in Godot/GDScript via print("something") that will also print to the Xcode console, handy!
Exporting the pck file from Godot to Xcode is quite a few clicks, so if youāre doing it a lot it would be nice to speed that up. We can use the command line to make this a lot nicer.
Godot.app also has a headless mode you can use by going inside the.app file, then Contents > MacOS > Godot. But typing the full path to that binary is no fun, so letās symlink the binary to /usr/local/bin.
sudo ln -s "/Applications/Godot.app/Contents/MacOS/Godot" /usr/local/bin/godot
Now we can simply type godot anywhere in the Terminal to either open the Godot app, or we can use godot --headless for some command line goodness.
My favorite way to do this, is to do something like the following within your Godot project directory:
godot --headless --export-pack "iOS" /path/to/xcodeproject/target/main.pck
This will handily export the pck and add it to our Xcode project, overwriting any existing pck file, from which point we can simply compile our iOS app.
I really think Godotās new interoperability with iOS is an incredibly exciting avenue for building games on iOS, be it a full fledged game or a small little easter egg integrated into an existing iOS app, and hats off to all the folks who did the hard work getting it working.
Hopefully this serves as an easy way to get things up and running! It might seem like a lot at first glance, but most of the code shown above is just boilerplate to get an example Godot and iOS project up and running, the actual work to embed a game and communicate across them is so delightfully simple!
(Also big shout out to Chris Backas and Miguel de Icaza for help getting this tutorial off the ground.)