CPListItem Image

Hello, I am build a CarPlay Audio Extension for our App. Users can listen to their Audio Playlist that they made in our App.

Loading remote images for CPListItem causes a problem. The images are always very small not filling out the full reserved space. If I put them as an asset in the app, the images are fully scaled. Anyone who ran into that problem and can help me out? Thank you. Cheers, Simon

Accepted Reply

We have actually contacted Developer Technical Support for this and while the response wasn't exactly what we needed it pointed us in the right direction to come up with something that works for us. Note that there are still scaling issues when ALL items within a section have no detail text (but that's another issue). Here is what we use:

extension CPListItem {
	convenience init(text: String?,
                   detailText: String?,
                   remoteImageUrl: URL?,
                   placeholder: UIImage?,
                   accessoryImage: UIImage? = nil,
                   accessoryType: CPListItemAccessoryType = .none,
                   imageOperation: RemoteImageOperation? = nil) {
		self.init(text: text, detailText: detailText, image: placeholder, accessoryImage: accessoryImage, accessoryType: accessoryType)

		setImageUrl(remoteImageUrl)
	}

  func setImageUrl(_ url: URL?) {
    guard let imageUrl = url else { return }

    Current.downloadImage(imageUrl) { image in
      guard
        let cropped = image?.cpCropSquareImage,
        let resized = cropped.resized(to: CPListItem.maximumImageSize),
        let carPlayImage = resized.carPlayImage 
      else { return }
      DispatchQueue.main.async { [weak self] in
        self?.setImage(carPlayImage)
      }
    }
  }
}

extension UIImage {
  var carPlayImage: UIImage? {
    guard let traits = Current.interfaceController?.carTraitCollection else { return nil }
    
    let imageAsset = UIImageAsset()
    imageAsset.register(self, with: traits)
    return imageAsset.image(with: traits)
  }

  var cpCropSquareImage: UIImage { /* basic image cropping ... */ }
  func resized(to newSize: CGSize) -> UIImage? { /* basic image resizing ... */ }
}

The basic idea is to put the image in a UIImageAsset and take it out again which magically makes things work. Other than that we also crop it to a square and resize it to the maximum allowed image size. Our Current is like a dependency container where download is just a function to access our image cache/downloader and the interfaceController is the one we get when connecting the CarPlay scene (this is important for the native scale of the CarPlay display instead of the connected iOS device's display scale).

  • Thanks for your reply. I already found a solution. Resizing the downloaded image to CPListItem.maximumImageSize and then set it did the trick. I like your medium article of the BR Radio CarPlay development and did nearly asked you for a hint. Wondering, why in your app the station images are not on full size ;-)

  • Haha after answering you I actually updated the article but of course not all the images 🙈 „just resizing“ still cause some issues in some situations which is why we went for the image asset magic trick. Maybe it‘s not necessary for anyone but it was so weird that it solved some issue for us that we thought it might be worth sharing (cause who would have thought of this)

Add a Comment

Replies

We have actually contacted Developer Technical Support for this and while the response wasn't exactly what we needed it pointed us in the right direction to come up with something that works for us. Note that there are still scaling issues when ALL items within a section have no detail text (but that's another issue). Here is what we use:

extension CPListItem {
	convenience init(text: String?,
                   detailText: String?,
                   remoteImageUrl: URL?,
                   placeholder: UIImage?,
                   accessoryImage: UIImage? = nil,
                   accessoryType: CPListItemAccessoryType = .none,
                   imageOperation: RemoteImageOperation? = nil) {
		self.init(text: text, detailText: detailText, image: placeholder, accessoryImage: accessoryImage, accessoryType: accessoryType)

		setImageUrl(remoteImageUrl)
	}

  func setImageUrl(_ url: URL?) {
    guard let imageUrl = url else { return }

    Current.downloadImage(imageUrl) { image in
      guard
        let cropped = image?.cpCropSquareImage,
        let resized = cropped.resized(to: CPListItem.maximumImageSize),
        let carPlayImage = resized.carPlayImage 
      else { return }
      DispatchQueue.main.async { [weak self] in
        self?.setImage(carPlayImage)
      }
    }
  }
}

extension UIImage {
  var carPlayImage: UIImage? {
    guard let traits = Current.interfaceController?.carTraitCollection else { return nil }
    
    let imageAsset = UIImageAsset()
    imageAsset.register(self, with: traits)
    return imageAsset.image(with: traits)
  }

  var cpCropSquareImage: UIImage { /* basic image cropping ... */ }
  func resized(to newSize: CGSize) -> UIImage? { /* basic image resizing ... */ }
}

The basic idea is to put the image in a UIImageAsset and take it out again which magically makes things work. Other than that we also crop it to a square and resize it to the maximum allowed image size. Our Current is like a dependency container where download is just a function to access our image cache/downloader and the interfaceController is the one we get when connecting the CarPlay scene (this is important for the native scale of the CarPlay display instead of the connected iOS device's display scale).

  • Thanks for your reply. I already found a solution. Resizing the downloaded image to CPListItem.maximumImageSize and then set it did the trick. I like your medium article of the BR Radio CarPlay development and did nearly asked you for a hint. Wondering, why in your app the station images are not on full size ;-)

  • Haha after answering you I actually updated the article but of course not all the images 🙈 „just resizing“ still cause some issues in some situations which is why we went for the image asset magic trick. Maybe it‘s not necessary for anyone but it was so weird that it solved some issue for us that we thought it might be worth sharing (cause who would have thought of this)

Add a Comment

You should use +[CPListItem maximumImageSize] to fetch the maximum list image size permitted for your category of app. This is a resolution-independent value in points.

Then, you should access -[CPInterfaceController carTraitCollection], which provides the displayScale for the current car screen - this will be either 2x or 3x, depending on the vehicle. Multiplying the image size (in points) by the display scale will result in a pixel value that you should use for sizing your images.

  • I am integrating CarPlay for our app. The interfaceController.carTraitCollection is always nil when testing on a car stereo(bought the box) and on the simulator when the template scene is connected. That is the time I am setting images and would like the scale from the carTraitCollection. Would you have any idea why that is. In my testing on the box it remains nil, but on the simulator eventually on later refreshes I do see the carTraitCollection set on the interfaceController.

Add a Comment