Simple iOS release with fastlane
While preparing your iOS app for release you will face with a bunch of tedious repetitive procedures like dealing with certificates, provisioning profiles, uploading translations, taking screenshots using all languages supported by the app and so on.
Below I’ll tell you about tool called Fastlane which will take care about most of those task for you.
Tools overview
For building, signing and publishing application we will use following tools:
- produce for creating new app in Apple Developer Portal and iTunes Connect
- match for creating certificates and provisioning profiles
- gym for building and archiving iOS app
- snapshot & frameit for taking screenshots on all supported languages
- precheck & deviler for checking metadata and publishing the app
Installation
To use fastlane XCode and homebrew need to be installed.
$ xcode-select --install # Ensure latest XCode command-line tools
$ brew cask install fastlane
It’s recommended to use Gemfile for Fastlane dependencies. Also it will speedup fastlane.
$ sudo gem install bundle
Create a Gemfile in the root of your project with the following content:
source "https://rubygems.org"
gem "fastlane"
Then run:
$ bundle update
Add both Gemfile and Gemfile.lock to version control.
Navigate to project’s directory and run.
$ bundle exec fastlane init -u <your-apple-id>
Fastlane will generate a basic configuration and fetch existing metadata from iTunes Connect if there is one. As a result ./fastlane directory with Appfile and Fastfile in it will be created.
Appfile could look like this.
app_identifier "com.datarockets.CoolApp"
apple_id "your-apple-id"
team_id "YOURTEAMID"
In case you have multiple apps and you don’t want to face with “Maximum number of certificates generated” one day, you can use single certificate for all your app. Just specify all your app identifiers as an array like this:
app_identifier ["com.datarockets.AppOne", "com.datarockets.AppTwo"]
Produce
Fastlane produce allows creating new iOS app in Apple Developer Portal and iTunes Connect from command line. Run following command.
$ bundle exec fastlane produce -u <your-apple-id> -a <your-app-identifier>
Match
Now, when new application was created it’s time to deal with certificates and provisioning profiles. Fastlane match allows syncing certificates and provisioning profiles across team using private git repository.
First of all creating a separate private git repository where all certificates and provisioning profiles will be stored. Is this secure?
You can use a separate branch for each of your applications. It’s working solution, however, currently I prefer to set app_identifier as array.
Run following command. It will create match configuration file — Matchfile in ./fastlane directory.
$ bundle exec fastlane match init
Matchfile could look like this:
git_url "https://github.com/datarockets/private-repository"
git_branch "your-app-branch"
team_id "YOURTEAMID"
username "your-apple-id"
force_for_new_devices "true"
Also you can use ssh git url with match. Just amend git_url to something like this: git@github.com:datarockets/private-repository.git
Generate certificates, and provisioning profiles using one of the following commands.
$ bundle exec fastlane match development # For Development
$ bundle exec fastlane match adhoc # For Ad-hoc
$ bundle exec fastlane match appstore # For App Store
$ bundle exec fastlane match enterprise # For Enterprise
That’s how private git repository will looks like.
Now go to XCode and select newly created profiles in the signing section.
Gym
Time to build app! Fastlane gym allows to generate a signed iOS app.
Following command will create a configuration file — Gymfile in ./fastlane directory.
$ bundle exec fastlane gym init
Gymfile could look like this.
scheme "CoolApp"
sdk "iphoneos10.0"
include_bitcode true
include_symbols true
clean true
output_directory "./fastlane/build"
output_name "CoolApp_1.0.0"
Now, to build and package our iOS app in .ipa file run the following command.
$ bundle exec fastlane gym
Snapshot
To submit our app for review we need to have at least one screenshot per localization, according to guidelines. The highest resolution screenshot for each device type can be used. Taking all this screenshot could be a tedious and time-consuming task, especially for numerous of supported languages. Fastlane snapshot allows to automate this task.
Following command will create a configuration file —Snapfile in ./fastlane directory.
$ bundle exec fastlane snapshot init
Snapfile could look like this.
devices([
"iPhone 7 Plus"
])
languages([
"ru",
"en",
"de",
"fr",
"it",
"es",
"nl"
])
scheme "CoolApp"
output_directory "./fastlane/screenshots"
clear_previous_screenshots true
To run fastlane snapshot
create a simple UI test, which will be ran to take desired screenshot. Also, you need to add SnapshotHelper.swift file generated by fastlane snapshot init
to your UI test target.
import XCTest
class MunchkinLevelCounterUITests: XCTestCase {
override func setUp() {
super.setUp()
continueAfterFailure = false
let app = XCUIApplication()
setupSnapshot(app)
app.launch()
}
override func tearDown() {
super.tearDown()
}
func testScreenshots() {
let app = XCUIApplication()
XCUIDevice.shared().orientation = .portrait
// Screen number onene
snapshot("0-First-screen")
// Screen number two
app.navigationBars.buttons.element(boundBy: 0).tap()
snapshot("1-Second-screen")
// Screen number three
app.navigationBars.buttons.element(boundBy: 1).tap()
app.alerts.element(boundBy: 0).buttons.element(boundBy: 1).tap()
snapshot("2-Third-screen")
// Retunr to the first screen
XCUIApplication().navigationBars.buttons.element(boundBy: 0).tap()
XCTAssert(true)
}
}
All is set up. Now just run following command.
$ bundle exec fastlane snapshot
Frameit
Optionally to make screenshots look better we can use fastlane frameit to put screenshots into real device frames. To do so navigate to ./fastlane/screenshots directory and run the following command.
$ bundle exec fastlane frameit gold
Deliver
Now fill in all required metadata and upload it along with screenshots and app binary to iTunes Connect. To do so use fastlane deliver. App metadata could be placed in Deliverfile itself or in separate .txt files in ./fastlane/metadata directory. Following command will create configuration file —Deliverfile in ./fastlane directory.
$ bundle exec fastlane deliver init
Deliverfile could look like this.
app_identifier "com.datarockets.CoolApp"
username "your-apple-id"
ipa "CoolApp_1.0.0.ipa"
app_version "1.0.0"
submit_for_review false
screenshots_path "fastlane/screenshots/"
metadata_path "fastlane/metadata/"
app_rating_config_path "fastlane/rating_config.json"
app_review_information(
first_name: "Pavel",
last_name: "Vashkel",
phone_number: "",
email_address: "your-email-address",
notes: "Some note for reviewers"
)
name(
'en-US' => "CoolApp",
# ...
'ru' => "CoolApp"
)
support_url(
'en-US' => "http://example.com/",
# ...
'ru' => "http://example.com/"
)
keywords(
'en-US' => "some, key, word",
# ...
'ru' => "some, key, word"
)
app_icon './AppIcon.png'
platform 'ios'
copyright "2017 datarockets, LLC"
primary_category 'MZGenre.Games'
secondary_category 'MZGenre.Entertainment'
primary_first_sub_category 'MZGenre.Card'
primary_second_sub_category 'MZGenre.Board'
In config file we pointing to rating_config.json file which is used to calculate app rating in iTunes Connect. Every category is scored from 0 to 2.
{
"CARTOON_FANTASY_VIOLENCE": 0,
"REALISTIC_VIOLENCE": 0,
"PROLONGED_GRAPHIC_SADISTIC_REALISTIC_VIOLENCE": 0,
"PROFANITY_CRUDE_HUMOR": 0,
"MATURE_SUGGESTIVE": 0,
"HORROR": 0,
"MEDICAL_TREATMENT_INFO": 0,
"ALCOHOL_TOBACCO_DRUGS": 0,
"GAMBLING": 0,
"SEXUAL_CONTENT_NUDITY": 0,
"GRAPHIC_SEXUAL_CONTENT_NUDITY": 0,
"UNRESTRICTED_WEB_ACCESS": 0,
"GAMBLING_CONTESTS": 0
}
Now run the following command.
$ bundle exec fastlane deliver
This step can take a while. Before submitting, Preview.html file with all app metadata and screenshots will be generated. You can check if everything is filled in correctly.
Use it all together
Above we used every fastlane tool as separate command. Although this is not the worst option, all this can be automated even further by using one more config file — Fastfile. Fastfile is a kinda top-level config file where you can define lanes for different tasks.
Fastfile could look like this.
fastlane_version "1.109.0"
default_platform :ios
platform :ios do
before_all do
cocoapods(
clean: true,
repo_update: true
)
end
desc "Submit a new build to AppStore"
lane :release do
produce
match(type: "appstore")
gym(
export_method: "app-store",
export_options: {
provisioningProfiles: {
"com.datarockets.CoolApp" => "match AppStore com.datarockets.CoolApp"
}
}
)
snapshot
frameit gold
deliver
desc "Post a message to slack channel"
slack(
message: "CoolApp was released",
slack_url: "https://hooks.slack.com/services/desired-chanel"
)
error do |lane, exception|
end
end
end
Now instead of bunch of separate commands only one need to be executed.
$ bundle exec fastlane ios release
Wrapping up
Fastlane is a great tool helping to add more automation to development process and make app development fun again.
Links
Github page: https://github.com/fastlane/fastlane
Official documentation: https://docs.fastlane.tools
Original post: https://datarockets.com/blog/ios-release-with-fastlane