What's New in tvOS11 まとめ

※ まだ正式リリースされていない情報なので変更の可能性があります⚠️

今回KeynoteではAmazon Prime Videoの話しかされなかったtvOSですが、API Referenceを見るとそれなりに変更があったので*What's New in tvOS*をまとめてみました👏 ※iOSと被っているところは省いています🙇

https://developer.apple.com/library/content/releasenotes/General/WhatsNewinTVOS/Articles/tvOS_11_0.html

## Focus Update Notifications
* Focusに対して更新処理が走る時に通知を受け取れるようになった
* Focusの移動に失敗した場合の通知も追加されている

```swift
static let UIFocusDidUpdate: NSNotification.Name

static let UIFocusMovementDidFail: NSNotification.Name
```

# Protocol Extensions
`UIFocusItem`プロトコルが拡張されて、自身が今Focusされているか確認できます。

```swift
var isFocused: Bool { get }
```

`UIFocusEnvironment`プロトコルが拡張されて、自身が他の*FocusEnvirionment*に含まれているか確認できます。

```swift
func contains(_ environment: UIFocusEnvironment) -> Bool
```

# Focus Animations
Focusの移動時にアニメーションを定義できます。

```swift
// for focusing item
func addCoordinatedFocusingAnimations(_ animations: ((UIFocusAnimationContext) -> Void)?,
                           completion: (() -> Void)? = nil)

// for unfocusing item
func addCoordinatedUnfocusingAnimations(_ animations: ((UIFocusAnimationContext) -> Void)?,
                             completion: (() -> Void)? = nil)
```

> Runs the specified set of animations together with the system animations for adding focus to an item.

今まで下記のようにやっていた処理はひとつの`addCoordinatedAnimations:completion`の中にフォーカス時とアンフォーカス時のアニメーションが入っていたので滑らかなアニメーションができませんでした😭

```swift
func collectionView(
  _ collectionView: UICollectionView,
  didUpdateFocusIn context: UICollectionViewFocusUpdateContext,
  with coordinator: UIFocusAnimationCoordinator) {

  coordinator.addCoordinatedAnimations({
    if let indexPath = context.nextFocusedIndexPath {

      let cell = collectionView.cellForItem(at: indexPath) as? NewCell
      cell?.imageView.transform = CGAffineTransform(rotationAngle: 90.0)
    }

    if let indexPath = context.previouslyFocusedIndexPath {

      let cell = collectionView.cellForItem(at: indexPath) as? NewCell
      cell?.imageView.transform = .identity
    }
  })
}
```

今回はフォーカス時とアンフォーカス時のアニメーションブロックが別で定義されている為、それぞれのアニメーションが滑らかに動きます。また、Focusの移動スピードに合わせてアニメーションの早さも調節されます👏(シミュレーターの`cmd+t`で見てみるとわかりやすいです)

```swift
  if let indexPath = context.nextFocusedIndexPath {

    let cell = collectionView.cellForItem(at: indexPath) as? NewCell
    coordinator.addCoordinatedFocusingAnimations({ animationContext in

      cell?.imageView.transform = CGAffineTransform(rotationAngle: 90.0)

    })
  }

  if let indexPath = context.previouslyFocusedIndexPath {

    let cell = collectionView.cellForItem(at: indexPath) as? NewCell
    coordinator.addCoordinatedUnfocusingAnimations({ animationContext in

      cell?.imageView.transform = .identity

    })
  }
```

# Custom Focus Sounds
* フォーカス移動時に流れる音をカスタマイズできるようになった
* `UIFocusSystem`というクラスが追加されたことによって可能になった

## UIFocusSystem
現在のユーザーインタフェース上にあるフォーカスの状態を管理するクラス。
今までは`updateFocusIfNeeded`のように自身のフォーカスの更新しかできませんでしたが、このクラスは全てのフォーカスの更新ができます。

```swift
func requestFocusUpdate(to environment: UIFocusEnvironment)

func updateFocusIfNeeded()
```

また、現在フォーカスされているオブジェクトやどのFocusEnvirionmentに含まれているかもチェックもできます。

```swift
weak var focusedItem: UIFocusItem? { get }

class func environment(_ environment: UIFocusEnvironment,
              contains otherEnvironment: UIFocusEnvironment) -> Bool
```

その中にひとつだけフォーカス時の音を変更するメソッドがあります。
ローカルのサウンドファイルのURLと識別子を指定するだけでアプリ内にグローバルに反映されます。ただし、サウンドファイルは30秒未満でなくてはいけません。

```swift
class func register(_ soundFileURL: URL,
 forSoundIdentifier identifier: UIFocusSoundIdentifier)
```

```swift
let url = Bundle.main.url(forResource: "a", withExtension: "m4a")!
let identifier = UIFocusSoundIdentifier(rawValue: "newSounds")
UIFocusSystem.register(url, forSoundIdentifier: identifier)
```

下記のようなサウンドの調整も行われるようです。

* スピードに合わせて音量が変更される
* フォーカスの移動方向(左右)によっても異なる

また、オブジェクトがフォーカスを更新するタイミングでカスタムとデフォルトとなしの切り替えもできます。例えばサイズの異なるオブジェクトになる場合は音を変えるというのは良いプラクティスだそうです👌

```swift
// UIFocusEnvironment Protocol
optional func soundIdentifierForFocusUpdate(in context: UIFocusUpdateContext) -> UIFocusSoundIdentifier?
```

```swift
override func soundIdentifierForFocusUpdate(
  in context: UIFocusUpdateContext) -> UIFocusSoundIdentifier? {

  return identifier
}
```

# Support for SceneKit
SceneKitとSpriteKitでもフォーカスを扱えるようになった
`UIFocusItem`の継承クラスに`SKNode`と`SCNode`が追加されている。

# Focus Update Logging
`-UIFocusUpdateLoggingEnabled=YES`の設定をXcode上で行うとフォーカスに関するログを吐く。素晴らしい。

```
The result of the focus update was determined from the following preferred focus search:
|
|	Starting preferred focus search:
|	|--> Searching <UIFocusSystem 0x60800008c1c0>...
|	     |--> Searching <UIScreen 0x6000001c2850>...
|	          |--> Searching <UIWindow 0x7fb5d1401c90>...
```

# UIFocusDebugger
フォーカスをデバッグするためのクラスが追加されました。
`po UIFocusDebugger.foo()`でFocusの状態を見たり検証したりできます。

* `status()`: フォーカスされているオブジェクトに関する情報
* `simulateFocusUpdateRequest(from: _)`: 特定のFocusEnvirionmentからフォーカスの更新を試せる
* `checkFocusability(for: _)`: Focusできるかチェックできる(できない場合は理由も分かる)
* `help()`: 使用できるコマンド(メソッド)全部出してくれる

```
(lldb) po UIFocusDebugger.status()
<TVOSample.SomeCell 0x7f80f5508340> is currently focused.
```

結構やりたかったことや実装が面倒だった部分が解消されている気がします。何より捉えにくいフォーカスをデバッグしやすくなったのは嬉しい🎉
[サンプルコード](https://github.com/hanawat/tvos11)もあげました。

おしまい👏