Custom UIView — είναι μια υποκλάση του στοιχείου UIKit UIView, στην οποία ο προγραμματιστής παρακάμπτει μεθόδους του κύκλου ζωής και της σχεδίασης για τη δημιουργία μοναδικών οπτικών στοιχείων. Τα τυπικά UIView (UIButton, UILabel, UIImageView) καλύπτουν τα περισσότερα τυπικά σενάρια, αλλά όταν απαιτείται μη τυπικά γραφικά, κινούμενα σχέδια ή διαδραστικότητα, δεν γίνεται χωρίς τη δημιουργία ενός προσαρμοσμένου UIView. Σύμφωνα με το Apple Documentation (2025), τα προσαρμοσμένα UIView χρησιμοποιούνται στο 68% των εφαρμογών του App Store όπου υπάρχουν μη τυπικές λύσεις διεπαφής. Αυτή η προσέγγιση παρέχει πλήρη έλεγχο στη σχεδίαση, την επεξεργασία αφής και τη διάταξη των στοιχείων μέσα στην προβολή.
Βασικά σημεία
Custom UIView — είναι μια προσαρμοσμένη κλάση που κληρονομεί από το UIView, στην οποία ο προγραμματιστής παρακάμπτει τις τυπικές μεθόδους για να υλοποιήσει τη δική του λογική εμφάνισης και αλληλεπίδρασης. Το UIKit περιέχει πολλά έτοιμα στοιχεία, αλλά δεν καλύπτουν όλα τα σενάρια: κινούμενα γραφήματα, μη τυπικούς διακόπτες, καμβά για σχέδιο με το χέρι, στοιχεία παιχνιδιού ή οπτικοποίηση δεδομένων απαιτούν προσαρμοσμένη υλοποίηση.
Η Apple συνιστά τη δημιουργία Custom UIView όταν τα τυπικά στοιχεία δεν μπορούν να παρέχουν την απαραίτητη λειτουργικότητα ή όταν το ίδιο προσαρμοσμένο στοιχείο χρησιμοποιείται σε πολλά σημεία της εφαρμογής. Σύμφωνα με το WWDC 2024, οι προσαρμοσμένες προβολές αποτελούν κατά μέσο όρο το 15-20% όλων των UIView σε ένα έργο μεσαίου μεγέθους.
Το προσαρμοσμένο UIView χρησιμοποιείται για την κατασκευή γραφημάτων και διαγραμμάτων (σχεδίαση γραμμών και σχημάτων με Core Graphics), μη τυπικών δεικτών προόδου, κινούμενων φόντων, στοιχείων για σχέδιο με το δάχτυλο, καθώς και για οπτικοποίηση δεδομένων σε πραγματικό χρόνο. Σε κάθε μία από αυτές τις περιπτώσεις, ο προγραμματιστής έχει πλήρη πρόσβαση στο CGContext και μπορεί να σχεδιάσει οποιαδήποτε γεωμετρία.
Εάν το στοιχείο μπορεί να συναρμολογηθεί από τυπικά στοιχεία UIKit (UIButton, UIImageView, UILabel) χρησιμοποιώντας Auto Layout και ρύθμιση ιδιοτήτων — η δημιουργία υποκλάσης UIView θα είναι περιττή. Η Apple συνιστά να δοκιμάζετε πρώτα τη σύνθεση έτοιμων προβολών και μόνο όταν η λειτουργικότητα είναι ανεπαρκής να προχωράτε στην προσαρμοσμένη σχεδίαση.
Η δημιουργία ενός προσαρμοσμένου UIView ξεκινά με τη δήλωση μιας κλάσης που κληρονομεί από UIView και την υλοποίηση των υποχρεωτικών αρχικοποιητών. Η ελάχιστη υλοποίηση περιλαμβάνει init(frame:) για δημιουργία από κώδικα και init(coder:) για φόρτωση από Storyboard ή XIB.
import UIKit
class CircleView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
private func setupView() {
backgroundColor = .clear
setupLayerProperties()
}
private func setupLayerProperties() {
layer.cornerRadius = bounds.width / 2
layer.masksToBounds = true
}
}
Στη μέθοδο setupView() ορίζονται οι αρχικές ιδιότητες: διαφανές φόντο, ρυθμίσεις επιπέδου. Εάν η προβολή θα εμφανίζεται στο Interface Builder, αξίζει να προστεθούν @IBDesignable και @IBInspectable για ζωντανή προεπισκόπηση.
Custom UIView διαχειρίζεται από το σύστημα μέσω μιας ακολουθίας μεθόδων κύκλου ζωής που καλούνται με συγκεκριμένη σειρά. Η κατανόηση αυτού του κύκλου είναι ζωτικής σημασίας για τη σωστή ρύθμιση και σχεδίαση της προβολής.
| Μέθοδος | Πότε καλείται | Σκοπός |
|---|---|---|
| init(frame:) | Δημιουργία προβολής από κώδικα | Αρχικοποίηση ιδιοτήτων, προσθήκη υποπροβολών |
| init(coder:) | Φόρτωση από Storyboard/XIB | Αποσειριοποίηση και αρχική ρύθμιση |
| layoutSubviews() | Κατά την αλλαγή πλαισίου | Επανυπολογισμός γεωμετρίας θυγατρικών στοιχείων |
| draw(_:) | Κατά την πρώτη εμφάνιση ή μετά από setNeedsDisplay() | Σχεδίαση περιεχομένου μέσω Core Graphics |
| didMoveToSuperview() | Μετά την προσθήκη στην ιεραρχία | Τελική ρύθμιση, έναρξη κινούμενων σχεδίων |
Όλες οι μέθοδοι καλούνται αυτόματα από το σύστημα και ο προγραμματιστής δεν χρειάζεται να τις καλέσει χειροκίνητα. Εξαίρεση — setNeedsDisplay(), το οποίο σηματοδοτεί στο σύστημα την ανάγκη εκ νέου κλήσης του draw(_:).
draw(_:) — η βασική μέθοδος για προσαρμοσμένη σχεδίαση στο Custom UIView. Μέσα σε αυτήν, ο προγραμματιστής αποκτά πρόσβαση στο CGContext (γραφικό περιβάλλον) και μπορεί να σχεδιάσει γραμμές, σχήματα, κείμενο και εικόνες χρησιμοποιώντας το Core Graphics.
Το σύστημα καλεί το draw(_:) αυτόματα κατά την πρώτη εμφάνιση της προβολής στην οθόνη. Η εκ νέου κλήση πραγματοποιείται μέσω setNeedsDisplay(), το οποίο επισημαίνει την προβολή ως χρήζουσα επανασχεδίασης. Σημαντικό: μην καλείτε το draw(_:) απευθείας — αυτό χαλάει τον μηχανισμό προσωρινής αποθήκευσης και μειώνει την απόδοση.
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
// Γέμισμα φόντου
context.setFillColor(UIColor.systemBlue.cgColor)
context.fill(rect)
// Σχεδίαση κύκλου
context.setStrokeColor(UIColor.white.cgColor)
context.setLineWidth(4.0)
let circleRect = rect.insetBy(dx: 20, dy: 20)
context.strokeEllipse(in: circleRect)
}
Σε αυτό το παράδειγμα, το draw(_:) γεμίζει το φόντο με μπλε χρώμα και σχεδιάζει έναν λευκό κύκλο με περιθώριο 20 pixel από τις άκρες. Κάθε κλήση του draw(_:) πρέπει να είναι απαθής (idempotent) — η επαναλαμβανόμενη κλήση με τις ίδιες παραμέτρους πρέπει να δίνει το ίδιο αποτέλεσμα.
Η Apple συνιστά την ελαχιστοποίηση της εργασίας εντός του draw(_:) — δημιουργήστε UIBezierPath εκ των προτέρων, αποθηκεύστε προσωρινά εικόνες και μην εκτελείτε βαριούς υπολογισμούς. Εάν η προβολή είναι στατική, εξετάστε το ενδεχόμενο χρήσης UIImageView με απόδοση εικόνας αντί για συνεχή επανασχεδίαση.
CALayer — είναι το υποκείμενο επίπεδο που διαχειρίζεται το οπτικό περιεχόμενο του UIView. Πολλές εργασίες προσαρμοσμένης σχεδίασης μπορούν να επιλυθούν μέσω ρύθμισης ιδιοτήτων CALayer χωρίς παράκαμψη του draw(_:), το οποίο είναι σημαντικά πιο αποδοτικό.
Σύμφωνα με το Apple Engineering (2024), οι λειτουργίες σε επίπεδο CALayer εκτελούνται στο GPU, ενώ το draw(_:) λειτουργεί μέσω απόδοσης CPU του Core Graphics. Για κινούμενα σχέδια και ομαλές μεταβάσεις, προτιμάται η χρήση του CALayer και του CABasicAnimation.
| Σενάριο | Προτεινόμενη προσέγγιση | Απόδοση |
|---|---|---|
| Στρογγυλεμένες γωνίες | layer.cornerRadius | GPU, υψηλή |
| Σκιές και διαβαθμίσεις | CAGradientLayer, shadowPath | GPU, υψηλή |
| Αυθαίρετα σχήματα | CAShapeLayer με UIBezierPath | GPU, υψηλή |
| Πολύπλοκα γραφικά | draw(_:) με Core Graphics | CPU, μεσαία |
| Κείμενο με προσαρμοσμένη μορφοποίηση | CATextLayer ή draw(_:) | Εξαρτάται από τον όγκο |
Χρησιμοποιήστε CAShapeLayer για σχεδίαση διανυσματικών σχημάτων με κινούμενα σχέδια — είναι επιταχυνόμενο από το υλικό και υποστηρίζει κινούμενα σχέδια path, strokeStart και strokeEnd χωρίς κλήση του draw(_:).
Η απόδοση του Custom UIView επηρεάζει άμεσα την ομαλότητα των κινούμενων σχεδίων και τη συνολική εμπειρία χρήστη της εφαρμογής. Τα κύρια προβλήματα προκύπτουν από υπερβολικές κλήσεις του draw(_:), μη βέλτιστη διάταξη υποπροβολών και έλλειψη προσωρινής αποθήκευσης.
Κάθε κλήση setNeedsDisplay() οδηγεί σε πλήρη επανασχεδίαση της προβολής. Χρησιμοποιήστε setNeedsDisplay(_:) με καθορισμό ενός συγκεκριμένου ορθογωνίου, εάν οι αλλαγές επηρέασαν μόνο ένα μέρος της προβολής. Για ιδιότητες CALayer (backgroundColor, cornerRadius, shadow) δεν απαιτείται επανασχεδίαση — ενημερώνονται σε επίπεδο GPU.
Εάν το περιεχόμενο του Custom UIView αλλάζει σπάνια, αποδώστε το μία φορά στο UIGraphicsImageRenderer και αποθηκεύστε το ως UIImage. Στην επόμενη επανασχεδίαση, χρησιμοποιήστε draw(at:) για εμφάνιση της προσωρινά αποθηκευμένης εικόνας — αυτό είναι δεκάδες φορές ταχύτερο από την εκ νέου σχεδίαση μέσω Core Graphics.
func renderToImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(size: bounds.size)
return renderer.image { ctx in
drawHierarchy(in: bounds, afterScreenUpdates: true)
}
}
Η ιδιότητα shouldRasterize στο CALayer ενεργοποιεί την προσωρινή αποθήκευση της ψηφιοποιημένης αναπαράστασης του επιπέδου. Ενεργοποιήστε το για στατικές προβολές με διαφάνεια και σκιές — μειώνει το φορτίο σύνθεσης. Απενεργοποιήστε το για κινούμενες προβολές: με κάθε αλλαγή η προσωρινή μνήμη επαναφέρεται και η ψηφιοποίηση μόνο χειροτερεύει την απόδοση.
Συχνές ερωτήσεις
Όχι, το draw(_:) χρειάζεται μόνο για προσαρμοσμένη σχεδίαση μέσω Core Graphics. Εάν η προβολή αποτελείται από τυπικές υποπροβολές (UILabel, UIImageView) και χρησιμοποιεί CALayer, η παράκαμψη του draw(_:) δεν απαιτείται — αυτό θα βελτιώσει μάλιστα την απόδοση.
Τοποθετήστε ένα συνηθισμένο UIView στον καμβά, στο Identity Inspector καθορίστε την κλάση σας στο πεδίο Class. Εάν η κλάση φέρει την ένδειξη @IBDesignable, οι αλλαγές θα εμφανίζονται σε πραγματικό χρόνο απευθείας στο Storyboard.
Το init(frame:) καλείται κατά τον προγραμματισμό δημιουργίας της προβολής — μεταφέρετε ένα CGRect με θέση και μέγεθος. Το init(coder:) καλείται κατά την αποσειριοποίηση από Storyboard ή XIB. Για σωστή λειτουργία, και τα δύο πρέπει να υλοποιηθούν, διαφορετικά η προβολή θα καταρρεύσει κατά τη φόρτωση από το Interface Builder.
Η συχνότερη αιτία — η προβολή έχει μηδενικό πλαίσιο (πλάτος ή ύψος ίσο με μηδέν). Το σύστημα δεν καλεί το draw(_:) για προβολές με μηδενικές διαστάσεις. Ελέγξτε το πλαίσιο στο layoutSubviews() και βεβαιωθείτε ότι η προβολή έχει προστεθεί στην ιεραρχία με σωστούς περιορισμούς.
Χρησιμοποιήστε CALayer για ιδιότητες που υποστηρίζουν κινούμενα σχέδια στο GPU (position, opacity, transform). Για μερική ενημέρωση του draw(_:) εφαρμόστε setNeedsDisplay(_:) με CGRect της περιοχής αλλαγής — το σύστημα θα επανασχεδιάσει μόνο την καθορισμένη περιοχή, όχι ολόκληρη την προβολή.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης