Pull-to-Refresh: základy, RefreshControl a UIRefreshControl

Autor: IT Sectr Publikováno: 2026-02-27 Doba čtení: 8 min
Pull-to-Refresh — vzor mobilního rozhraní, při kterém uživatel táhne seznam prstem dolů, čímž iniciuje načítání čerstvých dat. Gesto je doprovázeno vizuálním indikátorem — otáčejícím se spinnerem nebo animovanou ikonou — který po dokončení načítání zmizí. Podle UX analýzy Apple HIG se Pull-to-Refresh stal standardním mechanismem aktualizace obsahu v novinkových feedech, sociálních sítích a e-mailových klientech od zavedení v Tweetie (2008) a následné standardizace Apple a Google.

Hlavní body

  • Pull-to-Refresh — gesto tažení seznamu dolů pro aktualizaci dat, doprovázené vizuálním indikátorem načítání.
  • V iOS se používá UIRefreshControl (iOS 6+), přidávaný do UITableViewController nebo UIScrollView přes vlastnost refreshControl.
  • V Android se používá SwipeRefreshLayout (z Support Library) — ViewGroup obal pro RecyclerView nebo NestedScrollView.
  • Obě API podporují přizpůsobení barev, indikátorů a zpětných volání přes listener (iOS: UIRefreshControl.target-action, Android: setOnRefreshListener).
  • Pull-to-Refresh se automaticky blokuje, když seznam není v horní pozici — konflikt s posouváním je architektonicky vyloučen.

Co je Pull-to-Refresh?

Pull-to-Refresh — vzor uživatelského rozhraní, při kterém uživatel táhne (pull down) seznam nebo posouvatelnou oblast dolů pro aktualizaci obsahu. Vizuálně je gesto doprovázeno indikátorem načítání (spinner), který se objeví v horní části obrazovky a zmizí po obdržení dat. Vzor byl popularizován aplikací Tweetie pro iPhone (2008) a následně standardizován Apple (iOS 6 — UIRefreshControl) a Google (Android Support Library — SwipeRefreshLayout).

Z technického hlediska je Pull-to-Refresh kombinací posouvání (sledování pohybu prstu) a spouštěče při dosažení prahu. Uživatel táhne seznam dolů, překonává odpor (rezistivní overscroll), a po překročení prahu (~80px v iOS, ~64dp v Android) se spustí animace indikátoru a asynchronní načítání. Pokud uživatel pustí prst před prahem — seznam se vrátí do výchozí pozice bez aktualizace.

Podle Material Design Guidelines by Pull-to-Refresh neměl být používán pro navigaci nebo přepínání karet — jeho jediným účelem je aktualizace dat. V IT Sectr používáme Pull-to-Refresh v novinkových feedech, seznamech objednávek a chatech, kde je čerstvost dat kritická pro uživatelský zážitek.

Pull-to-Refresh v iOS: UIRefreshControl

UIRefreshControl — standardní ovládací prvek iOS pro Pull-to-Refresh, dostupný od iOS 6. UIRefreshControl se přidává do UITableViewController přes vlastnost refreshControl (iOS 10+) nebo jako subview tabulky ve starších verzích. Obsahuje vestavěný spinner s nastavitelnou barvou (tintColor), atributem title a atributovaným řetězcem s popiskem (např. „Aktualizace...").

UIRefreshControl funguje přes mechanismus target-action: při aktivaci gesta se zavolá určená metoda (např. refresh(_:)). Uvnitř metody probíhá asynchronní načítání dat. Po dokončení se zavolá endRefreshing(), který skryje indikátor s animací. UIRefreshControl automaticky spravuje citlivost gesta — aktivuje se pouze v horní poloze tabulky (contentOffset.y <= 0).

Vlastnost tintColor nastavuje barvu spinneru. Atributy title umožňují zobrazit text „Aktualizováno před 2 minutami" po dokončení. Od iOS 10 podporuje UIRefreshControl vlastní animace přes UIActivityIndicatorView nebo trvalá vlastní zobrazení. V IT Sectr přizpůsobujeme tintColor značce a zobrazujeme čas poslední aktualizace přes attributedTitle — to zvyšuje důvěru uživatelů v data.

Pull-to-Refresh v Android: SwipeRefreshLayout

SwipeRefreshLayout — ViewGroup z Android Support Library (androidx.swiperefreshlayout), který obaluje posouvatelný obsah (RecyclerView, NestedScrollView, ListView) a přidává funkcionalitu Pull-to-Refresh. Na rozdíl od UIRefreshControl (který je ovládacím prvkem, nikoli kontejnerem), je SwipeRefreshLayout kontejner, který zachycuje dotykové události dítěte a spouští indikátor aktualizace při překročení prahu.

SwipeRefreshLayout používá kruhový indikátor průběhu Material Design s nastavením barev přes setColorSchemeColors(). Metoda setOnRefreshListener nastavuje zpětné volání onRefresh(), ve kterém probíhá asynchronní načítání. Po dokončení se zavolá setRefreshing(false) pro skrytí indikátoru. Důležité: setRefreshing(true) znovu volá onRefresh() — proto pro programové spuštění aktualizace použijte flag nebo post metodu.

Vlastnost setProgressBackgroundColorSchemeResource mění pozadí indikátoru. setSize(SwipeRefreshLayout.LARGE) — velikost spinneru. V XML rozvržení SwipeRefreshLayout obaluje RecyclerView: swipe_refresh_layout → recycler_view. Podle Google I/O 2024 se SwipeRefreshLayout používá v 85% Android aplikací s obsahovými feedy. V IT Sectr obalujeme všechny obrazovky s asynchronně načítanými seznamy do SwipeRefreshLayout — to zajišťuje jednotné UX na všech verzích Android.

Material Pull-to-Refresh (Android 12+)

Od Android 12 (Material You) Google doporučuje používat nový Material Pull-to-Refresh z knihovny material-1.6.0+ (androidx.compose.material3.pulltorefresh pro Compose). Nové API používá animovaný indikátor s podporou spring animace a adaptivní barvy na základě tapety. SwipeRefreshLayout zůstává kompatibilní pro verze pod Android 12.

Nejlepší postupy a časté chyby

Pull-to-Refresh — snadno implementovatelný vzor, ale obsahuje několik typických chyb, které snižují UX. Podívejme se na ně a způsoby prevence.

  • Dvojitá aktualizace — uživatel může několikrát zatáhnout za seznam před dokončením načítání. Řešení: nastavte flag isRefreshing při startu a kontrolujte ho v onRefresh(). V iOS se endRefreshing() volá až po dokončení; blokování gesta v UIRefreshControl je vestavěné.
  • Chybějící zpětná vazba — indikátor načítání by se měl objevit až poté, co uživatel překročil práh. Nezobrazujte indikátor ihned při dotyku — to je matoucí. iOS a Android to dělají automaticky.
  • Ignorování času aktualizace — pokud se data aktualizují za 200 ms, indikátor by se měl zobrazovat alespoň 500 ms, aby si uživatel všiml aktualizace. UIRefreshControl má minimální dobu animace; v Android použijte Handler.postDelayed pro minimální dobu zobrazení.
  • Konflikt s klávesnicí — při otevřené klávesnici se Pull-to-Refresh může spustit náhodně. Skryjte klávesnici na začátku gesta pomocí view.endEditing(true) v iOS a InputMethodManager.hideSoftInputFromWindow() v Android.
  • Použití ne pro aktualizaci — nepoužívejte Pull-to-Refresh pro navigaci (přepínání karet, návrat). To porušuje HIG obou platforem a dezorientuje uživatele.

V IT Sectr jsme přidali kontrolu isRefreshing do každého projektu poté, co jsme objevili duplicitní požadavky v logech testovacího serveru — ukázalo se, že uživatelé s rychlými prsty spouštěli aktualizaci až 3krát za sebou.

Příklady kódu v Swift a Kotlin

Příklad 1: UIRefreshControl v iOS (Swift)

Přidává Pull-to-Refresh do UITableViewController s vlastní barvou spinneru a attributed title. Po načtení dat je indikátor skryt.

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: “Potáhněte pro aktualizaci”
        )
        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()
        }
    }
}

