Pull-to-Refresh: grunder, RefreshControl och UIRefreshControl

Författare: IT Sectr Publicerad: 2026-02-27 Lästid: 8 min
Pull-to-Refresh — ett mobilt gränssnittsmönster där användaren drar ner listan med fingret och initierar laddning av färsk data. Gesten åtföljs av en visuell indikator — en snurrande spinner eller animerad ikon — som försvinner efter att laddningen slutförts. Enligt UX-analys från Apple HIG har Pull-to-Refresh blivit standardmekanismen för att uppdatera innehåll i nyhetsflöden, sociala nätverk och e-postklienter sedan implementeringen i Tweetie (2008) och efterföljande standardisering av Apple och Google.

Huvudpunkter

  • Pull-to-Refresh — gest att dra ner listan för att uppdatera data, åtföljd av en visuell laddningsindikator.
  • I iOS används UIRefreshControl (iOS 6+), som läggs till i UITableViewController eller UIScrollView via egenskapen refreshControl.
  • I Android används SwipeRefreshLayout (från Support Library) — en ViewGroup-wrapper för RecyclerView eller NestedScrollView.
  • Båda API:erna stöder anpassning av färger, indikatorer och återanrop via listener (iOS: UIRefreshControl.target-action, Android: setOnRefreshListener).
  • Pull-to-Refresh blockeras automatiskt när listan inte är i topposition — konflikt med rullning är arkitektoniskt utesluten.

Vad är Pull-to-Refresh?

Pull-to-Refresh — ett användargränssnittsmönster där användaren drar (pull down) en lista eller ett rullningsbart område nedåt för att uppdatera innehållet. Visuellt åtföljs gesten av en laddningsindikator (spinner) som visas högst upp på skärmen och försvinner efter att data tagits emot. Mönstret populariserades av appen Tweetie för iPhone (2008) och standardiserades därefter av Apple (iOS 6 — UIRefreshControl) och Google (Android Support Library — SwipeRefreshLayout).

Tekniskt sett är Pull-to-Refresh en kombination av panorering (spårning av fingerförflyttning) och utlösare vid tröskelvärde. Användaren drar ner listan, övervinner motstånd (resistiv overscroll), och efter att tröskeln överskridits (~80px i iOS, ~64dp i Android) startar indikatoranimationen och asynkron laddning. Om användaren släpper fingret före tröskeln — återgår listan till startpositionen utan uppdatering.

Enligt Material Design Guidelines ska Pull-to-Refresh inte användas för navigering eller växling av flikar — dess enda syfte är att uppdatera data. Hos IT Sectr använder vi Pull-to-Refresh i nyhetsflöden, beställningslistor och chattar där färsk data är avgörande för användarupplevelsen.

Pull-to-Refresh i iOS: UIRefreshControl

UIRefreshControl — standard iOS-kontrollelement för Pull-to-Refresh, tillgängligt sedan iOS 6. UIRefreshControl läggs till i UITableViewController via egenskapen refreshControl (iOS 10+) eller som en subview av tabellen i äldre versioner. Den innehåller en inbyggd spinner med konfigurerbar färg (tintColor), title-attribut och en attribuerad sträng med etikett (till exempel “Uppdaterar...”).

UIRefreshControl fungerar via target-action-mekanismen: när gesten aktiveras anropas den angivna metoden (t.ex. refresh(_:)). Inuti metoden utförs asynkron dataladdning. Efter slutförande anropas endRefreshing(), som döljer indikatorn med animation. UIRefreshControl hanterar automatiskt gestens känslighet — den aktiveras endast i tabellens övre position (contentOffset.y <= 0).

Egenskapen tintColor ställer in spinnerfärgen. Title-attributen gör det möjligt att visa texten “Uppdaterad för 2 minuter sedan” efter slutförande. Från och med iOS 10 stöder UIRefreshControl anpassade animationer via UIActivityIndicatorView eller beständiga anpassade vyer. Hos IT Sectr anpassar vi tintColor till varumärket och visar tiden för senaste uppdatering via attributedTitle — detta ökar användarnas förtroende för data.

