Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Show loading Activity Indicator in CarPlay app as Apple's docs state you should

Tags:

ios

swift

carplay

According to Apple's Documentation one should show an Activity Indicator to indicate something is loading or taking time to show on screen. This is reasonable, and I'd love to do just that...
enter image description here

I feel like I'm missing something obvious, but I cannot figure out how to show an activity indicator in a template of a CarPlay app as I don't have access to the view. I can only interact with the templates available.

If I were trying to match the image from Apple's docs, I'd assume I would need to use the listTemplate and some how add the activity indicator to that.

Has anyone else come across this? Any help would be appreciated!


Update from an Apple Engineer during WWDC (7/2021)

I asked this question during WWDC21 and got the following response.

If you are using CPListTemplate, the system will invoke your -[CPListItem handler] when the user taps the list item. The system provides a completion block to your handler, which you should be sure to invoke after your app finishes processing the user's selection. If your app does not immediately call the completion block, that's okay! Maybe your app needs to perform a network request that takes some time. The system will automatically add a spinner to your list item after a short delay if the completion block has not yet been called.

Another option could be to present a list template with a single list item that indicates the rest of the items are still loading/in-progress.

For the tab bar template in particular, you can update the tabs displayed after it's already been presented. For example, you could dynamically add one or more tabs if they absolutely cannot be presented immediately upon app launch.

Or, for a full-screen presentation, you could present a CPAlertTemplate to indicate an error or loading state in your app.

like image 904
Xeaza Avatar asked Nov 06 '22 01:11

Xeaza


2 Answers

I had the same issue as you - I couldn't find how to show a loading spinner when asynchronously loading content from an API. The Apple Docs weren't very helpful in trying to figure this out. After lots of testing, particularly on a real CarPlay device in a car, I have an answer.

For me, when requesting data in MPPlayableContentDataSource's beginLoadingChildItems(), if I don't call the beginLoadingChildItems()'s completion handler for a few seconds then a loading spinner shows on the CarPlay screen. So beginLoadingChildItems() is called by CarPlay, I make an API request and only when the request returns data (or fails) do I call beginLoadingChildItems()'s completion handler. Most of the time the API request returns pretty quickly and the loading spinner doesn't show. But when the API is slow (a few seconds due to spotty connection), the loading spinner will show.

My MPPlayableContentDataSource beginLoadingChildItems() looks like:

class CarPlayContentManager: NSObject, MPPlayableContentDataSource {

    private let contentProvider = ContentProvider()

    func beginLoadingChildItems(at indexPath: IndexPath, completionHandler: @escaping (Error?) -> Void) {
    
        // Make a request to fetch data from the API, passing in completionHandler
        contentProvider.fetchContentItems(completionHandler: completionHandler)    
    }

}

ContentProvider is just a helper class to fetch data:

class ContentProvider {

    func fetchContentItems(completionHandler: @escaping (Error?) -> Void) {
        
        // Make the async API request
        // When the API request returns (either as success or error) call completionHandler(nil)
    }

}

Note: You can just test the loading spinner by setting up a delay in beginLoadingChildItems(). When beginLoadingChildItems() is called, just set up a delay of 5 seconds and then call completionHandler(nil) once the 5 seconds are up. That should show the loading spinner on the CarPlay screen.

like image 33
JTODR Avatar answered Nov 15 '22 05:11

JTODR


Thanks very much @Xeaza for updating the question with the answer from the Apple Engineer.

Like mentioned there, if you are using CPListTemplate you can show an activity indicator in the list item. Or rather, CarPlay does that for you.

One important thing is missing that took me a while to find out:

this only works if the accessoryType of the selected listItem is set to .none

If you have accessoryType of all items set to .none beforehand you're fine. If you have it set to .disclosureIndicator or .cloud, you need to set it to .none before you start the work (first line in handler).

let listItem = CPListItem(text: "Item Name", detailText: nil, image: UIImage(named: "yourImage")!, accessoryImage: nil, accessoryType: .disclosureIndicator)
listItem.handler = { [weak self] selectableListItem, completion in
    listItem.accessoryType = .none
    yourFunctionThatTakesSomeTime() { [weak self] (_, _) -> Void in
        // do your thing
        completion()
    }
}
like image 166
guido Avatar answered Nov 15 '22 05:11

guido