Poin Utama
Pull-to-Refresh — pola antarmuka pengguna di mana pengguna menarik (pull down) daftar atau area yang dapat digulir ke bawah untuk memperbarui konten. Secara visual, gerakan ini disertai dengan indikator pemuatan (spinner) yang muncul di bagian atas layar dan menghilang setelah data diterima. Pola ini dipopulerkan oleh aplikasi Tweetie untuk iPhone (2008) dan kemudian distandarisasi oleh Apple (iOS 6 — UIRefreshControl) dan Google (Android Support Library — SwipeRefreshLayout).
Dari segi teknis, Pull-to-Refresh adalah kombinasi dari panning (melacak pergerakan jari) dan pemicu ketika mencapai ambang batas. Pengguna menarik daftar ke bawah, mengatasi resistensi (overscroll resistif), dan setelah melewati ambang batas (~80px di iOS, ~64dp di Android) animasi indikator dan pemuatan asinkron dimulai. Jika pengguna melepaskan jari sebelum ambang batas — daftar kembali ke posisi awal tanpa pembaruan.
Menurut Material Design Guidelines, Pull-to-Refresh tidak boleh digunakan untuk navigasi atau perpindahan tab — satu-satunya tujuannya adalah memperbarui data. Di IT Sectr kami menerapkan Pull-to-Refresh di feed berita, daftar pesanan, dan obrolan, di mana kesegaran data sangat penting untuk pengalaman pengguna.
UIRefreshControl — elemen kontrol standar iOS untuk Pull-to-Refresh, tersedia sejak iOS 6. UIRefreshControl ditambahkan ke UITableViewController melalui properti refreshControl (iOS 10+) atau sebagai subview tabel di versi yang lebih lama. Ini berisi spinner bawaan dengan warna yang dapat dikonfigurasi (tintColor), atribut title, dan string beratribut dengan label (misalnya, “Memperbarui...”).
UIRefreshControl bekerja melalui mekanisme target-action: ketika gerakan diaktifkan, metode yang ditentukan dipanggil (misalnya, refresh(_:)). Di dalam metode, pemuatan data asinkron dilakukan. Setelah selesai, endRefreshing() dipanggil, yang menyembunyikan indikator dengan animasi. UIRefreshControl secara otomatis mengelola sensitivitas gerakan — hanya aktif di posisi atas tabel (contentOffset.y <= 0).
Properti tintColor mengatur warna spinner. Atribut title memungkinkan menampilkan teks “Diperbarui 2 menit yang lalu” setelah selesai. Mulai iOS 10, UIRefreshControl mendukung animasi kustom melalui UIActivityIndicatorView atau tampilan kustom persisten. Di IT Sectr kami menyesuaikan tintColor dengan merek dan menampilkan waktu pembaruan terakhir melalui attributedTitle — ini meningkatkan kepercayaan pengguna terhadap data.
SwipeRefreshLayout — ViewGroup dari Android Support Library (androidx.swiperefreshlayout) yang membungkus konten yang dapat digulir (RecyclerView, NestedScrollView, ListView) dan menambahkan fungsionalitas Pull-to-Refresh. Berbeda dengan UIRefreshControl (yang merupakan kontrol, bukan wadah), SwipeRefreshLayout adalah wadah yang menyadap peristiwa sentuhan anak dan mengaktifkan indikator pembaruan ketika melewati ambang batas.
SwipeRefreshLayout menggunakan indikator kemajuan melingkar Material Design dengan konfigurasi warna melalui setColorSchemeColors(). Metode setOnRefreshListener mengatur panggilan balik onRefresh(), di mana pemuatan asinkron dilakukan. Setelah selesai, setRefreshing(false) dipanggil untuk menyembunyikan indikator. Penting: setRefreshing(true) memanggil onRefresh() lagi — oleh karena itu untuk memulai pembaruan secara terprogram, gunakan flag atau metode post.
Properti setProgressBackgroundColorSchemeResource mengubah latar belakang indikator. setSize(SwipeRefreshLayout.LARGE) — ukuran spinner. Di tata letak XML, SwipeRefreshLayout membungkus RecyclerView: swipe_refresh_layout → recycler_view. Menurut Google I/O 2024, SwipeRefreshLayout digunakan di 85% aplikasi Android dengan feed konten. Di IT Sectr kami membungkus semua layar dengan daftar yang dimuat secara asinkron dalam SwipeRefreshLayout — ini memberikan UX yang seragam di semua versi Android.
Mulai Android 12 (Material You), Google merekomendasikan penggunaan Material Pull-to-Refresh baru dari perpustakaan material-1.6.0+ (androidx.compose.material3.pulltorefresh untuk Compose). API baru menggunakan indikator animasi dengan dukungan animasi spring dan warna adaptif berdasarkan wallpaper. SwipeRefreshLayout tetap kompatibel untuk versi di bawah Android 12.
Pull-to-Refresh — pola yang sederhana untuk diimplementasikan, tetapi mengandung beberapa kesalahan umum yang mengurangi UX. Mari kita bahas dan cara pencegahannya.
Di IT Sectr kami menambahkan pemeriksaan isRefreshing di setiap proyek setelah menemukan permintaan duplikat di log server pengujian — ternyata pengguna dengan jari cepat memicu pembaruan hingga 3 kali berturut-turut.
Menambahkan Pull-to-Refresh ke UITableViewController dengan warna spinner kustom dan attributed title. Setelah data dimuat, indikator disembunyikan.
import UIKit
class FeedTableViewController: UITableViewController {
private var items: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.refreshControl = UIRefreshControl()
refreshControl?.tintColor = .systemBlue
refreshControl?.attributedTitle = NSAttributedString(
string: “Tarik untuk memperbarui”
)
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()
}
}
}
Properti tableView.refreshControl (iOS 10+) mengatur UIRefreshControl. addTarget dengan peristiwa .valueChanged dipicu saat gerakan diaktifkan. endRefreshing() wajib — tanpanya indikator akan berputar tanpa henti. Pemuatan asinkron disimulasikan melalui DispatchQueue.main.asyncAfter — dalam proyek nyata akan menggunakan URLSession atau async/await.
Membungkus RecyclerView dalam SwipeRefreshLayout dengan warna indikator kustom. onRefresh memulai pemuatan dan menyembunyikan indikator setelah selesai.
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 mengatur warna indikator berputar Material Design. isRefreshing = false wajib dipanggil di finally untuk menyembunyikan indikator bahkan saat terjadi kesalahan pemuatan. ViewModelScope.launch menjalankan coroutine dalam siklus hidup fragmen — ketika fragmen dihancurkan, coroutine secara otomatis dibatalkan, mencegah kebocoran memori.
SwiftUI modern menyediakan pengubah .refreshable, yang secara otomatis menambahkan Pull-to-Refresh ke List atau ScrollView.
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()
}
}
}
Pengubah .refreshable menerima async-closure yang dieksekusi saat Pull-to-Refresh. SwiftUI secara otomatis menampilkan dan menyembunyikan indikator pembaruan, mengelola kondisi balapan (tidak memulai pemuatan ulang hingga yang saat ini selesai) dan menyesuaikan animasi dengan platform. Untuk iOS 15+ ini adalah cara preferred untuk mengimplementasikan Pull-to-Refresh di SwiftUI.
Pertanyaan yang Sering Diajukan
Ya, SwiftUI menyediakan pengubah .refreshable untuk List atau ScrollView, tersedia sejak iOS 15. Di dalam closure, kode async pemuatan data dijalankan. SwiftUI secara otomatis mengelola indikator pembaruan dan memblokir pemicuan ulang hingga pemuatan saat ini selesai — ini adalah pendekatan recommended standar untuk proyek baru.
Gunakan flag isRefreshing: set true saat memulai pemuatan dan false setelah selesai. Di iOS, UIRefreshControl secara otomatis memblokir panggilan ulang sampai endRefreshing() dipanggil. Di Android, periksa SwipeRefreshLayout.isRefreshing di awal onRefresh(): jika true — return. Ini menjamin satu permintaan per gerakan.
UIRefreshControl dan SwipeRefreshLayout hanya aktif di posisi atas daftar (contentOffset == 0). Arsitektur mengecualikan konflik: selama daftar digulir meski hanya 1px, gerakan Pull-to-Refresh tidak akan aktif. Jika terjadi konflik — periksa nestedScrollingEnabled di Android atau keberadaan GestureRecognizer kustom yang menyadap sentuhan.
Ringkasan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga