Building Better Systems, tvOS
tvOS Swift 3.0: Displaying Search Container Under Tab Bar PART 2
Due to popular demand for my previous post about implementing and displaying the search container under the tab bar for tvOS, I’m going to revise this topic and share with everyone an updated version for getting UISearchController to work properly in tvOS Swift 3.0. The purpose of this tutorial is to focus on getting the search container to display under the tab bar for navigation.
The end results will look like this:
As mentioned previously, one of the most unpleasant experiences on the new Apple TV is the control of the native keyboard. One of the main issues many developers struggled with is the lack of documentation to programmatically create UISearchController, and display the search results beneath the UITabBarController, inside a UINavigationController using Swift 3.0.
The navigation menu allows users to interact between each scene by swiping horizontally to segue to the next scene.
Here is how you’d create the Tab Bar Controller on the Storyboard Interface:
- Drag the Tab Bar Controller from the Objects “Modules” Library and give it a proper class name and Storyboard ID.
- Check the “Is Initial View Controller” so that Tab Bar Controller becomes the Storyboard initial entry point during app delegation.
- Make sure Search Results View Scene has proper class name and Storyboard ID.
- Right click on the Tab Bar Controller and connect each tab bar items to the corresponding view controllers by dragging it over. Exclude the Search Results tab bar item for now; we will create Search Results tab bar item programmatically in the code below. The scenes I’m using for this example are Home, Programs, and Watch Later.
- Give each item proper naming and labeling for the navigation tabs.
- Create a new file call TabBarViewController.swift and create a method and call it searchContainerDisplay() inside the UITabBarViewController class.
In my previous post, this method was living inside the AppDelegate.swift. However in this version, I’ve placed this method in the TabBarViewController.swift instead. This is a cleaner and a more isolated approach.
// // Create a file called TabBarViewController.swift // import UIKit class TabBarViewController: UITabBarController, UINavigationControllerDelegate { var window: UIWindow? func searchContainerDisplay(){ let resultsController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SearchResultsViewController") as! SearchResultsViewController let searchController = UISearchController(searchResultsController: resultsController) searchController.searchResultsUpdater = resultsController searchController.obscuresBackgroundDuringPresentation = true searchController.hidesNavigationBarDuringPresentation = false let searchPlaceholderText = NSLocalizedString("Search Title", comment: "") searchController.searchBar.placeholder = searchPlaceholderText searchController.searchBar.tintColor = UIColor.black searchController.searchBar.barTintColor = UIColor.black searchController.searchBar.searchBarStyle = .minimal searchController.searchBar.keyboardAppearance = UIKeyboardAppearance.dark let searchContainerViewController = UISearchContainerViewController(searchController: searchController) let navController = UINavigationController(rootViewController: searchContainerViewController) navController.view.backgroundColor = UIColor.black if var tbViewController = self.viewControllers{ //tbViewController.append(navController) //Inserts Search into the 3rd array position tbViewController.insert(navController,at: 3) self.viewControllers = tbViewController } } override func viewDidLoad() { super.viewDidLoad() self.searchContainerDisplay() // Sets the default color of the icon of the selected UITabBarItem and Title UIBarButtonItem.appearance().tintColor = UIColor.black UITabBar.appearance().tintColor = UIColor.black // Sets the default color of the background of the UITabBar UITabBar.appearance().barTintColor = UIColor.black UITabBar.appearance().isTranslucent = false if let tbItems = self.tabBar.items{ let tabBarItem1: UITabBarItem = tbItems[0] let tabBarItem2: UITabBarItem = tbItems[1] let tabBarItem3: UITabBarItem = tbItems[2] let tabBarItem4: UITabBarItem = tbItems[3] tabBarItem1.title = "Home" tabBarItem2.title = "Programs" tabBarItem3.title = "Favorites" tabBarItem4.title = "Search" } if let tabBarItems = self.tabBar.items{ for item in tabBarItems as [UITabBarItem] { //Preserves white Color on selected self.tabBar.tintColor = UIColor.white item.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.white], for:UIControlState()) item.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.gray], for:UIControlState.disabled) item.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.gray], for:UIControlState.selected) } } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }
- Lastly, create a new file and call it SearchResultsViewController.swift. Make sure the name of the class matches the class name and ID on the Storyboard for the Search Results View Scene. For convenience purposes–copy and paste the SearchResultsViewController example below to see how the Search keyboard works. You’ll need to create an API call and string match the keywords in the filterString variable.
// // Create a file called SearchResultsViewController.swift // import UIKit class SearchResultsViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UISearchResultsUpdating, UIScrollViewDelegate{ @IBOutlet var scrollView : UIScrollView! static let storyboardIdentifier = "SearchResultsViewController" fileprivate var allDataItems : [[String: AnyObject]]? //= //DataItem.sampleItems fileprivate var filteredDataItems : [[String: AnyObject]]? //!= //DataItem.sampleItems override func viewDidLayoutSubviews() { } override func viewDidLoad() { super.viewDidLoad() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) print("Bye Search") } func reloadSearch(_ notification: Notification){ print("reloadSearch") } var filterString = "" { didSet { guard filterString != oldValue else { return } // Apply the filter or show all items if the filter string is empty. if filterString.isEmpty { filteredDataItems = allDataItems } else { } } } // MARK: UICollectionViewDataSource func numberOfSections(in collectionView: UICollectionView) -> Int { return 1 } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 0 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { return UICollectionViewCell() } //Pass objects to nextView override func prepare(for segue: UIStoryboardSegue, sender: Any?) { } // MARK: UICollectionViewDelegate func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { } // MARK: UISearchResultsUpdating func updateSearchResults(for searchController: UISearchController) { filterString = searchController.searchBar.text ?? "" } //return sizes for all JsonCell Poster size func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize { return CGSize(width: 390,height: 270) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat { return 50 } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat { return 50 } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets { return UIEdgeInsets(top: 0.0, left: 90.0, bottom: 0.0, right: 90.0) } }
IEG and WNET are not responsible for your or any third party’s use of code from this tutorial. All the information on this website is published in good faith “as is” and for general information purposes only; WNET and IEG make no representation or warranty regarding reliability, accuracy, or completeness of the content on this website, and any use you make of this code is strictly at your own risk.