Banners place
Starting with SDK version 1.26.0, banner display functionality has been added.
Initialization
To embed the banner widget into your application, you must first initialize the SDK (if it has not been initialized previously with other functionality). SDK initialization is covered in the Basic initialization section.
- UIKit
- SwiftUI
Banners can be added as StoryView:
var bannersView: IASBannersView!
override func viewDidLoad() {
super.viewDidLoad()
bannersView = IASBannersView(placeID: <String> = "default",
appearance: <IASBannersAppearance> = .init(),
frame: <CGRect> = .zero)
view.addSubview(bannersView)
bannersView.create() // beginning of the widget's internal logic
}
After calling the create() method, the internal logic for opening a session and obtaining a list of banners will start. You can track the loading of banners in the bannersDidUpdated closure (see description below).
Banners can be added as StoryListView:
struct ContentView: View
{
var body: some View {
VStack(alignment: .leading) {
IASBannersListView(id: <String?>,
appearance: <IASBannersAppearance>)
/// when initializing IASBannersListView, you should specify its size,
/// otherwise it will stretch to the whole screen
.frame(height: 120)
.frame(maxWidth: .infinity)
Spacer()
}
}
}
After initializing IASBannersListView, the internal logic for opening a session and obtaining a list of banners is launched. You can track the loading of banners in the onUpdate callback (see description below).
Please note
Prior to version 1.28.6, the first call to the
create()method cached the data received from the server, and subsequent calls—even for newIASBannersViewinstances—retrieved data from the cacheStarting with version 1.28.6, the
create()method always fetches the banner list from the server unless preload banners place has been called. Read more about preloading here read here
Customization
To change the appearance of banners, use the IASBannersAppearance object:
struct IASBannersAppearance {
enum LayoutType: Hashable {
case horizontalCenterend, verticalList, tilesList
}
let layoutType: LayoutType // .horizontalCenterend (default), .verticalList, or .tilesList; since 1.29.9
let maxBannersCount: Int? // vertical and tiles lists: default 20; nil includes all available banners; since 1.29.9
let shouldLoop: Bool // if banner place is looped, default = true
let sideInset: CGFloat // which part (in dp) of other banners will be shown with current, default = 0.0
let leadingInset: CGFloat // setting the cast parameter for indentation (if not specified, it is taken from sideInset)
let trailingInset: CGFloat // setting the cast parameter for indentation (if not specified, it is taken from sideInset)
let interItemSpacing: CGFloat // distance (in dp) between banners, default = 8.0
let columnSpacing: CGFloat // tiles only: defaults to interItemSpacing; since 1.29.9
let rowSpacing: CGFloat // tiles only: defaults to interItemSpacing; since 1.29.9
let cornerRadius: CGFloat // radius (in dp) of banners, default = 16.0
}
Starting with SDK 1.29.9 (4370), IASBannersAppearance supports these layouts:
layoutType | Layout | Scrolling and height |
|---|---|---|
.horizontalCenterend | Horizontal carousel (default) | Internal horizontal scrolling; set the widget height as before. |
.verticalList | One-column vertical list | Internal vertical scrolling; set a viewport height. |
.tilesList | Two-column grid | No internal scrolling; the widget expands to its full content height inside an external scroll container. |
The new initializer parameters have defaults: layoutType: .horizontalCenterend, maxBannersCount: 20, columnSpacing: nil, and rowSpacing: nil. Omitted grid gaps use interItemSpacing. Keeping the defaults preserves the carousel behavior from before 1.29.9: the banner limit and grid gaps do not affect the carousel.
In .verticalList and .tilesList, maxBannersCount limits the available banners in backend order: nil includes all available banners, 0 gives an empty list, and negative values are clamped to 0. columnSpacing and rowSpacing affect only .tilesList; negative or non-finite gap values are clamped to 0.
- UIKit
- SwiftUI
This object must be set when initializing the banner widget. Once set, it cannot be changed. If you need to change the appearance of the widget, you must create a new instance of it.
var bannersView: IASBannersView!
override func viewDidLoad() {
super.viewDidLoad()
let bannersAppearance = IASBannersAppearance(shouldLoop: false,
sideInset: 24.0,
interItemSpacing: 16.0,
cornerRadius: 8.0)
bannersView = IASBannersView(appearance: bannersAppearance)
view.addSubview(bannersView)
bannersView.create()
}
This object must be set when initializing the banner widget. Once set, it cannot be changed. If you need to change the appearance of the widget, you must create a new instance of it.
struct ContentView: View
{
let bannersAppearance = IASBannersAppearance(shouldLoop: false,
sideInset: 24.0,
interItemSpacing: 16.0,
cornerRadius: 8.0)
var body: some View {
VStack(alignment: .leading) {
IASBannersListView(appearance: bannersAppearance)
/// when initializing IASBannersListView, you should specify its size,
/// otherwise it will stretch to the whole screen
.frame(height: 120)
.frame(maxWidth: .infinity)
Spacer()
}
}
}
Preloading
To make Banners place showing faster, you can preload banners data by placeID. Call InAppStory.shared.preloadBanners(placeID: String, complete: @escaping (Result<Bool, Error>) -> Void) and wait for completion.
Pay attention
Preloading is only possible after SDK initialization.
Basic preloading (for example, on a Splash Screen)
InAppStory.shared.preloadBanners(placeID: <placeID>) { result in
switch result {
case .success:
// Data was loaded
break
case .failure:
// Loading error
break
}
}
Please note
After preloading a banner place, all instances of theIASBannersViewobject with a preloadedplaceIDwill retrieve data only from the cache. To update the data in the list, you must either call therefresh()method or trigger the preload again.
Actions
To track the actions of the banner widget, you need to subscribe to the closures:
- UIKit
- SwiftUI
bannersDidUpdated: <(_ isContent: <Bool>, _ count: <Int>, _ listHeight: <CGFloat>) -> Void>- called when the banner list is updated, for example, when first loaded, after callingcreate()orrefresh()to update the list:isContent: <Bool>- a parameter showing whether there is content in the widget;count: <Int>- a parameter showing how many items are in the list (can be used for dot control navigation);listHeight: <CGFloat>- carousel height calculated from its width and the largest banner; for.verticalListand.tilesList(since 1.29.9), the full content height;
onActionWith: (_ target: <String>) -> Void: called when an action occurs inside the banner, such as clicking a button with a link;target: <String>- link obtained as a result of clicking a button in the banner;
bannersDidScroll: (_ index: <Int>) -> Void: called when the banner list is scrolled. This can be a swipe through the list, scrolling by an internal timer, or by external methods (described below);index: <Int>- the index to which the banner list has scrolled (can be used for dot control navigation);
extension ViewController {
func observers() {
bannersView.bannersDidUpdated = { isContent, count, listHeight in
// banner list updated
// skeleton or preloader (if present) can be removed
// use listHeight for carousel/grid height, not the vertical list viewport
}
bannersView.onActionWith = { target in
// an action was performed
// here you can process the link
// that came in the target parameter
}
bannersView.bannersDidScroll = { index in
// list of banners has been updated
// dot control navigation can be updated
}
}
}
onUpdate(_ bannersDidUpdte: <(_ isContent: <Bool>, _ count: <Int>, _ listHeight: <CGFloat>) -> Void>)- called when the banner list is updated, for example, during the first load, after callingcreate()orrefresh()to update the list:isContent: <Bool>- a parameter showing whether there is content in the widget;count: <Int>- a parameter showing how many items are in the list (can be used for dot control navigation);listHeight: <CGFloat>- carousel height calculated from its width and the largest banner; for.verticalListand.tilesList(since 1.29.9), the full content height;
onAction(_ action: (_ target: <String>) -> Void): called when an action occurs inside the banner, for example, clicking a button with a link;target: <String>- the link obtained as a result of clicking on a button in the banner;
didScroll(_ didScroll: (_ index: <Int>) -> Void): called when the banner list is scrolled. This can be a swipe through the list, scrolling by an internal timer, or by external methods (described below);index: <Int>- the index to which the banner list has scrolled (can be used for dot control navigation);
struct ContentView: View
{
let bannersAppearance = IASBannersAppearance(shouldLoop: false,
sideInset: 24.0,
interItemSpacing: 16.0,
cornerRadius: 8.0)
var body: some View {
VStack(alignment: .leading) {
IASBannersListView(appearance: bannersAppearance)
.onUpdate { isContent, count, height in
print("Content is \(isContent)")
}
.onAction { target in
print("Target: \(target), Type: \(type)")
}
.didScroll { index in
print("Did scroll to index: \(index)")
}
.frame(height: 120)
.frame(maxWidth: .infinity)
Spacer()
}
}
}
Navigation
Vertical list
Available since SDK 1.29.9 (4370). Use .verticalList to display banners in the SDK's own scrolling container. Give the widget a viewport height using UIKit constraints or SwiftUI's .frame(height:):
let appearance = IASBannersAppearance(
layoutType: .verticalList,
sideInset: 16,
interItemSpacing: 12,
maxBannersCount: 20
)
// UIKit: constrain this view to the desired viewport, then call create().
let banners = IASBannersView(placeID: "default", appearance: appearance)
// SwiftUI
IASBannersListView(id: "default", appearance: appearance)
.frame(height: 500)
The list preserves backend order after applying banner availability rules. maxBannersCount limits the displayed list, not concurrent downloads. It defaults to 20; set it to nil to include all available banners returned by the backend. Zero or a negative value produces an empty list.
Each banner's height is its available width divided by its own ratio, rounded to the nearest point. Banners may have different heights. sideInset, leadingInset, and trailingInset control horizontal padding; interItemSpacing separates rows. In this mode, the height reported by bannersDidUpdated / onUpdate is the full content height, so do not use it as the scrolling viewport height.
There is no pagination or automatic scrolling. shouldLoop is ignored for list navigation. showNext() and showPrevious() have no effect; use showBannerWith(index:) (SwiftUI: showWithIndex) to scroll to an explicit index.
Playback and loading rules for both list layouts are described below.
Two-column grid
Available since SDK 1.29.9 (4370). Use .tilesList for a two-column grid inside a screen's existing scroll container. This layout has no internal scrolling and expands to the full content height.
let appearance = IASBannersAppearance(
layoutType: .tilesList,
sideInset: 16,
maxBannersCount: 20,
columnSpacing: 8,
rowSpacing: 12
)
// UIKit: add to an external UIScrollView's content container.
let banners = IASBannersView(placeID: "default", appearance: appearance)
// Constrain width and position; intrinsicContentSize supplies the grid height.
// For manual frames, measure the height with sizeThatFits(_:).
banners.create() // After setting up the view and initializing the SDK.
// SwiftUI (iOS 15+): the grid height updates automatically.
ScrollView {
IASBannersListView(id: "default", appearance: appearance)
.frame(maxWidth: .infinity)
}
Cell width is (containerWidth - leadingInset - trailingInset - columnSpacing) / 2. Each cell's height is its width divided by its own ratio, rounded to the nearest point. The next row starts below the tallest cell in the previous row, separated by rowSpacing. An odd final banner occupies one column. Leading/trailing placement respects RTL.
shouldLoop is ignored. showNext(), showPrevious(), and showBannerWith(index:) (SwiftUI: showWithIndex) have no effect in this layout; scroll the external container instead. bannersDidUpdated / onUpdate reports the full grid content height.
List playback and loading
Since SDK 1.29.9 (4370), .verticalList and .tilesList share these rules:
- Every banner with strictly more than 75% of its area visible plays simultaneously and muted. For a banner taller than the visible container, more than 75% of the container's available height must be occupied by that banner.
- At 75% visibility or less, playback pauses. It resumes from the same position while the web context survives. After cell reuse, context reset, or deinitialization, it starts from the beginning.
- A completed banner repeats itself while eligible. Repetition does not advance the list or add another
viewstatistic. As with the carousel,viewis recorded once per banner per list load. - Backgrounding the app pauses playback. A manual pause remains in effect during scrolling and background/foreground transitions until
resume()is called. - Lists also pause while their input dialog or another SDK reader is displayed; playback eligibility is checked again after it closes.
- Clicks remain enabled below the playback threshold. Ready banners appear independently; failed rows retain their space and offer an individual retry.
In .tilesList only, if the SDK cannot determine the visible viewport, all banners play and count as viewed. Banners that are known to be hidden do not use this fallback.
Both list layouts use a separate standard loading indicator per cell. Custom views from placeholderBannerView or placeholderView are used for the horizontal carousel and are not reused across list cells. See separate loading placeholders.
Pause and resume
In SDK 1.29.9 (4370), the existing public IASBannersView.pause() / resume() methods and SwiftUI pause(_:) / resume(_:) modifiers also control playback in the new vertical and grid layouts. These methods and modifiers were already available for the carousel before 1.29.9. Call them on the main thread.
// UIKit
bannersView.pause()
bannersView.resume()
In SwiftUI, the modifiers provide closures that you can save and call later:
struct BannersScreen: View {
@State private var pauseBanners: (() -> Void)?
@State private var resumeBanners: (() -> Void)?
var body: some View {
VStack {
IASBannersListView(
id: "default",
appearance: IASBannersAppearance(layoutType: .verticalList)
)
.pause { pauseBanners = $0 }
.resume { resumeBanners = $0 }
.frame(height: 500)
Button("Pause") { pauseBanners?() }
Button("Resume") { resumeBanners?() }
}
}
}
In the vertical and grid layouts, pause() stops playback without disabling scrolling or clicks. resume() clears the manual pause and rechecks visibility; only eligible banners play while the app is active. Without manual calls, the SDK manages playback automatically. The persistent manual-pause rule described above applies to these two list layouts; the carousel retains its existing automatic visibility/foreground resume behavior.
Horizontal carousel
- UIKit
- SwiftUI
To control the display and movement of banners, you can use methods to flip to the nearest ones or jump to a specific index:
showNext()- used to jump to the next banner in the list;showPrevious()- used to go to the previous banner in the list;showBannerWith(index: <Int>)- used to go to a specific banner by index (can be used for dot control navigation);
extension ViewController {
func nextButtonTouch() {
bannersView.showNext()
}
func previosButtonTouch() {
bannersView.showPrevious()
}
func dotControlTouch(with index: Int) {
bannersView.showBannerWith(index: index)
}
}
To control the display and movement of banners, you can use methods for flipping to the nearest or moving to a specific index obtained from closures:
showNext(_ showNext: @escaping ((_ showNext: @escaping () -> Void) -> Void)) -> IASBannersListView- used to move to the next banner in the list;showPrevious(_ showPrevious: @escaping ((_ showPrevious: @escaping () -> Void) -> Void)) -> IASBannersListView- used to move to the previous banner in the list;showWithIndex(_ showWithIndex: @escaping ((_ showWithIndex: @escaping (_ index: Int) -> Void) -> Void)) -> IASBannersListView- used to navigate to a specific banner by index (can be used for dot control navigation);
struct ContentView: View
{
@State private var showNextBanner: () -> Void = {}
@State private var showPreviousBanner: () -> Void = {}
@State private var showBannerWithIndex: (_ index: Int) -> Void = { _ in }
let bannersAppearance = IASBannersAppearance(shouldLoop: false,
sideInset: 24.0,
interItemSpacing: 16.0,
cornerRadius: 8.0)
var body: some View {
VStack(alignment: .leading) {
IASBannersListView(appearance: bannersAppearance)
.showNext { showNexBanner in
self.showNextBanner = showNexBanner
}
.showPrevious { showPreviousBanner in
self.showPreviousBanner = showPreviousBanner
}
.showWithIndex { showWithIndex in
self.showBannerWithIndex = showWithIndex
}
.frame(height: 120)
.frame(maxWidth: .infinity)
Spacer()
HStack {
Button {
self.showPreviousBanner()
} label: {
Image(systemName: "arrow.left.circle")
.imageScale(.large)
.font(.system(size: 16.0))
.foregroundColor(.white)
}
.frame(maxWidth: 40, maxHeight: 40)
.cornerRadius(20)
Spacer()
Button {
self.showNextBanner()
} label: {
Image(systemName: "arrow.right.circle")
.imageScale(.large)
.font(.system(size: 16.0))
.foregroundColor(.white)
}
.frame(maxWidth: 40, maxHeight: 40)
.cornerRadius(20)
}
}
}
}
Similarly, there are methods for controlling playback in banners that control the internal page-turning timer.
- UIKit
- SwiftUI
pause()- stops the internal timer (the timer will restart upon any user interaction with the banner);resume()- resumes the logic of the internal timers;
extension ViewController {
func disappearController() {
bannersView.pause()
}
func appearController() {
bannersView.resume()
}
}
pause(_ pause: @escaping ((_ pause: @escaping () -> Void) -> Void)) -> IASBannersListView- stops the internal timer (the timer will restart upon any user interaction with the banner);resume(_ resume: @escaping ((_ resume: @escaping () -> Void) -> Void)) -> IASBannersListView- resumes the logic of internal timers;
struct ContentView: View
{
@State private var pauseBanners: () -> Void = {}
@State private var resumeBanners: () -> Void = {}
let bannersAppearance = IASBannersAppearance(shouldLoop: false,
sideInset: 24.0,
interItemSpacing: 16.0,
cornerRadius: 8.0)
var body: some View {
VStack(alignment: .leading) {
IASBannersListView(appearance: bannersAppearance)
.pause { pause in
self.pauseBanners = pause
}
.resume { resume in
self.resumeBanners = resume
}
.frame(height: 120)
.frame(maxWidth: .infinity)
Spacer()
HStack {
Button {
self.pauseBanners()
} label: {
Image(systemName: "pause")
.imageScale(.large)
.font(.system(size: 16.0))
.foregroundColor(.white)
}
.frame(maxWidth: 40, maxHeight: 40)
.cornerRadius(20)
Spacer()
Button {
self.resumeBanners()
} label: {
Image(systemName: "play")
.imageScale(.large)
.font(.system(size: 16.0))
.foregroundColor(.white)
}
.frame(maxWidth: 40, maxHeight: 40)
.cornerRadius(20)
}
}
}
}
Input fields and safe area
Since SDK 1.29.9 (4370), banners support text, email, and telephone input fields. The SDK displays its standard input dialog unless you set a custom inputPresenter. Existing content without input fields needs no changes.
The SDK also supplies safe-area data to banner content, as it does for stories. Banners use zero top and bottom insets because they are embedded in the application's container; keep the container's constraints responsible for its position on screen. No new safe-area setting is required.
Events
For banners, a new subobject IASBanner has been added to the IASEvent object (for more details, see the Events section).
IASEvent.IASBanner.bannersLoaded(placeID: String? = nil)- the list of banners has loaded,IASBannersViewis ready to work (fires every time the list is loaded, and also on refresh).placeID- banners place identifier;
IASEvent.IASBanner.show(bannerData: IASBannerData)- called when a banner is displayed on the screen.bannerData- containing a brief description of the selected banner;
IASEvent.IASBanner.preloaded(placeID: String, banners: Array<IASBannerData>)- called when a banners is preloaded by place.placeID- banners place identifier;bannerData- containing a brief description of the selected banner;
IASEvent.IASBanner.widgetEvent(bannerData: IASBannerData, name: String, data: Dictionary<String, Any>?)bannerData- containing a brief description of the selected banner;name- name of widget;data<Dictionary<String, Any>?>- activated widget data, detailed data fields;