Detailed instructions on how to configure and customize the Travelpayouts SDK for iOS.
Configuration is the most important step when working with this SDK. Configuration allows you to:
- Set the values of important constants (API keys, various links, etc.).
- Personalize the appearance of screens.
- Configure the display of advertisements within the SDK.
- Configure the analytics sending to your resources.
Please note! Before configuration, you need to connect the SDK to your project.
Connecting SDK to your project
To connect the SDK to your project, you need to modify your Podfile as follows:
- Enable dependency building in separate frameworks:
use_frameworks!
- In the postprocessor code set BUILD_LIBRARY_FOR_DISTRIBUTION = YES, for dependencies:
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0'
config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
end
end
end
Connecting modules
- Airline tickets: pod 'WLSDK/Flights'
- Hotels bookings: pod 'WLSDK/Hotels'
- Information and settings: pod 'WLSDK/Information'
Podfile example
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '14.0'
target 'MyApplication' do
pod 'WLSDK/Hotels', '1.2.0'
pod 'WLSDK/Flights', '1.2.0'
end
# Various post-install hooks
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0'
config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
end
end
end
SDK Configuration
First, you need to create a model that will pass the appearance parameters to the SDK. More details on this step can be found in the section: Appearance configuration.
After creating the Appearance model, you can start filling in the SDK configuration. To do this, you need to create a new instance of Configuration and fill it out.
Example of filling:
let configuration = Configuration(
appearance: Appearance(),
constants: Configuration.Constants(
marker: "<marker>",
clientDeviceHost: "<client_device_host>",
apiKey: "<api_key>",
appStoreId: "<appstore_id>",
privacyUrl: LocalizedUrlContainer(
fallback: "https://www.google.com",
storage: [
"en": "https://www.google.com",
"es": "https://www.google.es",
]
),
feedbackEmail: "test@test.com",
sharingData: SharingData(
sharingLink: "https://www.google.es"
)
)
)
Parameters:
-
marker – insert your Travelpayouts partner ID. You can find it in your Travelpayouts account on the White Label App page:
- clientDeviceHost – Identifier ClientDeviceHost.
-
apiKey – insert your API token to access flight tickets and hotels data. You can find the API token in your Travelpayouts account on the White Label Page
- appStoreId – ID of your application in the AppStore.
-
privacyUrl – Link to the privacy policy, where:
- fallback – this link will open by default unless a separate link to the language selected by the user in the application is specified.
- storage – links to privacy policies in different languages are specified here.
- feedbackEmail – Email for feedback.
- sharingLink – Link that will be copied when sharing a flight ticket.
If necessary, the following optional components can also be configured:
- Configuring Advertising within SDK Screens
- Analytics Configuration
- Replacing Text Strings on the screens
- Replacing Images on Screens
After filling in the configuration, it can be applied by calling the Configuration/setup(with:) method:
Example:
```
Configuration.setup(with: configuration)
```
Important: SDK configuration must be performed at the very beginning of the application initialization process with the highest priority.
Displaying the Flight Search Screen
To get the flight search screen, use the following expression:
```
import WLFlights
...
let viewController = WLFlights.ScreenProvider.shared.flightsViewController
```
The obtained UIViewController object can be displayed in any way that is convenient for you.
Note: Keep in mind that this screen contains a UINavigationController within it.
Creating a custom flight search start screen
If you need a completely custom design for your flight search start screen, you can design your own.
That said, to run your search and display results, you'll need our standard screens to select search options and view results. These can be accessed via: WLFlightsUnlimited.ScreenProvider.shared
Screens for selecting search parameters
To start a flight search, you first need to obtain all the necessary parameters. For this, we provide access to the following screens:
1. FlightsLocations Screen: To access this screen, use the following code:
import WLFlightsUnlimited
...
let screenProvider = WLFlightsUnlimited.ScreenProvider.shared
var originLocationsSelection: FlightsLocationsSelection?
...
let controller = screenProvider.flightsLocationsViewController(
type: .origin // Type of screen to call (.origin or .destination)
) { [weak self] selection in
guard let self else { return }
originLocationsSelection = selection // Save the selected value
}
Using the onFinish closure, the screen will pass the user-selected value of type FlightsLocationsSelection. To display the selected value on the start screen, use the properties name, cityName, and countryName or the properties title and attributedTitle with formatted and localized values.
You will also need to create a similar screen with the type .destination to select the destination location parameter.
2. FlightsCalendar Screen: To access the FlightsCalendar screen, use the following code:
import WLFlightsUnlimited
...
let screenProvider = WLFlightsUnlimited.ScreenProvider.shared
var calendarSelection: FlightsCalendarSelection?
...
let controller = screenProvider.flightsCalendarViewController(
currentSelection: calendarSelection, // Currently selected value
originLocationsSelection: originLocationsSelection, // Currently selected departure location
destinationLocationsSelection: destinationLocationsSelection // Currently selected destination location
) { [weak self] selection in
guard let self else { return }
calendarSelection = selection // Save the selected value
}
Using the onFinish closure, the screen will pass the user-selected value of type FlightsCalendarSelection. To display the selected value on the start screen, use the properties departureDate, returnDate, or the method title which returns a formatted localized value.
3. FlightsPassengers Screen: To access the FlightsPassengers screen, use the following code:
import WLFlightsUnlimited
...
let screenProvider = WLFlightsUnlimited.ScreenProvider.shared
var passengersSelection: FlightsPassengersSelection = .default // You can set a default value
...
let controller = screenProvider.flightsPassengersViewController(
currentSelection: passengersSelection // Currently selected value
) { [weak self] selection in
guard let self else { return }
if let selection {
passengersSelection = selection // Save the selected value
}
}
Using the onFinish closure, the screen will pass the user-selected value of type FlightsPassengersSelection. To display the selected value on the start screen, use the properties adultsCount, childrenCount, childrenAges, or the property title with a formatted and localized value.
Search results screen
Once all search parameters are ready, you can create the search results screen:
import WLFlightsUnlimited
...
let screenProvider = WLFlightsUnlimited.ScreenProvider.shared
var originLocationsSelection: FlightsLocationsSelection?
var destinationLocationsSelection: FlightsLocationsSelection?
var calendarSelection: FlightsCalendarSelection?
var guestsSelection: FlightsGuestsSelection = .default // You can set a default value
...
guard
let originLocationsSelection,
let destinationLocationsSelection,
let calendarSelection,
originLocationsSelection != destinationLocationsSelection // Departure and destination locations must not be the same
else { return }
let controller = screenProvider.flightsResultsViewController(
parameters: .init(
originSelection: originLocationsSelection, // Selected departure location
destinationSelection: destinationLocationsSelection, // Selected destination location
calendarSelection: calendarSelection, // Selected dates
guestsSelection: guestsSelection // Selected passengers
)
)
Important information for displaying created controllers
All the listed controllers contain a UINavigationController inside. Therefore, we strongly recommend showing these screens modally using the present method.
For search parameter selection screens, a special "grabber" for closing is provided, so it is not recommended to use the full-screen modalPresentationStyle, as the user will not be able to close the screen.
The search results screen has a button for closing it, so for user convenience, it is recommended to use modalPresentationStyle with the .fullScreen parameter.
Displaying the Hotel Search Screen
To get the hotel search screen, use the following expression:
```
import WLHotels
...
let viewController = WLHotels.ScreenProvider.shared.hotelsViewController
```
The obtained UIViewController object can be displayed in any way convenient for you.
Note: Keep in mind that this screen contains a UINavigationController within it.
Creating a custom hotel search screen
If you need to implement a completely custom hotel search start screen design, you can write your own and use it. In that case, you will need our search options selection screens and search results screen to run the search and show the results. These screens can be called via WLHotelsUnlimited.ScreenProvider.shared.
First, you need to set up a screen for selecting search parameters and next you can move on to setting up the search results screen.
Screens for selecting search parameters
To start a hotel search, you first need to gather all the necessary parameters. We provide access to the following screens:
1. HotelsLocations Screen: To access this screen, use the following code:
import WLHotelsUnlimited
...
let screenProvider = WLHotelsUnlimited.ScreenProvider.shared
var originLocationsSelection: HotelsLocationsSelection?
...
let controller = screenProvider.hotelsLocationsViewController() { [weak self] selection in
guard let self else { return }
locationsSelection = selection // Save the selected value
}
Using the onFinish closure, the screen will pass the user-selected value of type HotelsLocationsSelection. To display the selected value on the start screen, use the title property, which returns a formatted value.
2. HotelsCalendar Screen: To access the HotelsCalendar screen, use the following code:
import WLHotelsUnlimited
...
let screenProvider = WLHotelsUnlimited.ScreenProvider.shared
var calendarSelection: HotelsCalendarSelection?
...
let controller = screenProvider.hotelsCalendarViewController(
currentSelection: calendarSelection, // Currently selected value
) { [weak self] selection in
guard let self else { return }
calendarSelection = selection // Save the selected value
}
Using the onFinish closure, the screen will pass the user-selected value of type HotelsCalendarSelection. To display the selected value on the start screen, use the range property or the title method, which returns a formatted localized value.
3. HotelsGuests Screen: To access the HotelsGuests screen, use the following code:
import WLHotelsUnlimited
...
let screenProvider = WLHotelsUnlimited.ScreenProvider.shared
var guestsSelection: HotelsGuestsSelection = .default // You can set a default value
...
let controller = screenProvider.hotelsGuestsViewController(
currentSelection: guestsSelection // Currently selected value
) { [weak self] selection in
guard let self else { return }
if let selection {
guestsSelection = selection // Save the selected value
}
}
Using the onFinish closure, the screen will pass the user-selected value of type HotelsGuestsSelection. To display the selected value on the start screen, use the properties adultsCount, childrenCount, infantsCount, tripClass, or the title property with a formatted and localized value.
Search results screen
When all search parameters are ready, you can proceed to create the search results screen:
import WLHotelsUnlimited
...
let screenProvider = WLHotelsUnlimited.ScreenProvider.shared
private var locationsSelection: HotelsLocationsSelection?
private var calendarSelection: HotelsCalendarSelection?
private var guestsSelection: HotelsGuestsSelection = .default
...
guard
let locationsSelection,
let calendarSelection
else { return }
let controller = WLHotelsUnlimited.ScreenProvider.shared.hotelsResultsViewController(
parameters: .init(
locationsSelection: locationsSelection, // Selected location
calendarSelection: calendarSelection, // Selected dates
guestsSelection: guestsSelection // Selected guests
)
)
Important information for displaying created controllers
All the listed controllers contain a UINavigationController inside. Therefore, we strongly recommend showing these screens modally using the present method.
For search parameter selection screens, a special "grabber" is provided for closing. Therefore, it is not recommended to use the full-screen modalPresentationStyle as the user will not be able to close the screen.
The search results screen has a button for closing it, so for user convenience, it is recommended to use modalPresentationStyle with the .fullScreen parameter.
Displaying the Information Screen
To display this screen, you need to create and fill in the InformationScreenConfiguration configuration object:
let informationScreenConfiguration = InformationScreenConfiguration(
optionalItemsToDisplay: [.favorites], // optional items to display on this screen
aboutAppInformation: AboutAppInformation(
description: LocalizedStringContainer(
fallback: "Application description",
storage: [
"fr": "Description de l'application"
]
),
developer: LocalizedStringContainer(
fallback: NSLocalizedString("developer_info", comment: ""), // Option using NSLocalizedString
storage: [:]
),
partnerUrl: LocalizedUrlContainer(
fallback: "https://google.com",
storage: [
"fr": "https://yandex.fr"
]
)
)
)
Parameters
- optionalItemsToDisplay –- An array of optional UI elements to display on the screen.
- aboutAppInformation –- Configuration containing developer information, app description, and partner link.
To get the information screen, use the following expression:
```
import WLInformation
...
let viewController = WLInformation.ScreenProvider.shared.informationViewController(
informationScreenConfiguration: informationScreenConfiguration
)
```
The obtained UIViewController object can be displayed in any way convenient for you.
Note: Keep in mind that this screen contains a UINavigationController within it.
Analytics Configuration
To connect analytics, you need to create an object that implements the AnalyticsProvider protocol.
Example of an object:
final class MyAnalyticsProvider: AnalyticsProvider {
// MARK: - Internal methods
func log(event: String, with parameters: [String: Any]?) {
// Logic for sending the event to your analytics system
}
}
Next, you need to create a Configuration.Analytics object and pass it to the configuration constructor.
Configuration example
let configuration = Configuration(
appearance: Appearance(),
constants: Configuration.Constants(
marker: "<marker>",
clientDeviceHost: "<client_device_host>",
apiKey: "<api_key>",
appStoreId: "<appstore_id>",
privacyUrl: LocalizedUrlContainer(
fallback: "https://www.google.com",
storage: [
"en": "https://www.google.com",
"es": "https://www.google.es",
]
),
feedbackEmail: "test@test.com",
sharingData: SharingData(
sharingLink: "https://www.google.es"
)
),
advertising: nil,
analytics: .init(
analyticsProvider: MyAnalyticsProvider()
)
)
Replacing Text Strings on the screens
The SDK includes the possibility to replace text strings on screens.The replacement is done using the localization parameter in the configuration.
By default, the Configuration.Localization object does not contain any public methods. But they are added when WLFlights and/or WLHotels and/or WLInformation modules are connected.
WLFlights
After adding this module, the following enumeration will become available:
public enum WLFlights.LocalizationTable: String, Hashable, CaseIterable {
case flightBooking
case flightDetails
case flightProposals
case flightsAirports
case flightsCalendar
case flightsCommon
case flightsFavorites
case flightsFilters
case flightsFiltersAgents
case flightsFiltersAirlines
case flightsFiltersAirports
case flightsFiltersSorting
case flightsFiltersTransfersAirports
case flightsLayoverInfo
case flightsPassengers
case flightsPlurals
case flightsPriceCharts
case flightsResults
case flightsSearch
case flightsTransferTags
}
And an additional method in Configuration.Localization:
public extension Configuration.Localization {
func register(
bundle: Bundle,
flightsTable table: WLFlights.LocalizationTable,
overriddenTableName: String? = nil
)
}
Parameters
- bundle – bundle where the string table to be registered is located.
- flightsTable – One of the WLFlights.LocalizationTable options, the table from the WLFlights module in which you need to replace strings.
- overriddenTableName – The name of the string table inside the bundle.
You can find an example of the replacement below.
WLHotels
After adding this module, the following enumeration will become available:
public enum WLHotels.LocalizationTable: String, Hashable, CaseIterable {
case hotelBooking
case hotelDetails
case hotelPropertyTypes
case hotelProposalOptions
case hotelRooms
case hotelRoomsFilters
case hotelsCalendar
case hotelsFavorites
case hotelsFilterSearch
case hotelsFilters
case hotelsGuestsselection
case hotelsLocations
case hotelsMap
case hotelsMapLocationPicker
case hotelsPhotosPreview
case hotelsPlurals
case hotelsResults
case hotelsReviews
case hotelsSearch
case hotelsSorting
}
```
And an additional method in Configuration.Localization:
public extension Configuration.Localization {
func register(
bundle: Bundle,
hotelsTable table: WLHotels.LocalizationTable,
overriddenTableName: String? = nil
)
}
Parameters
- bundle – bundle where the string table to be registered is located.
- hotelsTable – One of the WLHotels.LocalizationTable options, the table from the WLHotels module in which you need to replace strings.
- overriddenTableName – The name of the string table inside the bundle.
You can find an example of the replacement below.
WLInformation
After adding this module, the following enumeration will become available:
public enum WLInformation.LocalizationTable: String, Hashable, CaseIterable {
case informationAboutApp
case informationConfidentiality
case informationCountryPicker
case informationCurrencyPicker
case informationDefaultCurrency
case informationFavourites
case informationMain
case informationPriceDisplay
case informationRegionalSettings
}
And an additional method in Configuration.Localization:
public extension Configuration.Localization {
func register(
bundle: Bundle,
informationTable table: WLInformation.LocalizationTable,
overriddenTableName: String? = nil
)
}
Parameters
- bundle – bundle where the string table to be registered is located.
- informationTable – One of the WLInformation.LocalizationTable options, the table from the WLInformation module in which you need to replace strings.
- overriddenTableName – The name of the string table inside the bundle.
Replacement Example
Note: The code described below uses access to the configuration through Configuration.current. This is only allowed after calling Configuration.setup(with:).
1. Add a new string table to the project, name it MyFlightsSearchStrings.strings.
2. Write a key in it that we want to replace:
```
"departure_airport_placeholder" = "Аэропорт вылета";
```
3. Register the table in Configuration.localization field:
import WLConfig
import WLFlights // Connect the flight search module
...
Configuration.current.localization.register(
bundle: .main, // The bundle where MyFlightsSearchStrings is located
flightsTable: .flightsSearch, // The SDK table keys we want to replace
overriddenTableName: "MyFlightsSearchStrings" // Table name
)
Replacing strings in other modules (Hotels, Information) is done in the same way.
Replacing Images on Screens
The SDK has the ability to replace images on certain screens. Replacement is done using the Configuration.ImagesProvider field within the configuration.
By default, the Configuration.ImagesProvider parameter does not contain any open methods. However, they are added when the WLFlights and/or WLHotels modules are connected.
WLFlights
After adding this module, the following enumeration will become available:
public enum FlightsImageSink: ImageSink {
/// Background of the flight search screen
case searchScreenBackground
}
And an additional method in Configuration.ImagesProvider:
public extension Configuration.ImagesProvider {
func register(
source: ImageSource,
forFlightsSink sink: FlightsImageSink
)
}
Parameters
- source — The image source to be placed in a specific location in the SDK.
- sink — The image sink in the WLFlights module.
WLHotels
Similarly to the flight search module, after adding the WLHotels module, the following enumeration will become available:
public enum HotelsImageSink: ImageSink {
/// Background of the hotel search screen
case searchScreenBackground
}
And an additional method in Configuration.ImagesProvider:
public extension Configuration.ImagesProvider {
func register(
source: ImageSource,
forHotelsSink sink: HotelsImageSink
)
}
```
Parameters
- source — The image source to be placed in a specific location in the SDK.
- sink — The image sink in the WLHotels module.
Example of Image Replacement
Note: The code described below uses access to the configuration through Configuration.current. This is only allowed after calling Configuration/setup(with:).
```
import WLConfig
import WLFlights // Connect the flight search module
import WLHotels // Connect the hotel search module
...
Configuration.current.imagesProvider.register(
source: .local(
bundle: .main, // The bundle where the my_plane_image resource is located
key: "my_plane_image", // Resource key
configuration: nil // UIImage configuration
),
forFlightsSink: .searchScreenBackground
)
Configuration.current.imagesProvider.register(
source: .local(
bundle: .main, // The bundle where the my_hotel_image resource is located
key: "my_hotel_image", // Resource key
configuration: nil // UIImage configuration
),
forHotelsSink: .searchScreenBackground
)
Configuring Advertising within SDK Screens
To connect advertising:
1. Create an object that implements the AdvertisingProvider protocol.
Example of an object:
final class MyAdvertisingProvider: AdvertisingProvider {
// MARK: - Private properties
private var bannerCompletions = [UIView: AdvertisingProviderBannerCompletion]()
// MARK: - Internal methods
func bannerView(for placement: String, completion: @escaping AdvertisingProviderBannerCompletion) {
// Logic for obtaining and displaying a `UIView` banner ad with the placement ID `placement`.
// The `completion` passes a `UIView` with the banner ad or an error of type `Error`.
}
func showInterstitial(for placement: String, in context: UIViewController) {
// Logic for obtaining and displaying a full-screen ad with the placement ID `placement` on the `context` screen.
}
}
Note: The placement IDs that are passed into the AdvertisingProvider methods are taken from the Configuration.Advertising object in the configuration.
2. Create a Configuration.Advertising object and pass it to the configuration constructor.
Configuration Example
let configuration = Configuration(
appearance: Appearance(),
constants: Configuration.Constants(
marker: "<marker>",
clientDeviceHost: "<client_device_host>",
apiKey: "<api_key>",
appStoreId: "<appstore_id>",
privacyUrl: LocalizedUrlContainer(
fallback: "https://www.google.com",
storage: [
"en": "https://www.google.com",
"es": "https://www.google.es",
]
),
feedbackEmail: "test@test.com",
sharingData: SharingData(
sharingLink: "https://www.google.es"
)
),
advertising: Configuration.Advertising(
advertisingProvider: MyAdvertisingProvider(),
placements: .init(
flights: .init(
searchInterstitial: "FLIGHTS_FULLSCREEN_PLACEMENT_ID",
searchResultsBanner: "FLIGHTS_BANNER_PLACEMENT_ID"
),
hotels: .init(
searchInterstitial: "HOTELS_FULLSCREEN_PLACEMENT_ID",
searchResultsBanner: "HOTELS_BANNER_PLACEMENT_ID"
)
)
)
)
Appearance Configuration
To set up the visual components, you need to create a model that will pass the appearance parameters to the SDK.
To do this, create a model (class or structure) and conform it to the UnlimitedVersionAppearanceConfiguration protocol. XCode will automatically suggest adding all the necessary fields.
Example of a minimal implementation of the model:
struct Appearance: UnlimitedVersionAppearanceConfiguration {
// MARK: - Internal properties
var baseColorVariant: ColorVariant? {
.hex("#7648C5")
}
var iconStyle: IconStyle {
.tint
}
var cornerStyle: CornerStyle {
.default
}
var palette: ColorPalette {
.lab
}
}
ColorPalette
Allows you to set an algorithm for generating the color palette of the application. The following options are available:
- hex — color generation based on the HSV color space
- lab — color generation based on the Lab color space
ColorVariant
Allows you to choose a color assignment format. The following formats are available:
- color — color in UIColor format
- hex — color in HEX format
IconStyle
Allows you to select the style of icons displayed within the application. The following options are available:
- filled — icons with fill
- lined — linear style icons
- tint — tonal style icons
CornerStyle
Allows you to set the type of corner rounding for elements. The following options are available:
- default — standard rounding
- round — significant rounding
- sharp — minimal rounding
Overriding Colors
To override the colors used in the SDK:
- Create a model that conforms to the ColorSet protocol.
- Override the necessary colors by adding corresponding properties of type Color (a list of all available colors for overriding can be found within the protocol).
- In the model that implements the UnlimitedVersionAppearanceConfiguration protocol, add a property overriddenColors of type ColorSet?. Assign the model created in the first step as its value.
Example of adding overridden colors:
struct OverridenColors: ColorSet {
// MARK: - Internal properties
var overlay: Color {
.custom(UIColor.red)
}
}
struct Appearance: UnlimitedVersionAppearanceConfiguration {
// MARK: - Internal properties
...
var overriddenColors: ColorSet? {
OverridenColors()
}
}
Overriding Icons
To override icons:
- Create a model that conforms to the IconSet protocol.
- In this model, override the necessary icons by adding the corresponding properties of type UIImage?. A list of all available icons for overriding can be found within the IconSet protocol.
- Add a property overriddenIcons of type IconSet? to the model implementing the UnlimitedVersionAppearanceConfiguration protocol. Assign the model created in the first step as its value.
Example of adding overridden icons:
struct OverridenIcons: IconSet {
// MARK: - Internal properties
var icTune: UIImage? {
UIImage.checkmark
}
}
struct Appearance: UnlimitedVersionAppearanceConfiguration {
// MARK: - Internal properties
...
var overriddenIcons: IconSet? {
OverridenIcons()
}
}