main
swift 107 lines 4.44 KB
Raw
1 //
2 // TVTabBarController.swift
3 // JomloCV tvOS Example
4 //
5 // Created by Seto Elkahfi on 2026-01-27.
6 // Copyright © 2026 Seto Elkahfi. All rights reserved.
7 //
8
9 import UIKit
10
11 /// A tab bar controller mimicking the Viaplay tvOS app navigation structure.
12 ///
13 /// The tvOS tab bar sits at the top of the screen and is revealed by swiping up
14 /// on the Siri Remote. Each tab represents a content category page, and focus
15 /// transitions from the tab bar to the selected view controller's content.
16 ///
17 /// This implementation includes the focus-handoff pattern from Viaplay's
18 /// `TabBarController` — when focus moves from the tab bar down to content,
19 /// we redirect focus to the selected view controller for smooth navigation.
20 @MainActor
21 final class TVTabBarController: UITabBarController {
22
23 private var shouldGiveFocusToSelectedViewController = false
24
25 override func viewDidLoad() {
26 super.viewDidLoad()
27 view.backgroundColor = UIColor(white: 0.05, alpha: 1.0)
28 setupTabs()
29 }
30
31 // MARK: - Focus Engine
32
33 /// Redirects focus to the selected view controller when transitioning
34 /// from the tab bar down to content — ensures the BlocksViewController
35 /// (or equivalent) gains proper focus control.
36 override var preferredFocusEnvironments: [UIFocusEnvironment] {
37 guard let selectedViewController, shouldGiveFocusToSelectedViewController else {
38 return super.preferredFocusEnvironments
39 }
40 shouldGiveFocusToSelectedViewController = false
41 return [selectedViewController]
42 }
43
44 /// Intercepts focus transitions from the tab bar to content.
45 /// When focus moves away from the tab bar downward, we set a flag
46 /// and force a focus update so `preferredFocusEnvironments` can
47 /// hand focus to the selected view controller cleanly.
48 override func shouldUpdateFocus(in context: UIFocusUpdateContext) -> Bool {
49 if let nextFocusedView = context.nextFocusedView,
50 !nextFocusedView.isDescendant(of: tabBar),
51 let previouslyFocusedView = context.previouslyFocusedView,
52 previouslyFocusedView.isDescendant(of: tabBar)
53 {
54 shouldGiveFocusToSelectedViewController = true
55 setNeedsFocusUpdate()
56 updateFocusIfNeeded()
57 return false
58 }
59
60 shouldGiveFocusToSelectedViewController = false
61 return true
62 }
63
64 // MARK: - Tab Configuration
65
66 private func setupTabs() {
67 let homeVC = TVStreamingHomeViewController()
68 homeVC.tabBarItem = UITabBarItem(title: "Home", image: nil, tag: 0)
69 homeVC.tabBarItem.accessibilityIdentifier = "Start"
70 let homeNav = UINavigationController(rootViewController: homeVC)
71
72 let seriesVC = TVSeriesViewController()
73 seriesVC.tabBarItem = UITabBarItem(title: "Series", image: nil, tag: 1)
74 seriesVC.tabBarItem.accessibilityIdentifier = "Series"
75 let seriesNav = UINavigationController(rootViewController: seriesVC)
76
77 let moviesVC = TVMoviesViewController()
78 moviesVC.tabBarItem = UITabBarItem(title: "Movies", image: nil, tag: 2)
79 moviesVC.tabBarItem.accessibilityIdentifier = "Movies"
80 let moviesNav = UINavigationController(rootViewController: moviesVC)
81
82 let sportsVC = TVSportsViewController()
83 sportsVC.tabBarItem = UITabBarItem(title: "Sport", image: nil, tag: 3)
84 sportsVC.tabBarItem.accessibilityIdentifier = "Sports"
85 let sportsNav = UINavigationController(rootViewController: sportsVC)
86
87 let kidsVC = TVKidsViewController()
88 kidsVC.tabBarItem = UITabBarItem(title: "Kids", image: nil, tag: 4)
89 kidsVC.tabBarItem.accessibilityIdentifier = "Kids"
90 let kidsNav = UINavigationController(rootViewController: kidsVC)
91
92 let searchVC = TVSearchViewController()
93 searchVC.tabBarItem = UITabBarItem(title: "Search", image: nil, tag: 5)
94 searchVC.tabBarItem.accessibilityIdentifier = "Search"
95 let searchNav = UINavigationController(rootViewController: searchVC)
96
97 // Copy tabBarItem from the root VC to the nav so the tab bar shows titles
98 homeNav.tabBarItem = homeVC.tabBarItem
99 seriesNav.tabBarItem = seriesVC.tabBarItem
100 moviesNav.tabBarItem = moviesVC.tabBarItem
101 sportsNav.tabBarItem = sportsVC.tabBarItem
102 kidsNav.tabBarItem = kidsVC.tabBarItem
103 searchNav.tabBarItem = searchVC.tabBarItem
104
105 viewControllers = [homeNav, seriesNav, moviesNav, sportsNav, kidsNav, searchNav]
106 }
107 }