Huvudpunkter
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.
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.
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.
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.
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.
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.
Lägger till Pull-to-Refresh i UITableViewController med anpassad spinnerfärg och attributed title. Efter att data laddats döljs indikatorn.
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.
Omsluter RecyclerView i SwipeRefreshLayout med anpassade indikatorfärger. onRefresh startar laddning och döljer indikatorn efter slutförande.
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.
Modernt SwiftUI tillhandahåller modifieraren .refreshable, som automatiskt lägger till Pull-to-Refresh i List eller 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()
}
}
}
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
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.
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.
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
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.
Läs också