7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

tvOSAdvent Calendar 2017

Day 12

フォーカスされてるセルが隣のセルの下に潜ってしまう

Last updated at Posted at 2017-12-12

問題

tvOSでコレクションビューを実装していると、フォーカスしているセルが隣のセルより下に潜り込んでしまうことがあります。

before.gif

なんとも気持ち悪いですね。

前に持ってくる

こういう時は、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を元に戻しているので、これではアニメーション中に重なる順序がおかしくなってしまいます。

before002.gif

右側のセルが被ってしまっているのがわかりますか?

後にフォーカスされた方がより前に来るようにする

幸い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が維持されるようになりました。

after.gif

ライブラリにしてありますので、ご利用ください。
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

以上です!

7
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?