Κύρια σημεία
UIGestureRecognizer — μια αφηρημένη βασική κλάση στο UIKit που αποσυνθέτει τη διαδικασία αναγνώρισης χειρονομιών σε ξεχωριστά αντικείμενα. Κάθε υποκλάση είναι υπεύθυνη για έναν τύπο χειρονομίας: UITapGestureRecognizer — για tap με συγκεκριμένο αριθμό δαχτύλων, UISwipeGestureRecognizer — για σύρσιμο σε συγκεκριμένη κατεύθυνση. Ο προγραμματιστής προσθέτει τον αναγνωριστή στο UIView μέσω της μεθόδου addGestureRecognizer(:), και το UIKit παρακολουθεί αυτόματα τις αφές, ενημερώνει την κατάσταση και καλεί τη δράση κατά την αναγνώριση.
Πριν από την εμφάνιση του UIGestureRecognizer (iOS 3.2, 2010), οι προγραμματιστές παρακάμπτουν τις μεθόδους UIResponder — touchesBegan, touchesMoved, touchesEnded — και ανέλυαν χειροκίνητα την τροχιά των αφών. Αυτό οδηγούσε σε διπλασιασμό κώδικα και σφάλματα κατά την επεξεργασία ταυτόχρονων αφών. Η Apple ενσωμάτωσε αυτή τη λογική στο UIGestureRecognizer, προσθέτοντας υποστήριξη για multitouch, ακύρωση χειρονομιών και ταυτόχρονη λειτουργία πολλών αναγνωριστών σε μία προβολή.
Σύμφωνα με τις συνεδρίες WWDC, το Gesture Recognizer επεξεργάζεται έως 11 ταυτόχρονες αφές στο iPad και 5 στο iPhone. Στην IT Sectr, χρησιμοποιούμε το UIGestureRecognizer ως τον τυπικό τρόπο επεξεργασίας εισόδου χρήστη σε όλα τα έργα UIKit — αυτό εξάλειψε σφάλματα που σχετίζονται με τη μη αυτόματη παρακολούθηση του touchesBegan.
Κάθε UIGestureRecognizer διέρχεται από 7 πιθανές καταστάσεις, που ορίζονται στο enum UIGestureRecognizer.State. Αυτές οι καταστάσεις αντικατοπτρίζουν τον κύκλο ζωής αναγνώρισης: από την ανίχνευση αφής έως την ολοκλήρωση ή ακύρωση της χειρονομίας. Η κατανόηση των καταστάσεων είναι κρίσιμη για την υλοποίηση προσαρμοσμένων αναγνωριστών και τον εντοπισμό σφαλμάτων συγκρούσεων.
| Κατάσταση | Σημασία | Πότε προκύπτει |
|---|---|---|
| .possible | Αρχική κατάσταση, η χειρονομία δεν έχει αναγνωριστεί ακόμα | Αμέσως μετά την προσθήκη στην προβολή |
| .began | Η χειρονομία αναγνωρίστηκε και άρχισε να εκτελείται | Στην πρώτη κίνηση δαχτύλου για pan/longPress |
| .changed | Οι παράμετροι της χειρονομίας άλλαξαν (συντεταγμένες, γωνία) | Σε κάθε κίνηση δαχτύλου |
| .ended | Ο χρήστης σήκωσε το δάχτυλο, η χειρονομία ολοκληρώθηκε | Στο touchesEnded |
| .cancelled | Η χειρονομία διακόπηκε από το σύστημα (εισερχόμενη κλήση, αλλαγή προσανατολισμού) | Στο touchesCancelled |
| .failed | Η χειρονομία δεν αναγνωρίστηκε σύμφωνα με τις συνθήκες | Στο touchesCancelled χωρίς αναγνώριση |
| .recognized | Συνώνυμο του .ended; η χειρονομία αναγνωρίστηκε επιτυχώς | Ίδιο με το .ended |
Οι διακριτές χειρονομίες (tap, swipe) μεταβαίνουν από .possible απευθείας σε .ended ή .failed. Οι συνεχείς χειρονομίες (pan, pinch, rotation, longPress) διέρχονται από .possible → .began → .changed (πολλαπλές φορές) → .ended. Στη μέθοδο δράσης, ελέγξτε το gestureRecognizer.state — αυτό επιτρέπει τη διάκριση της αρχής, της αλλαγής και του τέλους της χειρονομίας.
Το UIKit παρέχει 7 ενσωματωμένες υποκλάσεις του UIGestureRecognizer, που καλύπτουν τα περισσότερα σενάρια αλληλεπίδρασης. Κάθε υποκλάση έχει συγκεκριμένες ρυθμίσεις: numberOfTapsRequired για tap, direction για σύρσιμο, minimumPressDuration για παρατεταμένο πάτημα.
Για προσαρμοσμένες χειρονομίες (π.χ. σχεδίαση ζιγκ-ζαγκ) δημιουργείται υποκλάση του UIGestureRecognizer με παράκαμψη των μεθόδων touchesBegan, touchesMoved, touchesEnded και ενημέρωση κατάστασης. Η Apple συνιστά τη χρήση ενσωματωμένων κλάσεων όπου είναι δυνατόν — είναι βελτιστοποιημένες και αλληλεπιδρούν σωστά μεταξύ τους.
Όταν σε ένα UIView χρησιμοποιούνται πολλά UIGestureRecognizer (π.χ. tap και double-tap), προκύπτει σύγκρουση αναγνώρισης: στο διπλό tap, πρώτα ενεργοποιείται το μεμονωμένο tap. Για λύση, η Apple παρέχει τη μέθοδο require(toFail:), η οποία καθυστερεί την αναγνώριση μιας χειρονομίας έως ότου η άλλη αποτύχει.
Ο μηχανισμός λειτουργεί ως εξής: καλώντας το tapRecognizer.require(toFail: doubleTapRecognizer), ορίζετε ότι το tapRecogniser θα μεταβεί στην κατάσταση .recognized μόνο αφού το doubleTapRecogniser ολοκληρωθεί σε .failed. Αυτό προσθέτει καθυστέρηση ~0,3 δευτερολέπτου πριν από την εκτέλεση του μεμονωμένου tap — ο χρήστης πατά δύο φορές και το πρώτο tap αγνοείται. Εναλλακτική μέθοδος — ο εκπρόσωπος UIGestureRecognizerDelegate με τη μέθοδο gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:), που επιτρέπει ταυτόχρονη αναγνώριση (π.χ. pan + pinch για χάρτη).
Σύμφωνα με το WWDC 2020, περίπου 15% των σφαλμάτων σε εφαρμογές UIKit σχετίζονται με εσφαλμένη ρύθμιση συγκρούσεων χειρονομιών. Στην IT Sectr, τυποποιήσαμε την προσέγγιση: για κάθε οθόνη δημιουργείται ένα σχήμα χειρονομιών με προτεραιότητες require(toFail:) — αυτό εξάλειψε εντελώς τα σφάλματα με διπλή ενεργοποίηση tap.
Προσθέτει έναν χειριστή μεμονωμένου tap στο UIImageView. Κατά το tap, η εικόνα αλλάζει διαφάνεια — το απλούστερο παράδειγμα που δείχνει τη σύνδεση της χειρονομίας με μια προβολή.
import UIKit
class ImageViewController: UIViewController {
@IBOutlet private var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(
target: self,
action: #selector(handleTap(_:))
)
tap.numberOfTapsRequired = 1
imageView.addGestureRecognizer(tap)
imageView.isUserInteractionEnabled = true
}
@objc private func handleTap(_: UITapGestureRecognizer) {
UIView.animate(withDuration: 0.2) {
self.imageView.alpha = self.imageView.alpha == 1.0 ? 0.5 : 1.0
}
}
}
Βασικό σημείο: το isUserInteractionEnabled στο UIImageView είναι από προεπιλογή false — χωρίς αυτή τη σημαία, το Gesture Recognizer δεν θα λαμβάνει αφές. Για UIView και UIButton, η σημαία είναι ενεργοποιημένη από προεπιλογή. Η μέθοδος δράσης δέχεται μια παράμετρο UITapGestureRecognizer μέσω της οποίας μπορεί να ληφθεί το location(in:) για τον προσδιορισμό των συντεταγμένων του tap.
Υλοποιεί σύρσιμο προς τα αριστερά για επιστροφή στην προηγούμενη οθόνη. Δείχνει τη ρύθμιση κατεύθυνσης και τη σύνδεση της χειρονομίας στην προβολή ρίζας του ελεγκτή.
import UIKit
class DetailViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let swipeLeft = UISwipeGestureRecognizer(
target: self,
action: #selector(handleSwipe(_:))
)
swipeLeft.direction = .left
view.addGestureRecognizer(swipeLeft)
}
@objc private func handleSwipe(_: UISwipeGestureRecognizer) {
navigationController?.popViewController(animated: true)
}
}
Το UISwipeGestureRecognizer — είναι μια διακριτή χειρονομία: μεταβαίνει σε .recognised αμέσως μετά την αναγνώριση, χωρίς ενδιάμεσο .changed. Επομένως, στη δράση δεν χρειάζεται να ελέγχεται η κατάσταση — η χειρονομία είτε αναγνωρίζεται (καλείται η δράση) είτε όχι. Η ιδιότητα direction δέχεται μία από τέσσερις τιμές: .left, .right, .up, .down. Για υποστήριξη πολλαπλών κατευθύνσεων, δημιουργήστε ξεχωριστούς αναγνωριστές για κάθε μία.
Δείχνει πώς να ρυθμίσετε μεμονωμένο και διπλό tap στην ίδια προβολή χωρίς σύγκρουση. Ο αναγνωριστής διπλού tap έχει προτεραιότητα — το μεμονωμένο tap ενεργοποιείται μόνο εάν το διπλό tap δεν αναγνωριστεί.
import UIKit
class TapViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let singleTap = UITapGestureRecognizer(
target: self,
action: #selector(handleSingleTap)
)
singleTap.numberOfTapsRequired = 1
let doubleTap = UITapGestureRecognizer(
target: self,
action: #selector(handleDoubleTap)
)
doubleTap.numberOfTapsRequired = 2
singleTap.require(toFail: doubleTap)
view.addGestureRecognizer(singleTap)
view.addGestureRecognizer(doubleTap)
}
@objc private func handleSingleTap() {
print("Single tap — after 0.3s delay")
}
@objc private func handleDoubleTap() {
print("Double tap — instant")
}
}
Χωρίς require(toFail:), στο διπλό tap, πρώτα θα κληθεί το handleSingleTap, μετά το handleDoubleTap — αυτό χαλάει την εμπειρία χρήστη. Με το require(toFail:), το μεμονωμένο tap περιμένει ~0,3 δευτερόλεπτα για να βεβαιωθεί ότι δεν θα ακολουθήσει δεύτερο tap. Στην IT Sectr, αυτό το μοτίβο χρησιμοποιείται σε επεξεργαστές εικόνων και γκαλερί, όπου το διπλό tap μεγεθύνει και το μεμονωμένο επιλέγει ένα στοιχείο.
Συχνές ερωτήσεις
Ναι, το UIView υποστηρίζει πολλαπλά UIGestureRecognizer ταυτόχρονα. Για την επίλυση συγκρούσεων, χρησιμοποιήστε τη μέθοδο require(toFail:), η οποία καθορίζει τη σειρά αναγνώρισης. Για παράλληλη λειτουργία χειρονομιών (π.χ. pan + pinch σε χάρτη) υλοποιήστε τον εκπρόσωπο gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:), που επιστρέφει true.
UIGestureRecognizer — αφαίρεση υψηλού επιπέδου που αναγνωρίζει αυτόματα μοτίβα αφής και διαχειρίζεται καταστάσεις. Το touchesBegan — μέθοδος χαμηλού επιπέδου του UIResponder που απαιτεί μη αυτόματη παρακολούθηση συντεταγμένων, χρονισμού και ακύρωσης αφών. Το Gesture Recognizer είναι απλούστερο, αξιόπιστο και προτιμότερο για τυπικές χειρονομίες· το touchesBegan δικαιολογείται μόνο για προσαρμοσμένα γραφικά.
Στο εγγενές SwiftUI, χρησιμοποιούνται τροποποιητές: onTapGesture, onLongPressGesture, DragGesture, MagnificationGesture, RotationGesture. Αυτά είναι δηλωτικά ανάλογα του UIGestureRecognizer, ενσωματωμένα στην ιεραρχία SwiftUI. Εάν χρειαστεί, μπορείτε να τυλίξετε έναν αναγνωριστή UIKit μέσω UIViewRepresentable, αλλά η Apple συνιστά τη χρήση εγγενών χειρονομιών SwiftUI.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης