Simple iOS release with fastlane

Pavel Vashkel
6 min readNov 27, 2017

--

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.

./fastlane directory created by fastlane init

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.

Private git repository with certificates and provisioning profiles

Now go to XCode and select newly created profiles in the signing section.

Set provisioning profiles in XCode

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
Resulting ./fastlane directory

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

--

--