Pull-to-Refresh: مبانی، RefreshControl و UIRefreshControl

نویسنده: IT Sectr منتشر شده: 2026-02-27 زمان مطالعه: 8 دقیقه
Pull-to-Refresh — الگوی رابط کاربری موبایل است که در آن کاربر لیست را با انگشت به پایین می‌کشد و بارگذاری داده‌های تازه را آغاز می‌کند. این حرکت با یک نشانگر بصری — چرخنده یا آیکون متحرک — همراه است که پس از اتمام بارگذاری ناپدید می‌شود. بر اساس تحلیل UX اپل HIG، Pull-to-Refresh از زمان پیاده‌سازی در Tweetie (2008) و استانداردسازی بعدی توسط اپل و گوگل، به مکانیزم استاندارد به‌روزرسانی محتوا در فیدهای خبری، شبکه‌های اجتماعی و کلاینت‌های ایمیل تبدیل شده است.

نکات کلیدی

  • Pull-to-Refresh — حرکت کشیدن لیست به پایین برای به‌روزرسانی داده‌ها، همراه با نشانگر بصری بارگذاری.
  • در iOS از UIRefreshControl (iOS 6+) استفاده می‌شود که از طریق ویژگی refreshControl به UITableViewController یا UIScrollView اضافه می‌شود.
  • در Android از SwipeRefreshLayout (از Support Library) استفاده می‌شود — یک ViewGroup-wrapper برای RecyclerView یا NestedScrollView.
  • هر دو API از سفارشی‌سازی رنگ‌ها، نشانگرها و فراخوان‌ها از طریق listener پشتیبانی می‌کنند (iOS: UIRefreshControl.target-action، Android: setOnRefreshListener).
  • Pull-to-Refresh زمانی که لیست در موقعیت بالایی نیست به طور خودکار مسدود می‌شود — تداخل با اسکرول از نظر معماری حذف شده است.

Pull-to-Refresh چیست؟

Pull-to-Refresh — الگوی رابط کاربری است که در آن کاربر لیست یا منطقه قابل اسکرول را به پایین می‌کشد (pull down) تا محتوا را به‌روزرسانی کند. از نظر بصری، حرکت با یک نشانگر بارگذاری (spinner) همراه است که در بالای صفحه ظاهر می‌شود و پس از دریافت داده ناپدید می‌شود. این الگو توسط برنامه Tweetie برای iPhone (2008) محبوب شد و بعداً توسط اپل (iOS 6 — UIRefreshControl) و گوگل (Android Support Library — SwipeRefreshLayout) استانداردسازی شد.

از نظر فنی، Pull-to-Refresh ترکیبی از پانینگ (ردیابی جابجایی انگشت) و تریگر هنگام رسیدن به آستانه است. کاربر لیست را به پایین می‌کشد، بر مقاومت غلبه می‌کند (overscroll مقاومتی)، و پس از عبور از آستانه (~80px در iOS، ~64dp در Android) انیمیشن نشانگر و بارگذاری ناهمزمان شروع می‌شود. اگر کاربر انگشت خود را قبل از آستانه رها کند — لیست بدون به‌روزرسانی به موقعیت اولیه بازمی‌گردد.

طبق Material Design Guidelines، Pull-to-Refresh نباید استفاده شود برای ناوبری یا جابجایی بین تب‌ها — تنها هدف آن به‌روزرسانی داده‌ها است. در IT Sectr ما از Pull-to-Refresh در فیدهای خبری، لیست سفارشات و چت‌ها استفاده می‌کنیم، جایی که تازگی داده‌ها برای تجربه کاربری حیاتی است.

Pull-to-Refresh در iOS: UIRefreshControl

