1

So I am currently having to manually add new stations to our CarPlay app. However we have a JSON which our app uses for iPhone and iPad. So I am wondering how do I create a list that uses this information instead of me manually creating it.

func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene, didConnect interfaceController: CPInterfaceController) {
   
    
    let DRN1 = CPListItem(text: "DRN1", detailText: "Perth's No.1 Online Station.")
    let Hits = CPListItem(text: "DRN1 Hits", detailText: "Top 40 songs and yesturdays hits.")
    let United = CPListItem(text: "DRN1 United", detailText: "Perth's Dedicated LGBTIQA+ Station.")
    let Dance = CPListItem(text: "DRN1 Dance", detailText: "Playing the hottest dance tracks.")
    
   
    
    if #available(iOS 14.0, *) {
        let nowplay = CPNowPlayingTemplate.shared
        
        DRN1.setImage(UIImage(imageLiteralResourceName:"DRN1Logo"))
        DRN1.handler = { item, completion in
            print("selected DRN1")
            AdStichrApi.station = "DRN1"
            MusicPlayer.shared.startBackgroundMusic(url:"https://api.example.com.au:9000/station/DRN1", type: "radio")
            Nowplayinginfo().getsong()
            DispatchQueue.main.asyncAfter(deadline: .now() + 5.00) {
                interfaceController.pushTemplate(nowplay, animated: true,completion:nil)
                completion()
            }
        }
        
        Hits.setImage(UIImage(imageLiteralResourceName:"DRN1Hits"))
        Hits.handler = { item, completion in
            print("selected Hits")
            MusicPlayer.shared.player?.pause()
            AdStichrApi.station = "DRN1Hits"
            MusicPlayer.shared.startBackgroundMusic(url:"https://api.example.com.au:9000/station/DRN1Hits", type: "radio")
            Nowplayinginfo().getsong()
            DispatchQueue.main.asyncAfter(deadline: .now() + 5.00) {
               
                //interfaceController.presentTemplate(nowplay, animated: false)
                interfaceController.pushTemplate(nowplay, animated: true,completion:nil)
                completion()
                
            }
        }
        
        United.setImage(UIImage(imageLiteralResourceName:"DRN1United"))
        United.handler = { item, completion in
            print("selected United")
            AdStichrApi.station = "DRN1United"
            MusicPlayer.shared.startBackgroundMusic(url:"https://api.example.com.au:9000/station/DRN1United", type: "radio")
              // do work in the UI thread here
            Nowplayinginfo().getsong()
            DispatchQueue.main.asyncAfter(deadline: .now() + 5.00) {
                interfaceController.pushTemplate(nowplay, animated: true,completion:nil)
                completion()
            }
            
            
            
        }
        
        Dance.setImage(UIImage(imageLiteralResourceName:"DRN1Dance"))
        Dance.handler = { item, completion in
            print("selected Dance")
            AdStichrApi.station = "dance"
            MusicPlayer.shared.startBackgroundMusic(url:"https://api.example.com.au:9000/station/dance", type: "radio")
            Nowplayinginfo().getsong()
            DispatchQueue.main.asyncAfter(deadline: .now() + 5.00) {
                completion()
                interfaceController.pushTemplate(nowplay, animated: true,completion:nil)
            }
        }
        
        
        
        
    } else {
        // Fallback on earlier versions
    }
    
    
    
    let listTemplate = CPListTemplate(title: "Select a Station", sections: [CPListSection(items:[DRN1,United,Hits,Dance])])
   
   

However in my iOS app I just use

Api().getStations { (stations) in
                       self.stations = stations
        }

Which fetches the JSON from the backend and provides me with every station available.

I am wondering How can I create a CPList using this information instead.

Example: I found this example but it is fetching local data and I don't need the tab bar at this time.

https://github.com/T0yBoy/CarPlayTutorial/tree/master/CarPlayTutorial

From my understanding all I need to do is create a list

for station in (radios) {
            let item = CPListItem(text: station.name, detailText: station.name)
            item.accessoryType = .disclosureIndicator
            item.setImage(UIImage(named: station.imageurl))
            item.handler = { [weak self] item, completion in
                guard let strongSelf = self else { return }
               // strongSelf.favoriteAlert(radio: radio, completion: completion)
            }
            radioItems.append(item)
        
        }

I know I need to do something along the lines of


        Api().getStations { (stations) in
            self.radios = stations
            
            INSERT THE STATIONS INTO A LIST for 

let listTemplate = CPListTemplate(title: "Select a Station", sections: [CPListSection(items:stations)])
        }

This is how it should still look after creating a list from the API screenshot

Shawn Frank
  • 4,381
  • 2
  • 19
  • 29
Russell Harrower
  • 778
  • 1
  • 6
  • 20
  • Is the list template the first view the user should see with the list of stations ? And is your question only displaying the list or displaying the list after the API has sent back a response ? – Shawn Frank Mar 29 '22 at 05:13
  • @ShawnFrank so it is the firstView yes. I am trying to work out how to get it to fetch on CarPlay I know I need to do something like the example I have posted above. – Russell Harrower Mar 29 '22 at 05:26

1 Answers1

1

Give this a try:

// In some function in the CarPlaySceneDelegate
Api().getStations { (stations) in
    
    var stationItems: [CPListItem] = []
    
    self.radios = stations
    
    for station in stations {
        
        let item = CPListItem(text: station.name, 
                              detailText: station.description
                              image: station.image)
        
        item.handler = { [weak self] item, completion in
            
            // manage what should happen on tap
            // like navigate to NowPlayingView
            
        }
        
        stationItems.append(item)
    }
    
    loadList(withStations: stationItems)
}

// Load the list template
private func loadList(withStations stations: [CPListItem]) {
    
    let section = CPListSection(items: stations)
    let listTemplate = CPListTemplate(title: "Select a station",
                                      sections: [section])
    
    // Set the root template of the CarPlay interface
    // to the list template with a completion handler
    interfaceController?.setRootTemplate(listTemplate,
                                         animated: true) { success, error in
        
        // add anything you want after setting the root template
    }
}

The for loop to add the stations in a CPListItem array can be replaced by a map function but I did it this way for clarity.

Update

In your class CarPlaySceneDelegate, fix the spelling from

var interfactController: CPInterfaceController?

to

var interfaceController: CPInterfaceController?

In your templateApplicationScene didConnect comment out the interfactController?.setRootTemplate(listTemplate, animated: false) for now

and add this line to it self.interfaceController = interfaceController

So the updated function looks like this:

func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene,
                              didConnect interfaceController: CPInterfaceController) {
    
    // Add this
    self.interfaceController = interfaceController
    
    Api().getStations { (stations) in
        
        var stationItems: [CPListItem] = []
        
        self.stations = stations
        
        for station in stations {
            
            let item = CPListItem(text: station.name,
                                  detailText: station.name
                                  // image: imageLiteralResourceName:"DRN1Logo")
            )
            
            item.handler = { [weak self] item, completion in
                
                // manage what should happen on tap
                // like navigate to NowPlayingView
                print(item)
            }
            
            stationItems.append(item)
        }
        
        self.loadList(withStations: stationItems)
    }
}

Do you see any better results ?

Shawn Frank
  • 4,381
  • 2
  • 19
  • 29
  • I am getting Cannot find 'interfaceController' in scope – Russell Harrower Mar 29 '22 at 06:01
  • I have uploaded the Xcode project https://github.com/redimongo/iOS-Radio-App Not sure if that will help – Russell Harrower Mar 29 '22 at 06:16
  • @RussellHarrower - I had a look at your github repo and updated my answer, please see the **Update** section – Shawn Frank Mar 29 '22 at 06:30
  • thank you - that works :) Just now got to work out how to send the listenURL: station.listenlive inside the CPListItem - seems you can't add extra arguments – Russell Harrower Mar 29 '22 at 06:39
  • @RussellHarrower - Glad you got that working ! Not sure I get the second part of the question fully, however, you could probably open a new question with that if you are unable to figure that out and share the link with me here, I'll try to have a look at it. If this answer helped you get what you want, consider marking it as the answer - it would help me and others :) – Shawn Frank Mar 29 '22 at 06:44
  • 1
    It was easy just had to do if let listenNow = stations.first(where: {$0.name == item.text}) { inside the item.handler = { [weak self] item, completion in – Russell Harrower Mar 29 '22 at 06:46
  • Ah nice, I see what you mean. I remember having difficulties understanding CarPlay when starting out but good to see you are figuring it out faster. – Shawn Frank Mar 29 '22 at 06:50
  • Just wondering do you know how to convert string URL image to UIImage for this? – Russell Harrower Mar 29 '22 at 07:09
  • @RussellHarrower - You can use `Data(contentsOf: url)` to get the data representation of the image from the url. Then you can use `UIImage(data: data)`. However, you might also need to manage load time with async calls. Have a look at: https://www.hackingwithswift.com/example-code/uikit/how-to-load-a-remote-image-url-into-uiimageview – Shawn Frank Mar 29 '22 at 07:18
  • Yes definitely load them asynchronously and cache them. We have a snippet with a convenience initializer for a list item (https://gist.github.com/fruitcoder/4ccd2d2f52d12e2199228516e62cc693#file-carplaypost_carplay-extensions1-swift). If you need more detail on how it's used you can find it here https://medium.com/br-next/launching-br-radio-on-carplay-audio-8baab824b932#fd31 – fruitcoder Mar 29 '22 at 07:34