Főbb pontok
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
A modern SwiftUI .refreshable módosítót biztosít, amely automatikusan hozzáadja a Pull-to-Refresh-et List vagy ScrollView elemekhez.
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
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.
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.
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
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.
Olvassa el is