Search code examples
swiftuibuttonfocustvos

tvOS - preferredFocusEnvironments not working


Because I needed a UINavigationController inside another UINavigationController (and this is not possible by default), I created a UIViewController that acts as a UINavigationController, but dit does not subclass from UINavigationController.

The second NavigationController (the one that does to subclass UINavigationController), presents (depending on the ViewModel's state) a controller.

This is the custom NavigationController:

class OnboardingViewController: UIViewController {

    // MARK: - Outlets

    @IBOutlet weak var containerView: UIView!

    // MARK: - Internal

    private var viewModel: OnboardingViewModel = OnboardingViewModel()

    // MARK: - View flow

    override func viewDidLoad() {
        super.viewDidLoad()

        self.navigationController?.navigationBar.isHidden = true
        navigateToNextFlow()
    }

    override var preferredFocusEnvironments: [UIFocusEnvironment] {
        switch viewModel.state {
        case .recommendations: return recommendationsController.preferredFocusEnvironments
        default: return super.preferredFocusEnvironments
        }
    }

    // MARK: - Handlers

    var navigateToNextHandler: (() -> Void)?

    // MARK: - Controllers

    private var recommendationsController: OnboardingRecommendationsViewController {
        let controller = UIViewController.instantiate(from: "Onboarding Recommendations") as OnboardingRecommendationsViewController
        controller.navigateToNextHandler = { [unowned self] in
            self.viewModel.state = .done
            self.navigateToNextFlow(animated: true)
        }
        return controller
    }

    // MARK: - Navigation

    private func navigateToNextFlow(animated: Bool = false) {
        switch viewModel.state {
        case .recommendations:
            add(child: recommendationsController, to: containerView)
        case .done:
            viewModel.finish()
            navigateToNextHandler?()
        }
        updateFocusIfNeeded()
        setNeedsFocusUpdate()
    }
}

This is the childViewController:

class OnboardingRecommendationsViewController: UIViewController {

    // MARK: - Outlets

    @IBOutlet weak var onOffButton: UIButton!
    @IBOutlet weak var finishButton: UIButton!

    // MARK: - Internal

    fileprivate let viewModel: OnboardingRecommendationsViewModel = OnboardingRecommendationsViewModel()

    // MARK: - View flow

    override func viewDidLoad() {
        super.viewDidLoad()

        setupLabels()
        setupOnOffButton()
    }

    // MARK: - Handlers

    var navigateToNextHandler: (() -> Void)?

    // MARK: - Focus

    override var preferredFocusEnvironments: [UIFocusEnvironment] {
        return [finishButton, onOffButton]
    }
}

The finishButton is beneath the onOffButton in the storyboard. I'm trying to set the initial focus on the finishButton instead of the onOffButton. But the user can focus the onOffButton if he wants.

Whatever I try, it just doesn't work. The preferredFocusEnvironments gets called, but the focus of the buttons stays in the wrong order.

What am I doing wrong?


Solution

  • Sorry for the late answer. It turned out that the viewController I was pushing, defined was as let, so I pushed a few instances over themselves, and that's why it seems that the preferredFocusEnvironments was not working. I actually saw a new instance of the ViewController with another initial focus order. Changing the variable declaration of the viewController from let to lazy var did the trick. So, in the end, it had really nothing to do with preferredFocusEnvironments not working. But thanks for the input!