نکات کلیدی
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 در فیدهای خبری، لیست سفارشات و چتها استفاده میکنیم، جایی که تازگی دادهها برای تجربه کاربری حیاتی است.
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 نشان میدهیم — این اعتماد کاربران به دادهها را افزایش میدهد.
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 تضمین میکند.
از 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 را کاهش میدهد. بیایید آنها و راههای جلوگیری را بررسی کنیم.
در IT Sectr ما پس از کشف درخواستهای تکراری در لاگهای سرور تست، بررسی isRefreshing را در هر پروژه اضافه کردیم — معلوم شد کاربران با انگشتان سریع تا 3 بار متوالی بهروزرسانی را شروع میکردند.
Pull-to-Refresh را با رنگ چرخنده سفارشی و attributed title به UITableViewController اضافه میکند. پس از بارگذاری دادهها، نشانگر مخفی میشود.
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 خواهد بود.
RecyclerView را در SwipeRefreshLayout با رنگهای نشانگر سفارشی میپیچد. onRefresh بارگذاری را شروع میکند و پس از اتمام نشانگر را مخفی میکند.
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، کوروتین به طور خودکار لغو میشود و از نشت حافظه جلوگیری میکند.
SwiftUI مدرن اصلاحکننده .refreshable را ارائه میدهد که به طور خودکار Pull-to-Refresh را به List یا 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()
}
}
}
اصلاحکننده .refreshable یک async-closure میپذیرد که هنگام Pull-to-Refresh اجرا میشود. SwiftUI به طور خودکار نشانگر بهروزرسانی را نشان میدهد و مخفی میکند، شرایط مسابقه را مدیریت میکند (تا اتمام بارگذاری فعلی، بارگذاری مجدد را شروع نمیکند) و انیمیشن را با پلتفرم تطبیق میدهد. برای iOS 15+ این روش preferred برای پیادهسازی 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. این یک درخواست برای هر حرکت را تضمین میکند.
UIRefreshControl و SwipeRefreshLayout فقط در موقعیت بالای لیست (contentOffset == 0) فعال میشوند. معماری تداخل را حذف میکند: تا زمانی که لیست حتی 1px اسکرول شده باشد، حرکت Pull-to-Refresh فعال نمیشود. اگر تداخل رخ داد — nestedScrollingEnabled را در Android یا وجود GestureRecognizer سفارشی که لمس را رهگیری میکند بررسی کنید.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید