Wichtige Punkte
Pull-to-Refresh ist ein Benutzeroberflächenmuster, bei dem der Benutzer eine Liste oder einen scrollbaren Bereich nach unten zieht (pull down), um den Inhalt zu aktualisieren. Optisch wird die Geste von einem Ladeindikator (Spinner) begleitet, der oben auf dem Bildschirm erscheint und nach dem Empfang der Daten verschwindet. Das Muster wurde durch die Tweetie-App für das iPhone (2008) populär gemacht und anschließend von Apple (iOS 6 — UIRefreshControl) und Google (Android Support Library — SwipeRefreshLayout) standardisiert.
Aus technischer Sicht ist Pull-to-Refresh eine Kombination aus Schwenken (Verfolgung der Fingerbewegung) und einem Auslöser beim Erreichen eines Schwellenwerts. Der Benutzer zieht die Liste nach unten, überwindet einen Widerstand (resistives Overscroll), und nach Überschreiten des Schwellenwerts (~80px auf iOS, ~64dp auf Android) beginnen die Indikatoranimation und das asynchrone Laden. Lässt der Benutzer den Finger vor dem Schwellenwert los, kehrt die Liste ohne Aktualisierung in ihre ursprüngliche Position zurück.
Gemäß den Material Design Guidelines sollte Pull-to-Refresh nicht für die Navigation oder das Umschalten von Tabs verwendet werden — sein einziger Zweck ist die Datenaktualisierung. Bei IT Sectr verwenden wir Pull-to-Refresh in Nachrichtenfeeds, Bestelllisten und Chats, wo die Aktualität der Daten für die Benutzererfahrung entscheidend ist.
UIRefreshControl ist das standardmäßige iOS-Steuerelement für Pull-to-Refresh, verfügbar seit iOS 6. UIRefreshControl wird über die refreshControl-Eigenschaft (iOS 10+) zu UITableViewController oder als Subview der Tabelle in früheren Versionen hinzugefügt. Es enthält einen integrierten Spinner mit anpassbarer Farbe (tintColor), title-Attribut und einer attributed string mit einer Beschriftung (z. B. "Aktualisieren...").
UIRefreshControl funktioniert über den Target-Action-Mechanismus: Wenn die Geste aktiviert wird, wird die angegebene Methode aufgerufen (z. B. refresh(_:)). Innerhalb der Methode wird asynchrones Laden von Daten durchgeführt. Nach Abschluss wird endRefreshing() aufgerufen, das den Indikator mit einer Animation ausblendet. UIRefreshControl verwaltet automatisch die Gestenempfindlichkeit — es wird nur ausgelöst, wenn sich die Tabelle in der oberen Position befindet (contentOffset.y <= 0).
Die Eigenschaft tintColor legt die Spinnerfarbe fest. attributedTitle ermöglicht die Anzeige von Text wie "Vor 2 Minuten aktualisiert" nach Abschluss. Seit iOS 10 unterstützt UIRefreshControl benutzerdefinierte Animationen über UIActivityIndicatorView oder persistente benutzerdefinierte Ansichten. Bei IT Sectr konfigurieren wir tintColor entsprechend der Marke und zeigen die letzte Aktualisierungszeit über attributedTitle an — dies erhöht das Vertrauen der Benutzer in die Daten.
SwipeRefreshLayout ist ein ViewGroup aus der Android Support Library (androidx.swiperefreshlayout), das scrollbare Inhalte (RecyclerView, NestedScrollView, ListView) umschließt und Pull-to-Refresh-Funktionalität hinzufügt. Im Gegensatz zu UIRefreshControl (das ein Steuerelement und kein Container ist) ist SwipeRefreshLayout ein Container, der Touch-Ereignisse des Kindelements abfängt und den Aktualisierungsindikator auslöst, wenn der Schwellenwert überschritten wird.
SwipeRefreshLayout verwendet einen kreisförmigen Material Design-Fortschrittsindikator mit Farbanpassung über setColorSchemeColors(). Die Methode setOnRefreshListener legt den onRefresh()-Callback fest, in dem das asynchrone Laden durchgeführt wird. Nach Abschluss wird setRefreshing(false) aufgerufen, um den Indikator auszublenden. Wichtig: setRefreshing(true) löst onRefresh() erneut aus — verwenden Sie daher für den programmgesteuerten Start der Aktualisierung ein Flag oder eine post-Methode.
Die Eigenschaft setProgressBackgroundColorSchemeResource ändert den Hintergrund des Indikators. setSize(SwipeRefreshLayout.LARGE) legt die Spinnergröße fest. Im XML-Layout umschließt SwipeRefreshLayout RecyclerView: swipe_refresh_layout → recycler_view. Laut Google I/O 2024 wird SwipeRefreshLayout in 85 % der Android-Apps mit Inhaltsfeeds verwendet. Bei IT Sectr umschließen wir alle Bildschirme mit asynchron geladenen Listen in SwipeRefreshLayout — dies bietet eine einheitliche Benutzererfahrung auf allen Android-Versionen.
Ab Android 12 (Material You) empfiehlt Google die Verwendung des neuen Material Pull-to-Refresh aus der Bibliothek material-1.6.0+ (androidx.compose.material3.pulltorefresh für Compose). Die neue API verwendet einen animierten Indikator mit Unterstützung für Spring-Animationen und adaptive Farbe basierend auf dem Hintergrundbild. SwipeRefreshLayout bleibt für Versionen unter Android 12 kompatibel.
Pull-to-Refresh ist ein einfach zu implementierendes Muster, enthält jedoch mehrere häufige Fehler, die die Benutzererfahrung beeinträchtigen. Sehen wir sie uns an und wie man sie vermeidet.
Bei IT Sectr haben wir die isRefreshing-Prüfung in jedem Projekt hinzugefügt, nachdem wir doppelte Anfragen in den Testserver-Logs entdeckt hatten — es stellte sich heraus, dass Benutzer mit schnellen Fingern die Aktualisierung bis zu 3 Mal hintereinander auslösten.
Fügt UITableViewController Pull-to-Refresh mit benutzerdefinierter Spinnerfarbe und attributed title hinzu. Nach dem Laden der Daten wird der Indikator ausgeblendet.
import UIKit
class FeedTableViewController: UITableViewController {
private var items: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.refreshControl = UIRefreshControl()
refreshControl?.tintColor = .systemBlue
refreshControl?.attributedTitle = NSAttributedString(
string: "Zum Aktualisieren ziehen"
)
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()
}
}
}
Die Eigenschaft tableView.refreshControl (iOS 10+) setzt UIRefreshControl. addTarget mit dem .valueChanged-Ereignis wird ausgelöst, wenn die Geste aktiviert wird. endRefreshing() ist obligatorisch — ohne ihn dreht sich der Indikator unendlich weiter. Das asynchrone Laden wird mit DispatchQueue.main.asyncAfter simuliert — in einem echten Projekt verwenden Sie URLSession oder async/await.
Umschließt RecyclerView in SwipeRefreshLayout mit benutzerdefinierten Indikatorfarben. onRefresh startet das Laden und blendet den Indikator nach Abschluss aus.
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 legt die Farben des rotierenden Material Design-Indikators fest. isRefreshing = false wird im finally-Block aufgerufen, um den Indikator auch bei Ladefehlern auszublenden. ViewModelScope.launch führt eine Coroutine innerhalb des Fragmentlebenszyklus aus — wenn das Fragment zerstört wird, wird die Coroutine automatisch abgebrochen, um Speicherlecks zu verhindern.
Modernes SwiftUI bietet den Modifikator .refreshable, der automatisch Pull-to-Refresh zu List oder ScrollView hinzufügt.
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()
}
}
}
Der Modifikator .refreshable nimmt einen async-Closure entgegen, der bei Pull-to-Refresh ausgeführt wird. SwiftUI zeigt und verbirgt automatisch den Aktualisierungsindikator, verwaltet Race Conditions (startet keine neue Aktualisierung, bis die aktuelle abgeschlossen ist) und passt die Animation an die Plattform an. Für iOS 15+ ist dies die bevorzugte Methode zur Implementierung von Pull-to-Refresh in SwiftUI.
Häufig gestellte Fragen
Ja, SwiftUI bietet den .refreshable-Modifikator für List oder ScrollView, verfügbar seit iOS 15. Innerhalb des Closures wird asynchroner Datenladecode ausgeführt. SwiftUI verwaltet automatisch den Aktualisierungsindikator und blockiert wiederholte Auslösungen, bis der aktuelle Ladevorgang abgeschlossen ist — dies ist der empfohlene Standardansatz für neue Projekte.
Verwenden Sie ein isRefreshing-Flag: setzen Sie es zu Beginn des Ladens auf true und nach Abschluss auf false. In iOS blockiert UIRefreshControl automatisch wiederholte Aufrufe, bis endRefreshing() aufgerufen wird. In Android prüfen Sie SwipeRefreshLayout.isRefreshing am Anfang von onRefresh(): wenn true — return. Dies garantiert eine Anfrage pro Geste.
UIRefreshControl und SwipeRefreshLayout werden nur ausgelöst, wenn sich die Liste in der oberen Position befindet (contentOffset == 0). Die Architektur schließt Konflikte aus: Solange die Liste auch nur um 1px gescrollt ist, wird die Pull-to-Refresh-Geste nicht aktiviert. Tritt ein Konflikt auf, überprüfen Sie nestedScrollingEnabled in Android oder das Vorhandensein benutzerdefinierter GestureRecognizer, die Berührungen abfangen.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch