Xcode: two Apps with a shared private Framework, in a WorkSpace
I’ve been working on a reasonably large iOS project that has ended up consisting of multiple, related, iOS apps for which I’ve wanted to share a large amount of code through the use of shared, private, frameworks. This seems like it should be simple, but instead I’ve run into all kinds of issues.
When building, you might end up with a warning about a missing directory:
ld: warning: directory not found for option '-F…/build/Debug-iphoneos'
When trying to start the app in the iOS Simulator, without Xcode attached, it might crash with the error image not found:
Dyld Error Message: Library not loaded: @rpath/….framework/… Referenced from: …/Library/Developer/CoreSimulator/Devices/… Reason: image not found
If you try and run the app on a iOS device at all, whether with Xcode attached or not, you might also end up with an image not found error:
dyld: Library not loaded: @rpath/….framework/… Referenced from: /var/mobile/Containers/Bundle/Application/…/….app/… Reason: image not found
And most infuriating of all, once you come to create an archive of your app and attempt to validate it for TestFlight or the iOS App Store, you might end up with an error about missing BCSymbolMap files:
An error occurred during validation
The archive did not contain <DVTFilePath:…:‘…/Library/Developer/Xcode/Archives/….xcarchive/BCSymbolMaps/….bcsymbolmap’> as expected.
Searching the Internet has yielded increasingly complicated, and convoluted solutions up to, and including, custom build scripts. None of these were palatable, and after some experimentation, I believe I’ve found the way to make this work perfectly!
First you’ll want to create a new workspace to keep everything together. Use File → New → Workspace…, create a New Folder for your workspace to live in, and pick a name for the workspace. This will give you a new Xcode window, which unlike a usual project, contains no targets or files.
Now we’ll create the project for the first iOS app. With the workspace open, use File → New → Project…, pick the template, and enter the product name, etc. as usual. When you come to choose the location, make sure that the Add to: box has the workspace you selected.
This will return you to your workspace, but now with your new project added to it. Do it again for the second project, and be doubly careful at the last screen. The Add to: box will default to the workspace, but now there’s a new Group: box, and that defaults to the project you just created, not the workspace, so make sure you change it!
Now we can add the framework for the shared code. Once again use File → New → Project…, and this time pick the Cocoa Touch Framework project type.
Give your framework a name on the next screen, and again choose a location within your workspace folder, with the Add to: box containing the workspace, and changing the Group: box from the last project, to the workspace:
Now you have a workspace containing your two applications, and a framework for the code that you want them to share. The next bit is the tricky bit; we need both apps to depend on the framework, and while there are a number of ways of doing this, only one seems to work consistently for me without running into any of the warnings or errors above.
First you’ll want to reveal the Products of the framework you just added, and note that the final framework product is red, indicating that it doesn’t yet exist.
This is the first thing we want to fix, if we try and set things up without it being built, Xcode sometimes does the wrong thing! From the Scheme menu, select the framework itself, and from the Destination menu, select Generic iOS Device. Hit ⌘B or Product → Build to build the framework itself, and note that it turns black in the files list.
It’s safe to add it to the app. Select your first iOS app target, make sure the General tab is selected, and scroll until you can see Embedded Binaries. Drag the framework from the left hand list, and drop it into this section. I’ve found that dragging, rather than using the “+” button; and using the Embedded Binaries section, rather than Linked Frameworks and Libraries one, is key to making Xcode work.
This will not just add it as an embedded binary, it will also add it to the Linked Frameworks and Libraries section as well, and will create a copy of the framework under the app project in the left hand list.
We’re not quite there… one more thing to do, select that copy of the framework from the files list for the app project—not the framework project—and make sure you can see the File Inspector. Note that the Location currently has Absolute Path, you need to change this to Relative to Build Products:
Do the same sequence for your second app.
By following these steps:
- the App depends on the Framework, so builds with the latest version of the Framework source each time;
- the iOS Simulator correctly embeds the Debug-iphonesimulator version of the Framework,
- which means that the app will run both when attached to Xcode, and when not attached;
- when built for the iOS Device, it correctly embeds the Debug-iphoneos version of the Framework,
- so the app runs on the device both when attached to Xcode, and when not attached;
- and the correct target versions are built and embedded when archiving,
- which means you can validate and upload with both app symbols and bitcode.