Pull-to-Refresh: основе, RefreshControl и UIRefreshControl

Аутор: IT Sectr Објављено: 2026-02-27 Време читања: 8 мин
Pull-to-Refresh — образац мобилног интерфејса у којем корисник прстом повлачи листу надоле, покрећући учитавање свежих података. Покрет је праћен визуелним индикатором — ротирајућим спинером или анимираном иконицом — који нестаје након завршетка учитавања. Према UX анализи Apple HIG, Pull-to-Refresh је постао стандардни механизам ажурирања садржаја у News фидовима, друштвеним мрежама и имејл клијентима од имплементације у Tweetie (2008) и касније стандардизације од стране Apple и Google.

Главне тачке

  • Pull-to-Refresh — покрет повлачења листе надоле ради ажурирања података, праћен визуелним индикатором учитавања.
  • У iOS-у се користи UIRefreshControl (iOS 6+), који се додаје у UITableViewController или UIScrollView преко својства refreshControl.
  • У Android-у се користи SwipeRefreshLayout (из Support Library) — ViewGroup омотач за 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), а касније су га стандардизовали Apple (iOS 6 — UIRefreshControl) и Google (Android Support Library — SwipeRefreshLayout).

Са техничке тачке гледишта, Pull-to-Refresh је комбинација панорамирања (праћење померања прста) и окидача при достизању прага. Корисник повлачи листу надоле, савладавајући отпор (резистивни overscroll), и након преласка прага (~80px у iOS, ~64dp у Android) покреће се анимација индикатора и асинхроно учитавање. Ако корисник отпусти прст пре прага — листа се враћа у почетни положај без ажурирања.

Према Material Design Guidelines, Pull-to-Refresh не треба користити за навигацију или пребацивање картица — његова једина намена је ажурирање података. У IT Sectr користимо Pull-to-Refresh у News фидовима, листама поруџбина и четовима, где је свежина података критична за корисничко искуство.

Pull-to-Refresh у iOS: UIRefreshControl

UIRefreshControl — стандардни iOS контролни елемент за Pull-to-Refresh, доступан од iOS 6. UIRefreshControl се додаје у UITableViewController преко својства refreshControl (iOS 10+) или као subview табеле у старијим верзијама. Садржи уграђени спинер са подесивом бојом (tintColor), атрибутом title и атрибуираним стрингом са натписом (нпр. „Ажурирање...").

UIRefreshControl ради преко механизма target-action: при активацији покрета позива се наведени метод (нпр. refresh(_:)). Унутар метода се извршава асинхроно учитавање података. Након завршетка позива се endRefreshing(), који сакрива индикатор уз анимацију. UIRefreshControl аутоматски управља осетљивошћу покрета — активира се само у горњем положају табеле (contentOffset.y <= 0).

Својство tintColor поставља боју спинерa. Атрибути 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) — величина спинерa. У 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), Google препоручује коришћење новог 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 ms, индикатор треба да се приказује најмање 500 ms да би корисник приметио ажурирање. 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 у UITableViewController са прилагођеном бојом спинерa и attributed title. Након учитавања података, индикатор се сакрива.

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 извршава корутину у животном циклусу фрагмента — при уништењу фрагмента, корутина се аутоматски отказује, спречавајући цурење меморије.

Пример 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-у проверавајте SwipeRefreshLayout.isRefreshing на почетку onRefresh(): ако је true — return. Ово гарантује један захтев по покрету.

Да ли Pull-to-Refresh долази у сукоб са померањем листе?

UIRefreshControl и SwipeRefreshLayout се активирају само у горњем положају листе (contentOffset == 0). Архитектура искључује сукоб: док је листа померена макар за 1px, Pull-to-Refresh покрет се не активира. Ако дође до сукоба — проверите nestedScrollingEnabled у Android-у или присуство прилагођених GestureRecognizer-а који пресрећу додире.

Резиме

  • Pull-to-Refresh — образац ажурирања података повлачењем листе надоле, стандардизован од стране Apple и Google на свим мобилним платформама.
  • UIRefreshControl у iOS — контрола са target-action, tintColor, attributedTitle и обавезним endRefreshing().
  • SwipeRefreshLayout у Android — ViewGroup контејнер са setOnRefreshListener, setColorSchemeColors и isRefreshing.
  • Material Pull-to-Refresh (Android 12+) — нови API са spring анимацијом, препоручен за нове пројекте.
  • SwiftUI .refreshable — декларативни модификатор са async затварачем, доступан од iOS 15.
  • isRefreshing flag спречава двоструко ажурирање — обавезан на обе платформе.
  • Pull-to-Refresh није намењен за навигацију — само за ажурирање садржаја у складу са Material Design и Apple HIG.

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође