1 min read

Customise the Product Details screen to show related products based on custom data

Last Updated - Platform 22.0.1 - SDK 17.0.1

This guide will walk through how you can customise the ProductViewDataSource to present a Product Carousel using custom data. This would require a backend customisation and for the data to come from somewhere.

Mapping the Data

Before we begin we're going to assume that the backend has been customised to send an array of product identifiers. This data will be returned within the custom data payload of the product object.

"customData": {
"relatedProducts": [
"productId": "awesome-dress",
"listingId": "awesome-dress-red"
"productId": "another-dress",
"listingId": "another-dress-purple"
  1. Create a new file for your custom ProductData somewhere like Sources/Product/Models.
  2. Create a struct CustomProductData and implement Decodable.
struct CustomProductData: Decodable {
struct ProductIdentifierData: Decodable {
var productId: String?
var listingId: String?
var relatedProducts: [ProductIdentifierData]?

It's not a bad idea to make everything optional in data models incase the backend has issues.

Right now this data is raw data and harder to work with, so it's a good idea to map this to a more usable format.

  1. Create a new file for a custom domain model for Product in the same folder.
  2. Create another struct CustomProduct but implement Hashable instead.
struct CustomProduct: Hashable {
var relatedProductIds: [ProductIdentifier]
  1. Create a new file for a custom ProductMapper somewhere like Sources/Product/Mappers.
  2. Subclass the PoqProductDataMapper and map your custom data using the customDataMapper.

This can be done directly using the initialiser, without a subclass, but it may be better to keep code clearly separate.

import PoqCatalogueClient
import PoqFoundation
class CustomProductMapper: PoqProductMapper {
init() {
super.init { customData in
// Override the custom data mapper.
customData?.decode(CustomProductData.self).flatMap { data in
// Compact map the raw product identifiers into domain ProductIdentifiers.
relatedProductIds: data.relatedProducts?.compactMap { id in
guard let productId = id.productId else { return nil }
return ProductIdentifier(productId: productId, listingId: id.listingId)
} ?? []
  1. Inject your custom mapper in the AppModule in the didAddToPlatform or setUpDependencies function.
Container.shared.mappers.productMapper.replace { CustomProductMapper() }

Great, now we have the data in a usable format within product.customData. Now let's use that data.

For this we're going to customise the ProductViewDataSource. There are two variations of this so it's important to pick the one best for your app:

  • If you have a variant Wishlist or want to present all the inline form pickers then subclass the PoqInlineProductViewDataSource.
  • Otherwise subclass the PoqProductViewDataSource.
  1. Create a new file for the custom ProductViewDataSource somewhere like Sources/ProductDetails/Views.
  2. Subclass your desired data source based on above and override the makeViews function.
import PoqCatalogue
import PoqProductDetail
class CustomProductViewDataSource: PoqProductViewDataSource {
override func makeViews(for product: ProductViewData) -> [UIView] {
var views = super.makeViews(for: product)
return views
  1. Implement a function to create and return a cached Product Carousel using your custom data.
var relatedProductsView: UIView?
func makeRelatedProductsView(for product: ProductViewData) -> UIView? {
guard let customData = product.customData(CustomProduct.self) else { return nil }
guard !customData.relatedProductIds.isEmpty else { return nil }
// Return the cached carousel view (because the products do not change).
return relatedProductsView ?? {
// If not cached yet then build and cache the carousel.
let view = ProductCarouselBuilder()
.build(source: .products(ids: customData.relatedProductIds), title: "Related Products".localized())
.inSectionView(hasViewAllButton: true)
relatedProductsView = view
return view
  1. Insert your carousel within the makeViews function where you want it within the array.
override func makeViews(for product: ProductViewData) -> [UIView] {
var views = super.makeViews(for: product)
return views
  1. Optionally, if you don't like all the carousels you can remove the recently viewed carousel by returning nil from the function.
override func makeRecentlyViewedView(with product: ProductViewData) -> UIView? { nil }
  1. Inject your custom data source in the AppModule in the didAddToPlatform or setUpProductDetails function
Container.shared.dataSource.productViewDataSource.replace { CustomProductViewDataSource() }

Success! The customisation is complete and you can now see the related products carousel.

Full Example Code