List — είναι ένα View κοντέινερ στο SwiftUI για την εμφάνιση δεδομένων με τη μορφή κάθετα κυλιόμενης λίστας, ανάλογο του UITableView στο UIKit. Σύμφωνα με το Apple Developer Documentation, 2024, το List υποστηρίζει στατικές και δυναμικές ενότητες, ενέργειες σύρσης, μεταφορά γραμμών και pull-to-refresh. Σε αντίθεση με το UITableView, το List χρησιμοποιεί δηλωτικό API βασισμένο στο SwiftUI και το ForEach, διαχειριζόμενο αυτόματα την επαναχρησιμοποίηση κελιών και την απόδοση για μεγάλο αριθμό γραμμών.
Βασικά Σημεία
List — είναι ένα View που εμφανίζει μια ακολουθία στοιχείων σε μια κάθετα κυλιόμενη λίστα. Εμφανίστηκε στο iOS 13 μαζί με το SwiftUI και είναι ο κύριος τρόπος εμφάνισης λιστών δεδομένων, αντικαθιστώντας το UITableView από το UIKit. Το List διαχειρίζεται αυτόματα την επαναχρησιμοποίηση κελιών, την κύλιση και την απόδοση.
Το List χρησιμοποιεί lazy-loading: τα κελιά δημιουργούνται κατά την κύλιση, όχι όλα ταυτόχρονα. Αυτό το διακρίνει από το VStack με ForEach μέσα στο ScrollView, όπου όλα τα κελιά δημιουργούνται κατά την απόδοση. Το List παρέχει επίσης ενσωματωμένη υποστήριξη για ενέργειες σύρσης, pull-to-refresh, επεξεργασία (διαγραφή/μεταφορά) και επιλογή γραμμών.
Σύμφωνα με το Apple WWDC 2021 (Session 10072), το List στο iOS 15+ έλαβε σημαντικές βελτιώσεις απόδοσης χάρη σε έναν νέο μηχανισμό diffing σε επίπεδο συλλογών. Αυτό κατέστησε το List πιο αποδοτικό κατά την ενημέρωση δεδομένων, ειδικά για λίστες με εκατοντάδες γραμμές.
Οι προγραμματιστές συχνά επιλέγουν μεταξύ List και ScrollView με VStack για την εμφάνιση ενός συνόλου Views. Η βασική διαφορά: το List χρησιμοποιεί επαναχρησιμοποίηση κελιών (όπως το UITableView), ενώ το ScrollView + VStack δημιουργεί όλα τα Views ταυτόχρονα. Για λίστες με σταθερό αριθμό στοιχείων (έως 20), η διαφορά είναι αμελητέα. Για δυναμικές λίστες με 50+ γραμμές, το List είναι προτιμότερο από πλευράς απόδοσης.
Στατική List — είναι μια λίστα με σταθερό αριθμό γραμμών, που ορίζονται απευθείας στο σώμα του List. Χρησιμοποιείται για μενού, ρυθμίσεις και φόρμες με γνωστό σύνολο στοιχείων. Κάθε γραμμή δηλώνεται ρητά, χωρίς βρόχους ή ForEach.
// Στατική λίστα (για μενού και ρυθμίσεις)
List {
Text("Προφίλ")
Text("Ρυθμίσεις")
Text("Σχετικά")
}
// Δυναμική λίστα (για δεδομένα)
struct UserList: View {
let users: [User]
var body: some View {
List(users) { user in
HStack {
Text(user.name)
Text(user.role)
.foregroundColor(.secondary)
}
}
}
}
Δυναμική List χρησιμοποιεί τον αρχικοποιητή List(data:rowContent:) ή το ForWithin στο σώμα του List. Η πρώτη επιλογή είναι βολική όταν κάθε γραμμή αντιστοιχεί σε ένα στοιχείο δεδομένων. Η δεύτερη — όταν υπάρχουν ενότητες ή πρόσθετα στοιχεία μεταξύ των δεδομένων.
Αναγνώριση (Identifiable): Για δυναμικές λίστες, τα στοιχεία δεδομένων πρέπει να συμμορφώνονται με το πρωτόκολλο Identifiable ή στην πλειάδα data:id πρέπει να καθοριστεί KeyPath σε ένα μοναδικό αναγνωριστικό. Το SwiftUI χρησιμοποιεί αναγνωριστικά για την παρακολούθηση αλλαγών: προσθήκη, διαγραφή και μετακίνηση γραμμών.
Section — είναι ένα View για την ομαδοποίηση γραμμών στο List με επικεφαλίδα και προαιρετικό υποσέλιδο. Το Section δέχεται header και footer ως ViewBuilder, επιτρέποντας τη χρήση όχι μόνο κειμένου, αλλά και προσαρμοσμένων Views για τις επικεφαλίδες ενοτήτων.
struct SettingsView: View {
var body: some View {
List {
Section(header: Text("Λογαριασμός")) {
Text("Όνομα")
Text("Email")
}
Section(header: Text("Ειδοποιήσεις")) {
Toggle("Push", isOn: $pushEnabled)
Toggle("Email", isOn: $emailEnabled)
}
}
.listStyle(.insetGrouped)
}
}
// Δυναμικές ενότητες με ForEach
List {
ForEach(groupedData.keys.sorted(), id: \.self) { key in
Section(header: Text(key)) {
ForEach(groupedData[key]!) { item in
Text(item.title)
}
}
}
}
Στυλ List: Το SwiftUI παρέχει πολλά ενσωματωμένα στυλ μέσω του τροποποιητή .listStyle(). .insetGrouped — τυπικό για iOS Settings, .plain — μινιμαλιστικό, .inset — με εσοχές, .sidebar — για Sidebar στο iPad.
Σύμφωνα με το SwiftUI Cookbook (2024), το Section με δυναμικές ενότητες και ForEach μέσα είναι ένα τυπικό μοτίβο για ομαδοποίηση δεδομένων σε εφαρμογές με πολύπλοκη δομή. Βασικός κανόνας: μην τοποθετείτε Section μέσα σε Section και μη χρησιμοποιείτε τον αρχικοποιητή List(data:) μαζί με Section — χρησιμοποιήστε το ForEach μέσα στο σώμα του List.
.swipeActions(edge:allowsFullSwipe:content:) — τροποποιητής για iOS 15+ που προσθέτει ενέργειες σύρσης στις γραμμές του List. Επιτρέπει την εμφάνιση κουμπιών κατά το σύρσιμο προς τα αριστερά (προεπιλογή) ή προς τα δεξιά, με διαφορετικά χρώματα και ρόλους (destructive, cancel).
struct TaskList: View {
@Binding var tasks: [Task]
var body: some View {
List {
ForEach($tasks) { $task in
Text(task.title)
.swipeActions(edge: .trailing) {
Button("Διαγραφή", role: .destructive) {
tasks.removeAll { $0.id == task.id }
}
}
.swipeActions(edge: .leading) {
Button(task.isDone ? "Αναίρεση" : "Ολοκληρώθηκε") {
task.isDone.toggle()
}
.tint(.green)
}
}
}
.refreshable {
// Ασύγχρονη φόρτωση δεδομένων
await loadTasks()
}
}
}
.refreshable — τροποποιητής για iOS 15+ που προσθέτει pull-to-refresh. Δέχεται ένα ασύγχρονο closure (async) που εκτελείται όταν ο χρήστης τραβάει τη λίστα προς τα κάτω. Το SwiftUI εμφανίζει αυτόματα την ένδειξη φόρτωσης. Μετά την ολοκλήρωση της λειτουργίας, η ένδειξη κρύβεται.
.onDelete και .onMove — τροποποιητές για iOS 13+ που προσθέτουν υποστήριξη για διαγραφή και μετακίνηση γραμμών. Για να τα χρησιμοποιήσετε, τυλίξτε τα δεδομένα σε ForEach με Binding ή μεταβιβάστε closures μέσω .onDelete(perform:) στο List ή στο ForEach.
Η απόδοση του List εξαρτάται από τον αριθμό των γραμμών, την πολυπλοκότητα κάθε κελιού και τη συχνότητα ενημερώσεων δεδομένων. Το SwiftUI χρησιμοποιεί lazy-loading και επαναχρησιμοποίηση κελιών (παρόμοια με το UITableView.dequeueReusableCell), αλλά πρόσθετες βελτιστοποιήσεις μπορεί να χρειαστούν για λίστες με 500+ γραμμές.
| Βελτιστοποίηση | Περιγραφή | Έκδοση iOS |
|---|---|---|
| Identifiable | Μοναδικό ID για κάθε στοιχείο | iOS 13+ |
| EquatableView | Αποφεύγει την επαναπόδοση σε ίσα δεδομένα | iOS 13+ |
| id(_:) | Αναγκαστική αναδημιουργία View κατά αλλαγή ID | iOS 13+ |
| .equatable() | Αυστηρή σύγκριση βάσει Equatable | iOS 15+ |
| Diffable data | Αυτόματο diff σε αλλαγές | iOS 15+ |
Πρόβλημα 1: συχνές ενημερώσεις. Εάν τα δεδομένα στη λίστα ενημερώνονται συχνά (π.χ. κάθε δευτερόλεπτο), το List μπορεί να επαναποδίδει τα ορατά κελιά σε κάθε αλλαγή κατάστασης. Λύση: χρησιμοποιήστε δομές (value types) για δεδομένα — το SwiftUI τα συγκρίνει βάσει τιμής και επαναποδίδει μόνο τις αλλαγμένες γραμμές.
Πρόβλημα 2: βαριά κελιά. Εάν κάθε γραμμή περιέχει πολύπλοκη ιεραρχία Views, εικόνες και κινούμενα σχέδια, η κύλιση μπορεί να είναι αργή. Λύση: μεταφέρετε τα κελιά σε ξεχωριστά Views, χρησιμοποιήστε EquatableView για να αποτρέψετε περιττές επαναποδόσεις. Σύμφωνα με το SwiftUI Lab (2024), η διάσπαση μιας πολύπλοκης γραμμής σε υποσυστατικά μειώνει τον χρόνο απόδοσης κατά 30–50%.
Πρόβλημα 3: μεγάλος αριθμός γραμμών. Σε 1000+ γραμμές, το List εξακολουθεί να λειτουργεί αποδοτικά χάρη στο lazy-loading, αλλά η αρχική φόρτωση μπορεί να επιβραδυνθεί λόγω υπολογισμού διάταξης. Λύση: χρησιμοποιήστε LazyVStack μόνο για λίστες με ομοιογενείς γραμμές όπου δεν χρειάζονται οι λειτουργίες του List (σύρσιμο, ενότητες). Για πλήρως λειτουργικές λίστες, το List παραμένει η καλύτερη επιλογή.
Συχνές Ερωτήσεις
List — ένα View κοντέινερ για την εμφάνιση κυλιόμενης λίστας δεδομένων στο SwiftUI. Ανάλογο του UITableView στο UIKit με δηλωτικό API. Υποστηρίζει ενότητες, ενέργειες σύρσης, pull-to-refresh, επεξεργασία και προσαρμογή μέσω .listStyle().
Το List χρησιμοποιεί lazy-loading και επαναχρησιμοποίηση κελιών — τα κελιά δημιουργούνται κατά την κύλιση. Το ScrollView + VStack δημιουργεί όλα τα Views ταυτόχρονα. Για λίστες με 50+ γραμμές, το List είναι προτιμότερο. Για σταθερά μικρά σύνολα (έως 20 στοιχεία), η διαφορά είναι αμελητέα.
Χρησιμοποιήστε τον τροποποιητή .refreshable (iOS 15+). Μεταβιβάστε ένα ασύγχρονο closure με λογική ενημέρωσης δεδομένων. Το SwiftUI εμφανίζει αυτόματα την ένδειξη φόρτωσης και την κρύβει μετά την ολοκλήρωση της ασύγχρονης λειτουργίας.
Χρησιμοποιήστε Section View με επικεφαλίδα και προαιρετικό υποσέλιδο. Τοποθετήστε τις γραμμές λίστας μέσα στο Section. Για δυναμικές ενότητες, χρησιμοποιήστε ForEach με groupedData. Το στυλ λίστας διαμορφώνεται μέσω .listStyle(.insetGrouped) για εμφάνιση παρόμοια με iOS.
Χρησιμοποιήστε δομές (value types) για δεδομένα, μεταφέρετε πολύπλοκα κελιά σε ξεχωριστά Views με EquatableView, αποφύγετε συχνές ενημερώσεις κατάστασης σε κάθε γραμμή. Για λίστες με 1000+ γραμμές, εξετάστε το LazyVStack εάν δεν χρειάζονται οι λειτουργίες του List.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης