Pull-to-Refresh : bases, RefreshControl et UIRefreshControl

Auteur : IT Sectr Publié le : 2026-02-27 Temps de lecture : 8 min
Le Pull-to-Refresh est un modèle d'interface mobile où l'utilisateur tire la liste vers le bas avec son doigt, déclenchant le chargement de nouvelles données. Le geste est accompagné d'un indicateur visuel — un spinner rotatif ou une icône animée — qui disparaît une fois le chargement terminé. Selon l'analyse UX d'Apple HIG, le Pull-to-Refresh est devenu le mécanisme standard de mise à jour de contenu dans les fils d'actualité, les réseaux sociaux et les clients de messagerie depuis son introduction dans Tweetie (2008) et sa standardisation ultérieure par Apple et Google.

Points clés

  • Le Pull-to-Refresh est un geste de traction de la liste vers le bas pour actualiser les données, accompagné d'un indicateur visuel de chargement.
  • Sur iOS, UIRefreshControl (iOS 6+) est utilisé, ajouté à UITableViewController ou UIScrollView via la propriété refreshControl.
  • Sur Android, SwipeRefreshLayout (de la Support Library) est utilisé — un wrapper ViewGroup pour RecyclerView ou NestedScrollView.
  • Les deux API prennent en charge la personnalisation des couleurs, des indicateurs et des callbacks via des listeners (iOS : UIRefreshControl.target-action, Android : setOnRefreshListener).
  • Le Pull-to-Refresh est automatiquement bloqué lorsque la liste n'est pas en position haute — le conflit avec le défilement est exclu architecturalement.

Qu'est-ce que le Pull-to-Refresh ?

Pull-to-Refresh est un modèle d'interface utilisateur où l'utilisateur tire (pull down) une liste ou une zone défilable vers le bas pour actualiser le contenu. Visuellement, le geste est accompagné d'un indicateur de chargement (spinner) qui apparaît en haut de l'écran et disparaît après réception des données. Le modèle a été popularisé par l'application Tweetie pour iPhone (2008) et ensuite standardisé par Apple (iOS 6 — UIRefreshControl) et Google (Android Support Library — SwipeRefreshLayout).

D'un point de vue technique, le Pull-to-Refresh est une combinaison de panoramique (suivi du déplacement du doigt) et d'un déclencheur lors de l'atteinte d'un seuil. L'utilisateur tire la liste vers le bas, surmontant une résistance (overscroll résistif), et après avoir dépassé le seuil (~80px sur iOS, ~64dp sur Android), l'animation de l'indicateur et le chargement asynchrone commencent. Si l'utilisateur relâche le doigt avant le seuil, la liste revient à sa position d'origine sans actualisation.

Selon les Material Design Guidelines, le Pull-to-Refresh ne doit pas être utilisé pour la navigation ou le changement d'onglets — son seul but est l'actualisation des données. Chez IT Sectr, nous utilisons le Pull-to-Refresh dans les fils d'actualité, les listes de commandes et les chats où la fraîcheur des données est critique pour l'expérience utilisateur.

Pull-to-Refresh sur iOS : UIRefreshControl

UIRefreshControl est le contrôle standard iOS pour le Pull-to-Refresh, disponible depuis iOS 6. UIRefreshControl est ajouté à UITableViewController via la propriété refreshControl (iOS 10+) ou comme sous-vue du tableau dans les versions antérieures. Il comprend un spinner intégré avec une couleur personnalisable (tintColor), un attribut title et une chaîne attribuée avec une étiquette (par exemple, "Mise à jour...").

UIRefreshControl fonctionne via le mécanisme target-action : lorsque le geste est activé, la méthode spécifiée est appelée (par exemple, refresh(_:)). À l'intérieur de la méthode, le chargement asynchrone des données est effectué. Après l'achèvement, endRefreshing() est appelé, ce qui masque l'indicateur avec une animation. UIRefreshControl gère automatiquement la sensibilité du geste — il ne se déclenche que lorsque le tableau est en position haute (contentOffset.y <= 0).

La propriété tintColor définit la couleur du spinner. attributedTitle permet d'afficher du texte comme "Mis à jour il y a 2 minutes" après l'achèvement. Depuis iOS 10, UIRefreshControl prend en charge les animations personnalisées via UIActivityIndicatorView ou des vues personnalisées persistantes. Chez IT Sectr, nous configurons tintColor selon la marque et affichons l'heure de la dernière mise à jour via attributedTitle — cela augmente la confiance des utilisateurs dans les données.

Pull-to-Refresh sur Android : SwipeRefreshLayout

SwipeRefreshLayout est un ViewGroup de la Android Support Library (androidx.swiperefreshlayout) qui encapsule le contenu défilable (RecyclerView, NestedScrollView, ListView) et ajoute la fonctionnalité Pull-to-Refresh. Contrairement à UIRefreshControl (qui est un contrôle, pas un conteneur), SwipeRefreshLayout est un conteneur qui intercepte les événements tactiles de l'enfant et déclenche l'indicateur d'actualisation lorsque le seuil est dépassé.

SwipeRefreshLayout utilise un indicateur de progression circulaire Material Design avec personnalisation des couleurs via setColorSchemeColors(). La méthode setOnRefreshListener définit le callback onRefresh(), dans lequel le chargement asynchrone est effectué. Après l'achèvement, setRefreshing(false) est appelé pour masquer l'indicateur. Important : setRefreshing(true) appelle à nouveau onRefresh() — donc pour lancer l'actualisation par programmation, utilisez un flag ou une méthode post.

La propriété setProgressBackgroundColorSchemeResource modifie l'arrière-plan de l'indicateur. setSize(SwipeRefreshLayout.LARGE) définit la taille du spinner. Dans la mise en page XML, SwipeRefreshLayout encapsule RecyclerView : swipe_refresh_layout → recycler_view. Selon Google I/O 2024, SwipeRefreshLayout est utilisé dans 85 % des applications Android avec des fils de contenu. Chez IT Sectr, nous encapsulons tous les écrans avec des listes chargées de manière asynchrone dans SwipeRefreshLayout — cela offre une expérience utilisateur cohérente sur toutes les versions d'Android.

Material Pull-to-Refresh (Android 12+)

À partir d'Android 12 (Material You), Google recommande d'utiliser le nouveau Material Pull-to-Refresh de la bibliothèque material-1.6.0+ (androidx.compose.material3.pulltorefresh pour Compose). La nouvelle API utilise un indicateur animé avec prise en charge de l'animation spring et une couleur adaptative basée sur le fond d'écran. SwipeRefreshLayout reste compatible pour les versions antérieures à Android 12.

Bonnes pratiques et erreurs courantes

Le Pull-to-Refresh est un modèle simple à implémenter, mais il contient plusieurs erreurs typiques qui dégradent l'expérience utilisateur. Examinons-les et comment les éviter.

  • Double actualisation — l'utilisateur peut tirer la liste plusieurs fois avant la fin du chargement. Solution : définissez un flag isRefreshing au début et vérifiez-le dans onRefresh(). Sur iOS, endRefreshing() n'est appelé qu'après l'achèvement ; le blocage du geste dans UIRefreshControl est intégré.
  • Manque de feedback — l'indicateur de chargement doit apparaître strictement après que l'utilisateur a dépassé le seuil. N'affichez pas l'indicateur immédiatement au toucher — cela déroute les utilisateurs. iOS et Android le font automatiquement.
  • Ignorer la durée d'actualisation — si les données se mettent à jour en 200 ms, l'indicateur doit être affiché pendant au moins 500 ms pour que l'utilisateur remarque la mise à jour. UIRefreshControl a une durée d'animation minimale ; sur Android, utilisez Handler.postDelayed pour un temps d'affichage minimal.
  • Conflit avec le clavier — avec le clavier ouvert, le Pull-to-Refresh peut se déclencher accidentellement. Masquez le clavier au début du geste via view.endEditing(true) sur iOS et InputMethodManager.hideSoftInputFromWindow() sur Android.
  • Utilisation à d'autres fins que l'actualisation — n'utilisez pas le Pull-to-Refresh pour la navigation (changement d'onglets, retour en arrière). Cela viole les HIG des deux plateformes et désoriente les utilisateurs.

Chez IT Sectr, nous avons ajouté la vérification isRefreshing dans chaque projet après avoir découvert des requêtes en double dans les logs du serveur de test — il s'est avéré que les utilisateurs aux doigts rapides déclenchaient l'actualisation jusqu'à 3 fois de suite.

Exemples de code en Swift et Kotlin

Exemple 1 : UIRefreshControl sur iOS (Swift)

Ajoute le Pull-to-Refresh à UITableViewController avec une couleur de spinner personnalisée et un titre attribué. Après le chargement des données, l'indicateur est masqué.

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: "Tirez pour actualiser"
        )
        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()
        }
    }
}

La propriété tableView.refreshControl (iOS 10+) définit UIRefreshControl. addTarget avec l'événement .valueChanged se déclenche lorsque le geste est activé. endRefreshing() est obligatoire — sans lui, l'indicateur tourne indéfiniment. Le chargement asynchrone est simulé avec DispatchQueue.main.asyncAfter — dans un projet réel, utilisez URLSession ou async/await.

Exemple 2 : SwipeRefreshLayout sur Android (Kotlin)

Encapsule RecyclerView dans SwipeRefreshLayout avec des couleurs d'indicateur personnalisées. onRefresh lance le chargement et masque l'indicateur après l'achèvement.

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 définit les couleurs de l'indicateur rotatif Material Design. isRefreshing = false est appelé dans le bloc finally pour masquer l'indicateur même en cas d'erreur de chargement. ViewModelScope.launch exécute une coroutine dans le cycle de vie du fragment — lorsque le fragment est détruit, la coroutine est automatiquement annulée, évitant les fuites de mémoire.

Exemple 3 : SwiftUI .refreshable (iOS 15+)

SwiftUI moderne fournit le modificateur .refreshable qui ajoute automatiquement le Pull-to-Refresh à List ou 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()
        }
    }
}

Le modificateur .refreshable prend une closure asynchrone qui s'exécute lors du Pull-to-Refresh. SwiftUI affiche et masque automatiquement l'indicateur d'actualisation, gère les conditions de course (ne lance pas une nouvelle actualisation tant que l'actuelle n'est pas terminée) et adapte l'animation à la plateforme. Pour iOS 15+, c'est la méthode préférée pour implémenter le Pull-to-Refresh dans SwiftUI.

Foire aux questions

Le Pull-to-Refresh fonctionne-t-il dans SwiftUI ?

Oui, SwiftUI fournit le modificateur .refreshable pour List ou ScrollView, disponible depuis iOS 15. À l'intérieur de la closure, le code asynchrone de chargement de données est exécuté. SwiftUI gère automatiquement l'indicateur d'actualisation et bloque les déclenchements répétés jusqu'à la fin du chargement en cours — c'est l'approche standard recommandée pour les nouveaux projets.

Comment éviter la double actualisation ?

Utilisez un flag isRefreshing : définissez-le à true au début du chargement et à false après l'achèvement. Sur iOS, UIRefreshControl bloque automatiquement les appels répétés jusqu'à ce que endRefreshing() soit appelé. Sur Android, vérifiez SwipeRefreshLayout.isRefreshing au début de onRefresh() : si true — return. Cela garantit une requête par geste.

Le Pull-to-Refresh entre-t-il en conflit avec le défilement de la liste ?

UIRefreshControl et SwipeRefreshLayout ne se déclenchent que lorsque la liste est en position haute (contentOffset == 0). L'architecture élimine le conflit : tant que la liste est défilée ne serait-ce que de 1px, le geste Pull-to-Refresh ne s'active pas. Si un conflit survient, vérifiez nestedScrollingEnabled sur Android ou la présence de GestureRecognizers personnalisés interceptant les touches.

Résumé

  • Le Pull-to-Refresh est un modèle d'actualisation des données par geste de traction vers le bas, standardisé par Apple et Google sur toutes les plateformes mobiles.
  • UIRefreshControl sur iOS — un contrôle avec target-action, tintColor, attributedTitle et endRefreshing() obligatoire.
  • SwipeRefreshLayout sur Android — un conteneur ViewGroup avec setOnRefreshListener, setColorSchemeColors et isRefreshing.
  • Material Pull-to-Refresh (Android 12+) — une nouvelle API avec animation spring, recommandée pour les nouveaux projets.
  • SwiftUI .refreshable — un modificateur déclaratif avec closure asynchrone, disponible depuis iOS 15.
  • Le flag isRefreshing empêche la double actualisation — obligatoire sur les deux plateformes.
  • Le Pull-to-Refresh n'est pas destiné à la navigation — uniquement à l'actualisation de contenu selon Material Design et Apple HIG.

Nous développerons une application mobile clé en main

IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.

Discuter du projet

Lisez aussi