Using Carthage in a Catalyst app
“The process starts by checking a single box in Xcode” and then continues with compile errors. At least if your iOS app is using Carthage to manage its dependencies. Here my notes from working through those in my projects that use Carthage.
1.) Get Carthage to compile Catalyst frameworks
As of 21 October 2019, Carthage doesn’t yet support building specifically for macCatalyst nor does it support building xcframeworks (which would bundle a macCatalyst framework along with an iOS framework in the same meta framework). These features are coming up as part of Carthage’s Q4 2019 - Q1 2020 roadmap, but in the meantime you’ll have to use forks or PR that add those features.
A simple way to start is to use the fork from Glenn L. Austin which adds --platform macCatalyst
option to Carthage:
- Clone or download the fork of the Carthage repository
- Compile and install locally:
make install
. Warning: If you provide your admin password in the last step, you might overwrite your existing stable release of thecarthage
command. Instead you can callsudo cp -f .build/release/carthage /usr/local/bin/carthage-catalyst
to add it as a newcarthage-catalyst
command.
Now you’re set and you can use carthage-catalyst update --platform macCatalyst
to start building your dependencies for Catalyst. If that goes well, you can go straight to 3.) below, otherwise:
2.) Address Carthage issues
Firstly, if you have any Carthage compilation issues, update your depencies to the latest version as some projects have since added explicit support for Catalyst or might have since removed the use of deprecated APIs – many of which are unavailable in Catalyst.
As you’re working through these issues you’ll go through this:
- While
carthage-catalyst build --platform macCatalyst --no-use-binaries --cache-builds
fails- If updated branch exists for that library that adds support for Catalyst
- Update your
Cartfile
- Run
carthage-catalyst update --platform macCatalyst --no-use-binaries --cache-builds
- Update your
- Else
- Fix up the project manually in
Carthage/Checkouts
- Fix up the project manually in
- If updated branch exists for that library that adds support for Catalyst
Here’s the manual fixes that I had to apply to different projects:
Carthage raises Fatal error: No SDKs found for scheme X
If this happens, do the following:
- In the project configuration, check if “Base SDK” is set to “iOS”, if it’s not, set it to that.
- If it still fails, add “macosx” to “Supported Platforms”
- If it still fails, check if “Valid Architectures” overrides the default and if so, remove that override.
That should then make Carthage itself happy, though xcodebuild
might not:
Compile issues
- API not available: Your iOS library might be using deprecated classes that might still be available but have not been added to Catalyst.
UIWebView
in particular could be an offender here. Remove those uses. - Linked framework issue. If the library itself has dependencies that are managed using Carthage, you also need to update “Framework Search Paths”. In particular, you need to add new special cases for “Any macOS SDK” adding
$(PROJECT_DIR)/Carthage/Build/macCatalyst
and making sure it doesn’t use theBuild/iOS
directory.
Libraries that didn’t work out-of-the-box:
- Kingfisher: Set “Base SDK” to “iOS”
- KVNProgress: 1.) Add “macosx” to “Supported Platforms” + Delete overwrite of “Valid Architectures”; 2.) Delete as it depends on GLKit which Mac Catalyst doesn’t seem to have.
- RxSwift: Set “Base SDK” to “iOS”
- RxDataSources: Add “macosx” to “Supported Platforms” + fix up “Framework Search Paths”
- UserVoice: Removed as it’s deprecated and relies heavily on UIWebView
3.) Use it in your app
To use the Catalyst frameworks in your iOS app, you’ll need to adjust your build settings as follows – the first step isn’t necessary but makes the other steps easier:
1) Add a “User-Defined Setting” and call it CARTHAGE_SUBFOLDER
. It should default to “iOS”, and be overwritten as “macCatalyst” for “Any macOS SDK”:
Add a User-Defined Setting
2) Change your “Framework Search Paths” to use $(PROJECT_DIR)/Carthage/Build/$(CARTHAGE_SUBFOLDER)
rather than hard-coding the “iOS” subfolder, so that your builds are using the correct frameworks.
3) Adjust your carthage copy-frameworks
build phase, to use different input and output file lists depending on whether you build for iOS or Mac Catalyst. You can do this by using $(SRCROOT)/carthage_input_$(CARTHAGE_SUBFOLDER).xcfilelist
for the Input File Lists and $(SRCROOT)/carthage_output_$(CARTHAGE_SUBFOLDER).xcfilelist
for the Output File Lists. Your carthage_input_iOS.xcfilelist
and ``carthage_output_iOS.xcfilelist should list all your Carthage frameworks and their target paths as normal, however your the
carthage_input_macCatalyst.xcfilelist and
carthage_output_macCatalyst.xcfilelist` should be empty. Instead, you’ll embed the Catalyst frameworks as follows:
4) Add a new “Copy Files Phase” step, and add the Carthage frameworks there, setting them to “macOS” and ticking the “Code Sign On Copy” option.
Now things should be working. If they don’t, it’s possibly Xcode’s fault. Try nuking your “Derived Data” folders might help. If you’re using fastlane a quick fastlane action clear_derived_data
might do the trick.
4.) Archive and distribute
With the above steps doing an Archive build should already work.
If you get an error like MyFramework.framework: bundle format is ambiguous (could be app or framework)
, then you likely skipped step 3 above. I always got this when trying to use the carthage copy-frameworks
build phase, even when pointing these at $(SRCROOT)/Carthage/Build/macCatalyst/MyFramework
. Follow my step 3 and 4 above to address this, as the Carthage README states this step is not necessary for macOS. However, I haven’t yet confirm if this works for App Store builds as I’ve only distributed manually by signing using my Developer ID.
Next, if you try to distribute or notarize your achive, you might get an error if you don’t yet have a distribution certificate of type “Developer ID Application”. Create one on the Apple Developer portal.
In Xcode 12, notarization might fail with a not-very-descriptive Code signing "A" failed
error. If this happens press the button to view the logs, hunt the logs which framework failed. For me Eureka and RxSwift made issues as they overwrite the Enable Bitcode
setting. Modify their build settings, removing the overwrites, delete the cached Carthage builds, re-build them, re-archive your app, re-submit, and and repeat until it works.
Next steps
I hope all of this will get easier once Carthage supports XCFrameworks. Though, as of December 2020, that’s still not available and the support in PRs supports XCFrameworks but still not yet a --platform macCatalyst
setting…
Anyway, now that your app is running on macOS, off to refine it and make it feel more like a Mac app than an iOS app in a Mac window:
- Apple’s Mac Catalyst HIG
- Customising NSToolbar in UIKit
- Beyond the Checkbox
- Plugin Architecture in Swift (to get that AppKitGlue working)
- Designing LookUp for macOS
Changelog:
- 8 Dec ‘20: Added instructions for Xcode 12.
- 4 March ‘20: RxSwift 5.1 is now Carthage compatible.
- 10 November ‘19: Updated 3.) with specific instructions of a more robust case.
- 9 November ‘19: Added section ‘4.) Archive and distribute’. Removed note on KeychainAccess and OAuthSwift as those are Catalyst compatible as of the latest release.