Construirea interfețelor cu fluide

Cum se creează gesturi și animații naturale pe iOS

La WWDC 2018, designerii Apple au prezentat o discuție intitulată „Proiectarea interfețelor fluide”, care explică raționamentul de design din spatele interfeței gestale a iPhone X.

Prezentarea Apple WWDC18 „Proiectarea interfețelor cu fluide”

Este discuția mea preferată WWDC vreodată - o recomand cu mare drag.

Discuția a oferit câteva îndrumări tehnice, care este excepțională pentru o prezentare de design, dar a fost pseudo-cod, lăsând o mulțime de necunoscute.

Unele coduri asemănătoare Swift din prezentare.

Dacă încercați să implementați aceste idei, puteți observa un decalaj între inspirație și implementare.

Scopul meu este de a reduce acest decalaj prin furnizarea de exemple de cod de lucru pentru fiecare subiect important în prezentare.

Cele opt (8) interfețe pe care le vom crea. Butoane, arcuri, interacțiuni personalizate și multe altele!

Iată o prezentare a ceea ce vom acoperi:

  1. Un scurt rezumat al discuției „Proiectarea interfețelor cu fluide”.
  2. Opt interfețe fluide, teoria designului din spatele lor și codul pentru a le construi.
  3. Aplicații pentru designeri și dezvoltatori.

Ce sunt interfețele fluide?

O interfață fluidă poate fi de asemenea numită „rapidă”, „lină”, „naturală” sau „magică”. Este o experiență fără fricțiuni care se simte „corect”.

Prezentarea WWDC vorbește despre interfețele fluide ca „o extensie a minții tale” și „o extensie a lumii naturale”. O interfață este fluidă atunci când se comportă în funcție de modul în care cred oamenii, nu de modul în care gândesc mașinile.

Ce le face fluide?

Interfețele fluide sunt sensibile, întrerupătoare și redirecționabile. Iată un exemplu al gestului glisant-de-acasă pe iPhone X:

Aplicațiile pot fi închise în timpul animației lor de lansare.

Interfața reacționează imediat la intrarea utilizatorului, poate fi oprită în orice moment al procesului și poate chiar schimba cursul la jumătatea drumului.

De ce ne pasă de interfețele fluide?

  1. Interfețele fluide îmbunătățesc experiența utilizatorului, făcând fiecare interacțiune să se simtă rapid, ușor și semnificativ.
  2. Acestea oferă utilizatorului un sentiment de control, care creează încredere cu aplicația și marca dvs.
  3. Sunt greu de construit. O interfață fluidă este greu de copiat și poate fi un avantaj competitiv.

Interfețele

Pentru restul acestui post, vă voi arăta cum să construiți opt (8) interfețe care acoperă toate subiectele majore din prezentare.

Icoane reprezentând cele opt (8) interfețe pe care le vom construi.

Interfața nr. 1: Butonul Calculatorului

Acesta este un buton care imită comportamentul butoanelor din aplicația de calculatoare iOS.

Caracteristici cheie

  1. Repere instantaneu la atingere.
  2. Poate fi atins rapid chiar și atunci când este animat la mijloc.
  3. Utilizatorul poate atinge și trage în afara butonului pentru a anula robinetul.
  4. Utilizatorul poate atinge în jos, trage în afară, trage înapoi și confirma robinetul.

Teoria proiectării

Ne dorim butoane care se simt receptive, recunoscând utilizatorului că sunt funcționale. În plus, dorim ca acțiunea să fie anulabilă dacă utilizatorul decide împotriva acțiunii sale după ce a atins. Acest lucru permite utilizatorilor să ia decizii mai rapide, deoarece pot efectua acțiuni în paralel cu gândirea.

Diapozitive din prezentarea WWDC care arată modul în care gesturile în paralel cu gândul fac acțiunile mai rapide.

Cod critic

Primul pas pentru crearea acestui buton este utilizarea unei subclase UIControl, nu a unei subclase UIButton. Un UIButton ar funcționa bine, dar, deoarece personalizăm interacțiunea, nu vom avea nevoie de nicio caracteristică.

CalculatorButton: UIControl {
    valoare publică var: Int = 0 {
        didSet {label.text = "\ (valoare)"}
    }
    private lazy var label: UILabel = {...} ()
}

În continuare, vom folosi UIControlEvents pentru a atribui funcții diferitelor interacțiuni tactile.

addTarget (auto, acțiune: #selector (touchDown), pentru: [.touchDown, .touchDragEnter])
addTarget (auto, acțiune: #selector (touchUp), pentru: [.touchUpInside, .touchDragExit, .touchCancel])

Grupăm evenimentele touchDown și touchDragEnter într-un singur „eveniment” numit touchDown și putem grupa evenimentele touchUpInside, touchDragExit și touchCancel într-un singur eveniment denumit touchUp.

(Pentru o descriere a tuturor UIControlEvents disponibile, consultați documentația.)

Acest lucru ne oferă două funcții pentru a gestiona animațiile.

private var animator = UIViewPropertyAnimator ()
@objc private func touchDown () {
    animator.stopAnimation (true)
    backgroundColor = evidențiatColor
}
@objc private func touchUp () {
    animator = UIViewPropertyAnimator (durata: 0,5, curba: .easeOut, animații: {
        self.backgroundColor = self.normalColor
    })
    animator.startAnimation ()
}

Pe touchDown, anulăm animația existentă dacă este nevoie și setăm instantaneu culoarea pe culoarea evidențiată (în acest caz, un gri deschis).

Pe touchUp, creăm un nou animator și începem animația. Utilizarea unui UIViewPropertyAnimator simplifică anularea animației.

(Notă laterală: Acesta nu este comportamentul exact al butoanelor din aplicația de calculatoare iOS, care permite o atingere care a început într-un buton diferit să o activeze dacă atingerea a fost târâtă în interiorul butonului. În cele mai multe cazuri, un buton precum cel Am creat aici este comportamentul prevăzut pentru butoanele iOS.)

Interfața nr. 2: animații de primăvară

Această interfață arată cum poate fi creată o animație de primăvară prin specificarea unei „amortizări” (bounciness) și „response” (viteză).

Caracteristici cheie

  1. Utilizează parametrii „prietenoși cu designul”.
  2. Fără concept de durată a animației.
  3. Ușor de întrerupt.

Teoria proiectării

Arcurile fac modele de animație grozave datorită vitezei și aspectului natural. O animație de primăvară începe incredibil de repede, majoritatea timpului urmând să se apropie treptat de starea sa finală. Acest lucru este perfect pentru a crea interfețe care să se simtă receptive - primesc la viață!

Câteva memento-uri suplimentare la proiectarea animațiilor de primăvară:

  1. Izvoarele nu trebuie să fie primăverii. Utilizarea unei valori amortizante de 1 va crea o animație care se va odihni încet, fără a fi supărată. Majoritatea animațiilor ar trebui să utilizeze o valoare amortizantă de 1.
  2. Încercați să evitați să vă gândiți la durată. În teorie, un izvor nu vine niciodată complet în repaus, iar forțarea unei durate pe primăvară îl poate face să se simtă nefiresc. În schimb, jucați-vă cu valorile de amortizare și răspuns până când se simte corect.
  3. Întreruperea este critică. Deoarece resorturile își petrec o mare parte din timp aproape de valoarea lor finală, utilizatorii ar putea crede că animația s-a finalizat și vor încerca să interacționeze din nou cu ea.

Cod critic

În UIKit, putem crea o animație de primăvară cu un obiect UIViewPropertyAnimator și un obiect UISpringTimingParameters. Din păcate, nu există un inițializator care să ia doar un amortizor și un răspuns. Cel mai apropiat pe care îl putem obține este inițializatorul UISpringTimingParameters care are o masă, rigiditate, amortizare și viteza inițială.

UISpringTimingParameters (masă: CGFloat, rigiditate: CGFloat, amortizare: CGFloat, initialVelocity: CGVector)

Am dori să creăm un inițializator convenabil care să ia o amortizare și un răspuns și să-l mapăm la masa necesară, rigiditate și amortizare.

Cu un pic de fizică, putem deriva ecuațiile de care avem nevoie:

Rezolvarea constantei arcului și a coeficientului de amortizare.

Cu acest rezultat, putem crea propriile noastre UISpringTimingParameters cu exact parametrii pe care îi dorim.

extensie UISpringTimingParameters {
    init initia (amortizare: CGFloat, raspuns: CGFloat, initialVelocity: CGVector = .zero) {
        lasa rigiditatea = pow (2 * .pi / raspuns, 2)
        lasa umed = 4 * .pi * amortizare / raspuns
        self.init (masă: 1, rigiditate: rigiditate, amortizare: umedă, inițialăVelocity: initialVelocity)
    }
}

Astfel vom specifica animațiile de primăvară pentru toate celelalte interfețe.

Fizica în spatele animațiilor de primăvară

Vrei să aprofundezi mai mult în animațiile de primăvară? Vezi această postare incredibilă de Christian Schnorr: Demystifying UIKit Spring Animations.

După ce i-am citit postarea, animațiile de primăvară au făcut clic în final pentru mine. Strigări enorme către Christian pentru că m-au ajutat să înțeleg matematica din spatele acestor animații și că m-au învățat cum să rezolv ecuațiile diferențiale de ordinul doi.

Interfața nr. 3: Buton lanternă

Un alt buton, dar cu un comportament mult diferit. Acest lucru imită comportamentul butonului lanternei de pe ecranul de blocare al iPhone X.

Caracteristici cheie

  1. Necesită un gest intenționat cu atingere 3D.
  2. Bounciness indică gestul necesar.
  3. Feedback-ul Haptic confirmă activarea.

Teoria proiectării

Apple a dorit să creeze un buton ușor și rapid accesibil, dar nu a putut fi declanșat accidental. Necesarul de presiune forțată pentru activarea lanternei este o alegere excelentă, dar lipsește disponibilitatea și feedback-ul.

Pentru a rezolva aceste probleme, butonul este primăvara și crește pe măsură ce utilizatorul aplică forța, sugerând gestul necesar. În plus, există două vibrații separate ale feedback-ului haptic: una atunci când se aplică cantitatea necesară de forță și alta când butonul activează, deoarece forța este redusă. Aceste haptice imită comportamentul unui buton fizic.

Cod critic

Pentru a măsura cantitatea de forță aplicată pe buton, putem utiliza obiectul UITouch furnizat în evenimentele tactile.

Anulați funcția atingeMovedit (_ atinge: Setați , cu eveniment: UIEvent?) {
    super.touchesMoved (atingeri, cu: eveniment)
    guard let touch = touches.first else {return}
    let force = touch.force / touch.maximumPossibleForce
    let scale = 1 + (maxWidth / minWidth - 1) * force
    transform = CGAffineTransform (scaleX: scale, y: scale)
}

Calculăm o transformare a scării pe baza forței curente, astfel încât butonul să crească odată cu creșterea presiunii.

Deoarece butonul poate fi apăsat, dar nu a fost încă activat, trebuie să urmărim starea actuală a butonului.

enumera ForceState {
    resetarea cazului, activată, confirmată
}
private let resetForce: CGFloat = 0.4
private let activationForce: CGFloat = 0,5
private let confirmForce: CGFloat = 0.49

Având forța de confirmare să fie puțin mai mică decât forța de activare, împiedică utilizatorul să activeze rapid și să dezactiveze butonul trecând rapid pragul forței.

Pentru feedback haptic, putem utiliza generatoarele de feedback ale UIKit.

private let activationFeedbackGenerator = UIImpactFeedbackGenerator (stil: .light)
private let confirmFeedbackGenerator = UIImpactFeedbackGenerator (stil: .medium)

În cele din urmă, pentru animațiile bouncy, putem utiliza un UIViewPropertyAnimator cu inițializatoarele personalizate UISpringTimingParameters pe care le-am creat înainte.

let params = UISpringTimingParameters (amortizare: 0,4, răspuns: 0,2)
let animator = UIViewPropertyAnimator (durata: 0, timingParameters: params)
animator.addAnimations {
    self.transform = CGAffineTransform (scaleX: 1, y: 1)
    self.backgroundColor = self.isOn? self.onColor: self.offColor
}
animator.startAnimation ()

Interfața nr. 4: bandă de cauciuc

Bandajul de cauciuc apare atunci când o vedere rezistă la mișcare. Un exemplu este atunci când o vedere derulantă ajunge la sfârșitul conținutului său.

Caracteristici cheie

  1. Interfața este întotdeauna receptivă, chiar și atunci când o acțiune este invalidă.
  2. Urmărirea tactilă sincronizată indică o graniță.
  3. Cantitatea de mișcare scade mai departe de graniță.

Teoria proiectării

Bandă de cauciuc este o modalitate excelentă de a comunica acțiuni invalide, oferind în același timp utilizatorului un sentiment de control. Indică ușor o graniță, trăgându-le înapoi într-o stare valabilă.

Cod critic

Din fericire, banda de cauciuc este simplă de pus în aplicare.

offset = pow (compensare, 0,7)

Utilizând un exponent între 0 și 1, compensarea vizualizării este deplasată mai puțin cu cât este mai departe de poziția de repaus. Utilizați un exponent mai mare pentru mai puțină mișcare și un exponent mai mic pentru mai multă mișcare.

Pentru un context puțin mai mare, acest cod este de obicei implementat într-un apel invers UIPanGestureRecognizer ori de câte ori atingerea se mișcă. Decalarea poate fi calculată cu delta dintre locațiile actuale și cele originale, iar decalarea poate fi aplicată cu o transformare de traducere.

var offset = touchPoint.y - originalTouchPoint.y
offset = offset> 0? pow (offset, 0,7): -pow (-offset, 0,7)
view.transform = CGAffineTransform (traducereX: 0, y: offset)

Notă: Acesta nu este modul în care Apple realizează bandă de cauciuc cu elemente precum vizualizările de defilare. Îmi place această metodă datorită simplității sale, dar există funcții mai complexe pentru comportamente diferite.

Interfața nr. 5: Pauză de accelerare

Pentru a vizualiza comutatorul de aplicații de pe iPhone X, utilizatorul glisează din partea de jos a ecranului și se întrerupe la mijloc. Această interfață creează acest comportament.

Caracteristici cheie

  1. Pauză este calculată pe baza accelerării gestului.
  2. Oprirea mai rapidă are ca rezultat un răspuns mai rapid.
  3. Fără cronometre.

Teoria proiectării

Interfețele fluide ar trebui să fie rapide. Întârzierea de la un cronometru, chiar dacă este scurtă, poate face ca o interfață să se simtă lent.

Această interfață este deosebit de cool, deoarece timpul său de reacție se bazează pe mișcarea utilizatorului. Dacă se întrerup rapid, interfața răspunde rapid. Dacă se întrerup încet, răspunde încet.

Cod critic

Pentru a măsura accelerația, putem urmări cele mai recente valori ale vitezei gestului pan.

private var speeds [CGFloat] ()
track de funcții private (viteză: CGFloat) {
    if velocities.count 

Acest cod actualizează tabloul de viteze pentru a avea întotdeauna ultimele șapte viteze, care sunt utilizate pentru a calcula accelerația.

Pentru a determina dacă accelerația este suficient de mare, putem măsura diferența dintre prima viteză din tabloul nostru față de viteza curentă.

dacă abs (viteză)> 100 || abs (compensat) <50 {return}
let ratio = abs (firstRecordedVelocity - viteza) / abs (firstRecordedVelocity)
if ratio> 0,9 {
    pauseLabel.alpha = 1
    feedbackGenerator.impactOccurred ()
    hasPaused = true
}

De asemenea, verificăm să ne asigurăm că mișcarea are o deplasare și o viteză minime. Dacă gestul a pierdut mai mult de 90% din viteza sa, considerăm că este în pauză.

Implementarea mea nu este perfectă. În testarea mea pare să funcționeze destul de bine, dar există o oportunitate pentru un euristic mai bun pentru a măsura accelerația.

Interfața # 6: Momentul de recompensare

Un sertar cu stări deschise și închise care are bounciness bazat pe viteza gestului.

Caracteristici cheie

  1. Atingerea sertarului îl deschide fără zgomot.
  2. Făcând clic pe sertar îl deschide cu bounciness.
  3. Interactiv, întreruptibil și reversibil.

Teoria proiectării

Acest sertar arată conceptul de impuls răsplătitor. Când utilizatorul glisează o vedere cu viteză, este mult mai satisfăcător să animăm vizualizarea cu bounciness. Acest lucru face ca interfața să se simtă vie și distractivă.

Atunci când sertarul este apăsat, acesta se animă fără sărituri, ceea ce se simte adecvat, deoarece un robinet nu are un impuls într-o anumită direcție.

Atunci când proiectăm interacțiuni personalizate, este important să rețineți că interfețele pot avea animații diferite pentru interacțiuni diferite.

Cod critic

Pentru a simplifica logica apăsării versus panoramării, putem folosi o subclase de recunoaștere a gesturilor personalizate care intră imediat în starea de început la atingere.

class InstantPanGestureRecognizer: UIPanGestureRecognizer {
    Anulați funcția atingeBegan (_ atinge: Setați , cu eveniment: UIEvent) {
        super.touchesBegan (atingeri, cu: eveniment)
        self.state = .began
    }
}

Acest lucru permite, de asemenea, utilizatorului să atingă sertarul în timpul mișcării sale pentru a-l întrerupe, similar cu apăsarea pe o vedere de defilare care se derulează în prezent. Pentru a gestiona robinetele, putem verifica dacă viteza este zero la încheierea gestului și să continuăm animația.

if yVelocity == 0 {
    animator.continueAnimation (cuTimingParameters: nil, durataFactor: 0)
}

Pentru a gestiona un gest cu viteză, trebuie mai întâi să-i calculăm viteza în raport cu deplasarea totală rămasă.

let fractionRemaining = 1 - animator.fractionComplete
let distanceRemaining = fractionRemaining * închisTransform.ty
if distanceRemaining == 0 {
    animator.continueAnimation (cuTimingParameters: nil, durataFactor: 0)
    pauză
}
let relativeVelocity = abs (yVelocity) / distanceRemaining

Putem folosi această viteză relativă pentru a continua animația cu parametrii de sincronizare care includ un pic de bounciness.

let timingParameters = UISpringTimingParameters (amortizare: 0,8, răspuns: 0,3, inițialVelocity: CGVector (dx: relativeVelocity, dy: relativeVelocity))
let newDuration = UIViewPropertyAnimator (durata: 0, timingParameters: timingParameters) .duration
let durationFactor = CGFloat (newDuration / animator.duration)
animator.continueAnimation (cuTimingParameters: timingParameters, durationFactor: durationFactor)

Aici creăm un nou UIViewPropertyAnimator pentru a calcula timpul pe care ar trebui să-l dureze animația, astfel încât să putem oferi durata corectă a factorului atunci când continuăm animația.

Există mai multe complexități legate de inversarea animației pe care nu o voi acoperi aici. Dacă doriți să aflați mai multe, am scris un tutorial complet pentru această componentă: Building Better iOS App Animations.

Interfața # 7: FaceTime PiP

O re-creare a interfeței de imagine pentru imagini din aplicația iOS FaceTime.

Caracteristici cheie

  1. Interacțiune ușoară, aerisită.
  2. Poziția proiectată se bazează pe rata de decelerare a UIScrollView.
  3. Animație continuă care respectă viteza inițială a gestului.

Cod critic

Scopul nostru final este să scriem așa ceva.

let params = UISpringTimingParameters (amortizare: 1, răspuns: 0,4, inițialVelocity: relativeInitialVelocity)
let animator = UIViewPropertyAnimator (durata: 0, timingParameters: params)
animator.addAnimations {
    self.pipView.center = proksimCornerPosition
}
animator.startAnimation ()

Am dori să creăm o animație cu o viteză inițială care să se potrivească cu viteza gestului pan și să animăm pip-ul până în cel mai apropiat colț.

În primul rând, să calculăm viteza inițială.

Pentru a face acest lucru, trebuie să calculăm o viteză relativă pe baza vitezei curente, a poziției curente și a poziției țintă.

let relativeInitialVelocity = CGVector (
    dx: relativVelocity (pentruVelocity: velocity.x, de la: pipView.center.x, până la: nearestCornerPosition.x),
    dy: relativeVelocity (pentruVelocity: velocity.y, de la: pipView.center.y, până la: nearestCornerPosition.y)
)
func relativVelocity (for Velocity speed: CGFloat, from currentValue: CGFloat, to targetValue: CGFloat) -> CGFloat {
    guard currentValue - targetValue! = 0 else {return 0}
    viteza de retur / (targetValue - currentValue)
}

Putem împărți viteza în componentele sale x și y și să determinăm viteza relativă pentru fiecare.

În continuare, să calculăm colțul pentru care să fie animat PiP.

Pentru a face interfața noastră să se simtă naturală și ușoară, vom proiecta poziția finală a PiP pe baza mișcării sale actuale. Dacă PiP-ul ar glisa și s-ar opri, unde va ateriza?

let decelerationRate = UIScrollView.DecelerationRate.normal.rawValue
let speedity = recognizer.velocity (în: vizualizare)
let projectedPosition = CGPoint (
    x: proiectul pipView.center.x + (inițialVelocity: velocity.x, decelerationRate: decelerationRate),
    y: proiectul pipView.center.y + (inițialVelocity: velocity.y, decelerationRate: decelerationRate)
)
let nearestCornerPosition = nearestCorner (to: projectedPosition)

Putem folosi rata de decelerare a unui UIScrollView pentru a calcula această poziție de repaus. Acest lucru este important deoarece se referă la memoria musculară a utilizatorului pentru derulare. Dacă un utilizator știe cât de departe derulează o vedere, poate utiliza cunoștințele anterioare pentru a ghici intuitiv cât de multă forță este necesară pentru a muta PiP-ul la ținta dorită.

Această rată de decelerare este, de asemenea, destul de generoasă, ceea ce face ca interacțiunea să se simtă ușoară - este nevoie doar de un mic flick pentru a trimite PiP-ul care zboară pe tot ecranul.

Putem folosi funcția de proiecție furnizată în discuția „Proiectarea interfețelor cu fluide” pentru a calcula poziția finală proiectată.

/// Distanța parcursă după decelerarea la viteza zero la o viteză constantă.
proiect de funcții (initialVelocity: CGFloat, decelerationRate: CGFloat) -> CGFloat {
    return (initialVelocity / 1000) * decelerationRate / (1 - decelerationRate)
}

Ultima piesă care lipsește este logica de a găsi cel mai apropiat colț pe baza poziției proiectate. Pentru a face acest lucru, putem face buclă prin toate pozițiile colțului și a găsi cea cu cea mai mică distanță până la poziția de aterizare proiectată.

func nearCorner (la punctul: CGPoint) -> CGPoint {
    var minDistance = CGFloat.greatestFiniteMagnitude
    var closePosition = CGPoint.zero
    pentru poziție în pipPositions {
        let distance = point.distance (până la: poziție)
        dacă distanța 

Pentru a rezuma implementarea finală: Folosim rata de decelerare a UIScrollView pentru a proiecta mișcarea pipului în poziția finală de repaus și calculăm viteza relativă pentru a o alimenta pe toate în UISpringTimingParameters.

Interfața # 8: Rotire

Aplicarea conceptelor din interfața PiP la o animație de rotație.

Caracteristici cheie

  1. Utilizează proiecția pentru a respecta viteza gestului.
  2. Se încheie întotdeauna într-o orientare validă.

Cod critic

Codul de aici este foarte similar cu interfața PiP anterioară. Vom folosi aceleași blocuri de construcție, cu excepția schimbării celei mai apropiate funcții Corner pentru o funcție CloseAngle.

func proiect (...) {...}
func relativVelocity (...) {...}
func closeAngle (...) {...}

Când este timpul să creăm în cele din urmă UISpringTimingParameters, trebuie să folosim un CGVector pentru viteza inițială, chiar dacă rotația noastră are o singură dimensiune. În orice caz în care proprietatea animată are o singură dimensiune, setați valoarea dx la viteza dorită și setați valoarea dy la zero.

let timingParameters = UISpringTimingParameters (
    amortizare: 0,8,
    raspuns: 0,4,
    initialVelocity: CGVector (dx: relativeInitialVelocity, dy: 0)
)

În interior, animatorul va ignora valoarea dy și va folosi valoarea dx pentru a crea curba de sincronizare.

Incearca-l tu insuti!

Aceste interfețe sunt mult mai distractive pe un dispozitiv real. Pentru a vă juca singur cu aceste interfețe, aplicația demo este disponibilă pe GitHub.

Aplicația demonstrată de interfețe fluide, disponibilă pe GitHub!

Aplicații practice

Pentru designeri

  1. Gândiți-vă la interfețe ca mediu fluid de expresie, nu la colecții de elemente statice.
  2. Luați în considerare animațiile și gesturile devreme în procesul de proiectare. Instrumentele de aspect precum Sketch sunt fantastice, dar nu oferă expresivitatea completă a dispozitivului.
  3. Prototip cu dezvoltatori. Obțineți dezvoltatori gândiți la design care să vă ajute prototipuri de animații, gesturi și haptici.

Pentru dezvoltatori

  1. Aplicați sfaturile de la aceste interfețe la componentele dvs. personalizate. Gândiți-vă cum ar putea fi combinate în moduri noi și interesante.
  2. Educați-vă designerii cu privire la posibilitățile noi. Mulți nu sunt conștienți de puterea deplină a atingerii 3D, a hapticii, a gesturilor și a animațiilor primăverii.
  3. Prototip cu designeri. Ajută-i să-și vadă design-urile pe un dispozitiv real și să creeze instrumente care să-i ajute să proiecteze mai eficient.

Dacă v-a plăcut această postare, vă rugăm să lăsați câteva clapete.

Puteți aplauda de până la 50 de ori, așa că faceți clic / atingeți!

Vă rugăm să împărtășiți postarea cu prietenii dvs. de designeri / dezvoltatori iOS de pe site-ul dvs. de socializare.

Dacă vă plac acest gen de lucruri, ar trebui să mă urmați pe Twitter. Postez doar tweet-uri de înaltă calitate. twitter.com/nathangitter

Mulțumesc lui David Okun pentru revizuirea proiectelor acestei postări.