Vlastnost tableView.refreshControl (iOS 10+) nastavuje UIRefreshControl. addTarget s událostí .valueChanged se spouští při aktivaci gesta. endRefreshing() je povinný — bez něj by se indikátor točil donekonečna. Asynchronní načítání je simulováno přes DispatchQueue.main.asyncAfter — v reálném projektu by to bylo URLSession nebo async/await.

Příklad 2: SwipeRefreshLayout v Android (Kotlin)

Obalí RecyclerView do SwipeRefreshLayout s vlastními barvami indikátoru. onRefresh spouští načítání a po dokončení skryje indikátor.

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 nastavuje barvy otáčejícího se indikátoru Material Design. isRefreshing = false se povinně volá v finally, aby se indikátor skryl i při chybě načítání. ViewModelScope.launch provádí korutinu v životním cyklu fragmentu — při zničení fragmentu je korutina automaticky zrušena, čímž se zabraňuje úniku paměti.

Příklad 3: SwiftUI .refreshable (iOS 15+)

Moderní SwiftUI poskytuje modifikátor .refreshable, který automaticky přidává Pull-to-Refresh k List nebo 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()
        }
    }
}

Modifikátor .refreshable přijímá async-closure, který se provádí při Pull-to-Refresh. SwiftUI automaticky zobrazuje a skrývá indikátor aktualizace, spravuje závodní stavy (nespouští nové načítání, dokud není dokončeno aktuální) a přizpůsobuje animaci platformě. Pro iOS 15+ je to preferred způsob implementace Pull-to-Refresh ve SwiftUI.

Často kladené otázky

Funguje Pull-to-Refresh ve SwiftUI?

Ano, SwiftUI poskytuje modifikátor .refreshable pro List nebo ScrollView, dostupný od iOS 15. Uvnitř closure se provádí asynchronní kód načítání dat. SwiftUI automaticky spravuje indikátor aktualizace a blokuje opětovné spuštění do dokončení aktuálního načítání — to je standardní recommended přístup pro nové projekty.

Jak zabránit dvojité aktualizaci?

Použijte flag isRefreshing: nastavte true při startu načítání a false po dokončení. V iOS UIRefreshControl automaticky blokuje opakované volání, dokud není voláno endRefreshing(). V Android kontrolujte SwipeRefreshLayout.isRefreshing na začátku onRefresh(): pokud true — return. To zaručuje jeden požadavek na jedno gesto.

Koliduje Pull-to-Refresh s posouváním seznamu?

UIRefreshControl a SwipeRefreshLayout se aktivují pouze v horní pozici seznamu (contentOffset == 0). Architektura vylučuje konflikt: dokud je seznam posunut byť jen o 1px, gesto Pull-to-Refresh se neaktivuje. Pokud konflikt nastane — zkontrolujte nestedScrollingEnabled v Android nebo přítomnost vlastních GestureRecognizer, které zachycují dotyky.

Shrnutí

  • Pull-to-Refresh — vzor aktualizace dat tažením seznamu dolů, standardizovaný Apple a Google na všech mobilních platformách.
  • UIRefreshControl v iOS — ovládací prvek s target-action, tintColor, attributedTitle a povinným endRefreshing().
  • SwipeRefreshLayout v Android — ViewGroup kontejner s setOnRefreshListener, setColorSchemeColors a isRefreshing.
  • Material Pull-to-Refresh (Android 12+) — nové API s spring animací, doporučené pro nové projekty.
  • SwiftUI .refreshable — deklarativní modifikátor s async uzávěrem, dostupný od iOS 15.
  • Flag isRefreshing zabraňuje dvojité aktualizaci — povinný na obou platformách.
  • Pull-to-Refresh není určen pro navigaci — pouze pro aktualizaci obsahu podle Material Design a Apple HIG.

Vyvineme mobilní aplikaci na klíč

IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.

Prodiskutovat projekt

Přečtěte si také