Pull-to-Refresh i Android: SwipeRefreshLayout

SwipeRefreshLayout — en ViewGroup från Android Support Library (androidx.swiperefreshlayout) som omsluter rullningsbart innehåll (RecyclerView, NestedScrollView, ListView) och lägger till Pull-to-Refresh-funktionalitet. Till skillnad från UIRefreshControl (som är ett kontrollelement, inte en behållare), är SwipeRefreshLayout en behållare som fångar upp barnets beröringshändelser och startar uppdateringsindikatorn när tröskeln överskrids.

SwipeRefreshLayout använder Material Designs cirkulära förloppsindikator med färgkonfiguration via setColorSchemeColors(). Metoden setOnRefreshListener ställer in återanropet onRefresh(), där asynkron laddning utförs. Efter slutförande anropas setRefreshing(false) för att dölja indikatorn. Viktigt: setRefreshing(true) anropar onRefresh() igen — använd därför en flagga eller post-metod för programmatisk start av uppdatering.

Egenskapen setProgressBackgroundColorSchemeResource ändrar indikatorns bakgrund. setSize(SwipeRefreshLayout.LARGE) — spinnerstorlek. I XML-layout omsluter SwipeRefreshLayout RecyclerView: swipe_refresh_layout → recycler_view. Enligt Google I/O 2024 används SwipeRefreshLayout i 85% av Android-appar med innehållsflöden. Hos IT Sectr omsluter vi alla skärmar med asynkront laddade listor i SwipeRefreshLayout — detta ger en enhetlig UX på alla Android-versioner.

Material Pull-to-Refresh (Android 12+)

Från Android 12 (Material You) rekommenderar Google att använda den nya Material Pull-to-Refresh från biblioteket material-1.6.0+ (androidx.compose.material3.pulltorefresh för Compose). Det nya API:et använder en animerad indikator med stöd för spring-animation och adaptiv färg baserad på bakgrundsbild. SwipeRefreshLayout förblir kompatibel för versioner under Android 12.

Bästa praxis och vanliga misstag

Pull-to-Refresh — ett mönster som är enkelt att implementera, men innehåller flera vanliga misstag som försämrar UX. Låt oss granska dem och sätt att förebygga.

  • Dubbel uppdatering — användaren kan dra i listan flera gånger innan laddningen är klar. Lösning: ställ in flaggan isRefreshing vid start och kontrollera den i onRefresh(). I iOS anropas endRefreshing() först efter slutförande; blockering av gesten i UIRefreshControl är inbyggd.
  • Brist på återkoppling — laddningsindikatorn ska visas först efter att användaren har passerat tröskeln. Visa inte indikatorn omedelbart vid beröring — det är förvirrande. iOS och Android gör detta automatiskt.
  • Ignorera uppdateringstid — om data uppdateras på 200 ms bör indikatorn visas i minst 500 ms så att användaren märker uppdateringen. UIRefreshControl har en minsta animationstid; i Android använder du Handler.postDelayed för minsta visningstid.
  • Konflikt med tangentbord — med öppet tangentbord kan Pull-to-Refresh aktiveras av misstag. Dölj tangentbordet i början av gesten via view.endEditing(true) i iOS och InputMethodManager.hideSoftInputFromWindow() i Android.
  • Användning inte för uppdatering — använd inte Pull-to-Refresh för navigering (flikbyte, tillbaka). Det bryter mot båda plattformarnas HIG och desorienterar användare.

Hos IT Sectr lade vi till isRefreshing-kontroll i varje projekt efter att vi upptäckte dubblettförfrågningar i testserverns loggar — det visade sig att användare med snabba fingrar utlöste uppdatering upp till 3 gånger i rad.

Kodexempel i Swift och Kotlin

Exempel 1: UIRefreshControl i iOS (Swift)

Lägger till Pull-to-Refresh i UITableViewController med anpassad spinnerfärg och attributed title. Efter att data laddats döljs indikatorn.

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: “Dra för att uppdatera”
        )
        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()
        }
    }
}

