tvOS: Dynamic TopShelf Based on Remote JSON

How to build a sectioned top shelf with Alamofire, SwiftyJSON and love.

I’m currently working on an app for the new Apple TV. I was trying to build a sectioned TopShelf with dynamic content. During the first two betas I wasn’t having much success. In Xcode 7.1 beta 3 it finally started working for me. I’m not sure if it had to do with the beta 3 or incorporating Semaphore into it. Anyway, here is the sectioned TopShelf I am building:

top-shelf

As you can see it has a variety of different sizes and all of that is controlled in the remote JSON file that you will ping through Alamofire (check the tvOS branch). I’ve included my code below if you want to check it out. Also reviewing my JSON file should explain most of it as well.


appletv-topshelf.json ServiceProvider.swift

Setup Extension

Your extension ends up being one swift file: ServiceProvider.swift If you want help getting to that point, this video helped me get the Top Shelf Extension setup.

Here is my current ServiceProvider.swift:

//
//  ServiceProvider.swift
//  BT Top Shelf
//
//  Created by HalBook on 9/28/15.
//  Copyright © 2015 HalGatewood.com. All rights reserved.
//

import Foundation
import TVServices
import Alamofire

class ServiceProvider: NSObject, TVTopShelfProvider {

    override init() {
        super.init()
    }
    
    var pingURL = "https://bibletalk.tv/appletv-topshelf.json"
    
    var items: [TVContentItem] = []

    // MARK: - TVTopShelfProvider protocol

    var topShelfStyle: TVTopShelfContentStyle {
        // Return desired Top Shelf style.
        return .Sectioned
    }

    var topShelfItems: [TVContentItem] {

        // START SEMAPHORE
        let semaphore = dispatch_semaphore_create(0)
        
        
        // REQUEST REMOTE JSON
        Alamofire.request( .GET, self.pingURL )
            .responseJSON { response in
                
                
                var ContentItems = [TVContentItem]();
                
                let json = JSON(response.result.value!)
                let sections = json["sections"].arrayValue
                
                // LOOP ARRAY OF SECTIONS
                for section in sections
                {
                    let items = section["items"].arrayValue
                    ContentItems = [TVContentItem]();
                    
                    // LOOP ITEMS
                    for item in items
                    {
                        // PASS IN THE IMAGE SIZE
                        var image_shape : TVContentItemImageShape = .Square
                        switch item["image_size"].string!
                        {
                            case "HDTV": image_shape = .HDTV
                            case "ExtraWide": image_shape = .ExtraWide
                            case "Wide": image_shape = .Wide
                            case "SDTV": image_shape = .SDTV
                            case "Poster": image_shape = .SDTV
                            case "None": image_shape = .None
                            default : image_shape = .Square
                        }
                        
                        // APPEND THE LATEST ITEM
                        ContentItems.append( self.buildShelfItem( item["slug"].string!, Title: item["title"].string!, Image: item["image"].string!, ImageSize: image_shape ) )
                    }
                    
                    
                    // SET THE SECTION DETAILS
                    let sectionItem = TVContentItem(contentIdentifier: TVContentIdentifier(identifier: section["contentIdentifier"].string!, container: nil)!)
                    sectionItem!.title = section["title"].string!
                    sectionItem!.topShelfItems = ContentItems
                    
                    
                    // SET THE ITEM, IN THIS CASE THE WHOLE SECTION
                    self.items.append(sectionItem!)
                }

                // TELL THE SEMAPHORE WE'RE DONE
                dispatch_semaphore_signal(semaphore)
        }
        
        // WAIT FOR THE SIGNAL
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
        
        // RETURN
        return self.items
    }

    
    func buildShelfItem( Slug: String, Title: String = "", Image: String, ImageSize: TVContentItemImageShape = .Square) -> TVContentItem
    {
        // BUILDS THE ITEM, YOU WILL NEED TO MODIFY THE displayURL, etc.
        let item = TVContentItem(contentIdentifier: TVContentIdentifier(identifier: Slug, container: nil)!)
        item!.imageURL = NSURL(string:Image)
        item!.imageShape = ImageSize
        item!.displayURL = NSURL(string: "bibletalktv:///" + Slug);
        item!.title = Title
        return item!
    }
}