UIRefreshControl
UIScrollView
iOS development
Swift programming
iOS UI components

Can I use a UIRefreshControl in a UIScrollView?

Interview Questions practice on Codemia

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

Browse interview questions

Introduction

Yes, you can use UIRefreshControl with UIScrollView, and this is a common pattern for pull to refresh outside table views. The exact integration depends on iOS version and whether you use plain UIKit or custom scroll layouts. A clean setup attaches the refresh control once, triggers async reload, then ends refreshing on the main thread.

iOS Behavior and Supported Approaches

For modern iOS, UIScrollView supports refreshControl directly. For older compatibility targets, developers used addSubview on the scroll view.

Modern style:

swift
scrollView.refreshControl = refreshControl

Compatibility style:

swift
scrollView.addSubview(refreshControl)

If your app supports only recent iOS versions, prefer the property based API for clarity.

Basic Implementation in a View Controller

swift
1import UIKit
2
3final class FeedViewController: UIViewController {
4    private let scrollView = UIScrollView()
5    private let stackView = UIStackView()
6    private let refresh = UIRefreshControl()
7
8    override func viewDidLoad() {
9        super.viewDidLoad()
10        view.backgroundColor = .systemBackground
11
12        setupLayout()
13        setupRefresh()
14        loadContent()
15    }
16
17    private func setupRefresh() {
18        refresh.attributedTitle = NSAttributedString(string: "Pull to refresh")
19        refresh.addTarget(self, action: #selector(refreshTriggered), for: .valueChanged)
20        scrollView.refreshControl = refresh
21    }
22
23    @objc private func refreshTriggered() {
24        Task {
25            await reloadData()
26            await MainActor.run {
27                self.refresh.endRefreshing()
28            }
29        }
30    }
31
32    private func setupLayout() {
33        scrollView.translatesAutoresizingMaskIntoConstraints = false
34        stackView.axis = .vertical
35        stackView.spacing = 12
36        stackView.translatesAutoresizingMaskIntoConstraints = false
37
38        view.addSubview(scrollView)
39        scrollView.addSubview(stackView)
40
41        NSLayoutConstraint.activate([
42            scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
43            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
44            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
45            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
46
47            stackView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor, constant: 16),
48            stackView.leadingAnchor.constraint(equalTo: scrollView.frameLayoutGuide.leadingAnchor, constant: 16),
49            stackView.trailingAnchor.constraint(equalTo: scrollView.frameLayoutGuide.trailingAnchor, constant: -16),
50            stackView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor, constant: -16)
51        ])
52    }
53
54    private func loadContent() {
55        for i in 1...20 {
56            let label = UILabel()
57            label.text = "Item \(i)"
58            stackView.addArrangedSubview(label)
59        }
60    }
61
62    private func reloadData() async {
63        try? await Task.sleep(nanoseconds: 1_000_000_000)
64    }
65}

This example works with plain UIScrollView and vertical content.

Ensure Refresh Gesture Can Trigger

Pull to refresh requires enough vertical scroll interaction. If content is shorter than viewport, set always bounce vertical.

swift
scrollView.alwaysBounceVertical = true

Without this, users may not be able to drag enough to trigger refresh on short content.

Integrating with Async Network Calls

Refresh handlers should:

  1. start network request
  2. update UI state on success or failure
  3. call endRefreshing on main thread

A typical pattern uses async tasks or completion handlers with weak self capture to avoid retain cycles.

swift
1@objc private func refreshTriggered() {
2    APIClient.shared.fetchFeed { [weak self] result in
3        DispatchQueue.main.async {
4            guard let self else { return }
5            switch result {
6            case .success(let items):
7                self.apply(items)
8            case .failure(let error):
9                self.showError(error)
10            }
11            self.refresh.endRefreshing()
12        }
13    }
14}

Visual and UX Considerations

Keep refresh interaction predictable:

  • avoid nested scroll views competing for gesture ownership
  • show clear loading feedback if refresh takes longer than one second
  • prevent duplicate concurrent refresh requests

Simple guard:

swift
guard !refresh.isRefreshing else { return }

This avoids stacked requests during aggressive pull gestures.

Common Pitfalls

A common pitfall is forgetting endRefreshing, leaving spinner active forever and confusing users.

Another issue is running UI updates from background queue after network completion. Always return to main thread before changing views.

A third issue is disabling vertical bounce on short content, which makes refresh gesture appear broken.

Teams also place UIRefreshControl on inner views inside the scroll hierarchy instead of the scroll view itself, which can break gesture behavior.

Summary

  • UIRefreshControl works with UIScrollView and is widely used
  • Use scrollView.refreshControl in modern iOS projects
  • Set alwaysBounceVertical for short content pull support
  • End refreshing on main thread after async work completes
  • Avoid nested scroll conflicts and duplicate refresh requests

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.