Διασυνδέσεις δομικών υγρών

Πώς να δημιουργήσετε φυσικές κινήσεις και κινούμενα σχέδια στο iOS

Στο WWDC 2018, οι σχεδιαστές της Apple παρουσίασαν μια ομιλία με τίτλο "Designing Fluid Interfaces", εξηγώντας τη λογική του σχεδιασμού πίσω από τη χειρονομιακή διεπαφή του iPhone X.

Παρουσίαση της Apple WWDC18

Είναι η αγαπημένη μου διάλεξη για το WWDC ποτέ - το συνιστώ ανεπιφύλακτα.

Η συζήτηση παρέσχε κάποια τεχνική καθοδήγηση, η οποία είναι εξαιρετική για μια παρουσίαση σχεδίου, αλλά ήταν ψευδοκώδικας, αφήνοντας πολλά άγνωστα.

Κάποιο κωδικό τύπου Swift από την παρουσίαση.

Αν προσπαθήσετε να εφαρμόσετε αυτές τις ιδέες, ίσως παρατηρήσετε ένα κενό μεταξύ της έμπνευσης και της εφαρμογής.

Ο στόχος μου είναι να γεφυρώσω αυτό το κενό παρέχοντας παραδείγματα κώδικα εργασίας για κάθε σημαντικό θέμα της παρουσίασης.

Οι οκτώ (8) διεπαφές που θα δημιουργήσουμε. Κουμπιά, ελατήρια, προσαρμοσμένες αλληλεπιδράσεις και πολλά άλλα!

Ακολουθεί ένα περίγραμμα του τι θα καλύψουμε:

  1. Μια σύντομη περίληψη της ομιλίας "Designing Fluid Interfaces".
  2. Οκτώ ρευστοί διεπαφές, η θεωρία του σχεδιασμού πίσω από αυτούς, και ο κώδικας για την κατασκευή τους.
  3. Αιτήσεις για σχεδιαστές και προγραμματιστές.

Τι είναι οι διασυνδέσεις υγρών;

Μια διεπαφή υγρών μπορεί επίσης να ονομαστεί "γρήγορη", "ομαλή", "φυσική" ή "μαγική". Είναι μια εμπειρία χωρίς τριβές που απλά αισθάνεται "σωστή".

Η παρουσίαση του WWDC μιλά για τις διασυνδέσεις υγρών ως "επέκταση του μυαλού σας" και "επέκταση του φυσικού κόσμου". Μια διεπαφή είναι υγρή όταν συμπεριφέρεται σύμφωνα με τον τρόπο που σκέφτονται οι άνθρωποι, όχι ο τρόπος σκέψης των μηχανών.

Τι τους κάνει ρευστά;

Οι διασυνδέσεις υγρών είναι ευαίσθητες, διακοπτόμενες και ανακατευθυνόμενες. Ακολουθεί ένα παράδειγμα της χειρονομίας στο iPhone X:

Οι εφαρμογές μπορούν να κλείσουν κατά τη διάρκεια της κίνησης εκτόξευσης.

Η διεπαφή αμέσως αντιδρά στην είσοδο του χρήστη, μπορεί να σταματήσει σε οποιοδήποτε σημείο της διαδικασίας και μπορεί να αλλάξει ακόμη και τη μέση.

Γιατί μας ενδιαφέρει οι διεπαφές υγρών;

  1. Οι διεπαφές υγρών βελτιώνουν την εμπειρία του χρήστη, κάνοντας κάθε αλληλεπίδραση να αισθάνεται γρήγορη, ελαφριά και σημαντική.
  2. Δίνουν στον χρήστη μια αίσθηση ελέγχου, η οποία δημιουργεί εμπιστοσύνη με την εφαρμογή και το εμπορικό σήμα σας.
  3. Είναι δύσκολο να οικοδομηθούν. Μια διεπαφή υγρών είναι δύσκολο να αντιγραφεί και μπορεί να αποτελέσει ανταγωνιστικό πλεονέκτημα.

Οι διασυνδέσεις

Για το υπόλοιπο της παρούσας θέσης, θα σας δείξω πώς να δημιουργήσετε οκτώ (8) διεπαφές που καλύπτουν όλα τα σημαντικά θέματα της παρουσίασης.

Εικόνες που αντιπροσωπεύουν τις οκτώ (8) διεπαφές που θα δημιουργήσουμε.

Διασύνδεση # 1: Πλήκτρο αριθμομηχανής

Αυτό είναι ένα κουμπί που μιμείται τη συμπεριφορά των κουμπιών στην εφαρμογή αριθμομηχανής iOS.

Βασικά χαρακτηριστικά

  1. Επισημαίνει άμεσα την αφή.
  2. Μπορεί να τρυπηθεί γρήγορα ακόμη και όταν το μέσο της κινούμενης εικόνας.
  3. Ο χρήστης μπορεί να αγγίξει και να σύρει έξω από το κουμπί για να ακυρώσει τη βρύση.
  4. Ο χρήστης μπορεί να αγγίξει, να σύρει έξω, να σύρει πίσω και να επιβεβαιώσει τη βρύση.

