loading indicator
status bar
UI design
mobile app development
user experience

How to show the loading indicator in the top status bar

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

On modern iOS, the old global network spinner in the system status bar is no longer available. To show loading state near the top of the screen, you need a custom view that respects safe area, does not block navigation, and is easy to control from async code. A reusable top banner pattern works well across many screens.

Why a Custom Top Indicator Is the Right Pattern

A top indicator is useful when loading affects the whole screen but should still allow users to read content or cancel actions. Compared with full screen blockers, a narrow top banner is less disruptive and gives continuous feedback.

Design guidelines:

  • Keep height small so navigation remains visible.
  • Use short text such as Loading or Syncing.
  • Animate in and out quickly to avoid visual noise.
  • Avoid showing it for ultra short requests to prevent flicker.

In UIKit, this is straightforward with a shared presenter object that owns one banner view per active scene.

Build a Reusable Banner View in UIKit

The view below shows a spinner and message, anchored to the safe area top.

swift
1import UIKit
2
3final class TopLoadingBanner: UIView {
4    private let spinner = UIActivityIndicatorView(style: .medium)
5    private let titleLabel = UILabel()
6
7    override init(frame: CGRect) {
8        super.init(frame: frame)
9
10        backgroundColor = UIColor.systemGray6
11        layer.cornerRadius = 10
12
13        spinner.startAnimating()
14        titleLabel.text = "Loading"
15        titleLabel.font = .systemFont(ofSize: 13, weight: .semibold)
16
17        let stack = UIStackView(arrangedSubviews: [spinner, titleLabel])
18        stack.axis = .horizontal
19        stack.spacing = 8
20        stack.alignment = .center
21        stack.translatesAutoresizingMaskIntoConstraints = false
22
23        addSubview(stack)
24        NSLayoutConstraint.activate([
25            stack.centerXAnchor.constraint(equalTo: centerXAnchor),
26            stack.centerYAnchor.constraint(equalTo: centerYAnchor)
27        ])
28    }
29
30    required init?(coder: NSCoder) {
31        fatalError("init(coder:) has not been implemented")
32    }
33}

Create a coordinator to show and hide it without duplicating layout code in each controller.

swift
1import UIKit
2
3final class TopLoadingCoordinator {
4    private weak var hostView: UIView?
5    private var banner: TopLoadingBanner?
6
7    func show(in viewController: UIViewController) {
8        guard banner == nil else { return }
9        hostView = viewController.view
10
11        let b = TopLoadingBanner()
12        b.translatesAutoresizingMaskIntoConstraints = false
13        b.alpha = 0
14        viewController.view.addSubview(b)
15
16        NSLayoutConstraint.activate([
17            b.topAnchor.constraint(equalTo: viewController.view.safeAreaLayoutGuide.topAnchor, constant: 6),
18            b.centerXAnchor.constraint(equalTo: viewController.view.centerXAnchor),
19            b.widthAnchor.constraint(greaterThanOrEqualToConstant: 140),
20            b.heightAnchor.constraint(equalToConstant: 34)
21        ])
22
23        UIView.animate(withDuration: 0.2) { b.alpha = 1 }
24        banner = b
25    }
26
27    func hide() {
28        guard let b = banner else { return }
29        UIView.animate(withDuration: 0.2, animations: {
30            b.alpha = 0
31        }, completion: { _ in
32            b.removeFromSuperview()
33        })
34        banner = nil
35    }
36}

Connect Indicator State to Async Network Work

The most important engineering detail is consistent lifecycle handling. Show before request starts, then hide in every terminal state including success, failure, and cancellation.

swift
1func loadUserProfile() {
2    coordinator.show(in: self)
3
4    Task {
5        defer { DispatchQueue.main.async { self.coordinator.hide() } }
6
7        do {
8            let profile = try await api.fetchProfile()
9            await MainActor.run {
10                self.render(profile)
11            }
12        } catch {
13            await MainActor.run {
14                self.showError(error)
15            }
16        }
17    }
18}

defer helps guarantee cleanup, which prevents stuck loading banners when an exception path is missed.

Accessibility and Visual Quality

A loading indicator should be announced clearly for VoiceOver users and should not clash with navigation titles.

Practical checks:

  • Verify contrast for banner background and text.
  • Test with larger dynamic type sizes.
  • Confirm behavior during rotation and split screen.
  • Avoid multiple indicators stacking at the top.

If your app has many concurrent requests, consider reference counting in the coordinator so the banner hides only when all tracked operations finish.

Common Pitfalls

  • Trying to use removed status bar spinner APIs on modern iOS versions.
  • Hiding indicator only on success, leaving it visible after errors.
  • Updating UI from a background thread, causing inconsistent state.
  • Creating separate banner implementations per screen instead of one shared component.
  • Ignoring safe area constraints on devices with dynamic top insets.

Summary

  • Implement a custom top loading banner instead of deprecated status bar indicators.
  • Keep the component reusable and managed by a coordinator.
  • Tie show and hide logic to async lifecycle with guaranteed cleanup.
  • Validate accessibility and layout behavior across device states.
  • Use one consistent loading pattern across the app for predictable UX.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.