Controle nemen over de tvOS Focus Engine

Invoering

Op iOS communiceren gebruikers normaal gesproken met uw apps via het aanraakscherm van het apparaat. Bij tvOS wordt de gebruikersinteractie afgehandeld door de stroom te verplaatsen focus tussen weergaven op het scherm.

Gelukkig zorgen de tvOS-implementaties van de UIKit API's ervoor dat de focus automatisch wordt gewijzigd tussen weergaven. Hoewel dit ingebouwde systeem erg goed werkt, kan het voor specifieke indelingslay-outs en / of doeleinden soms nodig zijn om de focusengine handmatig te besturen.

In deze zelfstudie bekijken we de tvOS-focusengine grondig. Je leert hoe het werkt en hoe je het kunt besturen hoe je wilt.

voorwaarden

Voor deze zelfstudie moet je Xcode 7.3 of hoger gebruiken met de nieuwste tvOS 9.2 SDK. Als je mee wilt, moet je ook het startersproject van GitHub downloaden.

1. Focus Engine-overzicht

Het doel van de focusengine van tvOS is om ontwikkelaars te helpen zich te concentreren op de unieke inhoud van hun eigen app in plaats van het basisnavigatiegedrag opnieuw uit te voeren. Dit betekent dat, hoewel veel gebruikers de Siri Remote van Apple TV zullen gebruiken, de focus-engine automatisch alle huidige en toekomstige Apple TV-invoerapparaten ondersteunt.

Dit betekent dat u als ontwikkelaar geen zorgen hoeft te maken over hoe een gebruiker met uw app omgaat. Een ander belangrijk doel van de focusengine is het creëren van een consistente gebruikerservaring tussen applicaties. Daarom is er geen API waarmee een toepassing de focus kan verplaatsen.

Focusbeweging

Wanneer de gebruiker interactie heeft met de afstandsbediening van de Apple TV door op het aanraakoppervlak van het glas in een bepaalde richting te vegen, zoekt de focusmotor naar een mogelijk focusseerbaar beeld in die richting en verplaatst de focus, indien gevonden, naar die weergave. Als er geen focusbare weergave wordt gevonden, blijft de focus waar deze zich op dat moment bevindt.

Naast het verplaatsen van de focus in een bepaalde richting, behandelt de focusengine ook verschillende andere, geavanceerdere gedragingen, zoals:

  • de focus voorbij bepaalde weergaven verplaatsen, bijvoorbeeld als de gebruiker snel veegt op het aanraakoppervlak van de Apple TV-afstandsbediening
  • uitvoeren van animaties met snelheden op basis van de snelheid van de focusverandering
  • het afspelen van navigatiegeluiden wanneer de focus verandert
  • bewegende scroll-weergave compenseert automatisch wanneer de focus moet worden verplaatst naar een op dit moment niet-schermweergave

Bij het bepalen waar de focus naartoe moet gaan in een app, maakt de focusengine een interne afbeelding van de huidige interface van uw app en markeert alle zichtbare elementen die kunnen worden scherpgesteld. Dit betekent dat verborgen weergaven, inclusief weergaven met een alpha-waarde van 0, niet kunnen worden scherpgesteld. Dit betekent ook dat voor elke weergave die is verborgen door een andere weergave, alleen het zichtbare gedeelte door de focusengine wordt beschouwd.

Als de focusengine een weergave vindt die de focus kan verplaatsen, geeft deze de objecten die aan de UIFocusEnvironment protocol dat betrokken is bij de verandering. De UIKit-klassen die voldoen aan de UIFocusEnvironment protocol zijn UIWindow, UIViewControllerUIView, en UIPresentationController. De focusmotor roept de shouldUpdateFocusInContext (_ :) methode van alle focusomgeving-objecten die ofwel de huidige gefocuste weergave bevatten of de weergave waarnaar de focus verplaatst. Als een van deze methoden oproepen retourneert vals, de focus is niet veranderd.

Eerste focus

De UIFocusEnvironment protocol staat voor een object dat bekend staat als a focus omgeving. Het protocol definieert een preferredFocusView eigenschap die aangeeft waar de focus naartoe zou moeten gaan als de huidige omgeving zelf wordt gefocust.

Bijvoorbeeld a UIViewController de standaard van het object preferredFocusView is de rootweergave. Zoals elke UIView object kan ook zijn eigen gewenste focusweergave specificeren, a gewenste focusketen kan worden gemaakt. De focusmotor van tvOS volgt deze keten tot een bepaald object ook terugkeert zelf of nul van zijn preferredFocusView eigendom. Door deze eigenschappen te gebruiken, kunt u de focus omleiden door de gebruikersinterface en ook opgeven welke weergave als eerste moet worden scherpgesteld wanneer een weergaveregelaar op het scherm wordt weergegeven.

Het is belangrijk om op te merken dat, als u geen van de preferredFocusView eigenschappen van uw weergaven en view controllers, de focus by default engine richt de weergave die zich het dichtst bij de linkerbovenhoek van het scherm bevindt.

Focus Update

Een focusupdate vindt plaats wanneer een van de drie gebeurtenissen plaatsvindt:

  • de gebruiker veroorzaakt een focusbeweging
  • de app vraagt ​​expliciet om een ​​update van de focus
  • het systeem activeert en automatische update

Wanneer een update plaatsvindt, volgen de volgende gebeurtenissen:

  • De huidige UIScreen voorwerpen focusedView eigenschap is gewijzigd in de weergave waarnaar de focus wordt verplaatst.
  • De focusmotor roept de didUpdateFocusInContext (_: withAnimationCoordinator :) van elk focusomgevingobject dat betrokken is bij de focusupdate. Dit zijn dezelfde set objecten die de focusengine controleert door het object van elk object te bellen shouldUpdateFocusInContext (_ :) methode voordat de focus wordt bijgewerkt. Op dit punt kunt u aangepaste animaties toevoegen om te gebruiken in combinatie met de focusgerelateerde animaties die het systeem biedt.
  • Alle gecoördineerde animaties, zowel systeem- als aangepaste animaties, worden gelijktijdig uitgevoerd.
  • Als de weergave waarnaar de focus wordt verplaatst momenteel buiten het scherm ligt en in een schuifweergave schuift het systeem de weergave op het scherm zodat het beeld zichtbaar wordt voor de gebruiker.

Om de focus in de gebruikersinterface handmatig bij te werken, kunt u de setNeedsFocusUpdate () methode van elk focusomgevingobject. Dit stelt de focus opnieuw in en verplaatst deze terug naar de omgeving preferredFocusView.

Het systeem kan ook een automatische focusupdate activeren in verschillende situaties, inclusief wanneer een gefocuste weergave wordt verwijderd uit de weergavehiërarchie, een tabel of collectieweergave de gegevens herlaadt of wanneer een nieuwe weergavecontroller wordt gepresenteerd of ontslagen.

Hoewel de focusmotor van tvOS vrij complex is en veel bewegende delen bevat, maken de UIKit-API's die aan u worden verstrekt het zeer gemakkelijk om dit systeem te gebruiken en het te laten werken zoals u het wilt.

2. De focusmotor besturen

Focusgidsen

Om de focusengine uit te breiden, gaan we een wrap-around-gedrag implementeren. Onze huidige app heeft een raster van zes knoppen, zoals weergegeven in de onderstaande schermafbeelding.

Wat we gaan doen is de gebruiker toestaan ​​om de focus naar rechts te verplaatsen, van de knoppen 3 en 6, en de focus terug te wikkelen naar knoppen 1 en 4 respectievelijk. Omdat de focusmachine geen onzichtbare weergaven negeert, kan dit niet worden gedaan door een onzichtbare in te voegen UIView (inclusief een weergave met een breedte en hoogte van 0) en de bijbehorende te wijzigen preferredFocusedView eigendom.

In plaats daarvan kunnen we dit bereiken met behulp van de UIFocusGuide klasse. Deze klasse is een subklasse van UILayoutGuide en vertegenwoordigt een rechthoekig focusseerbaar gebied op het scherm terwijl het volledig onzichtbaar is en geen interactie heeft met de beeldhiërarchie. Op de top van alle UILayoutGuide eigenschappen en methoden, de UIFocusGuide klasse voegt de volgende eigenschappen toe:

  • preferredFocusedView: Deze eigenschap werkt zoals ik eerder heb beschreven. U kunt dit zien als de weergave waarnaar u in de focusgids wilt doorsturen.
  • ingeschakeld: Met deze eigenschap kunt u de focusgids in- of uitschakelen.

In uw project, open ViewController.swift en implementeer de viewDidAppear (_ :) methode van de ViewController klasse zoals hieronder getoond:

override func viewDidAppear (geanimeerd: Bool) super.viewDidAppear (geanimeerd) laat rightButtonIds = [3, 6] voor 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) laat leftButtonIds = [1, 4] voor buttonId in leftButtonIds 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.tr ailingAnchor.constraintEqualToAnchor (button.leadingAnchor, constant: -60.0) .active = true focusGuide.centerYAnchor.constraintEqualToAnchor (button.centerYAnchor) .active = true focusGuide.preferredFocusedView = buttonWithTag (buttonId + 2)

In viewDidAppear (_ :), we maken focusgidsen rechts van de knoppen 3 en 6 en links van de knoppen 1en 4. Aangezien deze focusgeleiders een focusseerbaar gebied in de gebruikersinterface vertegenwoordigen, moeten ze een ingestelde hoogte en breedte hebben. Met deze code maken we de gebieden even groot als de andere knoppen, zodat de op het momentum gebaseerde logica van de focusengine consistent aanvoelt met de zichtbare knoppen.

Gecoördineerde animaties

Om te illustreren hoe gecoördineerde animaties werken, werken we de alpha eigenschap van de knoppen wanneer de focus verandert. In ViewController.swift, implementeer de didUpdateFocusInContext (_: withAnimationCoordinator :) methode in de ViewController klasse:

negeer func didUpdateFocusInContext (context: UIFocusUpdateContext, metAnimationCoordinator coordinator: UIFocusAnimationCoordinator) super.didUpdateFocusInContext (context, withAnimationCoordinator: coordinator) if let focusedButton = context.previouslyFocusedView als? UIButton waar buttons.contains (focusedButton) coordinator.addCoordinatedAnimations (focusedButton.alpha = 0.5, completion: // voltooide animatie uitvoeren)

De context parameter van didUpdateFocusInContext (_: withAnimationCoordinator :) is een UIFocusUpdateContext object met de volgende eigenschappen:

  • previouslyFocusedView: verwijst naar de weergave van waaruit de focus verschuift
  • nextFocusedView: verwijst naar de weergave waar de focus naartoe gaat
  • focusHeading: een UIFocusHeading enumeratiewaarde die de richting aangeeft waarin de focus zich verplaatst

Met de implementatie van didUpdateFocusInContext (_: withAnimationCoordinator :), we voegen een gecoördineerde animatie toe om de alpha-waarde van de eerder gefocuste knop te wijzigen in 0,5 en die van de momenteel gefocuste knop in 1.0.

Start de app in de simulator en verplaats de focus tussen de knoppen in de gebruikersinterface. U kunt zien dat de knop die momenteel is scherpgesteld een alpha van 1.0 heeft, terwijl de knop die eerder is scherpgesteld een alpha van 0.5 heeft.

De eerste sluiting van de addCoordinatedAnimations (_: voltooiing :) methode werkt op dezelfde manier als een regulier UIView animatie sluiting. Het verschil is dat het de duur en timingfunctie van de focusmachine overneemt.

Als u een animatie met een aangepaste duur wilt uitvoeren, kunt u deze toevoegen UIView animatie binnen deze sluiting met de OverrideInheritedDuration animatie optie. De volgende code is een voorbeeld van het implementeren van een aangepaste animatie die in de helft van de tijd van de focusanimaties wordt uitgevoerd:

// Aangepaste getimede animaties laten doorlopen = UIView.inheritedAnimationDuration () UIView.animateWithDuration (duur / 2.0, vertraging: 0.0, opties: .OverrideInheritedDuration, animaties: // Animations, completion: (completed: Bool) in // Voltooiingsblok)

Door de UIFocusGuide klasse en door gebruik te maken van aangepaste animaties, kunt u het standaardgedrag van de tvOS-focusengine uitbreiden om aan uw behoeften te voldoen.

Beperking van de focus-engine

Zoals ik eerder al zei, vraagt ​​de focusmachine om het bepalen of de focus al dan niet van de ene naar de andere moet worden verplaatst shouldUpdateFocusInContext (_ :) methode op elke betrokken focusomgeving. Als een van deze methoden oproepen retourneert vals, de focus is niet veranderd.

In onze app gaan we deze methode negeren in de ViewController klasse zodat de focus niet naar beneden kan worden verplaatst als de op dat moment scherpgestelde knop 2 of 3 is. Implementeer dit shouldUpdateFocusInContext (_ :) in de ViewController klasse zoals hieronder getoond:

override func shouldUpdateFocusInContext (context: UIFocusUpdateContext) -> Bool let focusedButton = context.previouslyFocusedView as? UIButton if focusedButton == buttonWithTag (2) || focusedButton == buttonWithTag (3) if context.focusHeading == .Down return false retourneer super.shouldUpdateFocusInContext (context)

In shouldUpdateFocusInContext (_ :), we controleren eerst of de eerder gefocuste weergave knop 2 of 3 is. We inspecteren vervolgens de focuskop. Als de kop gelijk is aan naar beneden, we komen terug vals zodat de huidige focus niet verandert.

Voer uw app nog één keer uit. U kunt de focus niet verplaatsen van de knoppen 2 en 3 naar de knoppen 5 en 6.

Conclusie

Je zou nu comfortabel moeten zijn met het besturen en werken met de focus-engine van tvOS. Je weet nu hoe de focusengine werkt en hoe je hem kunt manipuleren om te voldoen aan wat je maar nodig hebt voor je eigen Apple TV-apps.

Laat zoals altijd uw opmerkingen en feedback achter in de opmerkingen hieronder.