setNeedsDisplay() — είναι μια μέθοδος στιγμιοτύπου UIView που επισημαίνει την προβολή ως χρήζουσα επανασχεδίασης. Σε αντίθεση με την άμεση κλήση draw(_:), το setNeedsDisplay() απλώς ορίζει μια σημαία dirty για την προβολή και επιστρέφει αμέσως τον έλεγχο. Το σύστημα επεξεργάζεται αυτό το αίτημα στον πλησιέστερο κύκλο απόδοσης, επιτρέποντας τον συνδυασμό πολλαπλών κλήσεων σε μία λειτουργία σχεδίασης. Σύμφωνα με το Apple Documentation (2025), η καθυστερημένη ανασχεδίαση μέσω setNeedsDisplay() είναι ο μόνος σωστός τρόπος ενημέρωσης του περιεχομένου μιας προσαρμοσμένης UIView.
Κύρια σημεία
setNeedsDisplay() — είναι μια μέθοδος UIView που σηματοδοτεί στο σύστημα ότι το περιεχόμενο της προβολής είναι παρωχημένο και απαιτεί εκ νέου απόδοση. Η μέθοδος δεν δέχεται παραμέτρους και επιστρέφει void. Μετά την κλήση, η προβολή επισημαίνεται ως dirty, και στον πλησιέστερο κύκλο σχεδίασης το σύστημα καλεί draw(_:) για αυτήν την προβολή.
Το βασικό χαρακτηριστικό είναι η ασυγχρονικότητα. Πολλαπλές διαδοχικές κλήσεις setNeedsDisplay() για την ίδια προβολή δεν θα οδηγήσουν σε πολλαπλές κλήσεις draw(_:) — το σύστημα τις συνδυάζει σε μία λειτουργία. Αυτό είναι κρίσιμο για την απόδοση: εάν μια ιδιότητα προβολής αλλάξει πολλές φορές ανά καρέ, το draw(_:) καλείται μόνο μία φορά.
Ο μηχανισμός καθυστερημένης ανασχεδίασης βασίζεται στον κύκλο απόδοσης UIKit. Όταν καλείται το setNeedsDisplay(), το σύστημα ορίζει την εσωτερική σημαία layer.needsDisplay σε true. Στο τέλος του τρέχοντος run loop, το σύστημα διατρέχει όλα τα dirty επίπεδα και καλεί draw(_:) για το καθένα.
Το UIKit run loop περιλαμβάνει τη φάση update, κατά την οποία επεξεργάζονται οι dirty προβολές. Σε αυτή τη φάση, καλείται layoutSubviews() εάν χρειάζεται, στη συνέχεια draw(_:) για όλες τις επισημασμένες προβολές. Μετά την ολοκλήρωση της σχεδίασης, το αποτέλεσμα μεταφέρεται στο GPU μέσω Core Animation για σύνθεση.
class ProgressView: UIView {
var progress: CGFloat = 0 {
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let ctx = UIGraphicsGetCurrentContext() else { return }
// Φόντο
ctx.setFillColor(UIColor.lightGray.cgColor)
ctx.fill(rect)
// Γραμμή προόδου
let fillRect = CGRect(x: rect.minX, y: rect.minY,
width: rect.width * progress, height: rect.height)
ctx.setFillColor(UIColor.systemGreen.cgColor)
ctx.fill(fillRect)
}
}
Σε αυτό το παράδειγμα, το progressView ενημερώνει την progress, και κάθε setter καλεί αυτόματα το setNeedsDisplay(). Το σύστημα θα περιμένει το τέλος του run loop και θα καλέσει draw(_:) με την τρέχουσα τιμή progress. Εάν η progress αλλάξει τρεις φορές σε ένα καρέ, το draw(_:) θα εκτελεστεί μόνο μία φορά — με την τελευταία τιμή.
Η μέθοδος setNeedsDisplay(_:) δέχεται μια παράμετρο rect τύπου CGRect, που περιορίζει την περιοχή ανασχεδίασης. Αυτό επιτρέπει την ανασχεδίαση μόνο του τροποποιημένου τμήματος της προβολής, όχι ολόκληρης. Η διαφορά απόδοσης γίνεται αισθητή όταν εργάζεστε με μεγάλες προβολές όπου οι αλλαγές επηρεάζουν μια μικρή περιοχή.
| Χαρακτηριστικό | setNeedsDisplay() | setNeedsDisplay(_:) |
|---|---|---|
| Παράμετρος | Όχι | CGRect — περιοχή ανασχεδίασης |
| Περιοχή ανασχεδίασης | Ολόκληρο το bounds της προβολής | Μόνο το καθορισμένο ορθογώνιο |
| Απόδοση | Χαμηλότερη σε μεγάλες προβολές | Υψηλότερη σε μερικές αλλαγές |
| Παράμετρος rect στο draw(_:) | Ολόκληρο bounds | Καθορισμένο ορθογώνιο |
| Πότε να χρησιμοποιείται | Αλλαγή ολόκληρου περιεχομένου | Σημειακές αλλαγές (δρομέας, highlight) |
Για σύνθετα γραφικά, συνιστάται πρώτα η σχεδίαση ολόκληρης της σκηνής σε ένα offscreen περιβάλλον, στη συνέχεια η κλήση setNeedsDisplay(_:) μόνο για την τροποποιημένη περιοχή. Αυτή είναι μια τυπική τεχνική σε εφαρμογές σχεδίασης και επεξεργαστές γραφικών στο iOS.
displayIfNeeded() — είναι μια σύγχρονη μέθοδος που ξεκινά αμέσως την ανασχεδίαση όλων των dirty προβολών στην ιεραρχία, χωρίς να περιμένει το τέλος του run loop. Σε αντίθεση με το setNeedsDisplay() που απλώς ορίζει μια σημαία, το displayIfNeeded() εξαναγκάζει την άμεση εκτέλεση του draw(_:).
Η Apple συνιστά τη χρήση του displayIfNeeded() μόνο σε ακραίες περιπτώσεις, όταν το αποτέλεσμα σχεδίασης χρειάζεται άμεσα — για παράδειγμα, πριν από τη δημιουργία στιγμιότυπου οθόνης ή κατά τη σύγχρονη δημιουργία εικόνας για UIGraphicsImageRenderer. Σε κανονικές περιπτώσεις, το ασύγχρονο μοντέλο μέσω setNeedsDisplay() προτιμάται επειδή δεν μπλοκάρει το κύριο νήμα.
// Ασύγχρονο — συνιστάται
someView.setNeedsDisplay()
// Σύγχρονο — μόνο όταν χρειάζεται
someView.setNeedsDisplay()
someView.displayIfNeeded() // draw(_:) κλήθηκε άμεσα
Η κλήση του displayIfNeeded() χωρίς προηγούμενο setNeedsDisplay() δεν θα έχει αποτέλεσμα — η μέθοδος επεξεργάζεται μόνο προβολές που έχουν ήδη επισημανθεί ως dirty. Εάν καμία προβολή δεν απαιτεί ανασχεδίαση, το displayIfNeeded() ολοκληρώνεται χωρίς να καλέσει draw(_:).
Οι συχνές κλήσεις setNeedsDisplay() μπορούν να μειώσουν την απόδοση, ιδιαίτερα κατά τη διάρκεια κινούμενων σχεδίων. Ας εξετάσουμε τις κύριες στρατηγικές βελτιστοποίησης.
Αντί να καλείτε setNeedsDisplay() σε κάθε αλλαγή μιας μεμονωμένης ιδιότητας, συσσωρεύστε τις αλλαγές και καλέστε τη μέθοδο μία φορά μετά την εφαρμογή όλων των ενημερώσεων. Για παράδειγμα, κατά την αλλαγή χρώματος, μεγέθους και θέσης ενός στοιχείου — καλέστε setNeedsDisplay() μία φορά μετά τον ορισμό όλων των ιδιοτήτων.
Εάν μια ιδιότητα μπορεί να κινηθεί μέσω CALayer (backgroundColor, opacity, position, transform), χρησιμοποιήστε Core Animation αντί για draw(_:). Οι ιδιότητες CALayer ενημερώνονται στο GPU χωρίς κλήση draw(_:), παρέχοντας 60 FPS χωρίς φόρτο CPU.
Για ομαδοποίηση πολλαπλών αλλαγών σε ένα πέρασμα απόδοσης, χρησιμοποιήστε CATransaction. Αυτό επιτρέπει τον συνδυασμό πολλαπλών setNeedsDisplay() σε μία λειτουργία draw(_:) στο τέλος της συναλλαγής.
CATransaction.begin()
CATransaction.setDisableActions(true)
view1.setNeedsDisplay()
view2.setNeedsDisplay()
view3.setNeedsDisplay()
CATransaction.commit()
Μην κάνετε κατάχρηση του displayIfNeeded() — η σύγχρονη ανασχεδίαση μπλοκάρει το κύριο νήμα και μπορεί να προκαλέσει πτώση καρέ. Χρησιμοποιήστε το μόνο για λειτουργίες όπου το αποτέλεσμα σχεδίασης είναι κρίσιμο πριν από την ολοκλήρωση του τρέχοντος run loop.
Συχνές Ερωτήσεις
Όχι, η άμεση κλήση draw(_:) απαγορεύεται. Το setNeedsDisplay() επισημαίνει σωστά την προβολή ως dirty, και το σύστημα καλεί draw(_:) στην κατάλληλη στιγμή του κύκλου απόδοσης. Η άμεση κλήση draw(_:) αγνοεί τον μηχανισμό προσωρινής αποθήκευσης και μπορεί να οδηγήσει σε ασυνεπή κατάσταση.
Πολλαπλές κλήσεις setNeedsDisplay() για την ίδια προβολή συνδυάζονται από το σύστημα σε μία κλήση draw(_:). Αυτό συμβαίνει επειδή η μέθοδος απλώς ορίζει το dirty flag, το οποίο επαναφέρεται μόνο μετά την πραγματική εκτέλεση του draw(_:) στο τέλος του run loop.
Το setNeedsDisplay() ζητά ανασχεδίαση του περιεχομένου της προβολής (κλήση draw(_:)). Το setNeedsLayout() ζητά αναδιάταξη των subview (κλήση layoutSubviews()). Το layoutSubviews() μπορεί να οδηγήσει σε αλλαγή μεγέθους, που με τη σειρά της μπορεί να προκαλέσει ανασχεδίαση.
Το setNeedsDisplay(_:) πρέπει να χρησιμοποιείται σε σημειακές αλλαγές: κίνηση δρομέα, αλλαγή χρώματος επιλεγμένης περιοχής, ενημέρωση τμήματος γραφήματος. Αυτό περιορίζει την περιοχή ανασχεδίασης και αυξάνει την απόδοση σε σύγκριση με την πλήρη ανασχεδίαση ολόκληρης της προβολής.
Από προεπιλογή, η αλλαγή frame ή bounds δεν καλεί setNeedsDisplay(). Το σύστημα απλώς μετακινεί την προβολή. Εάν απαιτείται ανασχεδίαση κατά την αλλαγή μεγέθους, ορίστε την ιδιότητα contentMode στην τιμή .redraw — τότε το UIKit θα καλεί αυτόματα setNeedsDisplay() σε κάθε αλλαγή bounds.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης