Image Overlayとは
tvOS11で登場した、UIImageViewにUIViewを重ねてParallaxアニメーションさせることができるAPIです。var overlayContentView: UIView
にaddSubviewしていく形になります。
この例だと、Free
のラベルがフォーカス時に外にはみ出してアニメーションしています。
ParallaxエフェクトするのはtvOS10まではUICollectionViewCellではなくUIImageViewのImageのみだったので、もし画像の上に何かオーバーレイしたい場合、tvOS10までは 全部画像にする必要があります。
動的なデータの場合無理やんと思うかもしれませんが、ゴリっとやってみたら意外とうまくいきました。AbemaTVでは少し前からこの実装が入っていたのですが、この機会にOSS化しました。
toshi0383/ImageOverlay
これを使うと、tvOS9,10でも簡単にImage OverlayっぽいUIを実現できます。
tvOS9,10ではFree
のラベルは画像としてレンダリングされるので、画像からはみ出さず一緒に拡大、アニメーションされてるのがわかると思います。
呼び出しはこんな感じになります。
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から初期化するのでももちろんいいです。
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を利用します。
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