Using SwiftLint to generate code quality report for Gitlab

Gitlab allows you to easily measure and report code quality of your merge requests to see your linting or other code issues right in every merge request you make in a nice and concise way.

I have been using SwiftLint for a very long time now in my iOS projects so I decided to integrate it to the Gitlab code quality flow.

This is quite easy because SwifLint can generate a Code Climate format that Gitlab understands. No need for any custom format conversion or data transformation.

To set it up in Gitlab you need to modify your .gitlab-ci.yml file. First include the code quality template

include:
  - template: Code-Quality.gitlab-ci.yml

and then add a new job called code_quality, it needs to have this exact name to match the included template

code_quality:
  dependencies: []
  stage: linting
  artifacts:
    paths:
      - codequality_report.json
    reports:
      codequality: codequality_report.json
  script:
    - swiftlint --reporter codeclimate > codequality_report.json 
  tags:
    - iOS-arm

This works for a simple project. In my projects I usually use an iOS application project and a few smaller framework projects that the iOS application uses, so instead of swiftlint --reporter codeclimate > codequality_report.json I call a custom shell script

[Read More]
Swift  iOS 

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)
player.prepareToPlay()
player.play()

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 

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 

Graying out images in Cocoa

I have been working on a macOS application recently where I encountered an interesting task to grey out flags for countries that are disabled.

I first tried overlaying a semi-transparent gray view (fun fact, you cannot directly set a background for a view in Cocoa, you need to use the layer) over the flag image but it did not look very good.

Luckily there is a way to convert a NSImage to grayscale directly in Cocoa.

You first need to create a bitmap representation your NSImage

let bitmap = NSBitmapImageRep(cgImage: cgImage)

convert it to grayscale

let grayscale = bitmap.converting(to: .genericGray, renderingIntent: .default)

and then construct an NSImage from the result.

let grayImage = NSImage(size: grayscale.size)
greyImage.addRepresentation(grayscale)

Putting it all together as an NSImage extension might look like this

[Read More]
Swift  Xcode  Cocoa 

Web scraping with Swift

In a few projects in the past I needed to do web scraping to get some data from websites that did not offer access via an API. I was using C# at the time and scraping web with Html Agility Pack was quite easy.

I now spend most of my time in macOS because of work projects so when I needed to do some web scraping again I did not want to install and set up Mono to do it again in C#. I decided to go with Swift, as I am now quite comfortable with the language after 4 years of using it daily.

SwiftSoup

The first thing I need to do was to found some library to parse HTML, some Swift equivalent to Html Agility Pack. I found SwiftSoup.

SwiftSoup allows you to access DOM in HTML documents and also HTML fragments. The usage is quite simple, you just need to know a thing or two about HTML.

Example

Let’s say you want to parse the Hacker News main page and scrap posts containing some specific keywords.

This is quite an artificial example but the idea is simple. You use the developer tools in your browser of choice to see the HTML of the parts of a website that you are interested in and try to get to them descending and filtering the DOM using SwiftSoup.

You first need to read the website and parse it

let content = try String(contentsOf: URL(string: "https://news.ycombinator.com/")!)
let doc: Document = try SwiftSoup.parse(content)

Looking at the HTML you can see it uses a table layout and all the posts are in a rows of a table with a class called itemlist.

[Read More]
Swift  Xcode