コレクションビューセルのImage OverlayをtvOS10でも実現する


# Image Overlayとは
tvOS11で登場した、UIImageViewにUIViewを重ねてParallaxアニメーションさせることができるAPIです。`var overlayContentView: UIView`にaddSubviewしていく形になります。
この例だと、`Free`のラベルがフォーカス時に外にはみ出してアニメーションしています。
![image](https://github.com/toshi0383/assets/raw/master/ImageOverlay/imageoverlay-tvos11.gif?raw=true)

ParallaxエフェクトするのはtvOS10まではUICollectionViewCellではなくUIImageViewのImageのみだったので、もし画像の上に何かオーバーレイしたい場合、tvOS10までは **全部画像にする必要があります。**
動的なデータの場合無理やんと思うかもしれませんが、ゴリっとやってみたら意外とうまくいきました。AbemaTVでは少し前からこの実装が入っていたのですが、この機会にOSS化しました。

# toshi0383/ImageOverlay
https://github.com/toshi0383/ImageOverlay


これを使うと、tvOS9,10でも簡単にImage OverlayっぽいUIを実現できます。
tvOS9,10では`Free`のラベルは画像としてレンダリングされるので、画像からはみ出さず一緒に拡大、アニメーションされてるのがわかると思います。
![image](https://github.com/toshi0383/assets/raw/master/ImageOverlay/imageoverlay-tvos10.gif?raw=true)


呼び出しはこんな感じになります。

```swift
import ImageOverlay
import UIKit

final class CollectionViewCell: UICollectionViewCell {
    @IBOutlet private weak var imageView: UIImageView!

...

    override func prepareForReuse() {
        super.prepareForReuse()
        imageView.io.clearOverlays()
    }
    func configure() {
        let image = UIImage(named: "High Sierra")!
        let overlays: [OverlayProtocol] = [ViewAsOverlay()]
        imageView.io.addOverlays(with: image, overlays: overlays)
    }
```

## OverlayViewProtocol
OverlayViewProtocolでUIViewを返してあげると、UIView -> [CALayer] => UIImageに変換してくれます。オートレイアウトにも対応しているので、いつも通りView階層を組んでいけば問題ありません。xibから初期化するのでももちろんいいです。

```swift
import ImageOverlay

struct ViewAsOverlay: OverlayViewProtocol {
    var view: UIView {
        let frame = CGRect(x: 0, y: 0, width: 400, height: 225)
        let v = UIView(frame: frame)
        v.backgroundColor = .red
        v.alpha = 0.3
        let child = UIView()
        child.alpha = 1.0
        child.backgroundColor = .blue
        child.translatesAutoresizingMaskIntoConstraints = false
        v.addSubview(child)
        NSLayoutConstraint.activate([
            child.centerXAnchor.constraint(equalTo: v.centerXAnchor),
            child.centerYAnchor.constraint(equalTo: v.centerYAnchor),
            child.widthAnchor.constraint(equalToConstant: 100),
            child.heightAnchor.constraint(equalToConstant: 100),
            ])
        return v
    }
}
```

## OverlayProtocol
細かく設定をしたCALayerを直接返したい場合は、OverlayProtocolを利用します。

```swift
public struct TextOverlay: OverlayProtocol {
    public let layers: [CALayer]
    public init(text: String, font: UIFont, foregroundColor: UIColor = .white, size: CGSize, textOrigin: CGPoint) {
        let textLayer = _textLayer(text: text, font: font, foregroundColor: foregroundColor, origin: textOrigin, size: size, scale: Scale.value)
        self.layers = [textLayer]
    }
}

private func _textLayer(text: String, font: UIFont, foregroundColor: UIColor, origin: CGPoint, size: CGSize, scale: CGFloat) -> CALayer {
    let textSize = NSString(string: text).size(withAttributes: [NSAttributedStringKey.font: font])
    let scaledTextSize = textSize.scaled(scale)
    let scaledOrigin = origin.scaled(scale)
    let textLayer = CATextLayer()
    textLayer.string = text
    textLayer.font = font
    textLayer.fontSize = font.pointSize * scale
    textLayer.foregroundColor = foregroundColor.cgColor
    textLayer.frame = CGRect(origin: .zero, size: scaledTextSize)
    textLayer.alignmentMode = kCAAlignmentCenter
    textLayer.contentsScale = UIScreen.main.scale
    let _layer = CALayer()
    _layer.bounds = CGRect(origin: scaledOrigin, size: size.scaled(scale))
    _layer.backgroundColor = UIColor.clear.cgColor
    _layer.contentsScale = UIScreen.main.scale
    _layer.addSublayer(textLayer)
    return _layer
}
```

## FillAspectRatioOverlay.swift
ビルトインでFillAspectRatioOverlayというOverlayを提供しています。

# tvOS11ならこんなハックいらないって？
そう思っていた頃が私にもありました。
今回実際に作ってみてわかりましたが、tvOS11だからってなんでも`overlayContentView`を使えばいいというわけではありません。例えば上で紹介した余白黒埋めOverlayですが、これをUIImageではなくCALayerとして`overlayContentView`に表示すると、フォーカス時にParallaxモーションしてしまうので、余白がずれてしまいます。

# まとめ
Image Overlay関連のハックを紹介しました。
このライブラリを使えば簡単にオーバーレイの画像レンダリングとImage Overlayの両刀遣いが実現できて便利です。オラにスターを分けてくれ🤤
https://github.com/toshi0383/ImageOverlay