Blog

Inside the Minds of the Machine

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:

searchcontainerresults

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.

storyboardtabbar

  • Check the “Is Initial View Controller” so that Tab Bar Controller becomes the Storyboard initial entry point during app delegation.

storyboardtabbarinitialized

  • Make sure Search Results View Scene has proper class name and Storyboard ID.

storyboardsearchresults

  • 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.

storyboarditemsdragging

  • Give each item proper naming and labeling for the navigation tabs.

storyboarditemsrenaming

  • 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.