Pull-to-Refresh: alapok, RefreshControl és UIRefreshControl

Szerző: IT Sectr Megjelenés: 2026-02-27 Olvasási idő: 8 perc
Pull-to-Refresh — egy mobil interfész minta, amelyben a felhasználó az ujjával lefelé húzza a listát, elindítva a friss adatok betöltését. A mozdulatot egy vizuális jelző — forgó spinner vagy animált ikon — kíséri, amely a betöltés befejezése után eltűnik. Az Apple HIG UX elemzése szerint a Pull-to-Refresh a Tweetie-ben (2008) történt bevezetése és az Apple és Google általi későbbi szabványosítása óta a tartalomfrissítés standard mechanizmusává vált a hírcsatornákban, közösségi hálózatokban és e-mail kliensekben.

Főbb pontok

  • Pull-to-Refresh — a lista lefelé húzásának mozdulata az adatok frissítéséhez, vizuális betöltésjelzővel kísérve.
  • iOS-ben az UIRefreshControl (iOS 6+) használatos, amely a refreshControl tulajdonságon keresztül adható hozzá UITableViewController-hez vagy UIScrollView-hoz.
  • Android-ban a SwipeRefreshLayout (a Support Library-ból) használatos — egy ViewGroup burkoló RecyclerView vagy NestedScrollView számára.
  • Mindkét API támogatja a színek, jelzők és visszahívások testreszabását listeneren keresztül (iOS: UIRefreshControl.target-action, Android: setOnRefreshListener).
  • A Pull-to-Refresh automatikusan blokkolódik, ha a lista nem a felső pozícióban van — a görgetéssel való ütközés architekturálisan ki van zárva.

Mi az a Pull-to-Refresh?

Pull-to-Refresh — egy felhasználói felület minta, amelyben a felhasználó lefelé húz (pull down) egy listát vagy görgethető területet a tartalom frissítéséhez. Vizuálisan a mozdulatot egy betöltésjelző (spinner) kíséri, amely a képernyő tetején jelenik meg, és eltűnik az adatok megérkezése után. A mintát a Tweetie alkalmazás tette népszerűvé iPhone-ra (2008), majd később az Apple (iOS 6 — UIRefreshControl) és a Google (Android Support Library — SwipeRefreshLayout) szabványosította.

Technikai szempontból a Pull-to-Refresh a pásztázás (ujj elmozdulásának követése) és a küszöb elérésekor történő trigger kombinációja. A felhasználó lefelé húzza a listát, legyőzi az ellenállást (rezisztív overscroll), és a küszöb átlépése után (~80px iOS-ben, ~64dp Android-ban) elindul a jelző animációja és az aszinkron betöltés. Ha a felhasználó a küszöb előtt elengedi az ujját — a lista frissítés nélkül visszatér a kiinduló pozícióba.

A Material Design Guidelines szerint a Pull-to-Refresh nem használható navigációra vagy lapozásra — egyetlen célja az adatok frissítése. Az IT Sectr-nél a Pull-to-Refresh-et hírcsatornákban, rendelési listákban és chatekben alkalmazzuk, ahol az adatok frissessége kritikus a felhasználói élmény szempontjából.

Pull-to-Refresh iOS-ben: UIRefreshControl