Θεωρία Σχεδιασμού

Θέλουμε κουμπιά που αισθάνονται ευαίσθητα, αναγνωρίζοντας στον χρήστη ότι είναι λειτουργικά. Επιπρόσθετα, θέλουμε η ενέργεια να ακυρωθεί αν ο χρήστης αποφασίσει ενάντια στη δράση του αφού άγγιξε. Αυτό επιτρέπει στους χρήστες να λαμβάνουν ταχύτερες αποφάσεις δεδομένου ότι μπορούν να εκτελούν ενέργειες παράλληλα με τη σκέψη.

Παρουσιάσεις από την παρουσίαση του WWDC που δείχνουν πως οι χειρονομίες παράλληλα με τη σκέψη γίνονται ταχύτερες.

Κρίσιμος κώδικας

Το πρώτο βήμα για τη δημιουργία αυτού του κουμπιού είναι να χρησιμοποιήσετε μια υποκατηγορία UIControl, όχι μια υποκατηγορία UIButton. Ένα UIButton θα λειτουργούσε ωραία, αλλά δεδομένου ότι προσαρμόζουμε την αλληλεπίδραση, δεν θα χρειαστεί κανένα από τα χαρακτηριστικά του.

Αριθμομηχανή: UIControl {
    δημόσια τιμή var: Int = 0 {
        didSet {label.text = "\ (τιμή)"}
    }}
    ιδιωτική χαλαρή ετικέτα var: UILabel = {...} ()
}}

Στη συνέχεια, θα χρησιμοποιήσουμε το UIControlEvents για την εκχώρηση λειτουργιών στις διάφορες αλληλεπιδράσεις αφής.

addTarget (αυτο, δράση: #selector (touchDown), για: [.touchDown, .touchDragEnter])
addTarget (αυτο, δράση: #selector (touchUp), για: [.touchUpInside, .touchDragExit, .touchCancel])

Συγκεντρώνουμε τα γεγονότα touchDown και touchDragEnter σε ένα "συμβάν" που ονομάζεται touchDown και μπορούμε να ομαδοποιήσουμε τα γεγονότα touchUpInside, touchDragExit και touchCancel σε ένα μόνο συμβάν που ονομάζεται touchUp.

(Για μια περιγραφή όλων των διαθέσιμωνUIControlEvents, ανατρέξτε στην τεκμηρίωση.)

Αυτό μας δίνει δύο λειτουργίες για να χειριστούμε τα κινούμενα σχέδια.

ιδιωτικός var animator = UIViewPropertyAnimator ()
@objc ιδιωτικό func touchDown () {
    animator.stopAnimation (true)
    backgroundColor = highlightedColor
}}
@objc ιδιωτικό func touchUp () {
    animator = UIViewPropertyAnimator (διάρκεια: 0.5, καμπύλη: .easeOut, κινούμενα σχέδια: {
        self.backgroundColor = self.normalColor
    })
    animator.startAnimation ()
}}

Στην touchDown ακυρώνουμε την υπάρχουσα κινούμενη εικόνα εάν χρειαστεί και ρυθμίζουμε άμεσα το χρώμα στο επισημασμένο χρώμα (στην περίπτωση αυτή ένα ανοιχτό γκρι χρώμα).

Στο touchUp, δημιουργούμε ένα νέο animator και ξεκινάμε το animation. Η χρήση του UIViewPropertyAnimator καθιστά εύκολη την ακύρωση της κινούμενης εικόνας.

(Πλευρική σημείωση: Δεν είναι η ακριβής συμπεριφορά των κουμπιών στην εφαρμογή αριθμομηχανής iOS, τα οποία επιτρέπουν σε ένα άγγιγμα που ξεκίνησε με ένα διαφορετικό κουμπί για να το ενεργοποιήσει αν το άγγιγμα σύρθηκε μέσα στο κουμπί.Σε τις περισσότερες περιπτώσεις ένα κουμπί όπως αυτό Δημιούργησα εδώ την προτιθέμενη συμπεριφορά για κουμπιά iOS.)

Διασύνδεση # 2: Εικαστικά εφέ

Αυτή η διεπαφή δείχνει πώς μπορεί να δημιουργηθεί μια κινούμενη εικόνα με ελατήρια καθορίζοντας μια "απόσβεση" και "απόκριση" (ταχύτητα).

Βασικά χαρακτηριστικά

  1. Χρησιμοποιεί παραμέτρους φιλικές προς το σχεδιασμό.
  2. Δεν υπάρχει έννοια της διάρκειας animation.
  3. Εύκολα διακοπτόμενη.

Θεωρία Σχεδιασμού

Οι πηγές δημιουργούν εξαιρετικά μοντέλα κινουμένων σχεδίων λόγω της ταχύτητας και της φυσικής τους εμφάνισης. Μια εικαστική εκκίνηση ξεκινά απίστευτα γρήγορα, ξοδεύοντας το μεγαλύτερο μέρος του χρόνου της προσεγγίζοντας σταδιακά την τελική της κατάσταση. Αυτό είναι ιδανικό για τη δημιουργία διεπαφών που αισθάνονται ευαίσθητες - πηγαίνουν στη ζωή!

Λίγα επιπλέον υπενθυμίσεις κατά το σχεδιασμό animations άνοιξη:

  1. Οι πηγές δεν πρέπει να είναι ελαστικές. Χρησιμοποιώντας μια τιμή απόσβεσης 1 θα δημιουργήσετε ένα κινούμενο σχέδιο που σιγά-σιγά έρχεται να ξεκουραστεί χωρίς καμιά βλάκα. Τα περισσότερα κινούμενα σχέδια πρέπει να χρησιμοποιούν τιμή απόσβεσης 1.
  2. Προσπαθήστε να μην σκεφτείτε τη διάρκεια. Θεωρητικά, μια πηγή δεν έρχεται ποτέ πλήρως να ξεκουραστεί και η αναγκαστική διάρκεια της άνοιξης μπορεί να την κάνει να αισθάνεται αφύσικη. Αντ 'αυτού, παίξτε με τις τιμές απόσβεσης και απόκρισης μέχρι να νιώσετε σωστό.
  3. Η διακοπή είναι κρίσιμη. Επειδή οι πηγές ξοδεύουν τόσο μεγάλο μέρος του χρόνου τους κοντά στην τελική τους αξία, οι χρήστες μπορεί να πιστεύουν ότι η κινούμενη εικόνα έχει ολοκληρωθεί και θα προσπαθήσει να την αλληλεπιδράσει ξανά.

Κρίσιμος κώδικας

Στο UIKit, μπορούμε να δημιουργήσουμε ένα animation άνοιξη με UIViewPropertyAnimator και UISpringTimingParameters αντικείμενο. Δυστυχώς, δεν υπάρχει αρχικοποιητής που να λαμβάνει απλώς μια απόσβεση και απόκριση. Το πλησιέστερο που μπορούμε να έχουμε είναι ο αρχικοποιητής UISpringTimingParameters που παίρνει μάζα, ακαμψία, απόσβεση και αρχική ταχύτητα.

UISpringTimingParameters (μάζα: CGFloat, ακαμψία: CGFloat, απόσβεση: CGFloat, αρχικήVelocity: CGVector)

Θα θέλαμε να δημιουργήσουμε έναν αρχικοποιητή ευκολίας που να λαμβάνει απόσβεση και απόκριση και να το αντιστοιχεί στην απαιτούμενη μάζα, ακαμψία και απόσβεση.

Με λίγη φυσική, μπορούμε να αντλήσουμε τις εξισώσεις που χρειαζόμαστε:

Επίλυση για τη σταθερά ελατηρίου και τον συντελεστή απόσβεσης.

Με αυτό το αποτέλεσμα, μπορούμε να δημιουργήσουμε τα δικά μας UISpringTimingParameters με ακριβώς τις παραμέτρους που επιθυμούμε.

παράταση UISpringTimingParameters {
    ευκολία init (απόσβεση: CGFloat, απόκριση: CGFloat, initialVelocity: CGVector = .zero) {
        ας ακαμψία = pow (2 * .pi / απόκριση, 2)
        ας πάρουμε υγρό = 4 * .pi * απόσβεση / απόκριση
        self.init (μάζα: 1, ακαμψία: ακαμψία, απόσβεση: υγρασία, αρχικήVelocity: initialVelocity)
    }}
}}

Έτσι θα προσδιορίσουμε τα εφέ κίνησης για όλες τις άλλες διεπαφές.

Η φυσική πίσω από τις ζωές της άνοιξης

Θέλετε να πάτε βαθύτερα στις κινήσεις της άνοιξης; Ελέγξτε αυτήν την απίστευτη δημοσίευση από τον Christian Schnorr: Απομυθοποίηση των εικονογραφημένων δράσεων της UIKit Spring.

Αφού διάβασα τη θέση του, οι εικονογραφημένες εφέ άνοιξαν τελικά για μένα. Τεράστια φωνή στο χριστιανικό για να με βοηθήσω να καταλάβω τα μαθηματικά πίσω από αυτά τα κινούμενα σχέδια και για να μάθω πώς να λύσω διαφορικές εξισώσεις δεύτερης τάξης.

Διασύνδεση # 3: Κουμπί φακού

Ένα άλλο κουμπί, αλλά με πολύ διαφορετική συμπεριφορά. Αυτό μιμείται τη συμπεριφορά του κουμπιού φανού στην οθόνη κλειδώματος του iPhone X.

Βασικά χαρακτηριστικά

  1. Απαιτεί σκόπιμη χειρονομία με 3D αφή.
  2. Το Bounciness υποδηλώνει την απαιτούμενη χειρονομία.
  3. Το haptic feedback επιβεβαιώνει την ενεργοποίηση.

Θεωρία Σχεδιασμού

Η Apple ήθελε να δημιουργήσει ένα κουμπί που ήταν εύκολα και γρήγορα προσβάσιμο, αλλά δεν μπορούσε να ενεργοποιηθεί τυχαία. Η απαίτηση πίεσης δύναμης για την ενεργοποίηση του φακού είναι μια μεγάλη επιλογή, αλλά δεν διαθέτει οικονομικά στοιχεία και ανατροφοδότηση.

Προκειμένου να επιλυθούν αυτά τα προβλήματα, το κουμπί είναι ελαστικό και μεγαλώνει καθώς ο χρήστης εφαρμόζει δύναμη, υπονοώντας την απαιτούμενη χειρονομία. Επιπλέον, υπάρχουν δύο ξεχωριστές δονήσεις απτικής ανάδρασης: μία όταν εφαρμόζεται η απαιτούμενη δύναμη και άλλη όταν το κουμπί ενεργοποιείται καθώς μειώνεται η δύναμη. Αυτά τα απτικά μιμούνται τη συμπεριφορά ενός φυσικού κουμπιού.

Κρίσιμος κώδικας

Για να μετρήσουμε την ποσότητα δύναμης που εφαρμόζεται στο κουμπί, μπορούμε να χρησιμοποιήσουμε το αντικείμενο UITouch που παρέχεται σε συμβάντα επαφής.

αντικαταστήσει τις λειτουργίεςΜετά (_ αγγίζει: Ρυθμίστε , με συμβάν: UIEvent;) {
    super.touchesΜετά (αγγίζει, με: συμβάν)
    φρουρά αφήστε το άγγιγμα = touches.first else {επιστροφή}
    αφήστε force = touch.force / touch.maximumPossibleForce
    αφήστε την κλίμακα = 1 + (maxWidth / minWidth - 1) * δύναμη
    μετασχηματισμός = CGAffineTransform (κλίμακαX: κλίμακα, y: κλίμακα)
}}

Υπολογίζουμε έναν μετασχηματισμό κλίμακας με βάση την τρέχουσα δύναμη, έτσι ώστε το κουμπί να αυξάνεται με αυξανόμενη πίεση.

Επειδή το κουμπί μπορεί να πατηθεί αλλά δεν έχει ενεργοποιηθεί ακόμα, πρέπει να παρακολουθούμε την τρέχουσα κατάσταση του κουμπιού.

enum ForceState {
    επανεκκίνηση, ενεργοποιημένη, επιβεβαιωμένη
}}
ιδιωτική αφήστε resetForce: CGFloat = 0,4
ιδιωτική ενεργοποίησηForce: CGFloat = 0.5
ιδιωτική αφήστε επιβεβαίωσηForce: CGFloat = 0,49

Η κατοχή της δύναμης επιβεβαίωσης είναι ελαφρώς χαμηλότερη από τη δύναμη ενεργοποίησης, εμποδίζοντας τον χρήστη να ενεργοποιήσει και να απενεργοποιήσει γρήγορα το κουμπί, παρακάμπτοντας γρήγορα το όριο της δύναμης.

Για απτική ανατροφοδότηση, μπορούμε να χρησιμοποιήσουμε τις γεννήτριες ανατροφοδότησης της UIKit.

ιδιωτική αφετηρία ενεργοποίησηςFeedbackGenerator = UIImpactFeedbackGenerator (στυλ: .light)
ιδιωτική αφετηρία επιβεβαίωσηςFeedbackGenerator = UIImpactFeedbackGenerator (στυλ: .medium)

Τέλος, για τα animations bouncy, μπορούμε να χρησιμοποιήσουμε ένα UIViewPropertyAnimator με τις προσαρμοσμένες εντολές UISpringTimingParameters που δημιουργήσαμε πριν.

αφήστε params = UISpringTimingParameters (απόσβεση: 0,4, απόκριση: 0,2)
ας animator = UIViewPropertyAnimator (διάρκεια: 0, timingParameters: params)
animator.addAnimations {
    self.transform = CGAffineTransform (κλίμακαΧ: 1, γ: 1)
    self.backgroundColor = self.isOn? self.onColor: self.offColor
}}
animator.startAnimation ()

Διασύνδεση # 4: Καουτσούκ

Η συγκόλληση με καουτσούκ συμβαίνει όταν μια προβολή αντιστέκεται στην κίνηση. Ένα παράδειγμα είναι όταν μια προβολή κύλισης φτάνει στο τέλος του περιεχομένου της.

Βασικά χαρακτηριστικά

  1. Η διασύνδεση ανταποκρίνεται πάντα, ακόμα και όταν μια ενέργεια είναι άκυρη.
  2. Η παρακολούθηση αφής χωρίς συγχρονισμό υποδεικνύει ένα όριο.
  3. Η ποσότητα της κίνησης μειώνεται περαιτέρω από το όριο.

Θεωρία Σχεδιασμού

Η καουτσούκ είναι ένας πολύ καλός τρόπος επικοινωνίας με άκυρες ενέργειες, δίνοντας ταυτόχρονα στον χρήστη μια αίσθηση ελέγχου. Υποδεικνύει απαλά ένα όριο, τραβώντας το ξανά σε έγκυρη κατάσταση.

Κρίσιμος κώδικας

Ευτυχώς, η ελαστικοποίηση είναι εύκολη στην εφαρμογή.

offset = pow (offset, 0,7)

Χρησιμοποιώντας έναν εκθέτη μεταξύ 0 και 1, η μετατόπιση της προβολής μετακινείται λιγότερο όσο πιο μακριά είναι από τη θέση ηρεμίας. Χρησιμοποιήστε έναν μεγαλύτερο εκθέτη για λιγότερη κίνηση και έναν μικρότερο εκθέτη για περισσότερη κίνηση.

Για λίγο περισσότερο περιβάλλον, αυτός ο κώδικας συνήθως υλοποιείται σε μια επανάκληση UIPanGestureRecognizer κάθε φορά που κινείται η αφή. Η μετατόπιση μπορεί να υπολογιστεί με το δέλτα μεταξύ της τρέχουσας και της αρχικής θέσης αφής και η μετατόπιση μπορεί να εφαρμοστεί με μετασχηματισμό μετάφρασης.

var offset = dotPoint.y - originalTouchPoint.y
offset = offset> 0; pow (μετατόπιση, 0,7): -pow (-offset, 0,7)
view.transform = CGAffineTransform (μετάφρασηΧ: 0, γ: μετατόπιση)

Σημείωση: Αυτό δεν είναι το πώς η Apple εκτελεί ελαστική ταινία με στοιχεία όπως οι προβολές κύλισης. Μου αρέσει αυτή η μέθοδος λόγω της απλότητας της, αλλά υπάρχουν πιο πολύπλοκες λειτουργίες για διαφορετικές συμπεριφορές.

Διασύνδεση # 5: Παύση επιτάχυνσης

Για να δείτε την εναλλαγή εφαρμογών στο iPhone X, ο χρήστης μετακινείται από το κάτω μέρος της οθόνης και παύει στο μέσο του. Αυτή η διεπαφή δημιουργεί αυτή τη συμπεριφορά.

Βασικά χαρακτηριστικά

  1. Η παύση υπολογίζεται βάσει της επιτάχυνσης της χειρονομίας.
  2. Η ταχύτερη διακοπή έχει ως αποτέλεσμα ταχύτερη απόκριση.
  3. Χωρίς χρονοδιακόπτες.

Θεωρία Σχεδιασμού

Οι διασυνδέσεις υγρών πρέπει να είναι γρήγορες. Μια καθυστέρηση από ένα χρονόμετρο, έστω και σύντομη, μπορεί να κάνει μια διεπαφή να αισθάνεται υποτονική.

Αυτή η διεπαφή είναι ιδιαίτερα δροσερή επειδή ο χρόνος αντίδρασής της βασίζεται στην κίνηση του χρήστη. Εάν σταματούν γρήγορα, η διεπαφή γρήγορα ανταποκρίνεται. Εάν σταματούν σιγά-σιγά, ανταποκρίνεται αργά.

Κρίσιμος κώδικας

Προκειμένου να μετρήσουμε την επιτάχυνση, μπορούμε να παρακολουθήσουμε τις πιο πρόσφατες τιμές της ταχύτητας της ταχείας κίνησης.

ιδιωτικές ταχύτητες var = [CGFloat] ()
ιδιωτικό κομμάτι func (ταχύτητα: CGFloat) {
    αν οι ταχύτητες 

Αυτός ο κώδικας ενημερώνει τον πίνακα ταχύτητας για να έχει πάντα τις τελευταίες επτά ταχύτητες, οι οποίες χρησιμοποιούνται για τον υπολογισμό της επιτάχυνσης.

Για να διαπιστώσουμε αν η επιτάχυνση είναι αρκετά μεγάλη, μπορούμε να μετρήσουμε τη διαφορά μεταξύ της πρώτης ταχύτητας στη σειρά μας έναντι της τρέχουσας ταχύτητας.

εάν η απόλυση (ταχύτητα)> 100 || abs (αντιστάθμιση) <50 {επιστροφή}
ας αναλογία = abs (firstRecordedVelocity - ταχύτητα) / abs (firstRecordedVelocity)
αν η αναλογία> 0,9 {
    pauseLabel.alpha = 1
    feedbackGenerator.impactOccurred ()
    hasPaused = true
}}

Ελέγουμε επίσης για να βεβαιωθείτε ότι η κίνηση έχει ελάχιστη μετατόπιση και ταχύτητα. Εάν η χειρονομία έχει χάσει πάνω από το 90% της ταχύτητάς της, θεωρούμε ότι έχει σταματήσει.

Η εφαρμογή μου δεν είναι τέλεια. Κατά τη δοκιμή μου φαίνεται να λειτουργεί αρκετά καλά, αλλά υπάρχει μια ευκαιρία για μια καλύτερη ευρετική μέτρηση της επιτάχυνσης.

Διασύνδεση # 6: Ανταπόκριση Momentum

Ένα συρτάρι με ανοικτές και κλειστές καταστάσεις που έχει καταιγισμό με βάση την ταχύτητα της χειρονομίας.

Βασικά χαρακτηριστικά

  1. Πατώντας το συρτάρι, το ανοίγει χωρίς καταιγισμό.
  2. Το τράβηγμα του συρταριού το ανοίγει με φουσκωτά.
  3. Διαδραστική, διακοπτόμενη και αναστρέψιμη.

Θεωρία Σχεδιασμού

Αυτό το συρτάρι δείχνει την έννοια της επιβράβευσης της ορμής. Όταν ο χρήστης σπρώξει μια άποψη με ταχύτητα, είναι πολύ πιο ικανοποιητικό να ζωντανέψει την θέα με καταιγισμό. Αυτό κάνει τη διασύνδεση ζωντανή και διασκεδαστική.

Όταν το συρτάρι τρυπηθεί, κινείται χωρίς καταιγισμό, που αισθάνεται κατάλληλη, καθώς μια βρύση δεν έχει δυναμική σε μια συγκεκριμένη κατεύθυνση.

Κατά το σχεδιασμό προσαρμοσμένων αλληλεπιδράσεων, είναι σημαντικό να θυμάστε ότι οι διεπαφές μπορούν να έχουν διαφορετικές κινούμενες εικόνες για διαφορετικές αλληλεπιδράσεις.

Κρίσιμος κώδικας

Για να απλοποιήσουμε τη λογική της απόκρισης σε σχέση με τη μετατόπιση, μπορούμε να χρησιμοποιήσουμε μια προσαρμοσμένη υποκλάση αναγνώρισης χειρονομίας που εισέρχεται αμέσως στην κατάσταση εκκίνησης σε επαφή με το άγγιγμα.

classInstantPanGestureRecognizer: UIPanGestureRecognizer {
    αντικατάσταση λειτουργιώνBegan (_ αγγίζει: Ρυθμίστε , με συμβάν: UIEvent) {
        super.touchesBegan (αγγίζει, με: εκδήλωση)
        αυτοκρατορία =
    }}
}}

Αυτό επιτρέπει επίσης στον χρήστη να αγγίξει το συρτάρι κατά τη διάρκεια της κίνησης του για να το θέσει σε παύση, όμοια με την πατώντας σε μια προβολή κύλισης που βρίσκεται αυτή τη στιγμή σε κύλιση. Για να χειριστούμε τις βρύσες, μπορούμε να ελέγξουμε αν η ταχύτητα είναι μηδενική όταν η χειρονομία τελειώσει και συνεχίσει την κίνηση.

αν yVelocity == 0 {
    animator.continueAnimation (μεTimingParameters: μηδέν, durationFactor: 0)
}}

Για να χειριστούμε μια κίνηση με ταχύτητα, πρέπει πρώτα να υπολογίσουμε την ταχύτητά της σε σχέση με τη συνολική εναπομένουσα μετατόπιση.

ας fractionRemaining = 1 - animator.fractionComplete
αφήστε distanceRemaining = fractionFemaining * closedTransform.ty
αν distanceRemaining == 0 {
    animator.continueAnimation (μεTimingParameters: μηδέν, durationFactor: 0)
    Διακοπή
}}
ας σχετικήVelocity = abs (yVelocity) / distanceRemaining

Μπορούμε να χρησιμοποιήσουμε αυτή τη σχετική ταχύτητα για να συνεχίσουμε την κίνηση με τις παραμέτρους χρονισμού που περιλαμβάνουν λίγη βροχή.

αφήστε timingParameters = UISpringTimingParameters (απόσβεση: 0,8, απόκριση: 0,3, αρχικήVelocity: CGVector (dx: relativeVelocity, dy: relativeVelocity))
αφήστε newDuration = UIViewPropertyAnimator (διάρκεια: 0, timingParameters: timingParameters) .duration
ας durationFactor = CGFloat (newDuration / animator.duration)
animator.continueAnimation (μεTimingParameters: timingParameters, durationFactor: durationFactor)

Εδώ δημιουργούμε ένα νέο UIViewPropertyAnimator για να υπολογίσουμε το χρόνο που θα πρέπει να λάβει η κινούμενη εικόνα, ώστε να μπορέσουμε να παρέχουμε το σωστό durationFactor όταν συνεχίζουμε την κίνηση.

Υπάρχουν περισσότερες περιπλοκές που σχετίζονται με την αντιστροφή της κινούμενης εικόνας που δεν πρόκειται να καλύψω εδώ. Εάν θέλετε να μάθετε περισσότερα, έγραψα ένα πλήρες σεμινάριο για αυτό το στοιχείο: Δημιουργία καλύτερο iOS App Animations.

Διασύνδεση # 7: FaceTime PiP

Επαναδημιουργία του UI της εικόνας σε εικόνα της εφαρμογής FaceTime iOS.

Βασικά χαρακτηριστικά

  1. Ελαφριά, ευάερη αλληλεπίδραση.
  2. Η προβαλλόμενη θέση βασίζεται στο ρυθμό επιβράδυνσης του UIScrollView.
  3. Συνεχής κινούμενη εικόνα που σέβεται την αρχική ταχύτητα της χειρονομίας.

Κρίσιμος κώδικας

Ο απώτερος στόχος μας είναι να γράψουμε κάτι τέτοιο.

ας params = UISpringTimingParameters (απόσβεση: 1, απόκριση: 0,4, initialVelocity: relativeInitialVelocity)
ας animator = UIViewPropertyAnimator (διάρκεια: 0, timingParameters: params)
animator.addAnimations {
    self.pipView.center = πλησιέστερηCornerPosition
}}
animator.startAnimation ()

Θα θέλαμε να δημιουργήσουμε ένα κινούμενο σχέδιο με μια αρχική ταχύτητα που να ταιριάζει με την ταχύτητα της pan χειρονομίας και να ζωντανέψει το pip στην πλησιέστερη γωνία.

Πρώτον, ας υπολογίσουμε την αρχική ταχύτητα.

Για να γίνει αυτό, πρέπει να υπολογίσουμε μια σχετική ταχύτητα με βάση την τρέχουσα ταχύτητα, την τρέχουσα θέση και τη θέση στόχου.

ας σχετικήInitialVelocity = CGVector (
    dx: σχετικήVelocity (γιαVelocity: velocity.x, από: pipView.center.x, έως: nearestCornerPosition.x),
    dy: relativeVelocity (γιαVelocity: velocity.y, από: pipView.center.y έως: nearestCornerPosition.y)
)
func relativeVelocity (για ταχύτητα ταχύτητας: CGFloat, από currentValue: CGFloat, για targetValue: CGFloat) -> CGFloat {
    guard currentValue - targetValue! = 0 αλλιώς {επιστροφή 0}
    ταχύτητα επιστροφής / (targetValue - currentValue)
}}

Μπορούμε να χωρίσουμε την ταχύτητα στα συστατικά x και y και να καθορίσουμε τη σχετική ταχύτητα για κάθε μία.

Στη συνέχεια, ας υπολογίσουμε τη γωνία για την κίνηση του PiP.

Για να καταστήσουμε τη διεπαφή μας φυσική και ελαφριά, θα προβάλλουμε την τελική θέση του PiP με βάση την τρέχουσα κίνηση του. Εάν η PiP έπρεπε να γλιστρήσει και να σταματήσει, πού θα έφτανε;

let decelerationRate = UIScrollView.DecelerationRate.normal.rawValue
ας ταχύτητα = αναγνωριστής.παρακάτω (σε: άποψη)
ας projectedPosition = CGPoint (
    x: pipView.center.x + έργο (αρχικήVelocity: velocity.x, decelerationRate: decelerationRate),
    y: pipView.center.y + project (initialVelocity: velocity.y, decelerationRate: decelerationRate)
)
αφήστε nearestCornerPosition = πλησιέστεροCorner (σε: projectedPosition)

Μπορούμε να χρησιμοποιήσουμε τον ρυθμό επιβράδυνσης μιας UIScrollView για να υπολογίσουμε αυτή τη θέση ηρεμίας. Αυτό είναι σημαντικό επειδή αναφέρει τη μυϊκή μνήμη του χρήστη για κύλιση. Εάν ένας χρήστης γνωρίζει πόσο μακριά προβάλλει μια προβολή, μπορεί να χρησιμοποιήσει αυτήν την προηγούμενη γνώση για να υπολογίσει με ενήλικες πόση δύναμη χρειάζεται για να μετακινήσει τον PiP στον επιθυμητό στόχο.

Αυτός ο ρυθμός επιβράδυνσης είναι επίσης πολύ γενναιόδωρος, καθιστώντας την αλληλεπίδραση να αισθάνεται ελαφριά - μόνο μια μικρή κίνηση είναι απαραίτητη για να στείλετε το PiP που πετάει σε όλη τη διαδρομή σε όλη την οθόνη.

Μπορούμε να χρησιμοποιήσουμε τη λειτουργία προβολής που παρέχεται στη φόρμα "Σχεδίαση διεπαφών υγρού" για να υπολογίσετε την τελική προβαλλόμενη θέση.

/// Απόσταση που διανύθηκε μετά την επιβράδυνση σε μηδενική ταχύτητα με σταθερό ρυθμό.
func (αρχικήVelocity: CGFloat, επιβράδυνσηRate: CGFloat) -> CGFloat {
    επιστροφή (initialVelocity / 1000) * επιβράδυνσηRate / (1 - decelerationRate)
}}

Το τελευταίο κομμάτι που λείπει είναι η λογική για να βρεθεί η πλησιέστερη γωνία με βάση την προβαλλόμενη θέση. Για να γίνει αυτό, μπορούμε να βγάλουμε από όλες τις γωνιακές θέσεις και να βρούμε εκείνο με τη μικρότερη απόσταση από την προβαλλόμενη θέση προσγείωσης.

func πλησιέστεροςΚορνέας (στο σημείο: CGPoint) -> CGPoint {
    var minDistance = CGFloat.greatestFiniteMagnitude
    var closestPosition = CGPoint.zero
    για θέση στο pipPositions {
        απόσταση απόσταση = σημείο.distance (σε: θέση)
        αν η απόσταση 

Για να συνοψίσουμε την τελική υλοποίηση: Χρησιμοποιούμε το ρυθμό επιβράδυνσης του UIScrollView για να προβάλουμε την κίνηση του pip στην τελική θέση ανάπαυσης και να υπολογίσουμε τη σχετική ταχύτητα για να τα τροφοδοτήσουμε όλα σε UISpringTimingParameters.

Διασύνδεση # 8: Περιστροφή

Εφαρμογή των εννοιών από τη διεπαφή PiP σε μια κινούμενη περιστροφή.

Βασικά χαρακτηριστικά

  1. Χρησιμοποιεί προβολή για να σεβαστεί την ταχύτητα της χειρονομίας.
  2. Πάντα τελειώνει με έναν έγκυρο προσανατολισμό.

Κρίσιμος κώδικας

Ο κώδικας εδώ είναι πολύ παρόμοιος με την προηγούμενη διεπαφή PiP. Θα χρησιμοποιήσουμε τα ίδια δομικά στοιχεία, εκτός από την εναλλαγή της πλησιέστερης συνάρτησηςCorner για μια πλησιέστερη συνάρτηση Angle.

λειτουργικό έργο (...) {...}
func relativeVelocity (...) {...}
funk closestAngle (...) {...}

Όταν είναι καιρός να δημιουργήσουμε τελικά τα UISpringTimingParameters, πρέπει να χρησιμοποιήσουμε ένα CGVector για την αρχική ταχύτητα ακόμα κι αν η περιστροφή μας έχει μόνο μία διάσταση. Σε κάθε περίπτωση όπου η κινούμενη ιδιότητα έχει μόνο μία διάσταση, ορίστε την τιμή dx στην επιθυμητή ταχύτητα και ρυθμίστε την τιμή dy στο μηδέν.

αφήστε timingParameters = UISpringTimingParameters (
    απόσβεση: 0,8,
    απόκριση: 0,4,
    αρχικήVelocity: CGVector (dx: relativeInitialVelocity, dy: 0)
)

Εσωτερικά ο εμψυχωτής θα αγνοήσει την τιμή dy και θα χρησιμοποιήσει την τιμή dx για να δημιουργήσει την καμπύλη χρονισμού.

Δοκιμάστε τον εαυτό σας!

Αυτές οι διεπαφές είναι πολύ πιο διασκεδαστικές σε μια πραγματική συσκευή. Για να παίξετε μόνοι σας με αυτές τις διεπαφές, η εφαρμογή επίδειξης είναι διαθέσιμη στο GitHub.

Η εφαρμογή demo ρευστών διασυνδέσεων, διαθέσιμη στο GitHub!

Πρακτικές εφαρμογές

Για τους σχεδιαστές

  1. Σκεφτείτε τις διεπαφές ως ρευστά μέσα έκφρασης, όχι συλλογές στατικών στοιχείων.
  2. Εξετάστε τις κινήσεις και τις χειρονομίες από τη διαδικασία σχεδιασμού. Τα εργαλεία διάταξης όπως το Σκίτσο είναι φανταστικά, αλλά δεν προσφέρουν την πλήρη έκφραση της συσκευής.
  3. Πρωτότυπο με προγραμματιστές. Αποκτήστε σχεδιαστικούς προγραμματιστές που θα σας βοηθήσουν να σχεδιάσετε κινούμενα σχέδια, χειρονομίες και απτικές.

Για προγραμματιστές

  1. Εφαρμόστε τις συμβουλές από αυτές τις διεπαφές στα δικά σας προσαρμοσμένα στοιχεία. Σκεφτείτε πώς θα μπορούσαν να συνδυαστούν με νέους και ενδιαφέροντες τρόπους.
  2. Εκπαιδεύστε τους σχεδιαστές σας για νέες δυνατότητες. Πολλοί δεν γνωρίζουν την πλήρη δύναμη της τρισδιάστατης επαφής, των απτικών, των χειρονομιών και των εφέ κίνησης.
  3. Πρωτότυπο με σχεδιαστές. Βοηθήστε τους να δουν τα σχέδιά τους σε μια πραγματική συσκευή και να δημιουργήσουν εργαλεία που θα τους βοηθήσουν να σχεδιάσουν πιο αποτελεσματικά.

Εάν σας άρεσε αυτό το post, παρακαλώ αφήστε μερικά χτυπήματα.

Μπορείτε να χτυπήσετε έως και 50 φορές, οπότε κάντε κλικ / πατώντας!

Παρακαλώ μοιραστείτε την ανάρτηση με τους φίλους προγραμματιστών iOS / iOS στο social media outlet της επιλογής σας.

Αν σας αρέσει κάτι τέτοιο, θα πρέπει να ακολουθήσετε εμένα στο Twitter. Δημοσιεύω μόνο υψηλής ποιότητας tweets. twitter.com/nathangitter

Χάρη στον David Okun για την αναθεώρηση των σχεδίων αυτής της θέσης.