At the peak lab we develop many different apps for iOS, tvOS, macOS, watchOS as well as server side swift applications. In the past few years the most common package managers were CocoaPods and Carthage. But for some large projects we discovered limitations for example the missing ability to nest dependencies in Carthage.
Swift Package Manager is a great tool for managing the dependencies of your project.
The Swift Package Manager is a tool for managing the distribution of Swift code. It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies.
The problem with Swift Package Manager is, that it is a Package Manager for Swift and not a platform package manager for iOS. So there is no explicit support for depending on UIKit or AppKit.
Many voices are saying that it is not possible to use Swift Package Manager for iOS. Others offer solutions which are not working when it comes to uploading the app to the appstore.
Real app walkthrough
In this post I’m going to explain how to use Swift Package Manager for iOS projects and which kind of problems I had. I’m explaining this with one of the apps I made: JP Fan App.
The app in my example is really simple and easy to understand. There is a backend service (using vapor, also written in swift) running on a small Digital Ocean droplet. The backend service provides data in form of a REST interface. To access the backend service, my idea was to write a basic HTTP client which wraps the REST interface: JPFanAppClient. The good thing: writing this HTTP client using Swift Package Manager enables me to use the Framework on all environments iOS, macOS, tvOS, watchOS, Linux. So its easy for me to use the Framework on all of these platforms as well.
It becomes really interesting when you think about using the same frameworks in the backend and frontend.
Setup Swift Package Manager
We’ll start from the project folder where my
.xcworkspace files are.
To use Swift Package Manager dependencies, you need to create a small “wrapper” package, generate the Xcode project and do the correct import into your existing iOS Xcode project.
Create the Dependencies Package
Add your dependencies to the new generated file
In my case
Consider the part where
JPFanAppClient must be included in the dependencies array in the
Fetch your dependencies.
Your output may be much smaller. In my case I’m using the Quack HTTP client which wrapps Alamofire/Vapor HTTP and works on all swift platforms as well.
Import into your Xcode project
To import the frameworks into the existing Xcode workspace I’ll take advantage of the feature to generate an Xcode Project from my package.
File > Add Files to “Your Workspace Name”
Select the new Dependencies.xcodeproj
Add all the frameworks from your Dependencies project to Embedded binaries.
When importing one of the new dependencies one of the first errors I got was:
Module file's minimum deployment target is ios11.4 v11.4: /Users/christoph/Library/Developer/Xcode/DerivedData/JPPerformance-cteaudztylounnaqgegjnjqthxbt/Build/Products/Debug-iphonesimulator/SwiftyJSON.framework/Modules/SwiftyJSON.swiftmodule/x86_64.swiftmodule
To solve this problem we have to set the deployment target to the matching target of the iOS project. In my case the JP Fan App iOS Deployment Target is iOS 10. Since we’ll regulary update our dependencies and regenerate the
Dependencies.xcodeproj it wasn’t an option for me to set anything in the project by hand. We’ll automate this with a Rakefile as described in other “solutions”.
Caution: this is not the final Rakefile i’ll describe the problems with using the other solutions and how I solved them.
rake dependencies will now update our dependencies, regenerate the
Dependencies.xcodeproj and change the settings so that they are valid for our iOS Project. You may need to change
IPHONEOS_DEPLOYMENT_TARGET to your desired deployment target. So you’ll never have to type
swift package generate-xcodeproj again, from now on we’re using Rake.
Problems seems to be fixed
Try to rebuild your Xcode project - everything should seem to work, the compiler error disappears. ✅🎉
I’m using fastlane to automate screenshot generation, testing or uploading for beta-testing or deployment to AppStore Connect. So the next error occured when calling
Compiling: worked ✅
Generating the Archive: worked ✅
Exporting the Archive: Boom 💥
Somewhere when it comes to the command
xcodebuild -exportArchive -exportOptionsPlist /var/folders/7q/004mxqbj4kn117z_rptqddg80000gn/T/gym_config20180822-62378-1urdrvm.plist -archivePath '/Users/christoph/Library/Developer/Xcode/Archives/2018-08-22/JPPerformance 2018-08-22 14.37.11.xcarchive' -exportPath /var/folders/7q/004mxqbj4kn117z_rptqddg80000gn/T/gym_output20180822-62378-g7reue I got a bunch of errors and warnings like the following:
IDEDistribution: Step failed: <IDEDistributionPackagingStep: 0x7fbf75727ef0>: Error Domain=IDEFoundationErrorDomain Code=1 "ipatool failed with an exception: #<CmdSpec::NonZeroExcitException: /Applications/Xcode.app/Contents/Developer/usr/bin/bitcode-build-tool exited with pid 64585 exit 1
warning: using sysroot for 'MacOSX' but targeting 'iPhone'
error: libswiftCore.dylib not found in dylib search path
If you want to build your app not just in Xcode for
debug but also, in
release to bring the app into the app store you can’t avoid the part of exporting the archive.
For me the errors looked like “there is some macOS stuff inside my iOS binary”. So I checked the configuration and found quite a few entries:
LD_RUNPATH_SEARCH_PATHS has a reference to macOS:
and MACOSX_DEPLOYMENT_TARGET is set to the Swift Package Manager’s default
Adjusting the Rakefile
By adding two simple lines to the
Rakefile we can reduce the amount of errors and warning when exporting the archive to: 0 🎉
So the final Rakefile looks like this:
Update your dependencies using
rake dependencies. From this point on, the export of the archive worked for me and I hope yours will work as well.
For uploads to AppStore Connect you may have to set
CURRENT_PROJECT_VERSION in your Build Settings.