Correctly playing audio in iOS applications

When you look for a way to play audio in your iOS application you usually find code like this

player = try AVAudioPlayer(contentsOf: url)

While this code works and will play the given audio file it does not deal with all the nuances of audio playback.

Imagine the user is playing a podcast or music, do you want the sound in your app to stop that playback? Or play over it?

The key to correct audio playback is understanding AVAudioSession categories. Let’s take it by example

Playing ambient sounds over existing audio

If you want to play an audio file in your application without affecting the existing music or podcast playback that might be going on you should use the .ambient category.

You use this category for example to play sound effects like the sound of a message being sent in a chat application where the sound is not that important and definitely not worth affecting other playback in iOS.

You set the category on the shared AVAudioSession instance

try AVAudioSession.sharedInstance().setCategory(.ambient, mode: .default)

and make it active

try AVAudioSession.sharedInstance().setActive(true)

All the sounds you now play using AVAudioPlayer will be played as ambient sounds.

Stopping existing audio to play your sound

If the audio file you want to play in your application is important, for example playing a received voice message in a chat application, you can stop existing audio that is playing on iOS.

You can achieve this by setting the category to .playback

try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)

Remember, setting a category makes it persist for all the consecutive audio playback until you set it to a different value.

When you play a sound now using AVAudioPlayer it will stop the music or podcast the user might be listening to.

But what if you want the users music or podcast to continue after you play your sound?

[Read More]
Swift  iOS 

Automatically merging conflicts in Xcode project files

Dealing with code conflicts is somethings every developer is probably used to, but it is never a good experience, especially when dealing with file formats that are not exactly human readable, like Xcode project files.

The Xcode projects files (project.pbxproj) are a proper mess with every file appearing multiple times, being referenced by an id, etc .. definitely not something you would want to deal with manually. Luckily there is a better way.


Kintsugi is lightweight tool to automatically resolve Git conflicts that occur in Xcode project files. You install the Kintsugi gem with

gem install kintsugi

and then simply calling it a path to a project as a parameter.

For example if you have a merge conflicts in App/project.pbxproj you execute

kintsugi App/project.pbxproj

and Kintsugi will take care of the conflicts for you.

I have been using Kintsugi for a few weeks now and I have to say it works really well. There was only one time so far it failed to automatically merge a conflicts I had in an Xcode project file.

Git  Xcode  iOS 

My experience dealing with Microsoft in the old Windows Phone times

This summer marks exactly a decade since I started doing mobile apps development. My first mobile app was called MyTVShows and was released to the Windows Phone Store in summer of 2011.

The app solved a practical problem for me, keeping track of new episodes. I later rewrote the app as TvTime which was one of my most successful Windows Phone apps.

My times of doing Windows Phone development are long over as the platform has been dead for many years but this reminded me of all the struggles I had to deal with when interacting with the local Microsoft branch.

Disclaimer: Microsoft is a huge company so my experience is hopefully not the general experience of everyone from those times, people on all the local branches are different.

Windows Phone app competitions

In 2012 the Czech Microsoft started a competition giving every developer a Nokia Lumia 800 for publishing 3 apps to the Windows Phone Store.

The problem was the quality of those apps did not matter at all. The goal was to increases the number of the apps as much as possible, probably reaching some arbitrary number set by Redmond.

The Czech Windows Phone Store was flooded by crappy apps created only because of this competition, not having any real value for the user. I remember an app with one input and one button doing some kind of number conversion.

Microsoft just wanted to bump the app numbers count by any means necessary to be able to compare the Windows Phone Store to other mobile app stores.

This also reminded me that Microsoft was really bad at competitions overall, another example was destroying DVLUP after purchasing Nokia.

[Read More]

Allowing parallel iOS UI tests runs in CI

If you have your CI machine set up to run multiple jobs in parallel you might have encountered a problem. You cannot run multiple iOS UI tests in the same simulator at the same time. They will fail.

The problem

Imagine this scenario. You have one CI machine that allows two or more jobs to run in parallel. You have a UI tests job set up. Two of your developers push changes to their branches at (almost) the same time. The CI machine then tries to run two UI tests at the same time in the same simulator and at least one of them fails and has to be retried.

You can of course solve this problem by not allowing parallel jobs on the CI machine, but that slows down the CI process. There is a better way.

The solution

You can create a brand new iOS simulator instance for every job, run the UI tests in this new simulator instance and then delete it when you are done. This way no matter how many iOS UI tests jobs you run in parallel they will all use a separate iOS simulator instance and not affect each other.

Creating a new iOS simulator instance

The first step is to choose a unique simulator name for each job. You can generate some random guid for example. In Gitlab CI I just use the ID of the Gitlab CI job that is stored in the ${CI_PIPELINE_ID} environment variable

First you need to create the new iOS simulator instance at the start of the CI job

xcrun simctl create ${CI_PIPELINE_ID} `xcrun simctl list runtimes | grep iOS | awk '{print $NF}'`

This command creates a new iOS simulator instance with the given name using the iPhone 11 device and the latest simulator runtime. Using the latest simulator runtime makes sure that when you update Xcode on the CI machine you do not have to do any changes to you CI scripts.

Using the new iOS simulator instance

Now that you have a new iOS simulator instance created you can use it to run your iOS UI tests. You can simply pass the name to the xcodebuild command if you use it directly or use it instead of the device type when using Fastlane

[Read More]
CI  Xcode  iOS 

Reading environment variables from iOS and macOS unit tests

In some environments like the CI/CD you might need to read environments variables from your iOS or macOS unit tests.

A typical example might be reading secrets or configuration that is CI/CD specific and you do not want to store it in source control.

In Swift you can read environment variables using the ProcessInfo.processInfo.environment dictionary, for example

let host = ProcessInfo.processInfo.environment["apiHost"] ?? "default fallback host"

The problem is this code does not just work, there is one other step you need to do first.

You need to edit your Test scheme and add every environment variable you want to read to the Environment Variables section.

You can rename the variables but for simplicity and readability you should probably keep the same names.

Swift  Xcode