CarPlay CPListImageRowItem causes Inverted Scrolling and Side Button malfunction

In my CarPlaySceneDelegate.swift, I have two tabs:

  • The first tab uses a CPListImageRowItem with a CPListImageRowItemRowElement. The scroll direction is inverted, and the side button does not function correctly.
  • The second tab uses multiple CPListItem objects. There are no issues: scrolling works in the correct direction, and the side button behaves as expected.

Steps To Reproduce

  1. Launch the app.
  2. Connect to CarPlay.
  3. In the first tab, scroll up and down, then use the side button to navigate.
  4. In the second tab, scroll up and down, then use the side button to navigate.
  5. As observed, the scrolling behavior is different between the two tabs.

Code Example:

import CarPlay
import UIKit

class CarPlaySceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate {

    var interfaceController: CPInterfaceController?

    func templateApplicationScene(
        _ templateApplicationScene: CPTemplateApplicationScene,
        didConnect interfaceController: CPInterfaceController
    ) {
        self.interfaceController = interfaceController
        downloadImageAndSetupTemplates()
    }

    func templateApplicationScene(
        _ templateApplicationScene: CPTemplateApplicationScene,
        didDisconnectInterfaceController interfaceController: CPInterfaceController
    ) {
        self.interfaceController = nil
    }

    private func downloadImageAndSetupTemplates() {
        let urlString = "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRcYUjd1FYkF04-8Vb7PKI1mGoF2quLPHKjvnR7V4ReZR8UjW-0NJ_kC7q13eISZGoTCLHaDPVbOthhH9QNq-YA0uuSUjfAoB3PPs1aXQ&s=10"

        guard let url = URL(string: urlString) else {
            setupTemplates(with: UIImage(systemName: "photo")!)
            return
        }

        URLSession.shared.dataTask(with: url) { [weak self] data, _, _ in
            let image: UIImage
            if let data = data, let downloaded = UIImage(data: data) {
                image = downloaded
            } else {
                image = UIImage(systemName: "photo")!
            }

            DispatchQueue.main.async {
                self?.setupTemplates(with: image)
            }
        }.resume()
    }

    private func setupTemplates(with image: UIImage) {
        // Tab 1 : un seul CPListImageRowItem avec 12 CPListImageRowItemRowElement
        let elements: [CPListImageRowItemRowElement] = (1...12).map { index in
            CPListImageRowItemRowElement(image: image, title: "test \(index)", subtitle: nil)
        }
        let rowItem = CPListImageRowItem(text: "Images", elements: elements, allowsMultipleLines: true)
        rowItem.listImageRowHandler = { item, elementIndex, completion in
            print("tapped element \(elementIndex)")
            completion()
        }
        let tab1Section = CPListSection(items: [rowItem])
        let tab1Template = CPListTemplate(title: "CPListImageRowItemRowElement", sections: [tab1Section])

        // Tab 2 : 12 CPListItem simples
        let tab2Items: [CPListItem] = (1...12).map { index in
            let item = CPListItem(text: "Item \(index)", detailText: "Detail \(index)")
            item.handler = { _, completion in
                print("handler Tab 2")
                completion()
            }
            return item
        }
        let tab2Section = CPListSection(items: tab2Items)
        let tab2Template = CPListTemplate(title: "CPListItem", sections: [tab2Section])

        // CPTabBarTemplate avec les deux tabs
        let tabBar = CPTabBarTemplate(templates: [tab1Template, tab2Template])
        interfaceController?.setRootTemplate(tabBar, animated: true)
    }
}

Here is a quick video:

Answered by DTS Engineer in 881878022

Hello Enguerrand,

CarPlay is intended to support a variety of car displays with different size and programming restrictions. In your sample code, CPListImageRowItem is hardcoded to displaying twelve elements. As the documentation for CPListImageRowItem states, at runtime you should consult the maximum number of images a row could display. Since the first tab section has a single row item with a large amount of elements, it might be causing some sort of overflow issue, breaking scrolling.

Please try amending it to check against CPMaximumNumberOfGridImages and create an array of row items rather than a single one:

var rowItems: [CPListImageRowItem] = []

for startIndex in stride(from: 0, to: allElements.count, by: CPMaximumNumberOfGridImages) {
    let chunk = Array(allElements[startIndex..<min(startIndex + CPMaximumNumberOfGridImages, allElements.count)])

    let rowItem = CPListImageRowItem(text: "Images", elements: chunk, allowsMultipleLines: true)

    rowItem.listImageRowHandler = { item, elementIndex, completion in
        print("tapped element \(elementIndex)")
        completion()
    }

    rowItems.append(rowItem)
}           

Similarly in your second tab, the CPListTemplate.maximumItemCount for maximum number of list items across all sections should be checked. Please see the CarPlay Developer Guide for more details.

Edit:

Sorry, I realized that you are on iOS 26, which adds the allowsMultipleLines parameter to the initializer for CPListImageRowItem. This issue is a known bug that we are tracking with your report FB22097223, but both the root cause and the fix is fairly similar.

Previously, the value of CPMaximumNumberOfGridImages was relatively small, but has now been increased to allow the option of a single row item having multiple rows' worth of image elements. Hence, the introduction of the allowsMultipleLines option.

The root cause is still an internal rendering issue that our engineers are aware of, but you can mitigate your issue by having an array of row items instead of one large row item, like in the above solution. The only difference is instead of using CPMaximumNumberOfGridImages for max elements per row, use a small number like 6 or 7. (It all depends on the size of the vehicle displays your app will be on, the number of expected image elements in your list, and your app design.) You could also set allowsMultipleLines to false to be extra-certain that the row item is not overly large.

Design tip: try sorting your element array so that the most important ones are displayed at the beginning of the list.

Hoping this helps,

Richard Yeh  Developer Technical Support

Hello Enguerrand,

CarPlay is intended to support a variety of car displays with different size and programming restrictions. In your sample code, CPListImageRowItem is hardcoded to displaying twelve elements. As the documentation for CPListImageRowItem states, at runtime you should consult the maximum number of images a row could display. Since the first tab section has a single row item with a large amount of elements, it might be causing some sort of overflow issue, breaking scrolling.

Please try amending it to check against CPMaximumNumberOfGridImages and create an array of row items rather than a single one:

var rowItems: [CPListImageRowItem] = []

for startIndex in stride(from: 0, to: allElements.count, by: CPMaximumNumberOfGridImages) {
    let chunk = Array(allElements[startIndex..<min(startIndex + CPMaximumNumberOfGridImages, allElements.count)])

    let rowItem = CPListImageRowItem(text: "Images", elements: chunk, allowsMultipleLines: true)

    rowItem.listImageRowHandler = { item, elementIndex, completion in
        print("tapped element \(elementIndex)")
        completion()
    }

    rowItems.append(rowItem)
}           

Similarly in your second tab, the CPListTemplate.maximumItemCount for maximum number of list items across all sections should be checked. Please see the CarPlay Developer Guide for more details.

Edit:

Sorry, I realized that you are on iOS 26, which adds the allowsMultipleLines parameter to the initializer for CPListImageRowItem. This issue is a known bug that we are tracking with your report FB22097223, but both the root cause and the fix is fairly similar.

Previously, the value of CPMaximumNumberOfGridImages was relatively small, but has now been increased to allow the option of a single row item having multiple rows' worth of image elements. Hence, the introduction of the allowsMultipleLines option.

The root cause is still an internal rendering issue that our engineers are aware of, but you can mitigate your issue by having an array of row items instead of one large row item, like in the above solution. The only difference is instead of using CPMaximumNumberOfGridImages for max elements per row, use a small number like 6 or 7. (It all depends on the size of the vehicle displays your app will be on, the number of expected image elements in your list, and your app design.) You could also set allowsMultipleLines to false to be extra-certain that the row item is not overly large.

Design tip: try sorting your element array so that the most important ones are displayed at the beginning of the list.

Hoping this helps,

Richard Yeh  Developer Technical Support

Dear @EnguerrandA,

Could you please try the workaround above?

Thank you,

Richard Yeh  Developer Technical Support

Dear @EnguerrandA,

You could track the progress of this issue via your bug report. We use Feedback Assistant to notify users if there are any solutions to their issues, and which versions they should verify with.

Regarding the auto-scroll, that is quite strange! I haven't seen a bug report for that yet (feel free to file one), but it might have the same root cause as this thread's main issue. What kind of update are you applying to the CPListImageRowItemRowElement? You could post a code snippet or even attach a focused sample project to demo that issue to your original bug report.

One last thing - as per #5 of the DevForums Tips, please use comments only for short acknowledgement-type messages, and replies for more substantive posts. Comments do not guarantee notifications.

Thank you for your due diligence,

Richard Yeh  Developer Technical Support

@DTS Engineer

About the update topic, I identified two issues:

  • When a single line expands into two lines, the template scrolls down.
  • Updating the image of any CPListImageRowItemRowElement causes the template to scroll down (even if it the first or the last). This may be desirable in some cases but problematic in others, so we should make this behavior configurable.

Demo project: https://github.com/EArminjon/carplay-issue/tree/main/carplayissue

CarPlay CPListImageRowItem causes Inverted Scrolling and Side Button malfunction
 
 
Q