Setting Up an SDL3 Mac App in XCode 16
This is mainly a note for my future self, but making it a blog post in case it helps anyone else. This weekend, I decided to try making a little SDL3 app on my new M4 MacBook Pro. It took me a few hours to figure out how to get XCode to play nice with the SDL3 framework given MacOS’s paranoia around unsigned code and malware.
Here’s the summary for anyone whose Google searching takes them here. The instructions are fairly similar to the SDL3 README for MacOS:
-
Start up XCode. Choose “File > New > Project…”.
-
Select “macOS”, then under “Application” choose “App”. This step is important. Choosing “Command Line Tool” will not work. Click “Next”.
-
Give your app a name. For “Interface”, choose “XIB”. For “Language”, choose “Objective-C”. Click “Next”. Choose a place to save your project.
Now you have a vanilla “Hello World” Mac app. The next step is integrating SDL:
-
Download a release of SDL. As I’m writing this, the latest is 3.2.16. Download the corresponding
.dmg
file. Open it in Finder. -
Open
SDL3.xcframework/macos-arm64_x86_64
and copySDL3.framework
from there to somewhere in your XCode project’s source tree. (I made alib
folder and put it there in mine.) -
In XCode, in the file explorer on the left, click the top project icon to open the settings for it. In the main panel on the right, click the icon for your app target under “Targets” to open the target settings.
-
Under “Frameworks, Libraries, and Embedded Content”, click the “+” to add a new framework. In the popup that appears, click “Add Other… > Add Files…”. Navigate to wherever you copied “SDL3.framework” and choose it.
Then let’s use SDL and verify everything is working.
-
In
main.m
, comment out the entiremain()
function. -
Make a new
main.c
file and copy the contents of hello.c from the SDL docs into it. -
Compile and run.
If that all works, you should see a delightful pixelated “Hello World!” fill your screen. Press any key to quit.
Let’s make sure the full iteration loop works. (Read below to see why I feel the need to verify this.):
-
In
main.c
insideSDL_AppIterate()
, change the message string. -
Hit Command-R to run. You should see your updated message on screen.
There you go, you now have a working shell of a Mac app with using a local
install of SDL3. You can probably delete the main.m
file entirely and maybe
some of the other generated files that XCode put in the new project, but I
haven’t figured that out yet.
Now here’s all the wrong stuff I tried first before I got that working…
Attempt 1: Download a release and use the xcframework#attempt-1-download-a-release-and-use-the-xcframework
In XCode, I created a new “CLI Command” C app. That got me a “Hello, world!” up and running without any problems. Then I copied the contents of this SDL hello example into my source file.
As expected, that failed to compile because it couldn’t find SDL3/SDL.h
. So
next is getting SDL linked in.
The first thing I did was download the 3.2.16 stable release
installer. I downloaded the DMG file and opened that. Then following
the instructions, I dragged the entire SDL3.xcframework
directory into the
XCode project.
That correctly added a reference to the Framework and got XCode to a point where it could resolve the include and compile the program. Great.
But when I ran it, it failed at startup. The OS tried to dynamically load the SDL3 framework, but then balked because it thinks the framework isn’t correctly signed and safe to use. The OS gave me a popup error with “SDL3.framework Not Opened” because “macOS cannot verify that this app is free from malware”.
Crap.
Attempt 2: Embed the framework#attempt-2-embed-the-framework
Poking around online, some people say this error can be fixed by embedding the
SDL framework in my app and having XCode sign it when it embeds it. In XCode, in
the target settings under “Build Phases”, there is a panel for “Embed
Frameworks”. I dragged SDL3.xcframework
under there and checked “Code Sign On
Copy”.
Alas, when I tried to compile, now I got a build error. It complained that two tasks in the build were trying to produce the same file. I’ve lost the exact error message but it looked like one of the tasks was a simple file copy and the other was a “ProcessXCFramework” invocation. I’m guessing the latter is the task that signs and embeds the framework.
I tried basically every combination of build settings to try to fix this: removing the framework from “Link Binary with Libraries”, checking “Copy only when installing”, and a bunch of other things I don’t remember. Nothing worked. Either I removed so much that the compiler couldn’t find the framework at all, or it got the duplicate build file error.
Attempt 3: Don’t use the entire “SDL3.xcframework”#attempt-3-dont-use-the-entire-sdl3xcframework
I started watching this video to see if it could help. He doesn’t use
the entire SDL3.xcframework
bundle. Instead, he opens that, goes into
macos-arm64_x86_64
and copies the SDL3.framework
framework out of there.
I removed SDL3.xcframework
from my XCode project and dragged SDL3.framework
into it instead. That gave me something that compiled, but when I tried to run,
it failed at load time with:
dyld[15605]: Library not loaded: @rpath/SDL3.framework/Versions/A/SDL3 Referenced from: <...> /Users/... Reason: tried: '/Users/.../Build/Products/Debug/SDL3.framework/ Versions/A/SDL3' (no such file)
In the target settings, I again added the framework (but this time
SDL3.framework
not SDL3.xcframework
to “Embed frameworks”). For reasons
that are entirely unclear to me, that worked. I hit Command-R and…

All done, right? Not so fast. I made a tiny tweak to the code and tried to run it again. The compile immediately failed with:
'SDL3/SDL.h' file not found
What? I literally just compiled and ran it. Just to make sure I wasn’t crazy, I cleaned the build directory and compiled again. That worked! “Hello World!” popped up on screen again.
Build again… same error.
Clean build and then build. Works!
So I can build once but then something in the build directly causes any subsequent compile to fail. The full error is:
Did not find header 'SDL.h' in framework 'SDL3' (loaded from '/Users/.../Index.noindex/Build/Products/Debug')
So what I think is happening is that in the first clean build, it locates the framework inside my app’s main directory. That framework does have the headers so the compiler can find what it needs.
Then after that first build, the compiler starts using the framework that it
copied into the build directory. But when I look in there, Debug
is completely
empty.
Attempt 4: Project-relative DerivedData#attempt-4-project-relative-deriveddata
I hate when tools stuff built data in random system directories unrelated to the project I’m working on because those files end up sitting around forever. So the next thing I did was go under “Project Settings” and changed the “Derived Data” to be “Project-relative Location”.
I cleaned and built successfully. But, again, if I made a trivial code change
and tried to build again, it failed. This time, though, there was actually
something in DerivedData/my_app/Build/Products/Debug/
.
I can see an SDL3.framework
directory under there. It contains
Versions/A/SDL3
(and some other stuff). So the library is there as expected.
But there is no Headers
directory under Versions/A/
, nor is there a
Headers
symlink in SDL3.framework
pointing to it.
In theory, that’s fine. In the compiled app, all that’s needed is the library itself. But for reasons I don’t understand, XCode is trying to use this semi-copied version of the framework for subsequent compiles instead of the original one in my project’s source tree.
Attempt 5: Explicit header search path#attempt-5-explicit-header-search-path
OK, if XCode can’t find the headers because it’s looking in the wrong place,
maybe I can just tell it where to look. Under “Search Paths”, I added
lib/SDL3.framework/Headers
to “Header Search Paths”. (I put the framework
under lib
in my project tree, hence the lib/
part.) Didn’t help. Tried
making it recursive. Nope.
Maybe just lib/SDL3.framework
? No.
I’m guessing the problem here is that the include paths look like SDL3/SDL.h
,
not just SDL.h
, so I need to get XCode to understand that the headers are
inside a framework to resolve the SDL3/
part.
Attempt 6: Framework search path#attempt-6-framework-search-path
OK, so maybe it’s not a header search path. Instead, I tried adding lib/
to
“Framework Search Paths”. No help. Making it recursive didn’t help. Neither
did lib/SDL3.framework
.
I do a bunch of Googling and eventually stumble onto this old StackOverflow post. He specifically mentions only getting the error after the first build, so this is my exact issue. And the author apparently found a solution!
…which they described only as “It works know after certain changes!”. So helpful.
I eventually found this Apple Developer forum thread, which also sounds like my problem.
Attempt 7: An App instead of a CLI Command#attempt-7-an-app-instead-of-a-cli-command
The forum thread suggests that the issue may have something to do with creating a “CLI Command” instead of an “App”. I’ve always done the former in XCode because that seemed like the simplest way to get a vanilla C/C++ app up and running. In older versions of XCode/macOS, it worked fine. But maybe it doesn’t play nice with frameworks in later versions?
I trash the entire XCode project and create a new one. This time I choose “App” instead of “CLI Command”. I set the language to “Objective-C” (because that’s the “closest” to C, I guess—I’m winging it here). I set “Interface” to “XIB” based on this README.
I run it and get an empty window, the “Hello World” of GUI apps. OK.
I copy SDL3.framework
out of SDL3.xcframework/macos-arm64_x86-x64
into
lib/
in my new app’s source tree. Then in XCode, in the “General” tab of the
main target, under “Frameworks, Libraries, and Embedded Content”, I click the
“+”. From there I choose “Add Other…”, “Add Files…”, and add
lib/SDL3.framework
.
I admit to feeling a moment of anticipation. Will this actually work?
I make a new .c
file and paste in the hello.c
SDL3 example code. I comment
out main()
in the main.m
that XCode generated. Compile and… it runs! I’ve
got an SDL3 “Hello World!” on screen again.
Now the real test… can I make a change and build again?
Yes! It works! Finally!
As far as I can tell, I am up and running now. I didn’t have to add SDL3 to my system framework path. (I didn’t want to do that because ultimately, I want an app that users can just run without having to install SDL themselves.)
So it looks like the failure mode was creating a “CLI Command” instead of an “App”. How on Earth would I be expected to know that?