# Content Widget (iOS)

Content widget is a feature in the Software Development Kit that allows you to embed an easily customizable view with [recommendations](/docs/ai-hub/recommendations-v2) in your application.

Two view layouts are available:

- Horizontal slider - a single row view that slides horizontally on the screen.
- Grid view - can be displayed as full- or half-screen grid layout within your app.

Both views offer a number of configuration options that allow you to style the view consistently in the app. Additionally, the Content widget automatically tracks 4 events:

- `recommendation.seen` or `recommendation.view` (depending on configuration) sent when a recommended item is visible to the customer.
<figure><img src="/api/docs/image/54176ad07f146575310749eba44b7c2f42c1b327/developers/mobile-sdk/_gfx/recommendation-seen.png" alt="Recommendation.seen event" class="large"><figcaption>Recommendation.seen event</figcaption></figure>

- `recommendation.click` sent when a customer clicks the recommended item.
<figure><img src="/api/docs/image/54176ad07f146575310749eba44b7c2f42c1b327/developers/mobile-sdk/_gfx/recommendation-click.png" alt="Recommendation.click event" class="large"><figcaption>Recommendation.click event</figcaption></figure>

- `product.like` sent when a customer clicks a selectable button in the recommendation. (The button must be added)
<figure><img src="/api/docs/image/54176ad07f146575310749eba44b7c2f42c1b327/developers/_gfx/add-to-favourites-event.png" alt="Event sent when a user clicks the "like" button on an item" class="large"><figcaption>Event sent when a user clicks the "like" button on an item</figcaption></figure>

- `product.dislike` sent when a customer clicks a selectable button in the recommendation a second time. (The button must be added)
<figure><img src="/api/docs/image/54176ad07f146575310749eba44b7c2f42c1b327/developers/mobile-sdk/_gfx/product-dislike.png" alt="Product.dislike event" class="large"><figcaption>Product.dislike event</figcaption></figure>


<div class="admonition admonition-note"><div class="admonition-icon"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg></div><div class="admonition-body"><div class="admonition-content">

Currently, the widget can only be used for displaying AI recommendations.

</div></div></div>


## Prerequisites
---

