Skip to main content
PESDK/iOS/Features

Stickers

The PhotoEditor SDK for iOS ships with a preset sticker library containing emoticons and shapes. Learn how to add custom sticker packages to the library.

Stickers tool

The PhotoEditor SDK ships with a categorized sticker library whose UI is optimized for exploration and discovery. You can easily leverage the API to complement the library with your custom sticker packages.

The tool allows placing, rotating, scaling and ordering stickers on your image. Once a sticker has been placed the user can reselect it by tapping the sticker again.

The tool is implemented in the StickerToolController class and can be customized using the StickerToolControllerOptions. For details on how to modify the options, take a look at the configuration section

Adding stickers#

Stickers are inserted into the SDK using the static property StickerCategory.all, which is an array of StickerCategory objects. A StickerCategory object holds the metadata of a sticker category, such as its preview image or the title and has an array of Sticker objects, which again hold the metadata for a Sticker, such as its imageURL and thumbnailURL. The Sticker class can handle local and remote resources. Supported formats are jpeg and png.

var categories = StickerCategory.all
let stickers = [
Bundle.main.url(forResource: "glasses_nerd", withExtension: "png"),
Bundle.main.url(forResource: "glasses_normal", withExtension: "png"),
Bundle.main.url(forResource: "glasses_shutter_green", withExtension: "png"),
Bundle.main.url(forResource: "glasses_shutter_yellow", withExtension: "png"),
Bundle.main.url(forResource: "glasses_sun", withExtension: "png"),
Bundle.main.url(forResource: "hat_cap", withExtension: "png"),
Bundle.main.url(forResource: "hat_party", withExtension: "png"),
Bundle.main.url(forResource: "hat_scherif", withExtension: "png"),
Bundle.main.url(forResource: "hat_zylinder 2", withExtension: "png"),
Bundle.main.url(forResource: "heart", withExtension: "png"),
Bundle.main.url(forResource: "mustache_long", withExtension: "png"),
Bundle.main.url(forResource: "mustache1", withExtension: "png"),
Bundle.main.url(forResource: "mustache2", withExtension: "png"),
Bundle.main.url(forResource: "mustache3", withExtension: "png"),
Bundle.main.url(forResource: "pipe", withExtension: "png"),
Bundle.main.url(forResource: "smile", withExtension: "png"),
Bundle.main.url(forResource: "snowflake", withExtension: "png"),
Bundle.main.url(forResource: "star", withExtension: "png"),
Bundle.main.url(forResource: "teardrop", withExtension: "png")
].compactMap { $0.map { Sticker(imageURL: $0, thumbnailURL: nil, identifier: $0.path) } }
if let previewURL = Bundle.main.url(forResource: "face_decor", withExtension: "png") {
categories.append(StickerCategory(title: "Oldschool", imageURL: previewURL, stickers: stickers))
}
StickerCategory.all = categories

Personal stickers#

This feature is disabled by default. It can be configured with StickerToolControllerOptions.personalStickersEnabled. If enabled, the end user can create personal stickers from the device's photo library. A button is added as first item in the menu in front of the sticker categories which modally presents an image selection dialog for personal sticker creation. Personal stickers will be added to a personal sticker category called "Custom" with the identifier "imgly_sticker_category_personal". The personal sticker category will be added between the button and the regular sticker categories if it does not exist.

You can configure the tint mode of all of these personal stickers with the StickerToolControllerOptions.defaultPersonalStickerTintMode option.

Please note that these types of personal stickers are always included in serialization files, which can increase the size of such a serialization by quite a lot.

let configuration = Configuration { builder in
builder.configureStickerToolController { options in
options.personalStickersEnabled = true
options.defaultPersonalStickerTintMode = .none
}
}

Smart Stickers#

There are currently six predefined smart stickers available in SmartSticker.defaultItems:

  1. Weekday
  2. Date
  3. Time
  4. Time Clock
  5. Weather Cloud (Requires a WeatherProvider)
  6. Weather Thermostat (Requires a WeatherProvider)

These smart stickers are part of the first default sticker category with the identifier "imgly_sticker_category_emoticons". To enable all of the smart stickers you need to define a WeatherProvider otherwise the weather stickers are hidden per default when StickerToolControllerOptions.weatherProvider is not set. The following code snippet shows how to configure and use the provided OpenWeatherProvider.

var unit = TemperatureFormat.celsius
if #available(iOS 10.0, *) {
unit = .locale
}
// Make sure to pass in your API key to display real weather data
// otherwise the sample data API is used!
let weatherProvider = OpenWeatherProvider(apiKey: nil, unit: unit)
weatherProvider.locationAccessRequestClosure = { locationManager in
locationManager.requestWhenInUseAuthorization()
}
let configuration = Configuration { builder in
builder.configureStickerToolController { options in
options.weatherProvider = weatherProvider
}
}

Example Weather Provider#

The following lists the implementation of the above used OpenWeatherProvider that is also part of the SDK. It exemplifies how you could implement your own WeatherProvider for your service of choice.

import CoreLocation
import Foundation
/// A `WeatherProvider` for the https://openweathermap.org service.
@objcMembers @objc(PESDKOpenWeatherProvider) public class OpenWeatherProvider: NSObject, WeatherProvider, CLLocationManagerDelegate {
// MARK: - Properties
/// The used API key. If `nil` or empty the sample API is used.
public let apiKey: String?
/// The minimum update interval to request new data from the service. It defaults to one hour.
public var updateInterval: TimeInterval = 60 * 60
// MARK: - Initializers
/// Create a new `OpenWeatherProvider`.
/// - Parameters:
/// - apiKey: The used API key. If `nil` or empty the sample API is used.
/// - unit: The temperature format that should be used for displaying temperature measurements to the user.
public init(apiKey: String?, unit: TemperatureFormat) {
self.apiKey = apiKey
temperatureFormat = unit
super.init()
locationManager.startUpdatingLocation()
}
deinit {
locationManager.stopUpdatingLocation()
}
// MARK: - WeatherProvider
private var url: URL? {
guard let location = lastLocation else {
return nil
}
let domain, appid: String
if let apiKey = apiKey, !apiKey.isEmpty {
domain = "api.openweathermap.org"
appid = apiKey
} else {
domain = "samples.openweathermap.org"
appid = "_"
}
let lat = location.coordinate.latitude
let lon = location.coordinate.longitude
let url = "https://\(domain)/data/2.5/weather?lat=\(lat)&lon=\(lon)&appid=\(appid)"
return URL(string: url)
}
private var lastTemperature: Temperature?
private var lastUpdate: Date?
private var lastTask: URLSessionTask?
private struct WeatherData: Codable {
let main: Main
}
private struct Main: Codable {
let temp: Double
}
/// The temperature format that should be used for displaying temperature measurements to the user.
public var temperatureFormat: TemperatureFormat
/// The temperature measurement.
public var temperature: Temperature? { lastTemperature }
/// Request to update the weather data.
public func updateData() {
guard let url = url else {
return
}
if let lastTask = lastTask {
switch lastTask.state {
case .running:
return
case .suspended:
lastTask.resume()
return
default:
break
}
}
if let lastUpdate = lastUpdate, lastUpdate.timeIntervalSinceNow < updateInterval {
return
}
let session = URLSession.shared
let task = session.dataTask(with: url) { data, _, _ in
guard let data = data, let weatherData = try? JSONDecoder().decode(WeatherData.self, from: data) else {
return
}
DispatchQueue.main.async {
self.lastUpdate = Date()
self.lastTemperature = Temperature(value: weatherData.main.temp, unit: '.kelvin)
}
}
task.resume()
lastTask = task
}
// MARK: - CoreLocation
private var lastLocation: CLLocation?
private lazy var locationManager: CLLocationManager = {
let locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.distanceFilter = 10
return locationManager
}()
/// When this closure is called, the SDK has determined that location access has not been granted
/// to the host app yet. Within this closure you should then request appropriate permissions from
/// the passed in `CLLocationManager` object. Location access is used to request weather data
/// for the current location for weather stickers.
///
/// - Attention: Starting Spring 2019, all apps submitted to the App Store that access user data
/// are required to include a purpose string as soon as location permissions requests appear
/// somewhere in the binary. Since we do not want to force developers integrating the SDK into
/// their app to include a purpose string even with weather stickers disabled, this closure was
/// introduced, so that developers can decide for themselves if it is appropriate to request
/// location access. Simply set this property like this:
/// ````
/// openWeatherProvider.locationAccessRequestClosure = { locationManager in
/// locationManager.requestWhenInUseAuthorization()
/// }
/// ````
public var locationAccessRequestClosure: ((CLLocationManager) -> Void)? {
didSet {
if CLLocationManager.authorizationStatus() == .notDetermined {
locationAccessRequestClosure?(locationManager)
}
}
}
/// :nodoc:
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
lastLocation = locations.last
updateData()
}
}