UIRefreshControl — عنصر کنترل استاندارد iOS برای Pull-to-Refresh است که از iOS 6 در دسترس است. UIRefreshControl از طریق ویژگی refreshControl (iOS 10+) یا به عنوان subview جدول در نسخه‌های قدیمی‌تر به UITableViewController اضافه می‌شود. این عنصر حاوی یک چرخنده داخلی با رنگ قابل تنظیم (tintColor)، ویژگی title و رشته منسوب با برچسب (مثلاً “در حال به‌روزرسانی...”) است.

UIRefreshControl از طریق مکانیزم target-action کار می‌کند: هنگام فعال شدن حرکت، متد مشخص شده (مثلاً refresh(_:)) فراخوانی می‌شود. در داخل متد، بارگذاری ناهمزمان داده انجام می‌شود. پس از اتمام، endRefreshing() فراخوانی می‌شود که نشانگر را با انیمیشن مخفی می‌کند. UIRefreshControl به طور خودکار حساسیت حرکت را مدیریت می‌کند — فقط در موقعیت بالای جدول (contentOffset.y <= 0) فعال می‌شود.

ویژگی tintColor رنگ چرخنده را تنظیم می‌کند. ویژگی‌های title امکان نمایش متن “به‌روزرسانی شده 2 دقیقه پیش” را پس از اتمام فراهم می‌کنند. از iOS 10، UIRefreshControl از انیمیشن‌های سفارشی از طریق UIActivityIndicatorView یا نمایش‌های سفارشی پایدار پشتیبانی می‌کند. در IT Sectr ما tintColor را با برند تنظیم می‌کنیم و زمان آخرین به‌روزرسانی را از طریق attributedTitle نشان می‌دهیم — این اعتماد کاربران به داده‌ها را افزایش می‌دهد.

Pull-to-Refresh در Android: SwipeRefreshLayout

SwipeRefreshLayout — ViewGroup از Android Support Library (androidx.swiperefreshlayout) است که محتوای قابل اسکرول (RecyclerView، NestedScrollView، ListView) را می‌پیچد و قابلیت Pull-to-Refresh را اضافه می‌کند. برخلاف UIRefreshControl (که یک کنترل است نه کانتینر)، SwipeRefreshLayout یک کانتینر است که رویدادهای لمسی فرزند را رهگیری می‌کند و هنگام عبور از آستانه نشانگر به‌روزرسانی را فعال می‌کند.

SwipeRefreshLayout از نشانگر پیشرفت دایره‌ای Material Design با تنظیم رنگ از طریق setColorSchemeColors() استفاده می‌کند. متد setOnRefreshListener فراخوان onRefresh() را تنظیم می‌کند که در آن بارگذاری ناهمزمان انجام می‌شود. پس از اتمام، setRefreshing(false) برای مخفی کردن نشانگر فراخوانی می‌شود. مهم: setRefreshing(true) دوباره onRefresh() را فراخوانی می‌کند — بنابراین برای شروع برنامه‌ریزی شده به‌روزرسانی از flag یا متد post استفاده کنید.

ویژگی setProgressBackgroundColorSchemeResource پس زمینه نشانگر را تغییر می‌دهد. setSize(SwipeRefreshLayout.LARGE) — اندازه چرخنده. در XML-چیدمان، SwipeRefreshLayout RecyclerView را می‌پیچد: swipe_refresh_layout → recycler_view. طبق Google I/O 2024، SwipeRefreshLayout در 85% برنامه‌های Android با فید محتوا استفاده می‌شود. در IT Sectr ما تمام صفحات با لیست‌های بارگذاری ناهمزمان را در SwipeRefreshLayout می‌پیچیم — این UX یکپارچه را در تمام نسخه‌های Android تضمین می‌کند.

Material Pull-to-Refresh (Android 12+)

از Android 12 (Material You)، گوگل استفاده از Material Pull-to-Refresh جدید از کتابخانه material-1.6.0+ (androidx.compose.material3.pulltorefresh برای Compose) را توصیه می‌کند. API جدید از نشانگر متحرک با پشتیبانی از انیمیشن spring و رنگ تطبیقی بر اساس والپیپر استفاده می‌کند. SwipeRefreshLayout برای نسخه‌های پایین‌تر از Android 12 سازگار باقی می‌ماند.