Egenskapen tableView.refreshControl (iOS 10+) ställer in UIRefreshControl. addTarget med händelsen .valueChanged utlöses när gesten aktiveras. endRefreshing() är obligatorisk — utan den snurrar indikatorn oändligt. Asynkron laddning simuleras via DispatchQueue.main.asyncAfter — i ett riktigt projekt skulle det vara URLSession eller async/await.

Exempel 2: SwipeRefreshLayout i Android (Kotlin)

Omsluter RecyclerView i SwipeRefreshLayout med anpassade indikatorfärger. onRefresh startar laddning och döljer indikatorn efter slutförande.

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 ställer in färgerna på den snurrande Material Design-indikatorn. isRefreshing = false anropas obligatoriskt i finally för att dölja indikatorn även vid laddningsfel. ViewModelScope.launch kör koroutinen i fragmentets livscykel — när fragmentet förstörs avbryts koroutinen automatiskt, vilket förhindrar minnesläckor.

Exempel 3: SwiftUI .refreshable (iOS 15+)

Modernt SwiftUI tillhandahåller modifieraren .refreshable, som automatiskt lägger till Pull-to-Refresh i List eller 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()
        }
    }
}

Modifieraren .refreshable tar emot en async-closure som körs vid Pull-to-Refresh. SwiftUI visar och döljer automatiskt uppdateringsindikatorn, hanterar race condition (startar inte om laddning förrän den aktuella är slutförd) och anpassar animationen till plattformen. För iOS 15+ är detta det preferred sättet att implementera Pull-to-Refresh i SwiftUI.

Vanliga frågor

Fungerar Pull-to-Refresh i SwiftUI?

Ja, SwiftUI tillhandahåller modifieraren .refreshable för List eller ScrollView, tillgänglig sedan iOS 15. Inuti closuren körs asynkron dataladdningskod. SwiftUI hanterar automatiskt uppdateringsindikatorn och blockerar omstarter tills den aktuella laddningen är slutförd — detta är standard recommended tillvägagångssätt för nya projekt.

Hur förhindrar jag dubbel uppdatering?

Använd flaggan isRefreshing: ställ in true vid laddningsstart och false efter slutförande. I iOS blockerar UIRefreshControl automatiskt omanrop tills endRefreshing() har anropats. I Android kontrollera SwipeRefreshLayout.isRefreshing i början av onRefresh(): om true — return. Detta garanterar en förfrågan per gest.

Kommer Pull-to-Refresh i konflikt med rullning av listan?

UIRefreshControl och SwipeRefreshLayout aktiveras endast i listans övre position (contentOffset == 0). Arkitekturen utesluter konflikt: så länge listan är rullad ens 1px aktiveras inte Pull-to-Refresh-gesten. Om konflikt uppstår — kontrollera nestedScrollingEnabled i Android eller förekomsten av anpassade GestureRecognizer som fångar upp beröringar.

Sammanfattning

  • Pull-to-Refresh — mönster för datauppdatering genom att dra ner listan, standardiserat av Apple och Google på alla mobila plattformar.
  • UIRefreshControl i iOS — kontrollelement med target-action, tintColor, attributedTitle och obligatorisk endRefreshing().
  • SwipeRefreshLayout i Android — ViewGroup-behållare med setOnRefreshListener, setColorSchemeColors och isRefreshing.
  • Material Pull-to-Refresh (Android 12+) — nytt API med spring-animation, rekommenderat för nya projekt.
  • SwiftUI .refreshable — deklarativ modifierare med async-closure, tillgänglig sedan iOS 15.
  • Flaggan isRefreshing förhindrar dubbel uppdatering — obligatorisk på båda plattformarna.
  • Pull-to-Refresh är inte avsett för navigering — endast för innehållsuppdatering enligt Material Design och Apple HIG.

Vi utvecklar en mobil applikation nyckelfärdigt

IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.

Diskutera projektet

Läs också