Pull-to-Refresh: basis, RefreshControl en UIRefreshControl

Auteur: IT Sectr Gepubliceerd: 2026-02-27 Leestijd: 8 min
Pull-to-Refresh — een mobiel interfacepatroon waarbij de gebruiker met een vinger de lijst naar beneden trekt en zo het laden van verse gegevens initieert. De beweging gaat gepaard met een visuele indicator — een draaiende spinner of een geanimeerd pictogram — die verdwijnt nadat het laden is voltooid. Volgens UX-analyse van Apple HIG is Pull-to-Refresh de standaardmechanisme geworden voor het vernieuwen van content in nieuwsfeeds, sociale netwerken en e-mailclients sinds de implementatie in Tweetie (2008) en de daaropvolgende standaardisatie door Apple en Google.

Belangrijkste punten

  • Pull-to-Refresh — beweging van de lijst naar beneden trekken om gegevens te vernieuwen, vergezeld van een visuele laadindicator.
  • In iOS wordt UIRefreshControl (iOS 6+) gebruikt, toegevoegd aan UITableViewController of UIScrollView via de eigenschap refreshControl.
  • In Android wordt SwipeRefreshLayout (uit Support Library) gebruikt — een ViewGroup-wrapper voor RecyclerView of NestedScrollView.
  • Beide API's ondersteunen het aanpassen van kleuren, indicatoren en callbacks via listener (iOS: UIRefreshControl.target-action, Android: setOnRefreshListener).
  • Pull-to-Refresh wordt automatisch geblokkeerd wanneer de lijst niet in de bovenste positie staat — conflict met scrollen is architecturaal uitgesloten.

Wat is Pull-to-Refresh?

Pull-to-Refresh — een gebruikersinterfacepatroon waarbij de gebruiker een lijst of schuifbaar gebied naar beneden trekt (pull down) om de inhoud te vernieuwen. Visueel gaat de beweging gepaard met een laadindicator (spinner) die bovenaan het scherm verschijnt en verdwijnt nadat de gegevens zijn ontvangen. Het patroon werd gepopulariseerd door de Tweetie-app voor iPhone (2008) en vervolgens gestandaardiseerd door Apple (iOS 6 — UIRefreshControl) en Google (Android Support Library — SwipeRefreshLayout).

Technisch gezien is Pull-to-Refresh een combinatie van pannen (het volgen van de vingerverplaatsing) en een trigger bij het bereiken van een drempel. De gebruiker trekt de lijst naar beneden, overwint weerstand (resistieve overscroll), en na het overschrijden van de drempel (~80px in iOS, ~64dp in Android) start de indicatoranimatie en asynchroon laden. Als de gebruiker zijn vinger voor de drempel loslaat — keert de lijst terug naar de beginpositie zonder vernieuwing.

Volgens de Material Design Guidelines mag Pull-to-Refresh niet worden gebruikt voor navigatie of het schakelen tussen tabbladen — het enige doel is het vernieuwen van gegevens. Bij IT Sectr gebruiken we Pull-to-Refresh in nieuwsfeeds, bestellijsten en chats, waar de versheid van gegevens cruciaal is voor de gebruikerservaring.

Pull-to-Refresh in iOS: UIRefreshControl

UIRefreshControl — het standaard iOS-besturingselement voor Pull-to-Refresh, beschikbaar sinds iOS 6. UIRefreshControl wordt toegevoegd aan UITableViewController via de eigenschap refreshControl (iOS 10+) of als subview van de tabel in oudere versies. Het bevat een ingebouwde spinner met aanpasbare kleur (tintColor), title-attribuut en een attributed string met een label (bijvoorbeeld “Bezig met vernieuwen...”).

UIRefreshControl werkt via het target-action-mechanisme: bij activering van de beweging wordt de opgegeven methode aangeroepen (bijv. refresh(_:)). Binnen de methode wordt asynchroon laden van gegevens uitgevoerd. Na voltooiing wordt endRefreshing() aangeroepen, die de indicator met een animatie verbergt. UIRefreshControl beheert automatisch de gevoeligheid van de beweging — het wordt alleen geactiveerd in de bovenste positie van de tabel (contentOffset.y <= 0).

De eigenschap tintColor stelt de kleur van de spinner in. De title-attributen maken het mogelijk om tekst “2 minuten geleden vernieuwd” weer te geven na voltooiing. Vanaf iOS 10 ondersteunt UIRefreshControl aangepaste animaties via UIActivityIndicatorView of persistente aangepaste weergaven. Bij IT Sectr passen we tintColor aan op het merk en tonen we de tijd van de laatste vernieuwing via attributedTitle — dit verhoogt het vertrouwen van gebruikers in de gegevens.

Pull-to-Refresh in Android: SwipeRefreshLayout

SwipeRefreshLayout — een ViewGroup uit Android Support Library (androidx.swiperefreshlayout) die schuifbare inhoud (RecyclerView, NestedScrollView, ListView) omhult en Pull-to-Refresh-functionaliteit toevoegt. In tegenstelling tot UIRefreshControl (dat een besturingselement is, geen container), is SwipeRefreshLayout een container die de aanraakgebeurtenissen van het kind onderschept en de vernieuwingsindicator activeert bij het overschrijden van de drempel.

SwipeRefreshLayout gebruikt de cirkelvormige voortgangsindicator van Material Design met kleurconfiguratie via setColorSchemeColors(). De methode setOnRefreshListener stelt de callback onRefresh() in, waarin asynchroon laden wordt uitgevoerd. Na voltooiing wordt setRefreshing(false) aangeroepen om de indicator te verbergen. Belangrijk: setRefreshing(true) roept opnieuw onRefresh() aan — gebruik daarom voor programmatisch starten van vernieuwing een flag of post-methode.

De eigenschap setProgressBackgroundColorSchemeResource verandert de achtergrond van de indicator. setSize(SwipeRefreshLayout.LARGE) — grootte van de spinner. In XML-layout omhult SwipeRefreshLayout RecyclerView: swipe_refresh_layout → recycler_view. Volgens Google I/O 2024 wordt SwipeRefreshLayout gebruikt in 85% van Android-apps met contentfeeds. Bij IT Sectr omhullen we alle schermen met asynchroon geladen lijsten in SwipeRefreshLayout — dit zorgt voor een uniforme UX op alle Android-versies.

Material Pull-to-Refresh (Android 12+)

Vanaf Android 12 (Material You) raadt Google aan om de nieuwe Material Pull-to-Refresh uit de bibliotheek material-1.6.0+ (androidx.compose.material3.pulltorefresh voor Compose) te gebruiken. De nieuwe API gebruikt een geanimeerde indicator met ondersteuning voor spring-animatie en adaptieve kleur op basis van het behang. SwipeRefreshLayout blijft compatibel voor versies onder Android 12.

Beste praktijken en veelvoorkomende fouten

Pull-to-Refresh — een eenvoudig te implementeren patroon, maar bevat enkele veelvoorkomende fouten die de UX verminderen. Laten we ze bekijken en manieren om ze te voorkomen.

  • Dubbele vernieuwing — de gebruiker kan de lijst meerdere keren trekken voordat het laden is voltooid. Oplossing: stel de isRefreshing-flag in bij de start en controleer deze in onRefresh(). In iOS wordt endRefreshing() pas na voltooiing aangeroepen; blokkering van de beweging in UIRefreshControl is ingebouwd.
  • Gebrek aan feedback — de laadindicator moet verschijnen nadat de gebruiker de drempel heeft overschreden. Toon de indicator niet meteen bij aanraking — dit is verwarrend. iOS en Android doen dit automatisch.
  • Negeren van vernieuwingstijd — als gegevens in 200 ms worden vernieuwd, moet de indicator ten minste 500 ms worden getoond zodat de gebruiker de vernieuwing opmerkt. UIRefreshControl heeft een minimale animatietijd; in Android gebruikt u Handler.postDelayed voor een minimale weergavetijd.
  • Conflict met toetsenbord — met een open toetsenbord kan Pull-to-Refresh per ongeluk worden geactiveerd. Verberg het toetsenbord bij het begin van de beweging via view.endEditing(true) in iOS en InputMethodManager.hideSoftInputFromWindow() in Android.
  • Gebruik voor iets anders dan vernieuwing — gebruik Pull-to-Refresh niet voor navigatie (tabbladen wisselen, teruggaan). Dit schendt de HIG van beide platforms en desoriënteert gebruikers.

Bij IT Sectr hebben we de isRefreshing-controle in elk project toegevoegd nadat we dubbele verzoeken in de logs van de testserver ontdekten — het bleek dat gebruikers met snelle vingers de vernieuwing tot 3 keer achter elkaar startten.

Codevoorbeelden in Swift en Kotlin

Voorbeeld 1: UIRefreshControl in iOS (Swift)

Voegt Pull-to-Refresh toe aan UITableViewController met aangepaste spinnerskleur en attributed title. Na het laden van gegevens wordt de indicator verborgen.

swift
import UIKit

class FeedTableViewController: UITableViewController {

    private var items: [String] = []

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.refreshControl = UIRefreshControl()
        refreshControl?.tintColor = .systemBlue
        refreshControl?.attributedTitle = NSAttributedString(
            string: “Trek om te vernieuwen”
        )
        refreshControl?.addTarget(
            self,
            action: #selector(refreshData),
            for: .valueChanged
        )
    }

    @objc private func refreshData() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
            self.items = FeedService().fetchLatest()
            self.tableView.reloadData()
            self.refreshControl?.endRefreshing()
        }
    }
}

De eigenschap tableView.refreshControl (iOS 10+) stelt UIRefreshControl in. addTarget met de gebeurtenis .valueChanged wordt geactiveerd bij de beweging. endRefreshing() is verplicht — zonder blijft de indicator oneindig draaien. Asynchroon laden wordt gesimuleerd via DispatchQueue.main.asyncAfter — in een echt project zou dit URLSession of async/await zijn.

Voorbeeld 2: SwipeRefreshLayout in Android (Kotlin)

Omhult RecyclerView in SwipeRefreshLayout met aangepaste indicator-kleuren. onRefresh start het laden en verbergt de indicator na voltooiing.

kotlin
class FeedFragment : Fragment() {

    private var _binding: FragmentFeedBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentFeedBinding.inflate(inflater, container, false)

        binding.swipeRefreshLayout.setColorSchemeColors(
            resources.getColor(R.color.brand_blue, null),
            resources.getColor(R.color.brand_green, null)
        )
        binding.swipeRefreshLayout.setOnRefreshListener {
            loadData()
        }
        return binding.root
    }

    private fun loadData() {
        viewModelScope.launch {
            try {
                val result = repository.getLatestFeed()
                adapter.submitList(result)
            } finally {
                binding.swipeRefreshLayout.isRefreshing = false
            }
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

setColorSchemeColors stelt de kleuren van de draaiende Material Design-indicator in. isRefreshing = false wordt verplicht in finally aangeroepen om de indicator zelfs bij laadfouten te verbergen. ViewModelScope.launch voert de coroutine uit in de levenscyclus van het fragment — bij vernietiging van het fragment wordt de coroutine automatisch geannuleerd, wat geheugenlekken voorkomt.

Voorbeeld 3: SwiftUI .refreshable (iOS 15+)

Modern SwiftUI biedt de modifier .refreshable, die automatisch Pull-to-Refresh toevoegt aan List of ScrollView.

swift
import SwiftUI

struct FeedView: View {

    @State private var items: [String] = []

    var body: some View {
        List(items, id: \.self) { item in
            Text(item)
        }
        .refreshable {
            items = await FeedService().fetchLatestAsync()
        }
    }
}

De modifier .refreshable accepteert een async-closure die wordt uitgevoerd bij Pull-to-Refresh. SwiftUI toont en verbergt automatisch de vernieuwingsindicator, beheert race-omstandigheden (start geen herladen tot de huidige is voltooid) en past de animatie aan het platform aan. Voor iOS 15+ is dit de preferred manier om Pull-to-Refresh in SwiftUI te implementeren.

Veelgestelde vragen

Werkt Pull-to-Refresh in SwiftUI?

Ja, SwiftUI biedt de modifier .refreshable voor List of ScrollView, beschikbaar sinds iOS 15. Binnen de closure wordt async-code voor het laden van gegevens uitgevoerd. SwiftUI beheert automatisch de vernieuwingsindicator en blokkeert herstarten tot de huidige lading is voltooid — dit is de standaard recommended aanpak voor nieuwe projecten.

Hoe voorkom ik dubbele vernieuwing?

Gebruik de isRefreshing-flag: stel true in bij het starten van het laden en false na voltooiing. In iOS blokkeert UIRefreshControl automatisch een nieuwe aanroep totdat endRefreshing() is aangeroepen. In Android controleer SwipeRefreshLayout.isRefreshing aan het begin van onRefresh(): indien true — return. Dit garandeert één verzoek per beweging.

Heeft Pull-to-Refresh een conflict met scrollen van de lijst?

UIRefreshControl en SwipeRefreshLayout worden alleen geactiveerd in de bovenste positie van de lijst (contentOffset == 0). De architectuur sluit conflict uit: zolang de lijst ook maar 1px is gescrolld, wordt de Pull-to-Refresh-beweging niet geactiveerd. Als er toch een conflict optreedt — controleer nestedScrollingEnabled in Android of de aanwezigheid van aangepaste GestureRecognizers die aanrakingen onderscheppen.

Samenvatting

  • Pull-to-Refresh — patroon voor het vernieuwen van gegevens door de lijst naar beneden te trekken, gestandaardiseerd door Apple en Google op alle mobiele platforms.
  • UIRefreshControl in iOS — besturingselement met target-action, tintColor, attributedTitle en verplichte endRefreshing().
  • SwipeRefreshLayout in Android — ViewGroup-container met setOnRefreshListener, setColorSchemeColors en isRefreshing.
  • Material Pull-to-Refresh (Android 12+) — nieuwe API met spring-animatie, aanbevolen voor nieuwe projecten.
  • SwiftUI .refreshable — declaratieve modifier met async-closure, beschikbaar sinds iOS 15.
  • De isRefreshing-flag voorkomt dubbele vernieuwing — verplicht op beide platforms.
  • Pull-to-Refresh is niet bedoeld voor navigatie — alleen voor het vernieuwen van content volgens Material Design en Apple HIG.

We ontwikkelen een mobiele applicatie turnkey

IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.

Bespreek het project

Lees ook