UIRefreshControl — a Pull-to-Refresh szabványos iOS vezérlőeleme, iOS 6 óta elérhető. Az UIRefreshControl a UITableViewController-hez a refreshControl tulajdonságon keresztül (iOS 10+) vagy a tábla subview-jeként adható hozzá régebbi verziókban. Beépített spinnert tartalmaz konfigurálható színnel (tintColor), title attribútummal és címkézett attributed stringgel (pl. „Frissítés...").

Az UIRefreshControl target-action mechanizmuson keresztül működik: a mozdulat aktiválásakor a megadott metódus hívódik meg (pl. refresh(_:)). A metóduson belül aszinkron adatbetöltés történik. Befejezés után az endRefreshing() hívódik meg, amely animációval elrejti a jelzőt. Az UIRefreshControl automatikusan kezeli a mozdulat érzékenységét — csak a tábla felső pozíciójában aktiválódik (contentOffset.y <= 0).

A tintColor tulajdonság állítja be a spinner színét. A title attribútumok lehetővé teszik a „Frissítve 2 perccel ezelőtt" szöveg megjelenítését a befejezés után. iOS 10-től kezdve az UIRefreshControl támogatja az egyéni animációkat UIActivityIndicatorView vagy perzisztens egyéni nézeteken keresztül. Az IT Sectr-nél a tintColor-t a márkához igazítjuk, és az utolsó frissítés idejét attributedTitle-en keresztül jelenítjük meg — ez növeli a felhasználók bizalmát az adatokban.

Pull-to-Refresh Android-ban: SwipeRefreshLayout

SwipeRefreshLayout — egy ViewGroup az Android Support Library-ból (androidx.swiperefreshlayout), amely görgethető tartalmat (RecyclerView, NestedScrollView, ListView) burkol be, és Pull-to-Refresh funkciót ad hozzá. Az UIRefreshControl-lal ellentétben (ami egy vezérlő, nem konténer), a SwipeRefreshLayout egy konténer, amely elfogja a gyermek érintési eseményeit, és a küszöb átlépésekor elindítja a frissítésjelzőt.

A SwipeRefreshLayout a Material Design kör alakú folyamatjelzőjét használja színkonfigurációval a setColorSchemeColors()-on keresztül. A setOnRefreshListener metódus beállítja az onRefresh() visszahívást, amelyben az aszinkron betöltés történik. Befejezés után a setRefreshing(false) hívódik meg a jelző elrejtéséhez. Fontos: a setRefreshing(true) újra meghívja az onRefresh()-et — ezért programozott frissítés indításához használjon flag-et vagy post metódust.

A setProgressBackgroundColorSchemeResource tulajdonság megváltoztatja a jelző hátterét. setSize(SwipeRefreshLayout.LARGE) — a spinner mérete. Az XML elrendezésben a SwipeRefreshLayout burkolja a RecyclerView-t: swipe_refresh_layout → recycler_view. A Google I/O 2024 szerint a SwipeRefreshLayout az Android alkalmazások 85%-ában használatos tartalomcsatornákkal. Az IT Sectr-nél az összes aszinkron betöltésű listát tartalmazó képernyőt SwipeRefreshLayout-ba burkoljuk — ez egységes UX-t biztosít az Android minden verzióján.

Material Pull-to-Refresh (Android 12+)

Android 12-től (Material You) kezdve a Google az új Material Pull-to-Refresh használatát ajánlja a material-1.6.0+ könyvtárból (Compose esetén androidx.compose.material3.pulltorefresh). Az új API animált jelzőt használ spring animáció támogatással és adaptív színnel a háttérkép alapján. A SwipeRefreshLayout kompatibilis marad az Android 12 alatti verziókhoz.

Legjobb gyakorlatok és gyakori hibák

A Pull-to-Refresh — egy egyszerűen implementálható minta, de tartalmaz néhány gyakori hibát, amelyek rontják a UX-et. Vizsgáljuk meg ezeket és a megelőzés módjait.

  • Dupla frissítés — a felhasználó többször is meghúzhatja a listát a betöltés befejeződése előtt. Megoldás: állítsa be az isRefreshing flag-et induláskor, és ellenőrizze az onRefresh()-ben. iOS-ben az endRefreshing() csak a befejezés után hívódik meg; a mozdulat blokkolása az UIRefreshControl-ban beépített.
  • Visszajelzés hiánya — a betöltésjelzőnek csak azután kell megjelennie, hogy a felhasználó átlépte a küszöböt. Ne jelenítse meg a jelzőt azonnal érintéskor — ez zavaró. Az iOS és Android ezt automatikusan teszi.
  • Frissítési idő figyelmen kívül hagyása — ha az adatok 200 ms alatt frissülnek, a jelzőt legalább 500 ms-ig meg kell jeleníteni, hogy a felhasználó észrevegye a frissítést. Az UIRefreshControl-nak minimális animációs ideje van; Android-ban használjon Handler.postDelayed-t a minimális megjelenítési időhöz.
  • Ütközés a billentyűzettel — nyitott billentyűzet mellett a Pull-to-Refresh véletlenül aktiválódhat. Rejtse el a billentyűzetet a mozdulat elején a view.endEditing(true) segítségével iOS-ben és az InputMethodManager.hideSoftInputFromWindow() segítségével Android-ban.
  • Nem frissítésre használat — ne használja a Pull-to-Refresh-et navigációra (lapozás, visszalépés). Ez megsérti mindkét platform HIG-jét és megzavarja a felhasználókat.

Az IT Sectr-nél az isRefreshing ellenőrzést minden projektben hozzáadtuk, miután duplikált kéréseket fedeztünk fel a teszt szerver naplóiban — kiderült, hogy a gyors ujjú felhasználók akár 3-szor egymás után is elindították a frissítést.

Kódpéldák Swift és Kotlin nyelven

1. példa: UIRefreshControl iOS-ben (Swift)

Pull-to-Refresh hozzáadása UITableViewController-hez egyedi spinner színnel és attributed title-lel. Az adatok betöltése után a jelző elrejtésre kerül.

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: “Húzza le a frissítéshez”
        )
        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()
        }
    }
}