بهترین روش‌ها و خطاهای رایج

Pull-to-Refresh — الگویی ساده در پیاده‌سازی است، اما حاوی چند خطای رایج است که UX را کاهش می‌دهد. بیایید آنها و راه‌های جلوگیری را بررسی کنیم.

  • به‌روزرسانی مضاعف — کاربر ممکن است چند بار لیست را بکشد تا بارگذاری کامل شود. راه‌حل: در شروع flag isRefreshing را تنظیم کنید و در onRefresh() آن را بررسی کنید. در iOS endRefreshing() فقط پس از اتمام فراخوانی می‌شود؛ مسدودسازی حرکت در UIRefreshControl داخلی است.
  • عدم بازخورد — نشانگر بارگذاری باید دقیقاً پس از عبور کاربر از آستانه ظاهر شود. نشانگر را بلافاصله هنگام لمس نشان ندهید — این گیج‌کننده است. iOS و Android این کار را به طور خودکار انجام می‌دهند.
  • نادیده گرفتن زمان به‌روزرسانی — اگر داده‌ها در 200 میلی‌ثانیه به‌روزرسانی شوند، نشانگر باید حداقل 500 میلی‌ثانیه نمایش داده شود تا کاربر متوجه به‌روزرسانی شود. UIRefreshControl حداقل زمان انیمیشن دارد؛ در Android از Handler.postDelayed برای حداقل زمان نمایش استفاده کنید.
  • تداخل با صفحه کلید — با صفحه کلید باز، Pull-to-Refresh ممکن است به طور تصادفی فعال شود. در شروع حرکت، صفحه کلید را از طریق view.endEditing(true) در iOS و InputMethodManager.hideSoftInputFromWindow() در Android مخفی کنید.
  • استفاده برای غیر به‌روزرسانی — از Pull-to-Refresh برای ناوبری (جابجایی تب‌ها، بازگشت به عقب) استفاده نکنید. این HIG هر دو پلتفرم را نقض می‌کند و کاربران را سردرگم می‌کند.

در IT Sectr ما پس از کشف درخواست‌های تکراری در لاگ‌های سرور تست، بررسی isRefreshing را در هر پروژه اضافه کردیم — معلوم شد کاربران با انگشتان سریع تا 3 بار متوالی به‌روزرسانی را شروع می‌کردند.

نمونه کد در Swift و Kotlin

مثال 1: UIRefreshControl در iOS (Swift)

Pull-to-Refresh را با رنگ چرخنده سفارشی و attributed title به UITableViewController اضافه می‌کند. پس از بارگذاری داده‌ها، نشانگر مخفی می‌شود.

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: “برای به‌روزرسانی بکشید”
        )
        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()
        }
    }
}

ویژگی tableView.refreshControl (iOS 10+) UIRefreshControl را تنظیم می‌کند. addTarget با رویداد .valueChanged هنگام فعال شدن حرکت اجرا می‌شود. endRefreshing() اجباری است — بدون آن نشانگر بی‌نهایت می‌چرخد. بارگذاری ناهمزمان با DispatchQueue.main.asyncAfter شبیه‌سازی می‌شود — در پروژه واقعی URLSession یا async/await خواهد بود.

مثال 2: SwipeRefreshLayout در Android (Kotlin)

RecyclerView را در SwipeRefreshLayout با رنگ‌های نشانگر سفارشی می‌پیچد. onRefresh بارگذاری را شروع می‌کند و پس از اتمام نشانگر را مخفی می‌کند.

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 رنگ‌های نشانگر چرخان Material Design را تنظیم می‌کند. isRefreshing = false حتماً در finally فراخوانی می‌شود تا نشانگر حتی در صورت خطای بارگذاری مخفی شود. ViewModelScope.launch کوروتین را در چرخه حیات fragment اجرا می‌کند — هنگام نابودی fragment، کوروتین به طور خودکار لغو می‌شود و از نشت حافظه جلوگیری می‌کند.

