We need to retain CarPlay's native A–Z button between the scroll arrows in an audio app. This is core navigation for an album/artist library. We use CPListSection(items:header:sectionIndexTitle:) with single-character labels. The sectionIndexTitle API is still documented and is not deprecated in the Xcode 27 SDK. We need the supported native implementation, not a custom picker.
On a parked physical head unit with an iPhone running iOS 27, our app displays populated album rows and section headers but no A–Z. Apple Music displays A–Z on the same head unit. We do not know Apple Music's internal implementation.
A standalone, public-API-only reproduction is prepared; its CarPlay scene code is included below. Each tab supplies four A/B/M/Z sections of ten synthetic rows. The variants are initially populated, updated after loading, the full section initializer, and securely archived and decoded sections. No network, authentication or artwork is required.
The original app displays functioning native alphabet navigation on the iOS 26.5 CarPlay simulator. The public probe compiles. We cannot run an end-to-end iOS 27 CarPlay simulator session because Device Hub requires a physical device, as confirmed in developer forum thread 834440.
Separately, an isolated local diagnostic hosted the native list renderer with CarPlay traits. Both runtimes receive four nonempty index labels and all 40 rows. On iOS 26.5 (23F77), its table data source returns A/B/M/Z. On iOS 27.0 (24A434), the attached UICollectionViewDiffableDataSource returns nil index titles. Both the full initializer and secure archive round-trip preserve titles but produce the same result. This diagnostic is not an end-to-end CarPlay session and does not establish the exact internals on the physical phone.
A standard UIKit table supplied with index titles and CarPlay traits still displays the native A–Z button on the same iOS 27 runtime. The control itself still exists; the observed failure is the template-to-index connection. This control experiment is also hosted in a phone window, not a CarPlay session.
A standard UIKit collection data source implementing indexTitles(for:) also shows index letters on this runtime; the unmodified template renderer's collection data source returns nil. We have not modified that renderer.
Questions:
- Did iOS 27 move native CPListTemplate alphabet navigation to another API, configuration, or presentation requirement? What is the supported migration?
- If sectionIndexTitle remains the correct input, is this a known regression in the new list renderer, and what supported correction is available?
- How should third-party audio apps reproduce Apple Music's native alphabet navigation on iOS 27 without private API or replacing it with a custom control?
Please provide a working public-API example or identify the relevant fix/version.
Reproduction: use the audio CarPlay entitlement and configure CPTemplateApplicationScene with CarScene as its delegate. This is the scene code from the compiling standalone probe:
import CarPlay
import UIKit
@MainActor
func indexedSections() -> [CPListSection] {
["A", "B", "M", "Z"].map { letter in
let items = (1...10).map { number in
let item = CPListItem(text: "\(letter) Album \(number)", detailText: "Synthetic test entry")
item.handler = { _, completion in completion() }
return item
}
return CPListSection(items: items, header: letter, sectionIndexTitle: letter)
}
}
final class CarScene: UIResponder, CPTemplateApplicationSceneDelegate {
func templateApplicationScene(_ scene: CPTemplateApplicationScene, didConnect controller: CPInterfaceController) {
let ready = CPListTemplate(title: "Ready", sections: indexedSections())
ready.tabTitle = "Ready"
ready.tabImage = UIImage(systemName: "list.bullet")
let updated = CPListTemplate(title: "Updated", sections: [CPListSection(items: [CPListItem(text: "Loading", detailText: nil)])])
updated.tabTitle = "Updated"
updated.tabImage = UIImage(systemName: "arrow.clockwise")
let full = CPListTemplate(title: "Full Init", sections: indexedSections().map {
CPListSection(items: $0.items, header: $0.header ?? "", headerSubtitle: nil,
headerImage: nil, headerButton: nil, sectionIndexTitle: $0.sectionIndexTitle)
})
full.tabTitle = "Full Init"
full.tabImage = UIImage(systemName: "list.bullet.rectangle")
let decoded: CPListTemplate
do {
let sections = try indexedSections().map { section in
let data = try NSKeyedArchiver.archivedData(withRootObject: section, requiringSecureCoding: true)
guard let restored = try NSKeyedUnarchiver.unarchivedObject(ofClass: CPListSection.self, from: data) else {
throw CocoaError(.coderReadCorrupt)
}
return restored
}
decoded = CPListTemplate(title: "Decoded", sections: sections)
} catch {
decoded = CPListTemplate(title: "Decode failed", sections: [CPListSection(items: [
CPListItem(text: "Section decoding failed", detailText: String(describing: error))
])])
}
decoded.tabTitle = "Decoded"
decoded.tabImage = UIImage(systemName: "shippingbox")
controller.setRootTemplate(CPTabBarTemplate(templates: [ready, updated, full, decoded]), animated: false) { _, _ in }
Task { @MainActor in
try? await Task.sleep(for: .seconds(2))
updated.updateSections(indexedSections())
}
}
}