To use the content widget feature, you must:
- Obtain a customer token from [Customer Authentication](/developers/mobile-sdk/user-identification-and-authorization/overview#authenticated-customers).
- [Create an AI Recommendation](/docs/ai-hub/recommendations-v2).
- [Create a document](/docs/assets/documents).  
    Such a document should contain the following content:
    
  <pre><code class="language-json">{
      "name": "Similar Products",
      "recommendations": "{% recommendations_json3 campaignId=COhsCCOdu8Cg %} {% endrecommendations_json3 %}"
  }</code></pre>


- In the notepad, save the document's slug and the ID of the recommendation for later use.



<div class="admonition admonition-tip"><div class="admonition-icon"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" /></svg></div><div class="admonition-body"><div class="admonition-content">

It's a good practice to name slugs based on the area of the app that you want to place the content in, for example `product-details`, `menu`, and so on.

</div></div></div>


## Basic implementation
---

Configure the [`ContentWidgetOptions`](/developers/mobile-sdk/class-reference/ios/content-widget#contentwidgetoptions) and [`ContentWidgetAppearance`](/developers/mobile-sdk/class-reference/ios/content-widget#contentwidgetappearance) settings first.

| Class | Description |
| --- | --- |
| [`ContentWidgetOptions`](/developers/mobile-sdk/class-reference/ios/content-widget#contentwidgetoptions) | [`ContentWidgetOptions`](/developers/mobile-sdk/class-reference/ios/content-widget#contentwidgetoptions) contains options for business logic, such as the slug, product identifier, and so on. [Read more](#options). |
| [`ContentWidgetAppearance`](/developers/mobile-sdk/class-reference/ios/content-widget#contentwidgetappearance) | [`ContentWidgetAppearance`](/developers/mobile-sdk/class-reference/ios/content-widget#contentwidgetappearance) contains the UI configuration. [Read more](#options). |

The example below is the most basic implementation.


<div class="content-tabs code-tabs" data-tab-group="tabgrp-570">
<div class="tab-buttons"><button class="tab-button" data-tab-id="tabgrp-570-0" data-tab-group="tabgrp-570" data-tab-active="true">swift</button><button class="tab-button" data-tab-id="tabgrp-570-1" data-tab-group="tabgrp-570">objective-c</button></div>

<div class="tab-panel" data-tab-id="tabgrp-570-0" data-tab-group="tabgrp-570" data-tab-active="true">

```swift
let options = ContentWidgetOptions()
options.slug = "similar"
options.mapping = { model in
    guard let imageURLString = model.attributes["imageLink"] as? String,
                let imageURL = URL(string: imageURLString),
                let title = model.attributes["title"] as? String,
                let priceDictionary = model.attributes["price"] as? [AnyHashable: Any],
                let priceValue = priceDictionary["value"] as? Double
    else {
        return nil
    }

    let dataModel = ContentWidgetRecommendationDataModel(imageURL: imageURL, title: title, priceCurrency: "PLN", price: NSNumber(value: priceValue), salePrice: nil)

    if let salePriceDictionary = model.attributes["salePrice"] as? [AnyHashable: Any],
        let salePriceValue = salePriceDictionary["value"] as? Double {
        dataModel.salePriceValue = NSNumber(floatLiteral: salePriceValue)
    }

    let badgeDataModel = ContentWidgetBadgeDataModel(backgroundColor: UIColor.black, textColor: UIColor.white, text: "Black Week")
    dataModel.badge = badgeDataModel

    return dataModel
}

let gridLayout = ContentWidgetGridLayout()
let itemLayout = ContentWidgetBasicProductItemLayout()
let appearance = ContentWidgetAppearance(widgetLayout: gridLayout, itemLayout: itemLayout)

let widget = ContentWidget(options: options, appearance: appearance)

let widgetView = widget.getView()
widgetView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height)

view.addSubview(widgetView)
```

</div>

<div class="tab-panel" data-tab-id="tabgrp-570-1" data-tab-group="tabgrp-570">

```objective-c
SNRContentWidgetOptions *options = [SNRContentWidgetOptions new];
options.slug = @"similar";
options.mapping = ^(SNRContentWidgetRecommendationModel *model) {
    NSString *imageURLString = model.attributes[@"imageLink"];
    NSString *imageURL = [[NSURL alloc] initWithString:imageURLString];
    NSString *title = model.attributes[@"title"];
    NSDictionary *priceDictionary = model.attributes[@"price"];
    NSNumber *priceValue = priceDictionary[@"value"];
    if (imageURL == nil || title == nil || priceValue == nil) {
        return nil;
    }


    SNRContentWidgetRecommendationDataModel *dataModel = [[SNRContentWidgetRecommendationDataModel alloc] initWithimageURL:imageURL title:title priceCurrency:@"PLN" price:priceValue salePrice:nil];

    NSDictionary *salePriceDictionary = model.attributes[@"salePrice"];
    NSNumber *salePriceValue = salePriceDictionary[@"value"];
    if (salePrice != nil) {
        dataModel.salePriceValue = salePriceValue;
    }

    SNRContentWidgetBadgeDataModel *badgeDataModel = [[SNRContentWidgetBadgeDataModel alloc] initWithBackgroundColor:backgroundColor textColor:textColor text:text];
    dataModel.badge = badgeDataModel;

    return dataModel;
}

SNRContentWidgetGridLayout *gridLayout = [SNRContentWidgetGridLayout new];
SNRContentWidgetBasicProductItemLayout *itemLayout = [SNRContentWidgetBasicProductItemLayout new];
SNRContentWidgetAppearance *appearance = [[SNRContentWidgetAppearance alloc] initWithLayout:gridLayout andItemLayout:itemLayout];

SNRContentWidget *widget = [[SNRContentWidget alloc] initWithOptions:options andAppearance:appearance];

UIView *widgetView = [widget getView];
widgetView.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);

[self.view addSubview:widgetView];
```

</div>
</div>


## Options
---

The [`ContentWidgetRecommendationsOptions`](/developers/mobile-sdk/class-reference/ios/content-widget#contentwidgetoptions) class is responsible for defining the business logic options of the widget, for example:

- slug of the document
- product identifier
- recommendation data model mapper

The table explains the parameters that can be configured in [`ContentWidgetRecommendationsOptions`](/developers/mobile-sdk/class-reference/ios/content-widget#contentwidgetoptions).

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| slug | `String` | nil | Slug of a document |
| productID | `String` | nil | Product identifier for generating data |
| mapping | `((ContentWidgetRecommendationModel) -> (ContentWidgetRecommendationDataModel?))` | nil | Mapping block responsible for mapping data from the feed to a `ContentWidgetRecommendationDataModel` |
| recommendationEventType | `ContentWidgetRecommendationEventType` | - | Recommendation event type. <ul><li>**.view** sends all products in one event. We highly recommend using this type of event in content widget.</li><li>**.seen** sends each event as a separate event.</li></ul> |


<div class="content-tabs code-tabs" data-tab-group="tabgrp-571">
<div class="tab-buttons"><button class="tab-button" data-tab-id="tabgrp-571-0" data-tab-group="tabgrp-571" data-tab-active="true">swift</button><button class="tab-button" data-tab-id="tabgrp-571-1" data-tab-group="tabgrp-571">objective-c</button></div>

<div class="tab-panel" data-tab-id="tabgrp-571-0" data-tab-group="tabgrp-571" data-tab-active="true">

```swift
let widgetOptions = ContentWidgetRecommendationsOptions()
widgetOptions.slug = "similar"
widgetOptions.productID = "12345"
widgetOptions.mapping = { model in
    guard let imageURLString = model.attributes["imageLink"] as? String,
                let imageURL = URL(string: imageURLString),
                let title = model.attributes["title"] as? String,
                let priceDictionary = model.attributes["price"] as? [AnyHashable: Any],
                let priceValue = priceDictionary["value"] as? Double
    else {
        return nil
    }

    let dataModel = ContentWidgetRecommendationDataModel(imageURL: imageURL, title: title, priceCurrency: "PLN", price: NSNumber(value: priceValue), salePrice: nil)

    if let salePriceDictionary = model.attributes["salePrice"] as? [AnyHashable: Any],
        let salePriceValue = salePriceDictionary["value"] as? Double {
        dataModel.salePriceValue = NSNumber(floatLiteral: salePriceValue)
    }

    let badgeDataModel = ContentWidgetBadgeDataModel(backgroundColor: UIColor.black, textColor: UIColor.white, text: "Black Week")
    dataModel.badge = badgeDataModel

    return dataModel
}
```

</div>

<div class="tab-panel" data-tab-id="tabgrp-571-1" data-tab-group="tabgrp-571">

```objective-c
SNRContentWidgetRecommendationsOptions *widgetOptions = [SNRContentWidgetRecommendationsOptions new];
widgetOptions.slug = @"similar";
widgetOptions.productID = @"12345";
widgetOptions.mapping = ^(SNRContentWidgetRecommendationModel *model) {
    NSString *imageURLString = model.attributes[@"imageLink"];
    NSString *imageURL = [[NSURL alloc] initWithString:imageURLString];
    NSString *title = model.attributes[@"title"];
    NSDictionary *priceDictionary = model.attributes[@"price"];
    NSNumber *priceValue = priceDictionary[@"value"];
    if (imageURL == nil || title == nil || priceValue == nil) {
        return nil;
    }


    SNRContentWidgetRecommendationDataModel *dataModel = [[SNRContentWidgetRecommendationDataModel alloc] initWithimageURL:imageURL title:title priceCurrency:@"PLN" price:priceValue salePrice:nil];

    NSDictionary *salePriceDictionary = model.attributes[@"salePrice"];
    NSNumber *salePriceValue = salePriceDictionary[@"value"];
    if (salePrice != nil) {
        dataModel.salePriceValue = salePriceValue;
    }

    SNRContentWidgetBadgeDataModel *badgeDataModel = [[SNRContentWidgetBadgeDataModel alloc] initWithBackgroundColor:backgroundColor textColor:textColor text:text];
    dataModel.badge = badgeDataModel;

    return dataModel;
}
```

</div>
</div>


## Appearance
---

The [`ContentWidgetAppearance`](/developers/mobile-sdk/class-reference/ios/content-widget#contentwidgetappearance) class is responsible for defining the appearance of the widget.

The class consists of parameters that define the widget's appearance, however, two of them are the most important:

-  **Main layout class**: defines the way of distributing elements in the widget. Currently, two layouts are provided: [`ContentWidgetHorizontalSliderLayout`](/developers/mobile-sdk/class-reference/ios/content-widget#contentwidgethorizontalsliderlayout) and [`ContentWidgetGridLayout`](/developers/mobile-sdk/class-reference/ios/content-widget#contentwidgetgridlayout).
-  **Item layout class**: defines appearance and parameters for the item in the widget. Currently, there is only one layout provided: [`ContentWidgetBasicProductItemLayout`](/developers/mobile-sdk/class-reference/ios/content-widget#contentwidgetbasicproductitemlayout).


The table explains the parameters that can be configured in [`ContentWidgetAppearance`](/developers/mobile-sdk/class-reference/ios/content-widget#contentwidgetappearance).

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| layout | [`ContentWidgetLayout`](/developers/mobile-sdk/class-reference/ios/content-widget#contentwidgetlayout) | - | Class that inherits from [`ContentWidgetLayout`](/developers/mobile-sdk/class-reference/ios/content-widget#contentwidgetlayout) contains the UI details of `widgetLayout` |
| itemLayout | [`ContentWidgetItemLayout`](/developers/mobile-sdk/class-reference/ios/content-widget#contentwidgetitemlayout) | - | Class that inherits from [`ContentWidgetItemLayout`](/developers/mobile-sdk/class-reference/ios/content-widget#contentwidgetitemlayout) contains the UI details of a single item in a widget |

## Widget layouts
---

### Horizontal Slider

This layout is intended to present recommendations in a fixed-hight horizontal scrollable slider.


**Example widget configuration with horizontal slider:**

<img src="/api/docs/image/54176ad07f146575310749eba44b7c2f42c1b327/developers/mobile-sdk/_gfx/ios-widget-slider.png" alt="Content Widget - Horizontal Slider" style="height:auto;max-width:50%">

#### Parameters

The table explains the parameters of `SNRContentWidgetHorizontalLayout`.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| backgroundColor | `UIColor` | UIColor.clearColor | Background color of a widget |
| insets | `UIEdgeInsets` | (8.0, 8.0, 8.0, 8.0) | Inner widget margins in pt |
| itemSize | `CGSize` | (150.0, 200.0) | Size of a single item in pt |
| itemSpacing | `CGFloat` | 16.0 | Horizontal spacing between items in pt |
| numberOfItems | `Int` | - | A **read-only** property. It returns the number of items after a widget is loaded |

#### Example


<div class="content-tabs code-tabs" data-tab-group="tabgrp-572">
<div class="tab-buttons"><button class="tab-button" data-tab-id="tabgrp-572-0" data-tab-group="tabgrp-572" data-tab-active="true">swift</button><button class="tab-button" data-tab-id="tabgrp-572-1" data-tab-group="tabgrp-572">objective-c</button></div>

<div class="tab-panel" data-tab-id="tabgrp-572-0" data-tab-group="tabgrp-572" data-tab-active="true">

```swift
let horizontalSliderLayout = ContentWidgetHorizontalSliderLayout()
horizontalSliderLayout.insets = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 16.0, right: 16.0)
horizontalSliderLayout.itemSize = CGSize(width: 150, height: 350)
horizontalSliderLayout.itemSpacing = 8.0
```

</div>

<div class="tab-panel" data-tab-id="tabgrp-572-1" data-tab-group="tabgrp-572">

```objective-c
SNRContentWidgetHorizontalSliderLayout *horizontalSliderLayout = [SNRContentWidgetHorizontalSliderLayout new];
horizontalSliderLayout.insets = UIEdgeInsetsMake(16.0f, 16.0f, 16.0f, 16.0f);
horizontalSliderLayout.itemSize = CGSizeMake(150.0f, 350.0f);
horizontalSliderLayout.itemSpacing = 8.0f;
```

</div>
</div>


### Grid View

This layout presents recommendations in a vertical scrollable grid, with elements organized into columns and rows. You can create a full- or half-screen widget.

**Example widget configuration with grid layout:**

<img src="/api/docs/image/54176ad07f146575310749eba44b7c2f42c1b327/developers/mobile-sdk/_gfx/ios-widget-fullscreen.png" alt="Content Widget - Grid View" style="height:auto;max-width:50%">

#### Parameters

The table explains the parameters of the grid layout.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| backgroundColor | `UIColor` | UIColor.clearColor | Background color of a widget |
| insets | `UIEdgeInsets` | (8.0, 8.0, 8.0, 8.0) | Inner widget margins in pt |
| itemSize | `CGSize` | (150.0, 200.0) | Size of a single item in pt|
| itemHorizontalSpacing | `CGFloat` | 16.0 | Horizontal spacing between items in pt |
| itemVerticalSpacing | `CGFloat` | 16.0 | Vertical spacing between items in pt |
| numberOfItems | `Int` | - | A **read-only** property. It returns the number of items after the widget is loaded |

#### Example


<div class="content-tabs code-tabs" data-tab-group="tabgrp-573">
<div class="tab-buttons"><button class="tab-button" data-tab-id="tabgrp-573-0" data-tab-group="tabgrp-573" data-tab-active="true">swift</button><button class="tab-button" data-tab-id="tabgrp-573-1" data-tab-group="tabgrp-573">objective-c</button></div>

<div class="tab-panel" data-tab-id="tabgrp-573-0" data-tab-group="tabgrp-573" data-tab-active="true">

```swift
let gridLayout = ContentWidgetGridLayout()
gridLayout.insets = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 16.0, right: 16.0)
gridLayout.itemSize = CGSize(width: 150, height: 350)
gridLayout.horizontalItemSpacing = 8.0
gridLayout.verticalItemSpacing = 8.0
```

</div>

<div class="tab-panel" data-tab-id="tabgrp-573-1" data-tab-group="tabgrp-573">

```objective-c
SNRContentWidgetGridLayout *gridLayout = [SNRContentWidgetGridLayout new];
gridLayout.insets = UIEdgeInsetsMake(16.0f, 16.0f, 16.0f, 16.0f);
gridLayout.itemSize = CGSizeMake(150.0f, 350.0f);
gridLayout.horizontalItemSpacing = 8.0f;
gridLayout.verticalItemSpacing = 8.0f;
```

</div>
</div>


## Widget Item layouts
---

### Basic Product Item Layout

This is the basic layout for items. It contains: the image, the title, and the price from the uploaded data.

#### Parameters

The table below contains all parameters you can configure in the basic item layout. 

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| backgroundColor | `UIColor` | UIColor.whiteColor | Background color of an item |
| cornerRadius | `CGFloat` | 0.0 | Radius of the item corners |
| borderWidth | `CGFloat` | 0.0 | Width of the item's border |
| borderColor | `CGFloat` | nil | Color of the item's border |
| shadowColor | `UIColor` | nil | Color of the item's shadow |
| imageWidthRatio | `CGFloat` | 1.0 | Image width. A ratio of `1.0` means that the image width equals to 100% of the entire height of the item |
| imageHeightRatio | `CGFloat` | 0.35 | Image height. A ratio of `0.35` means that image height equals to 35% of the entire height of the item |
| imageBackground | `UIColor` | UIColor.clearColor | Background color of the image |
| imageContentMode | `UIViewContentMode` | UIViewContentMode.scaleToFill | Display content mode of the image |
| topTextInsets | `UIEdgeInsets` | (8.0, 8.0, 8.0, 8.0) | Inner margins of the top text label |
| topTextFont | `UIFont` | UIFont.systemFont(ofSize: 16.0) | Font of the top text label |
| topTextFontColor | `UIColor` | UIColor.blackColor | Color of the top text label |
| topTextAlignment | `NSTextAlignment` | NSTextAlignment.center | Alignment of the top text label |
| titleInsets | `UIEdgeInsets` | (8.0, 8.0, 8.0, 8.0) | Inner margins of the title label |
| titleFont | `UIFont` | UIFont.systemFont(ofSize: 16.0) | Font of the title label |
| titleFontColor | `UIColor` | UIColor.blackColor | Color of the title label |
| titleAlignment | `NSTextAlignment` | NSTextAlignment.center | Alignment of the title label |
| subtitleInsets | `UIEdgeInsets` | (8.0, 8.0, 8.0, 8.0) | Inner margins of the subtitle label |
| subtitleFont | `UIFont` | UIFont.systemFont(ofSize: 16.0) | Font of the subtitle label |
| subtitleFontColor | `UIColor` | UIColor.blackColor | Color of the subtitle label |
| subtitleAlignment | `NSTextAlignment` | NSTextAlignment.center | Alignment of the subtitle label |
| identifierInsets | `UIEdgeInsets` | (8.0, 8.0, 8.0, 8.0) | Inner margins of the identifier label |
| identifierFont | `UIFont` | UIFont.systemFont(ofSize: 16.0) | Font of the identifier label |
| identifierFontColor | `UIColor` | UIColor.blackColor | Color of the identifier label |
| identifierAlignment | `NSTextAlignment` | NSTextAlignment.center | Alignment of the identifier label |
| priceInsets | `UIEdgeInsets` | (8.0, 8.0, 8.0, 8.0) | Inner margins of the price label |
| priceFont | `UIFont` | UIFont.systemFont(ofSize: 14.0) | Font of the price label |
| priceFontColor | `UIColor` | UIColor.blackColor | Color of the price label |
| priceAlignment | `NSTextAlignment` | NSTextAlignment.center | Alignment of the price label |
| priceGroupSeparator | `String` | nil | Separator of price group |
| priceDecimalSeparator | `String` | nil | Separator of price decimal |
| priceCurrencyPosition | `ContentWidgetPriceCurrencyPosition` | .right | Determines the side on which the price currency is |
| isSalePriceVisible | `Bool` | true | Flag determining whether to show the sale price label or not |
| salePriceOrientation | `UILayoutConstraintAxis` | UILayoutConstraintAxis.Horizontal | Orientation of the sale price label |
| isDiscountPercentageVisible | `Bool` | true | Flag determining whether to show the discount percentage label or not |
| discountPercentageFont | `UIFont` | UIFont.systemFont(ofSize: 10.0) | Font of the discount percentage label |
| discountPercentageFontColor | `UIColor` | UIColor.blackColor | Font of the discount percentage label |
| regularPriceFont | `UIFont` | nil | Font of the regular price label |
| regularPriceFontColor | `UIColor` | nil | Color of the sale regular label |
| salePriceFont | `UIFont` | nil | Font of the sale price label |
| salePriceFontColor | `UIColor` | nil | Color of the sale price label |
| loyaltyPointsInsets | `UIEdgeInsets` | (8.0, 8.0, 8.0, 8.0) | Inner margins of the loyalty points label |
| loyaltyPointsAlignment | `NSTextAlignment` | NSTextAlignment.left | Alignment of the loyalty points label |
| loyaltyPointsNumberFont | `UIFont` | UIFont.systemFont(ofSize: 16.0) | Font of the loyalty points number label |
| loyaltyPointsNumberFontColor | `UIColor` | UIColor.blackColor | Color of the loyalty points number label |
| loyaltyPointsTextFont | `UIFont` | UIFont.systemFont(ofSize: 16.0) | Font of the loyalty points text label |
| loyaltyPointsTextFontColor | `UIColor` | UIColor.blackColor | Color of the loyalty points text label |
| loyaltyPointsText | `UIFont` | 'Loyalty points' | Text after the number of loyalty points |
| badge | `SNRContentWidgetBadgeItemLayoutPartial` | nil | Optional badge view |
| actionButton | `SNRContentWidgetImageButtonCustomAction` | nil | Optional button for your own custom action |

#### Example


<div class="content-tabs code-tabs" data-tab-group="tabgrp-574">
<div class="tab-buttons"><button class="tab-button" data-tab-id="tabgrp-574-0" data-tab-group="tabgrp-574" data-tab-active="true">swift</button><button class="tab-button" data-tab-id="tabgrp-574-1" data-tab-group="tabgrp-574">objective-c</button></div>

<div class="tab-panel" data-tab-id="tabgrp-574-0" data-tab-group="tabgrp-574" data-tab-active="true">

```swift
let itemLayout = ContentWidgetBasicProductItemLayout()
itemLayout.imageWidthRatio = 1.0
itemLayout.imageHeightRatio = 0.4
itemLayout.borderWidth = 2.0
itemLayout.borderColor = UIColor.black
itemLayout.shadowColor = UIColor.black
itemLayout.cornerRadius = 12.0
```

</div>

<div class="tab-panel" data-tab-id="tabgrp-574-1" data-tab-group="tabgrp-574">

```objective-c
SNRContentWidgetBasicProductItemLayout *itemLayout = [SNRContentWidgetBasicProductItemLayout new];
itemLayout.imageWidthRatio = 1.0f;
itemLayout.imageHeightRatio = 0.4f;
itemLayout.borderWidth = 2.0f;
itemLayout.borderColor = [UIColor blackColor];
itemLayout.shadowColor = [UIColor blackColor];
itemLayout.cornerRadius = 12.0f;
```

</div>
</div>



## Interaction with the Widget
---

### Public Interface

`load()` - Starts fetching data and creates a view structure of the widget.

`isLoaded()` - Checks whether the widget is successfully loaded.

`getView()` - Gets the root view of the whole widget view structure.

### Delegation

[`ContentWidgetDelegate`](/developers/mobile-sdk/listeners-and-delegates/ios-delegates#content-widget-delegate) is used to inform developers about the state of a widget. 

- `snr_widgetIsLoading(widget:isLoading:)` - Called when the widget’s loading state changes. It's an **optional** method.
- `snr_widgetDidLoad(widget:)` - Called after the widget is loaded. It's a **required** method.
- `snr_widgetDidNotLoad(widget:error:)` - Called when an error occurs while loading. It's a **required** method.
- `snr_widgetDidChangeSize(widget:size:)` - Called when the widget size changes. It's an **optional** method.
- `snr_widgetDidReceiveClickAction(widget:model:)` - Called when the customer clicks a widget item. It's a **required** method.


  <div class="admonition admonition-note"><div class="admonition-icon"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg></div><div class="admonition-body"><div class="admonition-content">

  Check the [`ContentWidgetDelegate`](/developers/mobile-sdk/listeners-and-delegates/ios-delegates#content-widget-delegate) section for more details.

  </div></div></div>


### Image Button Custom Action

[`ContentWidgetImageButtonCustomAction`](/developers/mobile-sdk/class-reference/ios/content-widget#contentwidgetbasecustomaction) is used to add an image button to your widget (only if the item layout allows). You can add a button with a single state or make it selectable.

#### Parameters

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| predefinedActionType | [`ContentWidgetBaseCustomActionPredefiniedActionType`](/developers/mobile-sdk/class-reference/ios/content-widget#contentwidgetbasecustomactionpredefiniedactiontype) | .none | It determines which event is sent on click |
| size | `CGSize` | CGSize.Zero | Button size |
| position | `CGPoint` | CGPoint.Zero | Position |
| backgroundColor | `UIColor` | UIColor.clearColor | Background color of the button |
| tintColor | `UIColor` | UIColor.blackColor | Fill color of the button's image, if an asset supports it |
| image | `UIImage` | nil | Button image |
| isSelectable | `Bool` | nil | Flag determining whether the button is selectable |
| selectedImage | `UIImage` | nil | Image of the button when the button is selected |
| isSelected | `SNRContentWidgetImageButtonCustomActionIsSelectedBlock` | nil | Block/closure to be executed when the widget needs to determine the state of a button in the cell |
| onReceiveClickAction | `SNRContentWidgetImageButtonCustomActionReceiveClickActionBlock` | nil | Block/closure to be executed when the button is clicked |

#### Block/Closures

- `isSelected` - Called when the widget tries to determine button's state. The only one parameter is model of data for the cell (for example [`Recommendation`](/developers/mobile-sdk/class-reference/ios/recommendations-and-documents#recommendation)). It's an **optional** property.
- `onReceiveClickAction` - Called when the button was clicked. Parameters are model of data for the cell (for example [`Recommendation`](/developers/mobile-sdk/class-reference/ios/recommendations-and-documents#recommendation)) and current state of button. It's an **optional** property.


## Sample Implementations
---

### Horizontal Slider

This is an example with [`ContentWidgetHorizontalSliderLayout`](/developers/mobile-sdk/class-reference/ios/content-widget#contentwidgethorizontalsliderlayout). It always has fixed height, so after the widget is loaded, its content height can be calculated.

That is why it is done in the `snr_widgetDidLoad(widget:)` method.

The widget content size in a horizontal slider layout can be calculated by the `getSize()` method.


<div class="content-tabs code-tabs" data-tab-group="tabgrp-575">
<div class="tab-buttons"><button class="tab-button" data-tab-id="tabgrp-575-0" data-tab-group="tabgrp-575" data-tab-active="true">swift</button><button class="tab-button" data-tab-id="tabgrp-575-1" data-tab-group="tabgrp-575">objective-c</button></div>

<div class="tab-panel" data-tab-id="tabgrp-575-0" data-tab-group="tabgrp-575" data-tab-active="true">

```swift
class ContentWidgetHorizontalSliderSampleViewController: UIViewController, ContentWidgetDelegate {
    
    var widget: ContentWidget!

    @IBOutlet weak var widgetContainerView: UIView!

    // MARK: - Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()

        setupWidget()
    }

    // MARK: - Private

    func setupWidget() -> Void {
        let options = ContentWidgetRecommendationsOptions()
        options.slug = "similar"
        options.productID = "12345"
        options.mapping = { model in
            guard let imageURLString = model.attributes["imageLink"] as? String,
                        let imageURL = URL(string: imageURLString),
                        let title = model.attributes["title"] as? String,
                        let priceDictionary = model.attributes["price"] as? [AnyHashable: Any],
                        let priceValue = priceDictionary["value"] as? Double
            else {
                return nil
            }

            let dataModel = ContentWidgetRecommendationDataModel(imageURL: imageURL, title: title, priceCurrency: "PLN", price: NSNumber(value: priceValue), salePrice: nil)

            if let salePriceDictionary = model.attributes["salePrice"] as? [AnyHashable: Any],
                let salePriceValue = salePriceDictionary["value"] as? Double {
                dataModel.salePriceValue = NSNumber(floatLiteral: salePriceValue)
            }

            let badgeDataModel = ContentWidgetBadgeDataModel(backgroundColor: UIColor.black, textColor: UIColor.white, text: "Black Week")
            dataModel.badge = badgeDataModel

            return dataModel
        }

        let horizontalSliderLayout = ContentWidgetHorizontalSliderLayout()
        horizontalSliderLayout.insets = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 16.0, right: 16.0)
        horizontalSliderLayout.itemSize = CGSize(width: 150, height: 350)
        horizontalSliderLayout.itemSpacing = 8.0

        let itemLayout = ContentWidgetBasicProductItemLayout()
        itemLayout.imageWidthRatio = 1.0
        itemLayout.imageHeightRatio = 0.4
        itemLayout.borderWidth = 2.0
        itemLayout.borderColor = UIColor.black
        itemLayout.shadowColor = UIColor.black
        itemLayout.cornerRadius = 12.0

        let actionButton = ContentWidgetImageButtonCustomAction()
        actionButton.backgroundColor = UIColor.clear
        actionButton.tintColor = UIColor.black
        actionButton.image = UIImage(imageLiteralResourceName: "Shop Flow/icon_favorite_add")
        actionButton.isSelectable = true
        actionButton.selectedImage = UIImage(imageLiteralResourceName: "Shop Flow/icon_favorite_remove")
        actionButton.size = CGSize(width: 40, height: 40)
        actionButton.predefinedActionType = .sendLikeEvent
        actionButton.onReceiveClickAction = {
            model, isSelected in

            if let recommendationModel = model as? Recommendation {
                print("Content Widget did receive click action for action button \(recommendationModel.title)")
            }
        }
        actionButton.isSelected = {
            model in

            return false
        }

        itemLayout.actionButton = actionButton
        itemLayout.actionButtonPosition = CGPoint(x: (150.0 - 40 - 8), y: 8)

        let appearance = ContentWidgetAppearance(widgetLayout: horizontalSliderLayout, itemLayout: itemLayout)

        widget = ContentWidget(options: options, appearance: appearance)
        widget.delegate = self
        widget.load()
    }

    // MARK: - ContentWidgetDelegate

    func snr_widgetIsLoading(widget: ContentWidget, isLoading: Bool) {
        print("Content Widget is loading: \(isLoading)")
    }

    func snr_widgetDidLoad(widget: ContentWidget) {
        print("Content Widget did load")

        let widgetView: UIView = widget.getView()
        let widgetSize: CGSize = (widget.layout as! ContentWidgetHorizontalSliderLayout).getSize()

        widgetContainerView.addSubview(widgetView)

        widgetView.translatesAutoresizingMaskIntoConstraints = false
        widgetView.topAnchor.constraint(equalTo: widgetContainerView.topAnchor).isActive = true
        widgetView.bottomAnchor.constraint(equalTo: widgetContainerView.bottomAnchor).isActive = true
        widgetView.leftAnchor.constraint(equalTo: widgetContainerView.leftAnchor).isActive = true
        widgetView.rightAnchor.constraint(equalTo: widgetContainerView.rightAnchor).isActive = true

        widgetContainerView.heightAnchor.constraint(equalToConstant: widgetSize.height).isActive = true
    }

    func snr_widgetDidNotLoad(widget: ContentWidget, error: Error) {
        print("Content Widget did not load. Error: \(error.localizedDescription)")
    }

    func snr_widgetDidChangeSize(widget: ContentWidget, size: CGSize) {
        print("Content Widget did change size to: \(size)")
    }

    func snr_widgetDidReceiveClickAction(widget: ContentWidget, model: BaseModel) {
        if let recommendationModel = model as? Recommendation {
            print("Content Widget did receive click action for \(recommendationModel.title)")
        }
    }
}
```

</div>

<div class="tab-panel" data-tab-id="tabgrp-575-1" data-tab-group="tabgrp-575">

```objective-c
@interface ContentWidgetHorizontalSliderSampleViewController : UIViewController 

@property (weak, nonatomic, nonnull, readwrite) IBOutlet UIView *widgetContainerView;

@end

@@implementation ContentWidgetHorizontalSliderSampleViewController () <SNRContentWidgetDelegate>

@property (strong, nonatomic, nullable, readwrite) SNRContentWidget *widget;

@end

@@implementation ContentWidgetHorizontalSliderSampleViewController 
    
#pragma mark - Lifecycle

- (void)viewDidLoad {
    [super viewDidLoad];

    [self setupWidget];
}

#pragma mark - Private

- (void)setupWidget {
    SNRContentWidgetOptions *options = [SNRContentWidgetOptions new];
    options.slug = @"similar";
    options.productID = @"12345";
    options.mapping = ^(SNRContentWidgetRecommendationModel *model) {
        NSString *imageURLString = model.attributes[@"imageLink"];
        NSString *imageURL = [[NSURL alloc] initWithString:imageURLString];
        NSString *title = model.attributes[@"title"];
        NSDictionary *priceDictionary = model.attributes[@"price"];
        NSNumber *priceValue = priceDictionary[@"value"];
        if (imageURL == nil || title == nil || priceValue == nil) {
            return nil;
        }


        SNRContentWidgetRecommendationDataModel *dataModel = [[SNRContentWidgetRecommendationDataModel alloc] initWithimageURL:imageURL title:title priceCurrency:@"PLN" price:priceValue salePrice:nil];

        NSDictionary *salePriceDictionary = model.attributes[@"salePrice"];
        NSNumber *salePriceValue = salePriceDictionary[@"value"];
        if (salePrice != nil) {
            dataModel.salePriceValue = salePriceValue;
        }

        SNRContentWidgetBadgeDataModel *badgeDataModel = [[SNRContentWidgetBadgeDataModel alloc] initWithBackgroundColor:backgroundColor textColor:textColor text:text];
        dataModel.badge = badgeDataModel;

        return dataModel;
    }

    SNRContentWidgetHorizontalSliderLayout *horizontalSliderLayout = [SNRContentWidgetHorizontalSliderLayout new];
    horizontalSliderLayout.insets = UIEdgeInsetsMake(16.0f, 16.0f, 16.0f, 16.0f);
    horizontalSliderLayout.itemSize = CGSizeMake(150.0f, 350.0f);
    horizontalSliderLayout.itemSpacing = 8.0f;

    SNRContentWidgetBasicProductItemLayout *itemLayout = [SNRContentWidgetBasicProductItemLayout new];

    itemLayout.imageWidthRatio = 1.0f;
    itemLayout.imageHeightRatio = 0.4f;
    itemLayout.borderWidth = 2.0f;
    itemLayout.borderColor = [UIColor blackColor];
    itemLayout.shadowColor = [UIColor blackColor];
    itemLayout.cornerRadius = 12.0f;

    SNRContentWidgetImageButtonCustomAction *actionButton = [SNRContentWidgetImageButtonCustomAction new];
    actionButton.backgroundColor = [UIColor clearColor];
    actionButton.tintColor = [UIColor blackColor];
    actionButton.image = [UIImage imageNamed:@"Shop Flow/icon_favorite_add"];
    actionButton.isSelectable = YES
    actionButton.selectedImage = [UIImage imageNamed:@"Shop Flow/icon_favorite_remove"];
    actionButton.size = CGSizeMake(40.0f, 40.0f);
    actionButton.predefinedActionType = SNRContentWidgetBaseCustomActionPredefiniedActionTypeSendLikeEvent;
    actionButton.onReceiveClickAction = ^(SNRBaseModel *model, BOOL isSelected) {
        NSLog(@"Content Widget did receive click action for action button %@", ((SNRRecommednation *)recommendationModel.title));
    };
    actionButton.isSelected = ^(SNRBaseModel *model) {
        return NO;
    };

    itemLayout.actionButton = actionButton
    itemLayout.actionButtonPosition = CGPointMake((150.0f - 40.0f - 8), 8.0f)

    SNRContentWidgetAppearance *appearance = [[SNRContentWidgetAppearance alloc] initWithLayout:horizontalSliderLayout andItemLayout:itemLayout];

    SNRContentWidget *widget = [[SNRContentWidget alloc] initWithOptions:options andAppearance:appearance];
    widget.delegate = self

    [widget load];
    self.widget = widget;
}

#pragma mark - SNRContentWidgetDelegate

- (void)SNR_widget:(SNRContentWidget *)widget isLoading:(BOOL)isLoading {
    NSLog(@"Content Widget is loading: %@", isLoading ?? @"true" : @"false");
}

- (void)SNR_widgetDidLoad:(SNRContentWidget *)widget {
    NSLog(@"Content Widget did load");

    UIView *widgetView = [widget getView];
    CGSize widgetSize = [((SNRContentWidgetHorizontalSliderLayout *)widget.layout getSize];

    [self.widgetContainerView addSubview:widgetView];

    widgetView.translatesAutoresizingMaskIntoConstraints = NO;
    [widgetView.topAnchor constraintEqualTo:widgetContainerView.topAnchor].active = YES;
    [widgetView.bottomAnchor constraintEqualTo:widgetContainerView.bottomAnchor].active = YES;
    [widgetView.leftAnchor constraintEqualTo:widgetContainerView.leftAnchor].active = YES;
    [widgetView.rightAnchor constraintEqualTo:widgetContainerView.rightAnchor].active = YES;

    [widgetContainerView.heightAnchor constraintEqualToConstant:widgetSize.height].active = YES;
}

- (void)SNR_widget:(SNRContentWidget *)widget didNotLoadWithError:(NSError *)error {
    NSLog(@"Content Widget did not load. Error: %@", error.localizedDescription);
}

- (void)SNR_widget:(SNRContentWidget *)widget didChangeToSize:(CGSize)size {
    NSLog(@"Content Widget did change size to %@", NSStringFromCGSize(size));
}

- (void)SNR_widget:(SNRContentWidget *)widget didReceiveClickActionForModel:(SNRBaseModel *)model {
    if ([model isKindOfClass:[SNRRecommendation class]] == YES) {
        SNRRecommendation *recommendationModel = ((SNRRecommendation *)model);
        NSLog(@"Content Widget did receive click action for %@", recommendationModel.title);
    }
}

@end
```

</div>
</div>


### Grid View

A basic example with [`ContentWidgetGridLayout`](/developers/mobile-sdk/class-reference/ios/content-widget#contentwidgetgridlayout) and `UITableViewController`. Remember that cells are prototyped.

Initially, the height of the tenth row equals zero, because there is no possibility of getting the correct height of the widget. Before the widget is loaded, we don't know how many items it's going to contain.

The widget view is flexible, so it fits the dimensions that you set up. If the height of the widget that you set is smaller than the total height of the generated grid, the content can be scrolled vertically.

The grid's content height depends on:

- The widget width that you set up
- The number of items that have been loaded

That is why the code below reloads the tenth row after the widget is loaded. Earlier, it was impossible to calculate the height correctly.

In addition, the widget row is reloaded when the `snr_widgetDidChangeSize(widget:size:)` method is called. In this case, it's a required action, because the widget has pinned constraints to superview in a prototyped cell. 

The widget's content size changes with the tableview size, for example when the screen orientation changes, the widget's height needs to be re-calculated. Otherwise, the cell height may be larger that necessary.

The total widget content size in a grid layout can be calculated by the `getSize(preferredWidth:)` method.


<div class="content-tabs code-tabs" data-tab-group="tabgrp-576">
<div class="tab-buttons"><button class="tab-button" data-tab-id="tabgrp-576-0" data-tab-group="tabgrp-576" data-tab-active="true">swift</button><button class="tab-button" data-tab-id="tabgrp-576-1" data-tab-group="tabgrp-576">objective-c</button></div>

<div class="tab-panel" data-tab-id="tabgrp-576-0" data-tab-group="tabgrp-576" data-tab-active="true">

```swift
class ContentWidgetGridViewSampleViewController: UITableViewController, ContentWidgetDelegate {
    
    var widget: ContentWidget!

    @IBOutlet weak var widgetContainerView: UIView!

    // MARK: - Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()

        setupWidget()
    }

    func setupWidget() -> Void {
        let options = ContentWidgetRecommendationsOptions()
        options.slug = "similar"
        options.productID = "12345"
        options.mapping = { model in
            guard let imageURLString = model.attributes["imageLink"] as? String,
                        let imageURL = URL(string: imageURLString),
                        let title = model.attributes["title"] as? String,
                        let priceDictionary = model.attributes["price"] as? [AnyHashable: Any],
                        let priceValue = priceDictionary["value"] as? Double
            else {
                return nil
            }

            let dataModel = ContentWidgetRecommendationDataModel(imageURL: imageURL, title: title, priceCurrency: "PLN", price: NSNumber(value: priceValue), salePrice: nil)

            if let salePriceDictionary = model.attributes["salePrice"] as? [AnyHashable: Any],
                let salePriceValue = salePriceDictionary["value"] as? Double {
                dataModel.salePriceValue = NSNumber(floatLiteral: salePriceValue)
            }

            let badgeDataModel = ContentWidgetBadgeDataModel(backgroundColor: UIColor.black, textColor: UIColor.white, text: "Black Week")
            dataModel.badge = badgeDataModel

            return dataModel
        }

        let gridLayout = ContentWidgetGridLayout()
        gridLayout.insets = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 16.0, right: 16.0)
        gridLayout.itemSize = CGSize(width: 150.0, height: 350.0)
        gridLayout.horizontalItemSpacing = 8.0
        gridLayout.verticalItemSpacing = 8.0

        let itemLayout = ContentWidgetBasicProductItemLayout()
        itemLayout.imageWidthRatio = 1.0
        itemLayout.imageHeightRatio = 0.4
        itemLayout.borderWidth = 2.0
        itemLayout.borderColor = UIColor.black
        itemLayout.shadowColor = UIColor.black
        itemLayout.cornerRadius = 12.0

        actionButton.onReceiveClickAction = {
            model, isSelected in

            if let recommendationModel = model as? Recommendation {
                print("Content Widget did receive click action for action button \(recommendationModel.title)")
            }
        }
        actionButton.isSelected = {
            model in

            return false
        }

        itemLayout.actionButton = actionButton
        itemLayout.actionButtonPosition = CGPoint(x: (150.0 - 40.0 - 8.0), y: 8.0)

        let appearance = ContentWidgetAppearance(widgetLayout: gridLayout, itemLayout: itemLayout)

        widget = ContentWidget(options: options, appearance: appearance)
        widget.delegate = self
        widget.load()
    }

    // MARK: - UITableViewDataSource, UITableViewDelegate

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        if indexPath.row == 10 {
            if widget != nil && widget.isLoaded() {
                return (widget.layout as! ContentWidgetGridLayout).getSize(preferredWidth: tableView.bounds.size.width).height
            } else {
                return 0
            }
        }

        return 100.0
    }

    // MARK: - ContentWidgetDelegate

    func snr_widgetIsLoading(widget: ContentWidget, isLoading: Bool) {
        print("Content Widget is loading: \(isLoading)")
    }

    func snr_widgetDidLoad(widget: ContentWidget) {
        print("Content Widget did load")

        view.addSubview(widgetView)

        widgetView = widget.getView()
        widgetContainerView.addSubview(widgetView)

        widgetView.translatesAutoresizingMaskIntoConstraints = false
        widgetView.topAnchor.constraint(equalTo: widgetContainerView.topAnchor).isActive = true
        widgetView.bottomAnchor.constraint(equalTo: widgetContainerView.bottomAnchor).isActive = true
        widgetView.leftAnchor.constraint(equalTo: widgetContainerView.leftAnchor).isActive = true
        widgetView.rightAnchor.constraint(equalTo: widgetContainerView.rightAnchor).isActive = true

        tableView.reloadData()
    }

    func snr_widgetDidNotLoad(widget: ContentWidget, error: Error) {
        print("Content Widget did not load. Error: \(error.localizedDescription)")
    }

    func snr_widgetDidChangeSize(widget: ContentWidget, size: CGSize) {
        print("Content Widget did change size to: \(size)")

        tableView.reloadData()
    }

    func snr_widgetDidReceiveClickAction(widget: ContentWidget, model: BaseModel) {
        if let recommendationModel = model as? Recommendation {
            print("Content Widget did receive click action for \(recommendationModel.title)")
        }
    }
}
```

</div>

<div class="tab-panel" data-tab-id="tabgrp-576-1" data-tab-group="tabgrp-576">

```objective-c
@interface ContentWidgetGridViewSampleViewController : UITableViewController 

@property (weak, nonatomic, nonnull, readwrite) IBOutlet UIView *widgetContainerView;

@end

@@implementation ContentWidgetGridViewSampleViewController () <SNRContentWidgetDelegate>

@property (strong, nonatomic, nullable, readwrite) SNRContentWidget *widget;

@end

@@implementation ContentWidgetGridViewSampleViewController 
    
#pragma mark - Lifecycle

- (void)viewDidLoad {
    [super viewDidLoad];

    [self setupWidget];
}

#pragma mark - Private

- (void)setupWidget {
    SNRContentWidgetOptions *options = [SNRContentWidgetOptions new];
    options.slug = @"similar";
    options.productID = @"12345";
    options.mapping = ^(SNRContentWidgetRecommendationModel *model) {
        NSString *imageURLString = model.attributes[@"imageLink"];
        NSString *imageURL = [[NSURL alloc] initWithString:imageURLString];
        NSString *title = model.attributes[@"title"];
        NSDictionary *priceDictionary = model.attributes[@"price"];
        NSNumber *priceValue = priceDictionary[@"value"];
        if (imageURL == nil || title == nil || priceValue == nil) {
            return nil;
        }


        SNRContentWidgetRecommendationDataModel *dataModel = [[SNRContentWidgetRecommendationDataModel alloc] initWithimageURL:imageURL title:title priceCurrency:@"PLN" price:priceValue salePrice:nil];

        NSDictionary *salePriceDictionary = model.attributes[@"salePrice"];
        NSNumber *salePriceValue = salePriceDictionary[@"value"];
        if (salePrice != nil) {
            dataModel.salePriceValue = salePriceValue;
        }

        SNRContentWidgetBadgeDataModel *badgeDataModel = [[SNRContentWidgetBadgeDataModel alloc] initWithBackgroundColor:backgroundColor textColor:textColor text:text];
        dataModel.badge = badgeDataModel;

        return dataModel;
    }

    SNRContentWidgetGridLayout *gridLayout = [SNRContentWidgetGridLayout new];
    gridLayout.insets = UIEdgeInsetsMake(16.0f, 16.0f, 16.0f, 16.0f);
    gridLayout.itemSize = CGSizeMake(150.0f, 350.0f);
    gridLayout.horizontalItemSpacing = 8.0f;
    gridLayout.verticalItemSpacing = 8.0f;

    SNRContentWidgetImageButtonCustomAction *actionButton = [SNRContentWidgetImageButtonCustomAction new];
    actionButton.backgroundColor = [UIColor clearColor];
    actionButton.tintColor = [UIColor blackColor];
    actionButton.image = [UIImage imageNamed:@"Shop Flow/icon_favorite_add"];
    actionButton.isSelectable = YES
    actionButton.selectedImage = [UIImage imageNamed:@"Shop Flow/icon_favorite_remove"];
    actionButton.size = CGSizeMake(40.0f, 40.0f);
    actionButton.predefinedActionType = SNRContentWidgetBaseCustomActionPredefiniedActionTypeSendLikeEvent;
    actionButton.onReceiveClickAction = ^(SNRBaseModel *model, BOOL isSelected) {
        NSLog(@"Content Widget did receive click action for action button %@", ((SNRRecommednation *)recommendationModel.title));
    };
    actionButton.isSelected = ^(SNRBaseModel *model) {
        return NO;
    };

    itemLayout.actionButton = actionButton
    itemLayout.actionButtonPosition = CGPointMake((150.0f - 40.0f - 8), 8.0f)

    SNRContentWidgetAppearance *appearance = [[SNRContentWidgetAppearance alloc] initWithLayout:gridLayout andItemLayout:itemLayout];

    SNRContentWidget *widget = [[SNRContentWidget alloc] initWithOptions:options andAppearance:appearance];
    widget.delegate = self;

    [widget load];
}

#pragma mark - UITableViewDataSource, UITableViewDelegate

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 10;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.row == 10) {
        if (self.widget != nil && [self.widget isLoaded] == YES) {
            return [((ContentWidgetGridLayout *)widget.layout) getSizeForPreferredWidth:tableView.bounds.size.width].height;
        } else {
            return 0;
        }
    }

    return 100.0f;
}

#pragma mark - SNRContentWidgetDelegate

- (void)SNR_widget:(SNRContentWidget *)widget isLoading:(BOOL)isLoading {
    NSLog(@"Content Widget is loading: %@", isLoading ?? @"true" : @"false");
}

- (void)SNR_widgetDidLoad:(SNRContentWidget *)widget {
    NSLog(@"Content Widget did load");


    UIView *widgetView = [self.widget getView];
    CGSize widgetSize = [((SNRContentWidgetHorizontalSliderLayout *)widget.layout getSize];

    [self.widgetContainerView addSubview:widgetView];

    widgetView.translatesAutoresizingMaskIntoConstraints = NO;
    [widgetView.topAnchor constraintEqualTo:widgetContainerView.topAnchor].active = YES;
    [widgetView.bottomAnchor constraintEqualTo:widgetContainerView.bottomAnchor].active = YES;
    [widgetView.leftAnchor constraintEqualTo:widgetContainerView.leftAnchor].active = YES;
    [widgetView.rightAnchor constraintEqualTo:widgetContainerView.rightAnchor].active = YES;

    [widgetContainerView.heightAnchor constraintEqualToConstant:widgetSize.height].active = YES;
}

- (void)SNR_widget:(SNRContentWidget *)widget didNotLoadWithError:(NSError *)error {
    NSLog(@"Content Widget did not load. Error: %@", error.localizedDescription);
}

- (void)SNR_widget:(SNRContentWidget *)widget didChangeToSize:(CGSize)size {
    NSLog(@"Content Widget did change size to %@", NSStringFromCGSize(size));
}

- (void)SNR_widget:(SNRContentWidget *)widget didReceiveClickActionForModel:(SNRBaseModel *)model {
    if ([model isKindOfClass:[SNRRecommendation class]] == YES) {
        SNRRecommendation *recommendationModel = ((SNRRecommendation *)model);
        NSLog(@"Content Widget did receive click action for %@", recommendationModel.title);
    }
}

@end
```

</div>
</div>


## More information
---

You can find more information under the following links:

- [Sample App on GitHub](https://github.com/Synerise/ios-sdk/tree/master/SampleAppSwift/4.1.0) 
- [Horizontal Slider implementation in the Sample App on GitHub](https://github.com/Synerise/synerise-ios-sdk/blob/master/SampleAppSwift/4.1.0/SampleAppSwift/Main/Developer%20Tools%20Flow/ViewControllers/ContentAPI/RecommendationsWidgetAsSliderTableViewController.swift)
- [Grid implementation in the Sample App on GitHub](https://github.com/Synerise/synerise-ios-sdk/blob/master/SampleAppSwift/4.1.0/SampleAppSwift/Main/Developer%20Tools%20Flow/ViewControllers/ContentAPI/RecommendationsWidgetAsGridTableViewController.swift)