A tableView.refreshControl tulajdonság (iOS 10+) beállítja az UIRefreshControl-t. A .valueChanged eseményhez tartozó addTarget a mozdulat aktiválásakor indul. Az endRefreshing() kötelező — nélküle a jelző végtelenségig forogna. Az aszinkron betöltés a DispatchQueue.main.asyncAfter segítségével van szimulálva — valós projektben URLSession vagy async/await lenne.

2. példa: SwipeRefreshLayout Android-ban (Kotlin)

RecyclerView burkolása SwipeRefreshLayout-ba egyedi jelzőszínekkel. Az onRefresh elindítja a betöltést és elrejti a jelzőt a befejezés után.

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
    }
}

A setColorSchemeColors beállítja a forgó Material Design jelző színeit. Az isRefreshing = false kötelezően a finally blokkban hívódik meg, hogy a jelző betöltési hiba esetén is elrejtődjön. A ViewModelScope.launch a fragment életciklusában hajtja végre a korutint — a fragment megsemmisülésekor a korutin automatikusan törlődik, megakadályozva a memóriaszivárgást.

3. példa: SwiftUI .refreshable (iOS 15+)

A modern SwiftUI .refreshable módosítót biztosít, amely automatikusan hozzáadja a Pull-to-Refresh-et List vagy ScrollView elemekhez.

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()
        }
    }
}

A .refreshable módosító egy async-closure-t fogad, amely Pull-to-Refresh esetén hajtódik végre. A SwiftUI automatikusan megjeleníti és elrejti a frissítésjelzőt, kezeli a versenyhelyzeteket (nem indít újratöltést a jelenlegi befejezése előtt), és a platformhoz igazítja az animációt. iOS 15+ esetén ez a preferred módja a Pull-to-Refresh implementálásának SwiftUI-ban.

Gyakori kérdések

Működik a Pull-to-Refresh SwiftUI-ban?

Igen, a SwiftUI biztosítja a .refreshable módosítót List vagy ScrollView elemekhez, iOS 15-től elérhetően. A closure-ben aszinkron adatbetöltési kód hajtódik végre. A SwiftUI automatikusan kezeli a frissítésjelzőt és blokkolja az újraindításokat a jelenlegi betöltés befejezéséig — ez a standard recommended megközelítés új projektekhez.

Hogyan akadályozható meg a dupla frissítés?

Használja az isRefreshing flag-et: állítsa true-ra a betöltés indításakor és false-ra a befejezés után. iOS-ben az UIRefreshControl automatikusan blokkolja az újbóli meghívást, amíg az endRefreshing() meg nem hívódik. Android-ban ellenőrizze a SwipeRefreshLayout.isRefreshing értékét az onRefresh() elején: ha true — return. Ez garantálja a mozdulatonként egy kérést.

Ütközik-e a Pull-to-Refresh a lista görgetésével?

Az UIRefreshControl és a SwipeRefreshLayout csak a lista felső pozíciójában (contentOffset == 0) aktiválódik. Az architektúra kizárja az ütközést: amíg a lista akár 1px-el görgetve van, a Pull-to-Refresh mozdulat nem aktiválódik. Ha ütközés lép fel — ellenőrizze a nestedScrollingEnabled-t Android-ban, vagy az egyéni GestureRecognizer-ek jelenlétét, amelyek elfogják az érintéseket.

Összefoglalás

  • Pull-to-Refresh — adatfrissítési minta a lista lefelé húzásával, az Apple és Google által szabványosítva minden mobil platformon.
  • UIRefreshControl iOS-ben — vezérlő target-action, tintColor, attributedTitle és kötelező endRefreshing() funkciókkal.
  • SwipeRefreshLayout Android-ban — ViewGroup konténer setOnRefreshListener, setColorSchemeColors és isRefreshing funkciókkal.
  • Material Pull-to-Refresh (Android 12+) — új API spring animációval, új projektekhez ajánlott.
  • SwiftUI .refreshable — deklaratív módosító async-closure-ral, iOS 15-től elérhető.
  • Az isRefreshing flag megakadályozza a dupla frissítést — kötelező mindkét platformon.
  • A Pull-to-Refresh nem navigációra való — csak tartalomfrissítésre a Material Design és Apple HIG szerint.

Kulcsrakész mobilalkalmazást fejlesztünk

Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.

Projekt megbeszélése

Olvassa el is