Belangrijkste punten
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.
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.
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.
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.
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.
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.
Voegt Pull-to-Refresh toe aan UITableViewController met aangepaste spinnerskleur en attributed title. Na het laden van gegevens wordt de indicator verborgen.
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.
Omhult RecyclerView in SwipeRefreshLayout met aangepaste indicator-kleuren. onRefresh start het laden en verbergt de indicator na voltooiing.
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.
Modern SwiftUI biedt de modifier .refreshable, die automatisch Pull-to-Refresh toevoegt aan List of ScrollView.
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
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.
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.
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
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.
Lees ook