- Integration requirements
- Install and set up Framework
- iOS
- macOS
- Use the Vendor API to integrate apps into Setapp
- Logging
- Testing your app
- Setapp Framework wrappers
- Integrations samples
-
The Setapp iOS Framework can be integrated into apps developed with iOS 10.0 or later. The Framework doesn’t work with the watchOS and the tvOS yet.
-
The supported Swift version for the iOS Framework is 5.2 or later.
-
An iOS app must support custom URL schemes. The URL scheme must be the same as the app bundle ID. Related documentation by Apple: Defining a Custom URL Scheme for Your App.
-
The applications must be signed with a Developer ID certificate.
-
Compatibility with the latest macOS version must be tested and confirmed.
We don’t have strict requirements for supporting macOS versions. Setapp supports macOS versions from 10.15 (Catalina) to 12.0 (Monterey). However, we have a frozen version for our customers who use older than macOS 10.12 (Sierra) - 10.14 (Mojave) versions. So, if your app can support some of the older macOS versions - your revenue could increase.
Not allowed functionality of macOS apps:
- Paid features or app components;
- Proprietary installer and update frameworks;
- Activation and licensing mechanisms;
- Built-in stores and in-app purchases.
Linking the Framework with your project using the Swift Package Manager requires Xcode 12 or later.
Add the following dependency in your Package.swift
:
dependencies: [
.package(
name: "Setapp",
url: "https://github.com/MacPaw/Setapp-framework.git",
from: "2.0.1")
]
With CocoaPods, add the following string to your Podfile
:
pod 'Setapp'
To support usage of the Simulator on Macs with Apple Silicon, we've changed the source binary format from the universal binary (fat) framework to XCFramework. To work with the latest Framework format, you need CocoaPods version 1.9 or later and Xcode version 11.0 or later.
First step is to get Setapp Framework.
To add the Framework by using Git Submodules, execute the following Git command in your project's root directory:
git submodule add https://github.com/MacPaw/Setapp-framework.git
The Setapp Framework files are located in the Setapp-framework
folder of the project directory.
Second step is to add the Framework to your project.
Now you have 2 options: Install as package or Install as xcframework. The difference is that package will correctly understand Setapp package type, and with xcframework
As alternative to the first step with git submodules, you can also download and add the Framework manually by doing these steps:
- Download the
Source code (zip)
file from Assets on our latest release page.- Rename versioned folder
Setapp-framewrok-*
toSetapp-framework
.- Extract files from the archive and copy the unpacked
Setapp-framework
to your project directory.
- Open your project in XCode.
- Drag & Drop the whole
Setapp-framework
folder to your project. - Select your app target.
- Choose General tab.
- Press
+
in the Frameworks, Libraries, and Embedded Content section. - Choose
Setapp
library inWorkspace
/Setapp
group.
As alternative to the first step with git submodules, you can also download and add the Framework manually by doing these steps:
- Download the Framework here: Setapp.xcframework.zip.
- Extract the bundle from the archive and copy the unpacked
Setapp-framework
to your project directory.- (iOS only) Download the iOS resources bundle here: SetappFramework-Resources-iOS.bundle.zip.
https://github.com/MacPaw/Setapp-framework/releases/download/3.0.0/SetappFramework-Resources-iOS.bundle.zip Add the Framework to your project.
- Open your project in XCode.
- Select your app target.
- Click the General settings pane.
- Drag
Setapp.xcframework
to the Frameworks, Libraries, and Embedded Content section. - Choose the
Do Not Embed
option from the menu in theEmbed
column. - (iOS only) Extract the iOS resource bundle from the archive and copy the unpacked
SetappFramework-Resources-iOS.bundle
to your project directory, drag it to your Xcode project and make sure that it'sTarget membership
is your application target.
For more detailed information, see "Link a target to frameworks and libraries" in the Xcode Help.
To use Carthage, specify the line below in your Cartfile
:
github "MacPaw/Setapp-framework"
ℹ️ If you are using CocoaPods - you can skip this step.
Link libSetapp.a
to the application target. Go to the Build Settings
tab of your project and add the following value string to Other Linker Flags
(OTHER_LDFLAGS
):
If you are using Swift Package Manager, integrating Framework manually, or using Carthage:
-force_load "$(BUILT_PRODUCTS_DIR)/libSetapp.a"
⚠️ You must strictly follow the provided instructions to make your application function correctly within the Setapp environment.
To establish trust between your app and our service, the Framework needs a public key that is unique for every app.
-
Go to the Apps page of your developer account and click "Add iOS application" below the companion macOS app.
-
Enter the URL of your iOS app on the App Store, then click Generate.
Once the Setapp system processed the link, your app becomes registered, and the Setapp public key is generated for you. To download the key, click the link that appears below the field with the URL.
You won't need to specify the App Store URL again when submitting the app for review with Setapp — the info is stored in the Setapp system.
A public key is used to operate with the data received from the Setapp system. The public key is unique for every app in the Setapp suite and it is an essential part of the Framework's security.
To add the public key to your project in Xcode, simply drag the setappPublicKey.pem
key file to the navigator area. A new dialog box appears. Select the "Copy items if needed" checkbox on the top of the dialog box.
⚠️ Please note that public keys for iOS & macOS platforms differ.
Once you've added the public key, you should tell our Framework its location. By default, we assume that the public key file is named setappPublicKey.pem
and located in the app's main bundle.
The start(with:)
method of the SetappManager
class is responsible for these initialization operations:
- Providing configuration for the Framework so that it starts for your app;
- Starting reporting the app usage once it is successfully activated for a Setapp user.
If you have the UIApplicationDelegate
method in your app, add the following code to the application(_:, didFinishLaunchingWithOptions:)
function:
import Setapp
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
)
-> Bool
{
SetappManager.shared.start(with: .default)
return true
}
}
If you have UIWindowSceneDelegate
in your app, add the code below to the scene(_:, willConnectTo:, options:)
function:
import Setapp
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
)
{
SetappManager.shared.start(with: .default)
}
}
You’ll need to provide a custom configuration for the SetappManager
class in the following cases:
- You’re not using the main app bundle to store the public key.
- You have renamed the public key file in your project.
Providing configuration must take place while initializing the Framework.
let configuration = SetappConfiguration(
publicKeyBundle: .main,
publicKeyFilename: "setappPublicKey.pem"
)
SetappManager.shared.start(with: configuration)
As already mentioned in Integration requirements, we use custom URL schemes to unlock the restricted functionality of your app for Setapp users. To add a URL scheme, follow these steps:
- In Xcode, go to the Info tab of your target settings.
- Expand the URL Types section.
- Add a new URL Type with the following parameters:
- Identifier:
Setapp
- URL Schemes: your bundle identifier
- Role:
None
- Identifier:
Once the URL scheme setup is complete, you can proceed with adding the necessary code to handle the process of opening URLs in your app.
If you have UIApplicationDelegate
in your app, add the following code to the application(_:, open:, options:)
function:
import Setapp
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
)
-> Bool
{
if SetappManager.shared.canOpen(url: url) {
return SetappManager.shared.open(url: url, options: options) { result in
switch result {
case let .success(setappSubscription):
print("Successfully unlocked new features!")
print("Setapp subscription:", setappSubscription)
case let .failure(error):
print("Failed to unlock new app features due to the error:", error)
}
}
}
return false
}
}
If you have UIWindowSceneDelegate
in your app, add the following code to these functions:
scene(_:, willConnectTo:, options:)
scene(_:, openURLContexts:)
import Setapp
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
)
{
SetappManager.shared.start(with: .default)
if SetappManager.shared.canOpen(urlContexts: connectionOptions.urlContexts) {
SetappManager.shared.open(urlContexts: connectionOptions.urlContexts) { result in
switch result {
case let .success(setappSubscription):
print("Successfully unlocked new features!")
print("Setapp subscription:", setappSubscription)
case let .failure(error):
print("Failed to unlock new app features due to the error:", error)
}
}
}
}
func scene(
_ scene: UIScene,
openURLContexts URLContexts: Set<UIOpenURLContext>
)
{
if SetappManager.shared.canOpen(urlContexts: URLContexts) {
SetappManager.shared.open(urlContexts: URLContexts) { result in
switch result {
case let .success(setappSubscription):
print("Successfully unlocked new features!")
print("Setapp subscription:", setappSubscription)
case let .failure(error):
print("Failed to unlock new app features due to the error:", error)
}
}
}
}
}
Framework displays activation alerts automatically. However, if you want to customize this behavior, you can conform SetappMessagesPresenterProtocol
by your presenter object and provide it to the framework using the .setMessagesPresenter(_:)
method of the shared
instance of the SetappManager
class
final class CustomMessagesPresenter: SetappMessagesPresenterProtocol {
func present(_ statusMessage: SetappStatusMessage,
options: SetappStatusMessageOptions?) {
switch statusMessage {
case .activationInProgress:
// handle activationInProgress status
case .activationSuccess:
// handle activationSuccess status
case .error(let setappError):
// handle error, you can switch setappError.errorCode
// for more detailed info
}
}
}
You can monitor the subscription status for the Setapp member who uses your app with the help of the SetappSubscription
object. 3 monitoring options are available for you: SetappManager
delegate, notifications, and the Key-Value Observation (KVO).
Simply declare a class conforming to the SetappManagerDelegate
protocol and set up a delegate
property for the shared
instance of the SetappManager
class.
import Setapp
class SetappSubscriptionManagerDelegate: SetappManagerDelegate {
init() {
SetappManager.shared.delegate = self
}
// MARK: SetappManagerDelegate
func setappManager(
_ manager: SetappManager,
didUpdateSubscriptionTo newSetappSubscription: SetappSubscription
)
{
print("Manager:", manager)
print("Setapp subscription:", newSetappSubscription)
}
}
In addition to the delegate method, you can observe the SetappManager.didChangeSubscriptionNotification
notification for the shared
instance of the SetappManager
object. As you can see from the example below, the manager is the object, and a new Setapp subscription state is located in the NSKeyValueChangeKey.newKey
key in the userInfo
property of the notification.
import Setapp
class SetappSubscriptionNotificationObserver {
private var notificationObserver: NSObjectProtocol?
init() {
notificationObserver = NotificationCenter.default
.addObserver(forName: SetappManager.didChangeSubscriptionNotification,
object: SetappManager.shared,
queue: .none) { [weak self] (notification) in
self?.setappSubscriptionDidChange(notification: notification)
}
}
deinit {
notificationObserver.map(NotificationCenter.default.removeObserver(_:))
}
// MARK: Notification
func setappSubscriptionDidChange(notification: Notification) {
guard
let manager = notification.object as? SetappManager,
let newValue = notification.userInfo?[NSKeyValueChangeKey.newKey],
let newSetappSubscription = newValue as? SetappSubscription else {
return
}
print("Manager:", manager)
print("Setapp subscription:", newSetappSubscription)
}
}
If you prefer KVO, you can observe the subscription
property of the shared
instance of the SetappManager
class.
import Setapp
class SetappSubscriptionKVOObserver {
private var kvoObserver: NSObjectProtocol?
init() {
kvoObserver = SetappManager.shared
.observe(\.subscription, options: [.new]) { [weak self] (manager, change) in
self?.setappSubscriptionDidChange(manager: manager, change: change)
}
}
// MARK: KVO observation
func setappSubscriptionDidChange(
manager: SetappManager,
change: NSKeyValueObservedChange<SetappSubscription>
)
{
guard let newSetappSubscription = change.newValue else {
return
}
print("Manager:", manager)
print("Setapp subscription:", newSetappSubscription)
}
}
We utilize background tasks to send you a usage report when a user doesn't use your application at the moment. This allows us to ensure that usage tracking is delivered to our servers.
To send network requests with usage reports in the background, you must select the Background fetch
checkbox in the Background modes
capability group.
- Go to the Signing & Capabilities tab.
- Add
Background modes
capability. - Select
Background fetch
mode. - Select
Background processing
mode.
Add the following code to your UIApplicationDelegate
class:
func application(
_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void
)
{
if SetappManager.shared.isSetappBackgroundSessionIdentifier(identifier) {
SetappManager.shared.backgroundSessionCompletionHandler = completionHandler
}
}
For iOS 13 and later, we also utilize background tasks. That means that you must allow Setapp to run background tasks with specific identifiers. To do that:
- Open your
Info.plist
file. - Add the
Permitted background task scheduler identifiers
(BGTaskSchedulerPermittedIdentifiers
) key to the dictionary. - Append
com.setapp.usageReport
to the key values array.
The bundle ID of your app for Setapp must use the -setapp
suffix to follow this pattern:
<domain>.<companyName>.<appName>-setapp
For example:
com.macpaw.CleanMyMac-setapp
app.macpaw.Gemini-setapp
If your app has additional executables, their bundle IDs must conform to the following pattern:
<domain>.<companyName>.<appName>-setapp.<executableName>
For example:
com.macpaw.CleanMyMac-setapp.Menu
To add the bundle ID, follow these steps:
- Go to the Apps page in your developer account. You'll see that your app is already there. Click
Add First Version.
- Enter the bundle ID of your app in the dialog box that appeared.
It is critical to use the same bundle ID in the Xcode target of your app and in your developer account. Update your app's target if it needed.
⚠️ You will not be able to change the bundle ID once you set it.
📘 A bundle ID is a unique case-sensitive identifier that contains only alphanumeric characters (A-Z, a-z, 0-9), period (.), and hyphen (-). Please note that only the hyphen-minus sign (U+002D) can be used (don't press the Option key). Also, note that you mustn't specify an app version in the bundle ID.
📘 The string must be written in reverse-DNS format. Example: Your domain is mycompany.com, and your app's name is MyApp. In this case, you can use
com.mycompany.myapp-setapp
as a bundle ID of your app.
If your app is sandboxed, you must add a temporary exception to enable communication between the Library integrated into your app and Setapp Mach services.
- Open the entitlements file of your project.
- Add
com.apple.security.temporary-exception.mach-lookup.global-name
entitlement key. - Add the
com.setapp.ProvisioningService
string (the Setapp service name) value for thecom.apple.security.temporary-exception.mach-lookup.global-name
entitlement key’s value array.
As a result, your entitlements file must look similar to the following:
To establish trust between your app and our service, the Frameworks needs a public key that is unique for every app.
- Go to the Apps page of your developer account and click
New version
on your app. - On the right side of
Release info
, you can find a notionFor macOS library 2.0.0 and higher, find a public key here.
. - Click the link and download the public key.
The public key is used to operate with the data received from the Setapp system. The public key is unique for every app in the Setapp suite and it is an essential part of the Framework's security.
To add a public key to your project in Xcode, simply drag the setappPublicKey.pem
key file to the navigator area. A new dialog box appears. Select the "Copy items if needed" checkbox on the top of the dialog box.
⚠️ For macOS applications, you can only use this public key filename: (setappPublicKey.pem
).
⚠️ Public key must be located in the main application bundle.
⚠️ Please note that public keys for iOS & macOS platforms differ.
To allow Setapp to update your app on macOS 13 (Ventura) and higher, please add the following to your apps Info.plist file:
<key>NSUpdateSecurityPolicy</key>
<dict>
<key>AllowProcesses</key>
<dict>
<key>MEHY5QF425</key>
<array>
<string>com.setapp.DesktopClient.SetappAgent</string>
</array>
</dict>
</dict>
We highly recommend implementing the release notes functionality into your app to improve the user experience. However, the final dicision is up to you.
Show a dialog box with a list of changes in the updated app version automatically.
Call the showReleaseNotesWindowIfNeeded()
function of the shared SetappManager
in the applicationDidFinishLaunching(_:)
method (or add it to another appropriate place, for example, after the onboarding dialog of your app). Note that this function reveals a dialog only after opening a newly updated app.
func applicationDidFinishLaunching(_ aNotification: Notification) {
SetappManager.shared.showReleaseNotesWindowIfNeeded()
}
To allow users to view release notes anytime they want, add a corresponding option to the app's main menu. Then call the showReleaseNotesWindow()
function of the shared SetappManager
.
@IBAction private func showReleaseNotes(_ sender: Any) {
SetappManager.shared.showReleaseNotesWindow()
}
As a developer, you might want to stay in touch with your users on Setapp. We understand this intention and we can provide you with a list of their contacts if users will consent. But first, you need to implement a User Permissions API for asking a user to share their email address. Thus, you can create a permission-based email list of your active users. Later, you can download it directly from your Developer Account.
To show a dialog box for asking users to share their email with the current application, call the askUserToShareEmail()
function of the shared SetappManager
.
@IBAction private func showReleaseNotes(_ sender: Any) {
SetappManager.shared.askUserToShareEmail()
}
When a user made a choice, you cannot ask him/her again to change the decision.
However, if the user closes the dialog box without making a choice, Setapp will show the dialog box again:
- 2nd time - in 1 day
- 3rd time - in 2 days
- 4th time - in 3 days
- 5-7th times - in 7 days
- 8th time and further - in 30 days
ℹ️ The User Permissions API requires Setapp application version 3.2.1 or newer. If a user uses an older Setapp version, the email subscription form will not appear.
⚠️ Integrating apps into Setapp using the Vendor API is the next "big thing" we're actively working on. Most features are still under development, so please don't use them in Production yet.
Still, we'd like to share the main ideas in order to get your feedback at this early stage. We're looking forward to your comments at developer@setapp.com or in the Setapp Community Slack.
To start communicating with Setapp's server, you must request an authorization (auth) code from it. The auth code has a 20-minute lifetime, so you must pass the auth code to your server for further processing during this time.
How the communication between your app/server and the Setapp system via the Vendor API works:
- Your app requests and receives an auth code from the Setapp server.
- Your app passes the auth code to your server.
- Your server exchanges the auth code for the Vendor API's access token and refresh token.
- Your server uses the obtained tokens for further communication with Setapp using the API (exchanging subscription info, usage reporting, etc.).
You can get the auth code using the requestAuthorizationCode
function. The function requires an internet connection and fails with a corresponding error if a user's iOS or MacOS device was offline.
To request the auth code, you must specify these parameters:
clientID
: the app's client ID generated in your developer account. If you have several apps in Setapp, theclientID
must be different for them (including macOS apps and their iOS companions).scope
: a list of functionalities you wish to authorize for communication with the Setapp system. In Swift, the possible values are listed in theVendorAuthorizationScope
enum. In Objective-C, however, you’ll have to specify the values yourself asNSStrings
.The other possible functionalities scope values are mentioned in the GET /authorize method of the Vendor API.
// Make sure an active Setapp subscription is present.
// See subscription monitoring examples on this page for more info.
SetappManager.shared.requestAuthorizationCode(
clientID: "your_vendor_api_authorization_code",
scope: [.applicationAccess]
) { result in
switch result {
case let .success(code):
// Authentication code obtained successfully.
// Use the code to authorize your app or server for Setapp: exchange the auth code for the access token and the refresh token using the Setapp API.
print(code)
case let .failure(error):
// The request has failed.
// See the error message for details.
print(error)
}
}
If you want to extend or reduce your logs or override the console logs path with your own destination, it's easy to do so with just a few lines of code.
You can easily change your log depth just by setting the logLevel
property of the SetappManager
class to one of the standard options:
.verbose
.debug
.info
(default log level).warning
.error
.off
SetappManager.logLevel = .debug
To override the Setapp’s log destination with your own logger, use the setLogHandle
method of the SetappManager
class. This function takes a closure that accepts the message string and the SetappLogLevel parameter.
SetappManager.setLogHandle { (message: String, logLevel: SetappLogLevel) in
print("[\(logLevel)]", message)
}
To display the Framework logs in the Console app, follow these steps:
-
Open the app and paste the following query into the Search field:
subsystem:com.setapp.fmwk
-
Make sure these items have been selected in the Actions menu:
- Include Info Messages
- Include Debug Messages
Alternatively, you can allow showing debug & info messages from the Setapp Framework by executing this command in Terminal:
sudo log config --subsystem com.setapp.fmwk --mode "level:debug"
See "Testing your apps" for details.
You can find documentation about integrating Setapp Framework to your Electron app in the docs/Electron.md.
You can find integration samples in the Samples folder. There are:
SetappSample-Catalyst
with manually integrated Setapp Framework.SetappSample-macOS-ObjectiveC
that uses CocoaPods as a dependency manager for Setapp Framework integration.Electron
apps that utilize our node.js wrapper to integrate Setapp Framework into the Electron app.
For more details, you can visit "Integrating the iOS Framework" in Setapp Developer Documentation.