This framework extends the BlinkReceipt SDK to enable rewards and monetization functionality. You must first install the BlinkReceipt framework according to the instructions in that repository.
- iOS: 13.0+
- Swift: 5.9+
- Xcode: 16.4+
- Dependencies: Google-Mobile-Ads-SDK, BlinkReceipt SDK
- Complete the BlinkReceipt integration for receipt scanning: https://blinkreceipt.github.io/blinkreceipt-ios/
- In Xcode, go to File > Add Package Dependencies
- Enter:
https://github.com/BlinkReceipt/blinkengage-ios - Select latest version and add BlinkEngage product
pod 'BlinkEngage', '~> 1.0.0'Then run: pod install
import BlinkEngage
import BlinkReceipt
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Configure BlinkReceipt SDK (required for scanning experience)
BRScanManager.shared().licenseKey = "YOUR-BLINKRECEIPT-LICENSE-KEY"
// Enable BlinkEngage integration in BlinkReceipt
BRScanManager.shared().enableBlinkEngage = true
// Configure BlinkEngage SDK
// User identification (at least one required)
BlinkEngageSDK.shared.user.emailHash = "hashed_email_string"
BlinkEngageSDK.shared.user.phoneHash = "hashed_phone_string"
// Optional for extra mapping
BlinkEngageSDK.shared.user.clientUserId = "your_client_user_id"
BlinkEngageSDK.shared.rewardCurrencyName = "points"
BlinkEngageSDK.shared.rewardCurrencyPerDollar = 100.0
// Styling: pass a custom Theme (e.g. MyTheme from [Styling (Theme)](#styling-theme) below).
BlinkEngageSDK.shared.appearance = Appearance(theme: MyTheme())
// Optional: Enable debug mode for development (shows test ad units)
BlinkEngageSDK.shared.debugModeEnabled = false
// Set up reward callback
// Depending on `context`, this callback will either solicit a reward amount from the host app, which it should return as an `NSNumber`, or it will inform the host app, via the `rewardAmount` parameter, of an amount (in host app currency) that BlinkEngage awarded to the user
BlinkEngageSDK.shared.rewardCallback = { context, scanResults, rewardAmount in
switch context {
case "ScanFinished":
return NSNumber(value: 10.0) // Base reward for scan completion; use scanResults if amount varies.
case "Promo":
print("User earned \(rewardAmount?.doubleValue ?? 0) points from promo")
return nil
case "Boost":
print("User earned \(rewardAmount?.doubleValue ?? 0) points from boost")
return nil
case "BarcodeCollection":
print("User earned \(rewardAmount?.doubleValue ?? 0) points from barcode collection")
return nil
default:
return nil
}
}
return true
}
}Control colors, fonts, and images across the SDK (offer wall, receipt summary, loading screen, error modals, missed earnings, etc.) by implementing the Theme protocol and passing your theme when creating Appearance:
class MyTheme: NSObject, Theme {
var isRewardIconEnabled: Bool { true } // Show default reward icon when no custom image is provided
var isMerchantIconEnabled: Bool { true } // Load merchant logos on the Stores screen
var globalFontMatrix: NSDictionary? {
// Keys: NSNumber(UIFont.Weight.rawValue), values: font name String. Used when fontName(forKey:) returns nil.
[
NSNumber(value: UIFont.Weight.ultraLight.rawValue): "Outfit-Light",
NSNumber(value: UIFont.Weight.thin.rawValue): "Outfit-Light",
NSNumber(value: UIFont.Weight.light.rawValue): "Outfit-Light",
NSNumber(value: UIFont.Weight.regular.rawValue): "Outfit-Regular",
NSNumber(value: UIFont.Weight.medium.rawValue): "Outfit-Medium",
NSNumber(value: UIFont.Weight.semibold.rawValue): "Outfit-SemiBold",
NSNumber(value: UIFont.Weight.bold.rawValue): "Outfit-Bold",
NSNumber(value: UIFont.Weight.heavy.rawValue): "Outfit-ExtraBold",
NSNumber(value: UIFont.Weight.black.rawValue): "Outfit-Black",
] as NSDictionary
}
func color(forKey key: AppearanceColorKey) -> UIColor? {
switch key {
case .postScanHeaderBackground: return .systemBlue
case .offerWallHeaderBackground: return .systemIndigo
default: return nil // Use SDK default for all other keys
}
}
func fontName(forKey key: AppearanceFontNameKey) -> String? {
nil // Use SDK default fonts; or return e.g. "Outfit-Bold" for specific keys
}
func image(forKey key: AppearanceIconKey) -> UIImage? {
if key == .offerRewardIcon { return UIImage(named: "my_coin") }
return nil
}
}
// During setup:
BlinkEngageSDK.shared.appearance = Appearance(theme: MyTheme())- Return
nilfromcolor,fontName, orimagefor any key to use the SDK default. - Use
AppearanceColorKey,AppearanceFontNameKey, andAppearanceIconKey(see SDK headers) for the full list of customizable keys. - To use default styling, pass
Appearance(theme: nil)orAppearance().
class YourViewController: UIViewController {
func displayOfferWall() {
// Show all offers (default), or use .clipped to show only offers the user has clipped
let offerWallViewController = OffersWallViewController(offerWallViewType: .all)
offerWallViewController.delegate = self
present(offerWallViewController, animated: true)
}
}
extension YourViewController: OffersWallViewControllerDelegate {
// We’ll present a floating “Scan Receipt” action button and this callback will be triggered if the user clicks it.
func offerWallDidSelectFloatingAction(_ viewController: OffersWallViewController) {
// Handle floating action button tap
}
// A floating help icon will be presented to the user at X, Y and Z points in the journey
func offerWallShouldDisplayFloatingAction(_ viewController: OffersWallViewController) -> Bool {
return true // or false to hide floating action
}
// Called when the offer list loads or when the user clips/unclips an offer. Use the count to update a badge or other UI.
func offerWall(_ viewController: OffersWallViewController, didUpdateClippedOffersCount count: Int) {
tabBarItem.badgeValue = count > 0 ? "\(count)" : nil
}
}Clipped count delegate: Implement offerWall(_:didUpdateClippedOffersCount:) to be notified when the number of clipped offers changes (initial load, clip, or unclip). Use it to show a badge on a tab or button without polling.
// Example: update a tab bar badge or custom label when clipped count changes
func offerWall(_ viewController: OffersWallViewController, didUpdateClippedOffersCount count: Int) {
tabBarItem.badgeValue = count > 0 ? "\(count)" : nil
// or: clippedCountLabel.text = "\(count) saved"
}Offer wall view types:
.all— Show all offers (default)..clipped— Show only offers the user has clipped.
All offers (default):
let offerWall = OffersWallViewController(offerWallViewType: .all)
offerWall.delegate = self
present(offerWall, animated: true)Clipped offers only:
let clippedWall = OffersWallViewController(offerWallViewType: .clipped)
clippedWall.delegate = self
present(clippedWall, animated: true)class YourViewController: UIViewController {
func scanReceipt() {
let scanOptions = BRScanOptions()
BRScanManager.shared().startStaticCamera(
from: self,
cameraType: .standard,
scanOptions: scanOptions,
with: self
)
}
}Copyright (c) 2025 Actual. All rights reserved.