問題
tvOSでコレクションビューを実装していると、フォーカスしているセルが隣のセルより下に潜り込んでしまうことがあります。
なんとも気持ち悪いですね。
前に持ってくる
こういう時は、UIView#bringSubview(toFront:)
を呼ぶか、 layer.zPosition
を変更して前に持ってきてあげる必要があります。
didUpdateFocus
で実装するとこんな感じになると思います。
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
if context.nextFocusedView == self {
self.layer.zPosition = 1.0
} else {
self.layer.zPosition = 0.0
}
super.didUpdateFocus(in: context, with: coordinator)
}
これでフォーカスされているセルは一番前に来ますね。
しかし、フォーカスが他に移った瞬間にzPositionを元に戻しているので、これではアニメーション中に重なる順序がおかしくなってしまいます。
右側のセルが被ってしまっているのがわかりますか?
後にフォーカスされた方がより前に来るようにする
幸いaddCoordinatedAnimationsにcompletionを渡せるので、以下のように実装することができました。
private var intermediateZPosition: CGFloat = 0.5
private var workItem: DispatchWorkItem?
private func triggerResetSharedVariable() {
workItem?.cancel()
workItem = DispatchWorkItem {
intermediateZPosition = 0.5
}
if let workItem = workItem {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3.0, execute: workItem)
}
}
...
public func applyCoordinatedZPositionState(context: UIFocusUpdateContext, coordinator: UIFocusAnimationCoordinator) {
var completion: (() -> ())?
if let next = context.nextFocusedView, next.isDescendant(of: self) {
self.layer.zPosition = 1.0
} else if let previous = context.previouslyFocusedView, previous.isDescendant(of: self) {
intermediateZPosition += 0.000001
self.layer.zPosition = intermediateZPosition
completion = {
if let f = UIScreen.main.focusedView, !f.isDescendant(of: self) {
self.layer.zPosition = 0.0
triggerResetSharedVariable()
}
}
}
coordinator.addCoordinatedAnimations(nil, completion: completion)
}
これで、完全にフォーカスのアニメーションが終わるまではzPositionが維持されるようになりました。
ライブラリにしてありますので、ご利用ください。
https://github.com/toshi0383/FocusZPositionMutating
toshi0383/FocusZPositionMutating
必要な処理をコピー&ペーストしてもらってもいいのですが、これだけのためにいちいちそういうことするのも面倒だと思うので、AppDelegateに以下の1行を書くだけでセットアップが済むようにしてあります。
UIView.fzpm_swizzleDidUpdateFocus()
あとは、重なる順番を管理してほしいビューをFocusZPositionMutating
のprotocolに適合させれば、didUpdateFocus
で勝手に上の処理を呼んでくれます。
class ZPositionView: UIView, FocusZPositionMutating { }
UIView#isDescendant(of:)
で判定しているため、フォーカス可能なビューである必要はありません。
まとめ
フォーカスでビューが重なってしまう時はzPositionに気をつけましょう。
AbemaTVの番組表は基本的に隣のセルと被りまくるので管理が大変だったのですが、ライブラリ化できたのでコードがスッキリしました。
Exampleに何種類かサンプル用意してあるので気になる方はチェックしてみてください。
https://github.com/toshi0383/FocusZPositionMutating
以上です!