Pull-to-Refresh: dasar, RefreshControl dan UIRefreshControl

Penulis: IT Sectr Diterbitkan: 2026-02-27 Waktu membaca: 8 mnt
Pull-to-Refresh — pola antarmuka mobile di mana pengguna menarik daftar ke bawah dengan jari, memicu pemuatan data terbaru. Gerakan ini disertai dengan indikator visual — spinner berputar atau ikon animasi — yang menghilang setelah pemuatan selesai. Berdasarkan analisis UX Apple HIG, Pull-to-Refresh telah menjadi mekanisme standar untuk memperbarui konten di feed berita, jejaring sosial, dan klien email sejak implementasinya di Tweetie (2008) dan standarisasi selanjutnya oleh Apple dan Google.

Poin Utama

  • Pull-to-Refresh — gerakan menarik daftar ke bawah untuk memperbarui data, disertai indikator visual pemuatan.
  • Di iOS digunakan UIRefreshControl (iOS 6+), ditambahkan ke UITableViewController atau UIScrollView melalui properti refreshControl.
  • Di Android digunakan SwipeRefreshLayout (dari Support Library) — pembungkus ViewGroup untuk RecyclerView atau NestedScrollView.
  • Kedua API mendukung kustomisasi warna, indikator, dan panggilan balik melalui listener (iOS: UIRefreshControl.target-action, Android: setOnRefreshListener).
  • Pull-to-Refresh secara otomatis diblokir ketika daftar tidak dalam posisi atas — konflik dengan pengguliran dikecualikan secara arsitektural.

Apa itu Pull-to-Refresh?

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.

Pull-to-Refresh di iOS: UIRefreshControl

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.

Pull-to-Refresh di Android: SwipeRefreshLayout

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.

Material Pull-to-Refresh (Android 12+)

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.

Praktik terbaik dan kesalahan umum

Pull-to-Refresh — pola yang sederhana untuk diimplementasikan, tetapi mengandung beberapa kesalahan umum yang mengurangi UX. Mari kita bahas dan cara pencegahannya.

  • Pembaruan ganda — pengguna dapat menarik daftar beberapa kali sebelum pemuatan selesai. Solusi: atur flag isRefreshing saat mulai dan periksa di onRefresh(). Di iOS, endRefreshing() hanya dipanggil setelah selesai; pemblokiran gerakan di UIRefreshControl sudah terintegrasi.
  • Kurangnya umpan balik — indikator pemuatan harus muncul setelah pengguna melewati ambang batas. Jangan tampilkan indikator segera saat disentuh — ini membingungkan. iOS dan Android melakukannya secara otomatis.
  • Mengabaikan waktu pembaruan — jika data diperbarui dalam 200 md, indikator harus ditampilkan setidaknya 500 md agar pengguna melihat pembaruan. UIRefreshControl memiliki waktu animasi minimum; di Android gunakan Handler.postDelayed untuk waktu tampilan minimum.
  • Konflik dengan keyboard — dengan keyboard terbuka, Pull-to-Refresh dapat terpicu secara tidak sengaja. Sembunyikan keyboard di awal gerakan melalui view.endEditing(true) di iOS dan InputMethodManager.hideSoftInputFromWindow() di Android.
  • Penggunaan bukan untuk pembaruan — jangan gunakan Pull-to-Refresh untuk navigasi (perpindahan tab, kembali). Ini melanggar HIG kedua platform dan membingungkan pengguna.

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.

Contoh kode di Swift dan Kotlin

Contoh 1: UIRefreshControl di iOS (Swift)

Menambahkan Pull-to-Refresh ke UITableViewController dengan warna spinner kustom dan attributed title. Setelah data dimuat, indikator disembunyikan.

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: “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.

Contoh 2: SwipeRefreshLayout di Android (Kotlin)

Membungkus RecyclerView dalam SwipeRefreshLayout dengan warna indikator kustom. onRefresh memulai pemuatan dan menyembunyikan indikator setelah selesai.

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

Contoh 3: SwiftUI .refreshable (iOS 15+)

SwiftUI modern menyediakan pengubah .refreshable, yang secara otomatis menambahkan Pull-to-Refresh ke List atau 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()
        }
    }
}

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

Apakah Pull-to-Refresh berfungsi di SwiftUI?

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.

Bagaimana cara mencegah pembaruan ganda?

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.

Apakah Pull-to-Refresh bertentangan dengan pengguliran daftar?

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

  • Pull-to-Refresh — pola pembaruan data dengan menarik daftar ke bawah, distandarisasi oleh Apple dan Google di semua platform mobile.
  • UIRefreshControl di iOS — kontrol dengan target-action, tintColor, attributedTitle dan endRefreshing() wajib.
  • SwipeRefreshLayout di Android — wadah ViewGroup dengan setOnRefreshListener, setColorSchemeColors dan isRefreshing.
  • Material Pull-to-Refresh (Android 12+) — API baru dengan animasi spring, direkomendasikan untuk proyek baru.
  • SwiftUI .refreshable — pengubah deklaratif dengan async-closure, tersedia sejak iOS 15.
  • Flag isRefreshing mencegah pembaruan ganda — wajib di kedua platform.
  • Pull-to-Refresh tidak dimaksudkan untuk navigasi — hanya untuk pembaruan konten sesuai Material Design dan Apple HIG.

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.

Diskusikan proyek

Baca juga