Blog

Activity Indicator with Swift. How to make Spinner? Tutorial

Spinners let people know that your app isn't frozen while loading content. Check out how to make one that can be used on many views and storyboards.

Progress Indicators, spinner or - using Apple nomenclature - Activity Indicator, is a good thing. Great for showing users that something is going on in the app, i.e. that downloading process has started. In Swift we have many ways to create or just add a spinner to our app. Today I’d like to show you, how to make spinner, that can be used on many views/storyboards. Let’s go!

How to create an activity indicator with Swift?

As a junior developer, I’m trying to use most simple and stable solutions. My first spinner that I was using was Activity Indicator View added from Storyboard in Xcode. It’s great, but has some limitations.

First, if you have more than one view controller, you must create a single indicator for each view. And handle this in the code.

Second, if you want to block all view during an update, Activity Indicator isn’t enough.

Third, blocking Navigation Controller or TabBar Controller is just more work, if you’re using only Activity Indicator.

So, what to do? Obviously, there are many Pods with great spinners. In my products I'm using JGProgressHUD, but you can find on Github many, many other great solutions.

But, let’s stay simple. Just one spinner, just some lines of code.

So firstly, let’s make a new Swift file called simple Spinner.swift. We’ll use UIKit. Next, we should create a new class:

open class Spinner

Now, we need to declare our spinner:

internal static var spinner: UIActivityIndicatorView?

We should also declare spinner style. As you may know, Apple Activity Indicator has 3 default styles: white, whiteLarge and gray. Thanks to this open variable, we will be able to use all of them:

open static var style: UIActivityIndicatorViewStyle = .whiteLarge

Beside of this, we need to declare also default background color, and default color of our spinner:

open static var baseBackColor = UIColor(white: 0, alpha: 0.6)
open static var baseColor = UIColor.red

Our next goal is to create spinner function to start spinner:

open static func start(style: UIActivityIndicatorViewStyle = style, backColor: UIColor = baseBackColor, baseColor: UIColor = baseColor) {
    if spinner == nil, let window = UIApplication.shared.keyWindow {
        let frame = UIScreen.main.bounds
        spinner = UIActivityIndicatorView(frame: frame)
        spinner!.backgroundColor = backColor
        spinner!.activityIndicatorViewStyle = style
        spinner?.color = baseColor
        window.addSubview(spinner!)
        spinner!.startAnimating()
    }
}

Ok, we have start function that sets style, background color and base spinner color. What's next? Of course, stop function. It should not only stop animation, but also dismiss spinner.

open static func stop() {
    if spinner != nil {
        spinner!.stopAnimating()
        spinner!.removeFromSuperview()
        spinner = nil
    }
}

Now, if you want to implement Spinner, just go to the View Controller. Then, in place where indicator should start animating (i.e. viewDidLoad or viewWillAppear) just put Spinner.start(). Or, if you want personalize style, backColor and/or baseColor:

Spinner.start(style: .white, backColor: UIColor.white, baseColor: UIColor.green)

Then, in a place where processing is done, simply put: Spinner.stop(). Notice that our indicator is covering not only base view, but also Navigation Bar or TabBar.

But there's one little problem. If your app has not only portrait, but also landscape mode ... Well, we'll need to do more work. Why? Just check the pictures below:



This situation happens because our Spinner doesn't know that device's orientation did change. There are a few ways to fix it. For example, we can use viewWillTransition function in ViewController. Or handle device's orientation from Spinner. But first we need to add a simple function to our Spinner class:

open static func update() {
    if spinner != nil {
        stop()
        start()
    }
}

This func will check if indicator is running (or not), then launch it (or not). The next thing is to implement the function. We can do it in our ViewController:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    Spinner.update()
}

The second way is also very simple. Let's go back to Spinner model, and check function called 'start()'. We can easily add observer to the code:

NotificationCenter.default.addObserver(self, selector: #selector(update), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)

So now function start() will be checking if device's orientation has chagned. If yes - start() will run update() function. This is easy! Of course, if we want to use argument #selector, first we need to add @objc to the static function update:

@objc open static func update()

Now reusable spinner should work perfectly. No matter if an app is in landscape or portrait mode.

That’s all, folks :) All code you can get from our Git repository. Enjoy!

Update: Reddit user rohlmann82 suggested auto-layout support and the ability to put it on more than just a UIWindow import UIKit. Rohlmann82 allowed me on publishing his code here in this article.
You can find it here
. Thanks very much! Great job!

Credits: photo by Jeff Golenski from Unsplash

Check our latest product - it's based on our experience of managing over 50-people strong company. The tool we're missing as a small company and not an enterprise.

humadroid.io is an employee and performance management software. It's an unique tool allowing everyone to be in the loop - by having up to date info about co-workers, time-off, benefits, assets, helping with one-on-ones, being a go-to place for company-wide announcements.

Check out humadroid.io
Top

Contact us

* Required fields

The controller of your personal data provided via this contact form is Prograils sp. z o.o., with a registered seat at Sczanieckiej 9A/10, 60-215 Poznań. Your personal data will be processed in order to respond to your inquiries and for our marketing purposes (e.g. when you ask us for our post-development, maintenance or ad hoc engagements for your app). You have the rights to: access your personal data, rectify or erase your personal data, restrict the processing of your personal data, data portability and to object to the processing of your personal data. Learn more.

Notice

We do not track you online. We use only session cookies and anonymous identifiers for the purposes specified in the cookie policy. No third-party trackers.

I understand
Elo Mordo!Elo Mordo!