控制tvOS Focus Engine

介绍

在iOS上,用户通常通过设备的触摸屏与您的应用互动。 但是,在tvOS上,通过在屏幕视图之间移动当前焦点来处理用户交互。

幸运的是,UIKit API的tvOS实现自动处理了视图之间焦点的改变。 尽管此内置系统运行良好,但对于特定的视图布局和/或目的,有时可能需要手动控制焦点引擎。

在本教程中,我们将深入研究tvOS焦点引擎。 您将学习它的工作原理以及如何控制它。

先决条件

本教程要求您使用最新的tvOS 9.2 SDK运行Xcode 7.3或更高版本。 如果要继续学习,还需要从GitHub下载入门项目。

1.焦点引擎概述

tvOS焦点引擎的目的是帮助开发人员专注于自己应用程序的独特内容,而不是重新实现基本的导航行为。 这意味着,尽管许多用户将使用Apple TV的Siri Remote,但焦点引擎会自动支持所有当前和将来的Apple TV输入设备。

这意味着,作为开发人员,您不必担心用户如何与您的应用进行交互。 焦点引擎的另一个重要目标是在应用程序之间创建一致的用户体验。 因此,没有API允许应用程序移动焦点。

焦点运动

当用户通过沿特定方向在玻璃触摸屏上滑动而与Apple TV的遥控器进行交互时,聚焦引擎将在该方向上寻找可能的可聚焦视图,如果找到,则将焦点移至该视图。 如果找不到可聚焦的视图,则焦点将保持在当前位置。

除了在特定方向上移动焦点外,焦点引擎还处理其他一些更高级的行为,例如:

  • 例如,如果用户在Apple TV遥控器的Touch表面上快速滑动,则将焦点移过特定视图
  • 根据焦点变化的速度运行动画
  • 焦点改变时播放导航声音
  • 当焦点需要移动到当前屏幕外的视图时,可为动画滚动视图的偏移自动设置动画

在确定应将焦点移至应用程序中的位置时,焦点引擎会为应用程序当前界面拍摄内部图片,并突出显示所有可聚焦的可见元素。 这意味着任何隐藏的视图(包括Alpha值为0的视图)都无法聚焦。 这也意味着,对于被另一个视图隐藏的任何视图,焦点引擎仅考虑可见部分。

如果焦点引擎找到可以将焦点移至的视图,它将通知与更改有关的符合UIFocusEnvironment协议的对象。 符合UIFocusEnvironment协议的UIKit类为UIWindowUIViewControllerUIViewUIPresentationController 。 焦点引擎调用包含当前焦点视图或焦点正在移动到的视图的所有焦点环境对象的shouldUpdateFocusInContext(_:)方法。 如果这些方法调用中的任何一个返回false ,则焦点不会改变。

最初的重点

UIFocusEnvironment协议表示称为焦点环境的对象。 该协议定义了preferredFocusView属性,该属性指定当当前环境本身成为焦点时焦点应移动到的位置。

例如, UIViewController对象的默认preferredFocusView是其根视图。 由于每个UIView对象还可以指定其自己的首选焦点视图,因此可以创建首选焦点链 。 tvOS焦点引擎遵循此链,直到特定对象从其preferredFocusView属性返回selfnil为止。 通过使用这些属性,您可以在整个用户界面上重定向焦点,还可以指定当视图控制器出现在屏幕上时应首先聚焦哪个视图。

重要的是要注意,如果您不更改视图和视图控制器的任何preferredFocusView属性,则默认情况下,焦点引擎会将焦点集中在最靠近屏幕左上角的视图上。

焦点更新

当发生以下三个事件之一时,就会发生焦点更新:

  • 用户引起焦点移动
  • 该应用明确请求焦点更新
  • 系统触发并自动更新

每当进行更新时,都会发生以下事件:

  • 当前UIScreen对象的focusedView属性已更改为焦点移至的视图。
  • 焦点引擎调用涉及焦点更新的每个焦点环境对象的didUpdateFocusInContext(_:withAnimationCoordinator:) 。 这些对象与焦点引擎通过在更新焦点之前调用每个对象的shouldUpdateFocusInContext(_:)方法检查的对象集合相同。 至此,您可以添加自定义动画以与系统提供的与焦点相关的动画一起运行。
  • 所有协调的动画(系统动画和自定义动画)均同时运行。
  • 如果焦点正在移动到的视图当前不在屏幕上并且处于滚动视图中,则系统在屏幕上滚动视图,以便用户可以看到该视图。

要在用户界面中手动更新焦点,可以调用任何焦点环境对象的setNeedsFocusUpdate()方法。 这将重置焦点并将其移回到环境的preferredFocusView

该系统还可以在几种情况下触发自动焦点更新,包括当从视图层次结构中删除焦点视图,表或集合视图重新加载其数据时,或者在新视图控制器出现或关闭时。

尽管tvOS焦点引擎非常复杂并且有很多活动部件,但是提供给您的UIKit API使使用该系统非常容易,并且可以按您希望的方式工作。

2.控制聚焦引擎

重点指南

为了扩展焦点引擎,我们将实现环绕行为。 我们当前的应用程序具有六个按钮的网格,如下面的屏幕截图所示。

项目设置

我们要做的是允许用户将焦点从按钮3和6向右移动,并使焦点分别回绕到按钮1和4。 由于焦点引擎会忽略任何不可见的视图,因此无法通过插入不可见的UIView (包括宽度和高度为0的视图)并更改其preferredFocusedView属性来实现。

相反,我们可以使用UIFocusGuide类完成此操作。 此类是UILayoutGuide的子类, UILayoutGuide表示屏幕上的矩形可聚焦区域,同时完全不可见并且不与视图层次结构交互。 在所有UILayoutGuide属性和方法的顶部, UIFocusGuide类添加以下属性:

  • preferredFocusedView :该属性的作用与我之前所述的相同。 您可以将其视为希望焦点指南重定向到的视图。
  • enabled :此属性使您可以启用或禁用聚焦指南。

在您的项目中,打开ViewController.swift并实现ViewController类的viewDidAppear(_:)方法,如下所示:

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    
    let rightButtonIds = [3, 6]
    for 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)
        }
    }
    
    let leftButtonIds = [1, 4]
    for 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.trailingAnchor.constraintEqualToAnchor(button.leadingAnchor, constant: -60.0).active = true
            focusGuide.centerYAnchor.constraintEqualToAnchor(button.centerYAnchor).active = true
            focusGuide.preferredFocusedView = buttonWithTag(buttonId+2)
        }
    }
}

viewDidAppear(_:) ,我们在按钮3和6的右侧以及按钮1的左侧创建焦点指南。   和4.由于这些聚焦指南表示用户界面中的可聚焦区域,因此它们必须具有设置的高度和宽度。 使用此代码,我们可以使区域的大小与其他按钮相同,以便聚焦引擎基于动量的逻辑与可见按钮保持一致。

协调动画

为了说明协调动画的工作方式,我们在焦点改变时更新了按钮的alpha属性。 在ViewController.swift,实现didUpdateFocusInContext(_:withAnimationCoordinator:)在方法ViewController类:

override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
    super.didUpdateFocusInContext(context, withAnimationCoordinator: coordinator)
    
    if let focusedButton = context.previouslyFocusedView as? UIButton where buttons.contains(focusedButton) {
        coordinator.addCoordinatedAnimations({ 
            focusedButton.alpha = 0.5
        }, completion: { 
            // Run completed animation
        })
    }
}

didUpdateFocusInContext(_:withAnimationCoordinator:)context参数是一个UIFocusUpdateContext对象,它具有以下属性:

  • previouslyFocusedView :引用焦点从其移动的视图
  • nextFocusedView :引用焦点移至的视图
  • focusHeading :一个UIFocusHeading枚举值,表示焦点移动的方向

使用didUpdateFocusInContext(_:withAnimationCoordinator:) ,我们添加了一个协调动画,以将先前关注的按钮的alpha值更改为0.5,将当前关注的按钮的alpha值更改为1.0。

在模拟器中运行该应用程序,然后在用户界面中的按钮之间移动焦点。 您可以看到当前关注的按钮的alpha为1.0,而先前关注的按钮的alpha为0.5。

透明按钮

addCoordinatedAnimations(_:completion:)方法的第一个关闭与常规的UIView动画关闭类似。 不同之处在于它从焦点引擎继承了其持续时间和计时功能。

如果要运行具有自定义持续时间的动画,则可以使用OverrideInheritedDuration动画选项在此闭包内添加任何UIView动画。 以下代码是如何实现自定义动画的示例,该自定义动画的运行时间是焦点动画的一半:

// Running custom timed animation
let duration = UIView.inheritedAnimationDuration()
UIView.animateWithDuration(duration/2.0, delay: 0.0, options: .OverrideInheritedDuration, animations: { 
    // Animations
}, completion: { (completed: Bool) in
    // Completion block
})

通过使用UIFocusGuide类并利用自定义动画,可以扩展tvOS焦点引擎的标准行为来满足您的需求。

限制焦点引擎

如前所述,在决定是否应将焦点从一个视图移动到另一个视图时,焦点引擎会在涉及的每个焦点环境上调用shouldUpdateFocusInContext(_:)方法。 如果这些方法调用中的任何一个返回false ,则焦点不会改变。

在我们的应用程序中,我们将在ViewController类中重写此方法,以便如果当前焦点位于2或3的按钮不能向下移动焦点。为此,请在ViewController类中实现shouldUpdateFocusInContext(_:) ,如下所示:

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
        }
    }
    
    return super.shouldUpdateFocusInContext(context)
}

shouldUpdateFocusInContext(_:) ,我们首先检查先前聚焦的视图是按钮2还是3。然后检查聚焦标题。 如果标题等于Down ,则返回false ,以使当前焦点不变。

上一次运行您的应用程序。 您不能将焦点从按钮2和3向下移动到按钮5和6。

结论

现在,您应该可以轻松地控制和使用tvOS的焦点引擎。 现在,您知道了焦点引擎的工作原理,以及如何对其进行调整以适应您自己的Apple TV应用程序的任何需求。

与往常一样,请务必在下面的评论中留下您的评论和反馈。

翻译自: https://code.tutsplus.com/tutorials/taking-control-of-the-tvos-focus-engine--cms-26572

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值