Use the Mopinion Mobile SDK to collect feedback from iOS apps based on events. For this you can include the SDK as a Framework in your Xcode project.
Other Mopinion SDK's are also available:
- Release notes
- 1. Installation
- 1.1 Install using Swift PM
- 1.2 Install using CocoaPods
- 2. Implement the SDK
- 2.1 Display a form
- 2.2 Configure dark mode
- 2.3 Submit extra data
- 2.4 Evaluate if a form will open
- 2.5 Using callback mode
- 3. Edit triggers
- Extended methods to open native forms in dark or light mode.
- Forms can change their colour scheme to OS-setting changes.
- Comes with dark variants for our standard form themes.
- Some colors (Bar, CES, Emoji, Star and Thumbs) were adjusted to meet WCAG 2.1 contrast requirements.
- New date picker for date fields.
- Fixed CES submit values, they were off by one.
- Fixed Thumb submit values when using custom labels, now they will also be flagged as positive or negative in the feedback inbox.
- Fixed reverse NPS submit values, they reflected the non-reversed score.
- Fixed VoiceOver support for the page navigation and submit buttons.
- VoiceOver will now also read emoji that do not show their labels.
- This readme applies to both the CocoaPods and Swift Package Manager distribution, as the latter uses the same binaries as the GitHub release for CocoaPods.
- Built with Xcode 26.2, tested on iOS 18 and iOS/iPadOS 26.2 in iOS 18 compatibility mode.
- In some cases, the Xcode iOS 26 simulators may render views with unexpected height, position or decorations. Please verify the appearance on physical devices.
Install the Mopinion Mobile SDK Framework via either the Swift Package Manager or the popular dependency manager Cocoapods.
-
If you no longer want to use CocoaPods for your project, then in terminal, at the root folder of your project execute:
pod deintegrate
After that you can optionally remove the<your-project-name>.xcworkspaceif it is no longer needed. -
Open your project's
<your-project-name>.xcodeprojfile in Xcode. -
In Xcode, from the menu, select
File -> Add Package Dependencies…. The Swift Package Collections panel appears. -
In the search field of the panel, enter
https://github.com/mopinion-com/mopinion-sdk-ios-swiftpmand press enter. -
From the drop-down button
Dependency Rule, choose one of the following options:Exact Versionand in the version field enter1.3.0.Up to Next Major Versionand in the version field enter1.3.0.
-
Click the button
Add Package. A package product selection panel appears. -
Choose
MopinionSDKand click the buttonAdd Package. -
If Xcode 14.2 shows a warning
PackageIndex.findPackages failed: featureDisabled, then clean your project, close the project and open your project again in Xcode. The warning will have disappeared.
Skip this section if you've installed with swiftpm.
- Install CocoaPods if you didn't have it installed yet. From macOS Monterey 12.1 installation of cocoapods 1.11.2 works out of the box on ARM based Macs:
sudo gem install cocoapods- In the terminal, create a
Podfilein the folder that contains your Xcode project :
platform :ios, '12.0'
use_frameworks!
target '<YOUR TARGET>' do
pod 'MopinionSDK', '>= 1.3.0'
end- From that folder, install the needed pods:
$ pod install- After this, going forward you should use the newly created
<your-project-name>.xcworkspacefile to open in Xcode.
In your app code, for instance the AppDelegate.swift file, put:
import MopinionSDK
...
// debug mode
MopinionSDK.load("<MOPINION DEPLOYMENT KEY>", true)
// live
MopinionSDK.load("<MOPINION DEPLOYMENT KEY>")Replace the <MOPINION DEPLOYMENT KEY> by your specific deployment key. Copy this key using a web browser from your Mopinion account, in side menu Data collection, section Deployments, via the button with symbol <>.
In an UIViewController, for example ViewController.swift, put:
import MopinionSDK
...
MopinionSDK.event(self, "_button")where "_button" is the default name for a passive form event.
You are free to name events after custom events in your app and define them in the Mopinion deployment interface.
In the Mopinion system you can enable or disable the feedback form when (a user of) your app submits the event. The SDK will ignore unknown events, so you can implement all events beforehand in your app and freely assign/remove forms to them after your app has been released.
The event could be a touch of a button, at the end of a transaction, proactive, etc.
Previous versions of the SDK always opened forms in light mode and did not change appearance to dark mode.
SDK version 1.3.0 introduced automatic support for dark mode. With the default setting, forms that are designed for dark mode will automatically show the designed behaviour, others remain light. So no code changes are needed.
But for other cases, use the configuration.setColorScheme(_ colorSchemeMode: ColorScheme) with the ColorScheme parameter to control the color scheme behaviour. You can specify this globally (static) or per event() call.
Supported ColorScheme parameter values to configuration.setColorScheme():
| Value | Description |
|---|---|
| .auto | Default. Forms with a custom dark mode design theme in the Form Editor web application allow the os to select the starting mode and change apparance. Forms without dark mode design will open in light mode and do not change appearance. |
| .dark | Forms always open in dark mode and do not change appearance. |
| .light | Forms always open in light mode and do not change appearance. |
| .system | Use what the device OS has set for light or dark mode and allow the OS to change appearance of forms. Uses a default dark mode theme if a form has no dark mode form design settings. |
The SDK has a global configuration, which you can use to globally set the ColorScheme mode:
func MopinionSDK.configuration.setColorScheme(_ colorSchemeMode: ColorScheme)The default is auto which makes the behaviour backwards compatible with previous SDK versions and automatically supports dark mode.
Configuration changes affect all following calls to the MopinionSDK, not any currently open form. It can be set at any time before a event() call or the load() call.
Example:
import MopinionSDK
...
MopinionSDK.configuration.setColorScheme(.system)
MopinionSDK.load("abcd1234")
...Apart from the aforementioned global configuration, you can also pass a temporary MopinionConfiguration via the parameter configuration to the following new method variants:
func event(parentView: event: configuration:)
func event(parentView: event: configuration: onCallbackEvent: onCallbackEventError:)
func event(parentView: event: configuration: onCallbackEventDelegate:)
func event(parentView: event: configuration: onCallbackEventDelegate: onCallbackEventErrorDelegate:)
func evaluate(event: configuration: onEvaluateDelegate:)
func openFormAlways(parentView: formKey: forEvent: configuration:)The passed temporary configuration overrides the static configuration for the duration of that call, but doesn't modify it.
Usage: you obtain a copy of the general configuration object, modify it to your needs and pass it to the parameter configuration: of selected method variants.
Example:
import MopinionSDK
...
MopinionSDK.load("abcd1234")
...
// only this call will open a form in dark mode
var tmpConfig = MopinionSDK.configuration
tmpConfig.setColorScheme(.dark)
MopinionSDK.event(self, "_button", configuration: tmpConfig)
...
// other calls still implicitly use the static MopinionSDK.configuration, which by default is .auto, so this form would open in light mode
MopinionSDK.event(self, "_button")Your app can send extra (string based) data to your form.
SDK version 0.3.1 introduced the data() method to supply a key and a value.
The data should be added before the event() method is called if you want to include the data in the form that comes up for that event.
MopinionSDK.data(_ key: String, _ value: String)Example:
import MopinionSDK
...
MopinionSDK.load("abcd1234")
...
MopinionSDK.data("first name", "Steve")
MopinionSDK.data("last name", "Jobs")
...
MopinionSDK.event(self, "_button")Note: In the set of meta data, the keys are unique. If you re-use a key, the previous value for that key will be overwritten.
From version 0.3.4 it's possible to remove all or a single key-value pair from the extra data previously supplied with the data(key,value) method.
To remove a single key-value pair use this method:
MopinionSDK.removeData(forKey: String)Example:
MopinionSDK.removeData(forKey: "first name")To remove all supplied extra data use this method without arguments:
MopinionSDK.removeData()Example:
MopinionSDK.removeData()The event() method of the SDK autonomously checks deployment conditions and opens a form, or not.
From SDK version 0.4.6 you can use the evaluate() and related methods to give your app more control on opening a form for proactive events or take actions when no form would have opened.
It can also be used on passive events, but such forms will always be allowed to open.
- Call the
evaluate()method and pass it the delegate object that implements theMopinionOnEvaluateDelegateprotocol. - In your delegate's callback method
mopinionOnEvaluateHandler(), check the response parameters and retrieve theformKeyif there is any. - Optionally, pass the
formKeyto the methodopenFormAlways()to open your form directly, ignoring any conditions in the deployment.
Evaluates whether or not a form would have opened for the specified event. If without errors, the delegate object will receive the mopinionOnEvaluateHandler() call with the response.
func evaluate( _ event: String, onEvaluateDelegate: MopinionOnEvaluateDelegate )Parameters:
event: The name of the event as definied in the deployment. For instance "_button".onEvaluateDelegate: The object implementing theMopinionOnEvaluateDelegateprotocol to handle themopinionOnEvaluateHandler()callback method.
Method where the app receives the response of the evaluate call. Defined by the MopinionOnEvaluateDelegate protocol. Note that in case of any system errors this may not be called at all.
func mopinionOnEvaluateHandler(hasResult: Bool, event: String, formKey: String?, response: [String : Any]?)Parameters:
hasResult: if true then the form identified by the formKey would have opened. If false then the form would not have opened and the formKey might be null in case no forms were found associated with the event.event: the original event name that was passed to the evaluate call to check in the deployment.formKey: identifying key of the first feedback form found associated with the event. Only one formKey will be selected even if multiple forms matched the event name in the deployment.response: optional dictionary object for extra response details on success/failure and forms. Reserved for future extensions.
Opens the form specified by the formkey for the event, regardless of any proactive conditions set in the deployment.
func openFormAlways(_ parentView: UIViewController, formKey: String, forEvent: event) Parameters:
parentView: Your UIViewController object that can act as a parent view controller for the SDK.formKey: key of a feedback form as provided by the mopinionOnEvaluateHandler() call.forEvent: The same event as passed to theevaluate()call. For instance "_button".
This snippet of pseudo code highlights the key points on how the aforementioned procedure fits together to implement the MopinionOnEvaluateDelegate protocol.
...
import MopinionSDK
...
// assuming that in your AppDelegate, you already did MopinionSDK.load(<MOPINION DEPLOYMENT KEY>)
...
class ViewController: UIViewController, MopinionOnEvaluateDelegate {
...
func doSomething() {
// check if a form would open
MopinionSDK.evaluate("_myproactiveevent", onEvaluateDelegate: self)
// the actual result will be in the mopinionOnEvaluateHandler call
}
...
// callback handler for protocol MopinionOnEvaluateDelegate
func mopinionOnEvaluateHandler(hasResult: Bool, event: String, formKey: String?, response: [String : Any]?) {
if(hasResult) {
// at least one form was found and all optional parameters are non-null
// because conditions can change every time, use the form key to open it directly
MopinionSDK.openFormAlways(self, formKey: formKey!, forEvent: event)
}else{
if let _ = formKey {
// Found form wouldn't open for event
// we'll open it anyway using the formKey and event
MopinionSDK.openFormAlways(self, formKey: formKey!, forEvent: event)
}else{
// no form found for event
...
}
}
}
...
By default the SDK manages the feedback form autonomously without further involving your app.
SDK version 0.5.0 introduced callbacks to inform your code of certain actions (MopinionCallbackEvent).
Provide a callback handler to receive a response, containing either data or possible error information.
- Call the
event()method and pass it a callback method that implements theMopinionCallbackEventDelegate.onMopinionEventprotocol. - In your callback method
onMopinionEvent(), check the kind ofmopinionEventand optionally calldidSucceed()orhasErrors()on theresponseto check for errors. - Optionally, call
hasData()on theresponseobject to check if there is data. - Depending on the kind of
mopinionEvent, check for the presence of data specified by aResponseDataKeyusing the callhasData(ResponseDataKey)on theresponse. - To get the data, call
getString(ResponseDataKey)respectivelygetJSONObject(ResponseDataKey)on theresponse, depending on the type of data to retrieve.
You can also provide an optional error-callback handler to event() to seperately receive responses with error information. In that case the primary handler only receives responses without errors.
Triggers an event you defined in your deployment to open a form and receive MopinionCallbackEvent callbacks. If you don't specify a failHandler, the callback handler will also receive error responses.
func event(parentView: event: onCallbackEvent: onCallbackEventError:)
func event(parentView: event: onCallbackEventDelegate:)
func event(parentView: event: onCallbackEventDelegate: onCallbackEventErrorDelegate:)Parameters:
parentView: The UIViewController that serves as parent view of the app.event: The name of the event as defined in the deployment for the form. For instance "_button".onCallbackEvent: a closure implementing theonMopinionEvent()callback.onCallbackEventError: a closure implementing theonMopinionEventError()callback for MopinionCallbackEvents that resulted in errors.onCallbackEventDelegate: The object implementing theMopinionCallbackEventDelegateprotocol to handle theonMopinionEvent()callback.onCallbackEventErrorDelegate: The object implementing theMopinionCallbackEventErrorDelegateprotocol to handle theonMopinionEventError()callback for MopinionCallbackEvents that resulted in errors.
These methods you implement in your code to receive MopinionCallbackEvents. They have the same parameters to pass you a response with optional additional information.
What information is provided depends on the type of MopinionCallbackEvent and its origin.
func onMopinionEvent(mopinionEvent: MopinionCallbackEvent, response: MopinionResponse)
func onMopinionEventError(mopinionEvent: MopinionCallbackEvent, response: MopinionResponse)Parameters:
-
mopinionEvent: The kind of response event that you receive from the SDK. Currently one of the following:FORM_OPEN: when the form is shownFORM_SENT: when the user has submitted the formFORM_CLOSED: when the form has closedNO_FORM_WILL_OPEN: when no form will open
-
response: The MopinionResponse object containing additional information on the MopinionEvent. The response is nevernil, but use itshasData()methods to check if it contains any additional data, orhasErrors()for errors.
The data collection present in this object depends on the kind of MopinionCallbackEvent and its origin. The data is a key-value collection. Both data and errors can be missing. The response object contains methods to inspect and retrieve them.
Check with hasData(key) first, as the get<>(key) methods can return null. Pass a standard ResponseDataKey to these methods for the data you're interested in.
| ResponseDataKey | Method to read it | Description |
|---|---|---|
| DATA_JSONOBJECT | .getJSONObject() | dictionary of the 'raw' JSONObject with all available data |
| FORM_KEY | .getString() | an internal unique identifier for the form |
| FORM_NAME | .getString() | the name of the form. Distinct from the title of the form. |
This is the data that can be present for a certain MopinionCallbackEvent:
| MopinionCallbackEvent | ResponseDataKeys | Remarks |
|---|---|---|
| NO_FORM_WILL_OPEN | - | |
| FORM_OPEN | DATA_JSONOBJECT | |
| FORM_KEY | ||
| FORM_NAME | ||
| FORM_SENT | DATA_JSONOBJECT | |
| FORM_KEY | ||
| FORM_NAME | ||
| FORM_CLOSED | DATA_JSONOBJECT | Currently only automatically closed forms provide data |
| FORM_KEY | only when autoclosed | |
| FORM_NAME | only when autoclosed |
The order in which MopinionCallbackEvents occur is:
1. NO_FORM_WILL_OPEN
- or -
1. FORM_OPEN
2. FORM_SENT (only if the user submits a form)
3. FORM_CLOSED
Call response.hasErrors() , followed by response.getError() to get the error object.
The getError() method might return nil.
At the moment there is only one pre-defined error condition:
| MopinionCallbackEvent | response.getError() | Meaning |
|---|---|---|
| NO_FORM_WILL_OPEN | MopinionError.formConfigFormNotFound | The form associated with the event and deployment does not exist |
| NO_FORM_WILL_OPEN | any other | Other error prevented loading the form |
Pseudo code to show the usage of the event() callback with closures and some involved objects to implement running code after send.
You must wait for the form to be closed after send before running any code affecting your own UI.
...
import MopinionSDK
...
// assuming that in your AppDelegate, you already did MopinionSDK.load(<MOPINION DEPLOYMENT KEY>)
...
class YourViewController: UIViewController, MopinionOnEvaluateDelegate {
...
var wasFormSent: Bool = false // track state outside closure
...
func demonstrateMopinionCallback() {
self.wasFormSent = false
// open the form associated with the event "_myfeedbackbutton" from the deployment and receive callbacks in the closures
MopinionSDK.event(self, "_myfeedbackbutton", onCallbackEvent: { (mopinionEvent, response) -> (Void) in
print("callback in success closure")
if(mopinionEvent == .FORM_SENT) {
let formKey = response.getString(.FORM_KEY)!
print("The form with formKey=\(formKey) has been sent, but is still displayed")
self.wasFormSent = true
} else if(mopinionEvent == .FORM_CLOSED) {
if(self.wasFormSent) {
let formKey = response.getString(.FORM_KEY) ?? ""
print("The form \(formKey) has beent sent and closed, now you can run some code.")
}
} else if(mopinionEvent == .NO_FORM_WILL_OPEN) {
print("No form will open for this event.")
}
}, onCallbackEventError: { (mopinionEvent, response) -> (Void) in
if let error = response.getError() {
print("there was an error during callback: \(String(describing: error))")
}
} )
}
...
}
...In the Mopinion deployment editor you can define event names and triggers that will work with the SDK event names that you used in your app. Login to your Mopinion account and go to Data collection, Deployments to use this functionality.
The custom defined events can be used in combination with rules/conditions:
- trigger:
passiveorproactive. A proactive trigger can show its form only once; you can set the refresh duration after which the trigger can fire again. A passive trigger behaves almost the same, but can fire every time as it ignores the refresh duration (except when the percentage condition is set) - refresh duration: the number of days to wait before the trigger can fire again in an app instance, specified by the setting "Refresh condition settings per visitor after {x} days". Sometimes also referred to as "session".
- submit: allow opening a proactive form until it has been submitted at least once. This affects the trigger rule, to allow opening a form more than once. Support for this appeared in SDK version 0.4.3.
- percentage: % of users that should see the form. This setting makes the trigger fire once per refresh duration, even if no form was shown or it is a passive trigger.
- date: only show the form at, after or before a specific date or date range
- time: only show the form at, after or before a specific time or time range
- target: only show the form for a specific OS (iOS or Android) and optional list of versions.