مثال 3: SwiftUI .refreshable (iOS 15+)

SwiftUI مدرن اصلاح‌کننده .refreshable را ارائه می‌دهد که به طور خودکار Pull-to-Refresh را به List یا 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()
        }
    }
}

اصلاح‌کننده .refreshable یک async-closure می‌پذیرد که هنگام Pull-to-Refresh اجرا می‌شود. SwiftUI به طور خودکار نشانگر به‌روزرسانی را نشان می‌دهد و مخفی می‌کند، شرایط مسابقه را مدیریت می‌کند (تا اتمام بارگذاری فعلی، بارگذاری مجدد را شروع نمی‌کند) و انیمیشن را با پلتفرم تطبیق می‌دهد. برای iOS 15+ این روش preferred برای پیاده‌سازی Pull-to-Refresh در SwiftUI است.

سوالات متداول

آیا Pull-to-Refresh در SwiftUI کار می‌کند؟

بله، SwiftUI اصلاح‌کننده .refreshable را برای List یا ScrollView فراهم می‌کند که از iOS 15 در دسترس است. در داخل closure کد async بارگذاری داده اجرا می‌شود. SwiftUI به طور خودکار نشانگر به‌روزرسانی را مدیریت می‌کند و راه‌اندازی مجدد را تا اتمام بارگذاری فعلی مسدود می‌کند — این رویکرد recommended استاندارد برای پروژه‌های جدید است.

چگونه از به‌روزرسانی مضاعف جلوگیری کنیم؟

از flag isRefreshing استفاده کنید: در شروع بارگذاری true و پس از اتمام false تنظیم کنید. در iOS UIRefreshControl تا زمانی که endRefreshing() فراخوانی نشده است، فراخوانی مجدد را به طور خودکار مسدود می‌کند. در Android در ابتدای onRefresh() مقدار SwipeRefreshLayout.isRefreshing را بررسی کنید: اگر true — return. این یک درخواست برای هر حرکت را تضمین می‌کند.

آیا Pull-to-Refresh با اسکرول لیست تداخل دارد؟

UIRefreshControl و SwipeRefreshLayout فقط در موقعیت بالای لیست (contentOffset == 0) فعال می‌شوند. معماری تداخل را حذف می‌کند: تا زمانی که لیست حتی 1px اسکرول شده باشد، حرکت Pull-to-Refresh فعال نمی‌شود. اگر تداخل رخ داد — nestedScrollingEnabled را در Android یا وجود GestureRecognizer سفارشی که لمس را رهگیری می‌کند بررسی کنید.

خلاصه

  • Pull-to-Refresh — الگوی به‌روزرسانی داده با کشیدن لیست به پایین، استاندارد شده توسط اپل و گوگل در تمام پلتفرم‌های موبایل.
  • UIRefreshControl در iOS — کنترل با target-action، tintColor، attributedTitle و endRefreshing() اجباری.
  • SwipeRefreshLayout در Android — کانتینر ViewGroup با setOnRefreshListener، setColorSchemeColors و isRefreshing.
  • Material Pull-to-Refresh (Android 12+) — API جدید با انیمیشن spring، توصیه شده برای پروژه‌های جدید.
  • SwiftUI .refreshable — اصلاح‌کننده اعلانی با async-closure، قابل دسترس از iOS 15.
  • flag isRefreshing از به‌روزرسانی مضاعف جلوگیری می‌کند — در هر دو پلتفرم اجباری است.
  • Pull-to-Refresh برای ناوبری در نظر گرفته نشده است — فقط برای به‌روزرسانی محتوا مطابق Material Design و Apple HIG.

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید