Su iOS, gli utenti interagiscono normalmente con le tue app tramite il touch screen del dispositivo. Su tvOS, tuttavia, l'interazione dell'utente viene gestita spostando la corrente messa a fuoco tra le visualizzazioni sullo schermo.
Fortunatamente, le implementazioni tvOS delle API UIKit gestiscono automaticamente il cambio di focalizzazione tra le viste. Sebbene questo sistema integrato funzioni molto bene, per specifici layout di visualizzazione e / o scopi, può essere necessario a volte controllare manualmente il motore di messa a fuoco.
In questo tutorial, diamo uno sguardo approfondito al motore di messa a fuoco tvOS. Impari come funziona e come controllarlo come vuoi.
Questo tutorial richiede l'esecuzione di Xcode 7.3 o versioni successive con l'ultimo SDK di TVOS 9.2. Se vuoi seguire, devi anche scaricare il progetto iniziale da GitHub.
Lo scopo del motore di messa a fuoco di tvOS è aiutare gli sviluppatori a concentrarsi sui contenuti esclusivi della propria app piuttosto che reimplementare i comportamenti di navigazione di base. Ciò significa che, mentre molti utenti useranno Siri Remote di Apple TV, il motore di messa a fuoco supporta automaticamente tutti i dispositivi di input Apple TV attuali e futuri.
Ciò significa che, come sviluppatore, non devi preoccuparti di come un utente sta interagendo con la tua app. Un altro obiettivo importante del motore di messa a fuoco è creare un'esperienza utente coerente tra le applicazioni. Per questo motivo, non esiste un'API che consenta a un'applicazione di spostare l'attenzione.
Quando l'utente interagisce con il telecomando di Apple TV facendo scorrere la superficie del Touch Glass in una direzione particolare, il motore di messa a fuoco cerca una possibile vista focalizzabile in quella direzione e, se trovato, sposta lo stato attivo su quella vista. Se non viene trovata alcuna vista focalizzabile, la messa a fuoco rimane dove si trova attualmente.
Oltre a spostare lo stato attivo in una particolare direzione, il motore di messa a fuoco gestisce anche molti altri comportamenti più avanzati, come ad esempio:
Quando determini dove spostare l'attenzione in un'app, il motore di messa a fuoco acquisisce un'immagine interna dell'interfaccia corrente della tua app e mette in risalto tutti gli elementi visibili che sono focalizzabili. Ciò significa che le visualizzazioni nascoste, comprese le visualizzazioni con un valore alfa pari a 0, non possono essere focalizzate. Ciò significa anche che, per qualsiasi vista nascosta da un'altra vista, solo la parte visibile viene considerata dal motore di messa a fuoco.
Se il motore di messa a fuoco trova una vista su cui può spostare lo stato attivo, notifica gli oggetti conformi a UIFocusEnvironment
protocollo che sono coinvolti con il cambiamento. Le classi UIKit conformi al UIFocusEnvironment
protocollo sono UIWindow
, UIViewController
, UIView
, e UIPresentationController
. Il motore di messa a fuoco chiama il shouldUpdateFocusInContext (_ :)
metodo di tutti gli oggetti dell'ambiente di messa a fuoco che contengono la vista attualmente focalizzata o la vista su cui si sposta l'elemento attivo. Se qualcuno di questi metodi chiama restituisce falso
, l'attenzione non è cambiata.
Il UIFocusEnvironment
protocollo rappresenta un oggetto noto come a ambiente di messa a fuoco. Il protocollo definisce a preferredFocusView
proprietà che specifica dove spostare l'attenzione se l'ambiente corrente si focalizza.
Ad esempio, a UIViewController
l'oggetto predefinito preferredFocusView
è la sua vista radice. Come ciascuno UIView
l'oggetto può anche specificare la propria vista di messa a fuoco preferita, a catena di messa a fuoco preferita può essere creato. Il motore di messa a fuoco tvOS segue questa catena fino a quando non restituisce un oggetto particolare se stesso
o zero
dal suo preferredFocusView
proprietà. Utilizzando queste proprietà, è possibile reindirizzare lo stato attivo dell'interfaccia utente e specificare anche quale vista deve essere focalizzata per prima quando viene visualizzato sullo schermo un controller di visualizzazione.
È importante notare che, se non si modifica nessuno dei preferredFocusView
proprietà delle viste e dei controller di visualizzazione, il focus per motore predefinito focalizza la vista più vicina all'angolo in alto a sinistra dello schermo.
Un aggiornamento di messa a fuoco si verifica quando uno dei tre eventi ha luogo:
Ogni volta che si verifica un aggiornamento, seguono i seguenti eventi:
UIScreen
oggetto di focusedView
la proprietà viene modificata in base alla vista su cui si sta concentrando l'attenzione.didUpdateFocusInContext (_: withAnimationCoordinator :)
di ogni oggetto dell'ambiente di messa a fuoco coinvolto nell'aggiornamento della messa a fuoco. Si tratta della stessa serie di oggetti che il motore di messa a fuoco controlla chiamando ogni oggetto shouldUpdateFocusInContext (_ :)
metodo prima di aggiornare lo stato attivo. È a questo punto che è possibile aggiungere animazioni personalizzate da eseguire in concomitanza con le animazioni correlate al fuoco fornite dal sistema.Per aggiornare manualmente lo stato attivo nell'interfaccia utente, è possibile richiamare il setNeedsFocusUpdate ()
metodo di qualsiasi oggetto dell'ambiente di messa a fuoco. Questo ripristina la messa a fuoco e la riporta all'ambiente preferredFocusView
.
Il sistema può anche attivare un aggiornamento automatico della messa a fuoco in diverse situazioni, anche quando una vista focalizzata viene rimossa dalla gerarchia della vista, una vista tabella o collezione ricarica i suoi dati o quando un nuovo controller di visualizzazione viene presentato o ignorato.
Mentre il motore di messa a fuoco tvOS è piuttosto complesso e ha molte parti mobili, le API UIKit fornite rendono molto facile utilizzare questo sistema e farlo funzionare come si desidera.
Per estendere il motore di messa a fuoco, implementeremo un comportamento di avvolgimento. La nostra app attuale ha una griglia di sei pulsanti, come mostrato nello screenshot qui sotto.
Quello che faremo è consentire all'utente di spostare la messa a fuoco verso destra, dai pulsanti 3 e 6, e fare in modo che la messa a fuoco ritorni attorno ai pulsanti 1 e 4 rispettivamente. Poiché il motore di messa a fuoco ignora qualsiasi vista invisibile, ciò non può essere fatto inserendo un invisibile UIView
(includendo una vista con larghezza e altezza pari a 0) e cambiandone la sua preferredFocusedView
proprietà.
Invece, possiamo realizzare questo usando il UIFocusGuide
classe. Questa classe è una sottoclasse di UILayoutGuide
e rappresenta una regione rettangolare focalizzabile sullo schermo mentre è completamente invisibile e non interagisce con la gerarchia della vista. Oltre a tutto il UILayoutGuide
proprietà e metodi, il UIFocusGuide
la classe aggiunge le seguenti proprietà:
preferredFocusedView
: Questa proprietà funziona come ho descritto in precedenza. Puoi pensare a questo come alla vista in cui desideri che la guida di focus reindirizzi.abilitato
: Questa proprietà consente di abilitare o disabilitare la guida alla messa a fuoco.Nel tuo progetto, apri ViewController.swift e implementare il viewDidAppear (_ :)
metodo del ViewController
classe come mostrato di seguito:
override func viewDidAppear (animato: Bool) super.viewDidAppear (animato) let rightButtonIds = [3, 6] per buttonId in rightButtonIds if let button = buttonWithTag (buttonId) let focusGuide = UIFocusGuide () view.addLayoutGuide (focusGuide) focusGuide .widthAnchor.constraintEqualToAnchor (button.widthAnchor) .active = true focusGuide.heightAnchor.constraintEqualToAnchor (button.heightAnchor) .active = true focusGuide.leadingAnchor.constraintEqualToAnchor (button.trailingAnchor, constant: 60.0) .active = true focusGuide.centerYAnchor.constraintEqualToAnchor (button.centerYAnchor) .active = true focusGuide.preferredFocusedView = buttonWithTag (buttonId-2) lascia leftButtonIds = [1, 4] per buttonId in leftButtonIds if let button = buttonWithTag (buttonId) let focusGuide = UIFocusGuide () visualizza .addLayoutGuide (focusGuide) focusGuide.widthAnchor.constraintEqualToAnchor (button.widthAnchor) .active = true focusGuide.heightAnchor.constraintEqualToAnchor (button.heightAnchor) .active = true focusGuide.tr ailingAnchor.constraintEqualToAnchor (button.leadingAnchor, constant: -60.0) .active = true focusGuide.centerYAnchor.constraintEqualToAnchor (button.centerYAnchor) .active = true focusGuide.preferredFocusedView = buttonWithTag (buttonId + 2)
Nel viewDidAppear (_ :)
, creiamo le guide di messa a fuoco a destra dei pulsanti 3 e 6 e a sinistra dei pulsanti 1e 4. Poiché queste guide di messa a fuoco rappresentano una regione focalizzabile nell'interfaccia utente, devono avere un'altezza e una larghezza impostate. Con questo codice, rendiamo le regioni delle stesse dimensioni degli altri pulsanti in modo che la logica basata sul momentum del motore di messa a fuoco sia coerente con i pulsanti visibili.
Per illustrare come funzionano le animazioni coordinate, aggiorniamo il alfa
proprietà dei pulsanti quando cambia lo stato attivo. Nel ViewController.swift, implementare il didUpdateFocusInContext (_: withAnimationCoordinator :)
metodo nel ViewController
classe:
override func didUpdateFocusInContext (context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) super.didUpdateFocusInContext (context, withAnimationCoordinator: coordinator) se let focusedButton = context.previouslyFocusedView as? UIButton dove buttons.contains (focusedButton) coordinator.addCoordinatedAnimations (focusedButton.alpha = 0.5, completamento: // Esegui animazione completata)
Il contesto
parametro di didUpdateFocusInContext (_: withAnimationCoordinator :)
è un UIFocusUpdateContext
oggetto che ha le seguenti proprietà:
previouslyFocusedView
: fa riferimento alla vista dalla quale si sta spostando l'attenzionenextFocusedView
: fa riferimento alla vista su cui si sta concentrando l'attenzionefocusHeading
: a UIFocusHeading
valore di enumerazione che rappresenta la direzione in cui si sta muovendo l'attenzioneCon l'implementazione di didUpdateFocusInContext (_: withAnimationCoordinator :)
, aggiungiamo un'animazione coordinata per cambiare il valore alfa del pulsante precedentemente focalizzato a 0.5 e quello del pulsante correntemente focalizzato a 1.0.
Esegui l'app nel simulatore e sposta lo stato attivo tra i pulsanti nell'interfaccia utente. Puoi vedere che il pulsante attualmente focalizzato ha un alfa di 1.0 mentre il pulsante focalizzato in precedenza ha un alfa di 0,5.
La prima chiusura del addCoordinatedAnimations (_: completamento :)
il metodo funziona in modo simile a un normale UIView
chiusura dell'animazione. La differenza è che eredita la sua durata e la funzione di cronometraggio dal motore di messa a fuoco.
Se si desidera eseguire un'animazione con una durata personalizzata, è possibile aggiungerne uno UIView
animazione all'interno di questa chiusura con il OverrideInheritedDuration
opzione di animazione. Il seguente codice è un esempio di come implementare un'animazione personalizzata che viene eseguita in metà tempo delle animazioni di attivazione:
// L'esecuzione di un'animazione temporizzata personalizzata lascia duration = UIView.inheritedAnimationDuration () UIView.animateWithDuration (duration / 2.0, delay: 0.0, opzioni: .OverrideInheritedDuration, animazioni: // Animations, completamento: (completato: Bool) in // Blocco di completamento)
Usando il UIFocusGuide
classe e utilizzando animazioni personalizzate, è possibile estendere il comportamento standard del motore di messa a fuoco tvOS in base alle proprie esigenze.
Come ho detto prima, quando si decide se spostare o meno la messa a fuoco da una vista a un'altra, il motore di messa a fuoco chiama il shouldUpdateFocusInContext (_ :)
metodo su ogni ambiente di messa a fuoco coinvolto. Se qualcuno di questi metodi chiama restituisce falso
, l'attenzione non è cambiata.
Nella nostra app, abbiamo intenzione di sovrascrivere questo metodo in ViewController
classe in modo che lo stato attivo non possa essere spostato verso il basso se il pulsante attualmente focalizzato è 2 o 3. Per fare ciò, attuare shouldUpdateFocusInContext (_ :)
nel ViewController
classe come mostrato di seguito:
override func shouldUpdateFocusInContext (context: UIFocusUpdateContext) -> Bool let focusedButton = context.previouslyFocusedView as? UIButton se focusedButton == buttonWithTag (2) || focusedButton == buttonWithTag (3) if context.focusHeading == .Down return false restituisce super.shouldUpdateFocusInContext (contesto)
Nel shouldUpdateFocusInContext (_ :)
, per prima cosa controlliamo se la vista focalizzata in precedenza è il pulsante 2 o 3. Verifichiamo quindi la direzione della messa a fuoco. Se l'intestazione è uguale a Giù
, torniamo falso
in modo che la messa a fuoco corrente non cambi.
Esegui l'app un'ultima volta. Non è possibile spostare lo stato attivo dai pulsanti 2 e 3 ai pulsanti 5 e 6.
Ora dovresti essere a tuo agio nel controllare e lavorare con il motore di messa a fuoco di tvOS. Ora sai come funziona il motore di messa a fuoco e come puoi manipolarlo per adattarlo a qualsiasi esigenza delle tue app di Apple TV.
Come sempre, assicurati di lasciare i tuoi commenti e feedback nei commenti qui sotto.