SwiftLint & SwiftFormat

These two tools are now part of my tool belt when creating an iOS project, I don’t see how I can go back working without, and I recommend them for any type of project.

SwiftLint

Automatically enforce Swift style, conventions and best practices. It’s a good idea to just install it when creating a new project and treat all warnings and errors as they come. Everything can be configured or disabled using a .swiftlint.yml file at the root of the project.

Repository: https://github.com/realm/SwiftLint

SwiftFormat

You have a preferred style for formatting your code, your teammates eventually have a different preferred style when formatting their code, and you find yourself spending too much time arguing about the format and/or asking others to update their code during reviews over format issues?

It’s helpful to agree on a common coding style with your team, and it’s even better if you can enforce it. SwiftFormat to the rescue, it enforces the coding style by automatically formatting the code.

I personally installed it for it to run during each build, the overhead is small and I don’t have to think about formatting anymore.

Repository: https://github.com/nicklockwood/SwiftFormat

Detecting when UIScrollView is bouncing

Let’s say you want to have your UI change when a scroll view is bouncing (or over-scrolling).

First you need to conform to the UIScrollViewDelegate methods, more specifically scrollViewDidScroll(_ scrollView: UIScrollView).

extension ViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        // TODO
    }
}

For example, let’s change the background color of the scroll view depending on bouncing status. If user is over-scrolling from the top, let’s change it to red, green if over-scrolling from the bottom, and white otherwise.

First, let’s detect when bouncing from the top:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let topInsetForBouncing = scrollView.safeAreaInsets.top != 0.0 ? -scrollView.safeAreaInsets.top : 0.0
    let isBouncingTop: Bool = scrollView.contentOffset.y < topInsetForBouncing - scrollView.contentInset.top

    if isBouncingTop {
        scrollView.backgroundColor = .red
    } else {
        scrollView.backgroundColor = .white
    }
}

It’s fairly easy, if the current vertical contentOffset is smaller than all space at the top, then we are over-scrolling. Because the top space will differ depending on devices, we have to use the safeAreaInsets, but mind that -0.0 is different than 0, thus the ternary operator.

Now let’s detect when the scroll view is bouncing from the bottom:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let topInsetForBouncing = scrollView.safeAreaInsets.top != 0.0 ? -scrollView.safeAreaInsets.top : 0.0
    let isBouncingTop: Bool = scrollView.contentOffset.y < topInsetForBouncing - scrollView.contentInset.top

    let bottomInsetForBouncing = scrollView.safeAreaInsets.bottom
    let threshold: CGFloat
    if scrollView.contentSize.height > scrollView.frame.size.height {
        threshold = (scrollView.contentSize.height - scrollView.frame.size.height + scrollView.contentInset.bottom + bottomInsetForBouncing)
    } else {
        threshold = topInsetForBouncing
    }
    let isBouncingBottom: Bool = scrollView.contentOffset.y > threshold

    if isBouncingTop {
        scrollView.backgroundColor = .red
    } else if isBouncingBottom {
        scrollView.backgroundColor = .green
    } else {
        scrollView.backgroundColor = .white
    }
}

It’s a bit more tricky in this case, scroll views are bouncing wether or not there is enough content for them to reach them, so we need to define what threshold the content offset needs to pass. When the content size is not big enough, then any movement that is not bouncing from the top is bouncing from the bottom. If the content size is big enough, then we can use a similar logic than before.

Let’s cleanup a little, this looks like a good candidate for an extension:

extension UIScrollView {
var isBouncing: Bool {
return isBouncingTop || isBouncingBottom
}
var isBouncingTop: Bool {
return contentOffset.y < topInsetForBouncing – contentInset.top
}
var isBouncingBottom: Bool {
let threshold: CGFloat
if contentSize.height > frame.size.height {
threshold = (contentSize.height – frame.size.height + contentInset.bottom + bottomInsetForBouncing)
} else {
threshold = topInsetForBouncing
}
return contentOffset.y > threshold
}
private var topInsetForBouncing: CGFloat {
return safeAreaInsets.top != 0.0 ? -safeAreaInsets.top : 0.0
}
private var bottomInsetForBouncing: CGFloat {
return safeAreaInsets.bottom
}
}

Now we simply need to call isBouncingTop or isBouncingBottom on the scroll view:

extension ViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if scrollView.isBouncingTop {
            scrollView.backgroundColor = .red
        } else if scrollView.isBouncingBottom {
            scrollView.backgroundColor = .green
        } else {
            scrollView.backgroundColor = .white
        }
    }
}

And because UITableView and UICollectionView inherit from UIScrollView, it will work for these as well.

Reverse engineering private APIs

We would all prefer to have access to a public API but sometimes, services you would love to add automation to only offer their website to use.

There is no public API, now what?

First let’s state that not all mobile or web applications can be (easily) reversed engineered, some companies invest a lot of money to secure and protect their API, user data. I’m not a hacker, but sometimes being resourceful let you create scripts and automations that can make you save tons of time. Note that private API, as in not documented, are usually not intended to be used by others than developers internal to the service or product, so don’t count on it to be stable, don’t expect to receive a notice if something is going to break, don’t base your entire business on something that can disappear any day.

In some cases, web browser inspectors comes handy. With the surge nowadays of websites developed using React, or more generally client side web applications, the network inspector of your browser often shows all the requests the website is doing in the background in response to your actions on the interface. Coupled with a HTTP proxy like Charles to inspect traffic going through your phone, you can scan pretty much all endpoints a service is using and try to mimic this behavior in scripts.

What’s the catch?

There is often only one problem: authentication. Most web services nowadays are using JWT session tokens, passed in the headers of each authenticated request, and even though you could re-execute a request you copied from your network inspector / HTTP proxy using the same URL, parameters and headers, this session token will eventually expire after an hour (a day at best depending on how their authentication is configured) and you will have to manually extract your access token again and again.

To be honest, I’m often able to reverse engineer everything but the authentication, as a user, I’m fine with that 🙂

Refreshing tokens

This is the best case scenario: if you can sniff and extract the refresh token from the original authentication request, and reverse engineer the token retrieval request, then you can generate a fresh access token every time your script run.

Let the website refresh tokens for you

Chances are the website is refreshing its own access token periodically as well as when launching it, this provides a seamless experience without having to log in every time. Obviously websites trying to secure sensitive data like banks are not doing this, but a lot of other consumer websites and tools are.

In that case, it’s pretty common for these web applications to store access tokens and / or refresh tokens in their local storage. You can see the content of the local storage using the “application” tab of the web inspector (Google Chrome), sometimes this information can also be stored in cookies.

Final note

Don’t be evil.

Remember that you can be in trouble for doing that, only use these techniques to automate something you have the right to do.

Localize your app early

And when I say early, I mean immediately, as soon as you create your project. Trust me, just do it, take the few minutes it takes to setup localization and do it from the first word that will appear on your interface.

Regardless of your app supporting several languages at launch, doing so will not consume additional time, and these few moments will literally save you hours later on, when you actually have to translate the app.

Consistent keys

When adding a new localized key, I always group it with other keys of the same screen and use the same naming convention: snake case + name of the screen followed by a dot and followed by a description of what the key is, eg:

// EventList
"event_list.title" = "Events";
"event_list.new" = "Create Event";

There are many systems out there, find your own and stick to it.

Take shortcuts

Especially if you don’t have to, using NSLocalizedString(_:comment:) everywhere and in the proper way can be cumbersome. So unless your team and process requires it (and in most apps it will certainly not be required), you can take a small shortcut that will greatly help: build an extension to wrap the function and make the comment parameter optional.

//  String+i18n.swift

import Foundation

extension String {
    func i18n(comment: String = "") -> String {
        return NSLocalizedString(self, comment: comment)
    }
}

It’s makes it really to use by calling the i18n() method on the key.

class EventListViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // i18n
        title = "event_list.title".i18n()
    }
}

Automate

You are now supporting several languages, nice, it’s time to automate! Please don’t open the string file in each language to add your keys, this is the best way to make mistakes.

Instead, for small to medium projects, you can easily create a shared document on Google Spreadsheet with a “key” column and a column for each supported language, and write a small script to download the sheet as a CSV file and generate one .string file per language.

Good habits makes your life easier

What if I’m doing a really quick and dirty test and don’t want to take the few seconds to write the localizable for it? I understand, and I do it sometimes, in this case, just make sure to:

  1. take note that this piece of code will need localization later
  2. be consistent so that you can retrieve all the places that need localization in one go.

I usually add a // TODO: i18n before or after the code using hard-coded text, it’s easy, and because I always use the same text in the comment, all the occurrences will show up in one project search.

Please use MARK pre-processor directive

In Xcode, the // MARK: and // MARK: - pre-processor directive allows you to create separations in your Swift source files. This directive does not change the behavior of your code, it’s just here to help organizing it.

When, why and how should I use them?

You should use marks to separate the code of your source file into sections. These sections should represent logic units and/or group of declarations (outlets / functions) that are going with each other.

By grouping your code this way, you will often come to realize something doesn’t belong to any category: in this case, it probably belongs elsewhere than in this class, this is a good signal that you could refactor this function or piece of code into another file / class.

Additionally if you end up having groups named “utils” or “utilities”, it’s again a good signal a refactor is needed.

Finally in Xcode 11, the mini-map will highlight these marks, using them will therefore become more important than ever.

Difference between // MARK: and // MARK: –

The difference is subtle but exists, with the added - at the end of // MARK:, Xcode symbol explorer adds a separator before the mark, so when clicking on your symbols in this bar:

it will look like this:

instead of this:

I personally only use the // MARK: - syntax.

Create a snippet

Creating a Xcode snippet allows you to add marks faster and ensure you are using a consistent format thanks to auto-completion.

To create a snippet in Xcode:

  1. from the status bar, click on “Editor” then “Create Code Snippet”
  2. give a name to your snippet (eg: MARK)
  3. Choose a “Completion Shortcut” keyword, I use lowercase “mark” so that writing the beginning of the word triggers the auto-completion
  4. Choose “All” for Completion Scopes
  5. Enter // MARK: - <#category#> in the text box

To use the snippet, start tapping “mark” in your code editor and let the auto-completion do the work.